Controlar la orientación del dispositivo con MediaCapture

Cuando la aplicación captura una foto o un vídeo para su uso fuera de la aplicación, como guardar en un archivo o compartir, debes codificar la imagen con los metadatos de orientación correctos para que el contenido se muestre correctamente en otras aplicaciones y dispositivos. En este artículo se muestra cómo usar una clase auxiliar para administrar la orientación de la cámara en una aplicación de escritorio winUI 3.

Prerequisites

Antes de comenzar, asegúrese de que tiene:

  • Un proyecto de escritorio de WinUI 3 (empaquetado o desempaquetado).
  • Una cámara compatible conectada al dispositivo.
  • Acceso a la cámara declarado para la aplicación:
    • Aplicaciones empaquetadas: agregue la funcionalidad del webcam dispositivo a Package.appxmanifest:

      <Capabilities>
          <DeviceCapability Name="webcam" />
      </Capabilities>
      
    • Aplicaciones sin empaquetar: no hay ninguna capacidad del manifiesto que declarar. El acceso a la cámara se controla mediante la opción Permitir que las aplicaciones de escritorio accedan a la configuración de la cámara en Configuración>Privacidad y seguridad>Cámara en el dispositivo del usuario.

El usuario todavía puede denegar el acceso a la cámara en el nivel de sistema operativo en cualquier caso. Compruebe el acceso antes de inicializar la cámara y controle el caso denegado correctamente, como se describe en Controlar la configuración de privacidad de la cámara Windows.

Conceptos de orientación

Normalmente, las aplicaciones de escritorio se ejecutan en dispositivos con pantallas fijas, por lo que no es necesario controlar la rotación continua de la forma en que hace una aplicación móvil. Sin embargo, todavía tiene que tener en cuenta lo siguiente:

  • Orientación del sensor de cámara : el ángulo de montaje físico del sensor de cámara, que varía según el dispositivo.
  • Rotación de cámara externa : el usuario puede girar cámaras web externas.

La idea clave es aplicar una corrección de rotación al codificar una foto capturada o vídeo para que la salida coincida con lo que ve el usuario en la vista previa.

Creación de una clase CameraRotationHelper

La siguiente clase auxiliar administra los valores de rotación en función de la orientación del sensor de cámara y los sensores de orientación del dispositivo:

using Windows.Devices.Enumeration;
using Windows.Devices.Sensors;
using Windows.Media.Capture;
using Windows.Storage.FileProperties;

public class CameraRotationHelper
{
    private readonly EnclosureLocation _cameraEnclosureLocation;
    private readonly SimpleOrientationSensor _orientationSensor;
    private SimpleOrientation _deviceOrientation =
        SimpleOrientation.NotRotated;

    public event EventHandler<bool> OrientationChanged;

    public CameraRotationHelper(
        EnclosureLocation cameraEnclosureLocation)
    {
        _cameraEnclosureLocation = cameraEnclosureLocation;

        _orientationSensor =
            SimpleOrientationSensor.GetDefault();

        if (_orientationSensor != null)
        {
            _orientationSensor.OrientationChanged +=
                OrientationSensor_OrientationChanged;
        }
    }

    private void OrientationSensor_OrientationChanged(
        SimpleOrientationSensor sender,
        SimpleOrientationSensorOrientationChangedEventArgs args)
    {
        if (args.Orientation != SimpleOrientation.Faceup &&
            args.Orientation != SimpleOrientation.Facedown)
        {
            _deviceOrientation = args.Orientation;
            OrientationChanged?.Invoke(this, true);
        }
    }

    public static bool IsEnclosureLocationExternal(
        EnclosureLocation enclosureLocation)
    {
        return enclosureLocation == null ||
            enclosureLocation.Panel == Windows.Devices.Enumeration.Panel.Unknown;
    }

    private bool IsCameraMirrored()
    {
        // Front panel cameras are mirrored by convention
        return _cameraEnclosureLocation?.Panel == Windows.Devices.Enumeration.Panel.Front;
    }

    private SimpleOrientation GetCameraOrientation()
    {
        if (IsEnclosureLocationExternal(_cameraEnclosureLocation))
        {
            return SimpleOrientation.NotRotated;
        }

        // Get the sensor orientation from the device
        return _deviceOrientation;
    }

    /// <summary>
    /// Gets the rotation to apply to the camera preview stream.
    /// </summary>
    public VideoRotation GetCameraPreviewOrientation()
    {
        if (IsEnclosureLocationExternal(_cameraEnclosureLocation))
        {
            return VideoRotation.None;
        }

        return ConvertSimpleOrientationToVideoRotation(
            GetCameraOrientation());
    }

    /// <summary>
    /// Gets the rotation to apply when encoding a photo.
    /// </summary>
    public PhotoOrientation GetCapturePhotoOrientation()
    {
        if (IsEnclosureLocationExternal(_cameraEnclosureLocation))
        {
            return PhotoOrientation.Normal;
        }

        int encodingRotation = ConvertDeviceOrientationToDegrees(
            GetCameraOrientation());

        if (IsCameraMirrored())
        {
            encodingRotation = (360 - encodingRotation) % 360;
        }

        return ConvertDegreesToPhotoOrientation(encodingRotation);
    }

