Hantera enhetsorientering med MediaCapture

När din app samlar in ett foto eller en video för användning utanför appen, till exempel spara till en fil eller delning, måste du koda bilden med rätt orienteringsmetadata så att innehållet visas korrekt i andra appar och enheter. Den här artikeln visar hur du använder en hjälpklass för att hantera kameraorientering i en WinUI 3-skrivbordsapp.

Förutsättningar

Innan du kan börja bör du kontrollera att du har:

  • Ett WinUI 3-skrivbordsprojekt (paketerat eller uppackat).
  • En kompatibel kamera som är ansluten till enheten.
  • Kameraåtkomst deklarerad för din app:
    • Paketerade appar: Lägg till enhetsfunktionen i webcamPackage.appxmanifest:

      <Capabilities>
          <DeviceCapability Name="webcam" />
      </Capabilities>
      
    • Opaketerade appar: Det finns ingen manifestbehörighet att deklarera. Kameraåtkomst styrs i stället av inställningen Låt skrivbordsappar komma åt kameran under Inställningar>Sekretess och säkerhetskamera> på användarens enhet.

Användaren kan fortfarande neka kameraåtkomst på OS-nivå i båda fallen. Kontrollera åtkomsten innan du initierar kameran och hantera det nekade ärendet på ett smidigt sätt, enligt beskrivningen i Hantera sekretessinställningen för Windows kamera.

Orienteringsbegrepp

Skrivbordsappar körs vanligtvis på enheter med fasta skärmar, så du behöver inte hantera kontinuerlig rotation som en mobilapp gör. Du måste dock fortfarande ta hänsyn till:

  • Kamerasensororientering – Kamerasensorns fysiska monteringsvinkel, som varierar beroende på enhet.
  • Rotation av extern kamera – Externa webbkameror kan roteras av användaren.

Huvudidén är att tillämpa en rotationskorrigering när du kodar ett avbildat foto eller en video så att utdata matchar vad användaren ser i förhandsversionen.

Skapa en CameraRotationHelper-klass

Följande hjälpklass hanterar rotationsvärden baserat på kamerans sensororientering och enhetens orienteringssensorer:

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,
        };
    }
}

Använda hjälpklassen

Initialisera hjälpfunktionen när du har skapat din instans av MediaCapture och känner till platsen för kamerans hölje:

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);
    };
}

Tillämpa rotation på förhandsversionen

Ange förhandsgranskningsrotationen på din MediaCapture instans när orienteringen ändras:

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

Använd rotation när du tar ett foto

Ange orienteringsmetadata när du sparar ett avbildat foto:

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);
}

Important

ApplicationData.Current.LocalFolder kräver paketidentitet (MSIX). Appar utan paket kan inte använda ApplicationData utan paketidentitet. För opacketerade appar använder du Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) eller någon annan Win32-filsökväg i stället.

Note

SimpleOrientationSensor är inte tillgängligt på alla skrivbordsenheter. Sök efter en null retur från SimpleOrientationSensor.GetDefault() och hantera fallet där ingen orienteringssensor finns. För externa kameror är rotation vanligtvis NotRotated.