    /// <summary>
    /// Gets the clockwise rotation to apply when encoding a video.
    /// </summary>
    public int GetCaptureVideoOrientation()
    {
        if (IsEnclosureLocationExternal(_cameraEnclosureLocation))
        {
            return 0;
        }

        int rotation = ConvertDeviceOrientationToDegrees(
            GetCameraOrientation());

        if (IsCameraMirrored())
        {
            rotation = (360 - rotation) % 360;
        }

        return rotation;
    }

    public void Dispose()
    {
        if (_orientationSensor != null)
        {
            _orientationSensor.OrientationChanged -=
                OrientationSensor_OrientationChanged;
        }
    }

    private static int ConvertDeviceOrientationToDegrees(
        SimpleOrientation orientation)
    {
        // TODO: This mapping from counterclockwise SimpleOrientation values
        // to clockwise degree values (for example, mapping
        // Rotated90DegreesCounterclockwise to 90) was carried over from the
        // original UWP sample this article is based on. Verify this mapping
        // against physical devices before relying on it in production; do
        // not change these values without hardware verification.
        return orientation switch
        {
            SimpleOrientation.Rotated90DegreesCounterclockwise => 90,
            SimpleOrientation.Rotated180DegreesCounterclockwise => 180,
            SimpleOrientation.Rotated270DegreesCounterclockwise => 270,
            _ => 0,
        };
    }

    private static VideoRotation ConvertSimpleOrientationToVideoRotation(
        SimpleOrientation orientation)
    {
        // TODO: See the verification note on ConvertDeviceOrientationToDegrees
        // above — this CCW-to-CW mapping needs the same device verification
        // before the values are changed.
        return orientation switch
        {
            SimpleOrientation.Rotated90DegreesCounterclockwise =>
                VideoRotation.Clockwise90Degrees,
            SimpleOrientation.Rotated180DegreesCounterclockwise =>
                VideoRotation.Clockwise180Degrees,
            SimpleOrientation.Rotated270DegreesCounterclockwise =>
                VideoRotation.Clockwise270Degrees,
            _ => VideoRotation.None,
        };
    }

    private static PhotoOrientation ConvertDegreesToPhotoOrientation(
        int degrees)
    {
        return degrees switch
        {
            90 => PhotoOrientation.Rotate90,
            180 => PhotoOrientation.Rotate180,
            270 => PhotoOrientation.Rotate270,
            _ => PhotoOrientation.Normal,
        };
    }
}

Use la clase auxiliar

Inicialice el asistente después de crear la instancia MediaCapture y de conocer la ubicación de la carcasa de la cámara:

private CameraRotationHelper _rotationHelper;
private MediaCapture _mediaCapture;

private async Task InitializeCameraAsync()
{
    _mediaCapture = new MediaCapture();
    await _mediaCapture.InitializeAsync();

    var cameraDevice = _mediaCapture.MediaCaptureSettings;

    // Find the camera device info to get enclosure location
    var devices = await DeviceInformation.FindAllAsync(
        DeviceClass.VideoCapture);
    var deviceInfo = devices.FirstOrDefault(
        d => d.Id == cameraDevice.VideoDeviceId);

    _rotationHelper = new CameraRotationHelper(
        deviceInfo?.EnclosureLocation);

    _rotationHelper.OrientationChanged += (s, e) =>
    {
        // Update preview rotation when device orientation changes
        DispatcherQueue.TryEnqueue(UpdatePreviewRotation);
    };
}

Aplicar rotación a la vista previa

Establece la rotación de la vista previa en tu instancia MediaCapture cuando cambie la orientación:

private void UpdatePreviewRotation()
{
    var rotation = _rotationHelper.GetCameraPreviewOrientation();
    _mediaCapture.SetPreviewRotation(rotation);
}

Aplicar rotación al capturar una foto

Establezca los metadatos de orientación al guardar una foto capturada:

using System.Collections.Generic;
using Windows.Storage;
using Windows.Storage.FileProperties;

private async Task CapturePhotoWithOrientationAsync()
{
    var file = await ApplicationData.Current.LocalFolder
        .CreateFileAsync("photo.jpg",
            CreationCollisionOption.GenerateUniqueName);

    await _mediaCapture.CapturePhotoToStorageFileAsync(
        ImageEncodingProperties.CreateJpeg(), file);

    // Set the orientation metadata. ImageProperties.Orientation is
    // read-only, so save the EXIF orientation value directly through
    // the file's property store instead.
    var photoOrientation =
        _rotationHelper.GetCapturePhotoOrientation();

    var propertiesToSave = new List<KeyValuePair<string, object>>
    {
        new KeyValuePair<string, object>(
            "System.Photo.Orientation", photoOrientation)
    };
    await file.Properties.SavePropertiesAsync(propertiesToSave);
}

Importante

ApplicationData.Current.LocalFolder requiere la identidad del paquete (MSIX). Las aplicaciones sin empaquetar no pueden usarse ApplicationData sin identidad de paquete. Para las aplicaciones sin empaquetar, use Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) u otra ruta de archivo Win32 en su lugar.

Note

SimpleOrientationSensor no está disponible en todos los dispositivos de escritorio. Comprueba si hay un valor de retorno null de SimpleOrientationSensor.GetDefault() y administra el caso en el que no haya ningún sensor de orientación. En el caso de las cámaras externas, la rotación suele ser NotRotated.