Freigeben über


Handhaben der Geräte- und Bildschirmausrichtung mit „MediaCapture“

Wenn Ihre App ein Foto oder Video aufnimmt, das außerhalb der App angezeigt werden soll, z. B. in eine Datei auf dem Gerät des Benutzers gespeichert oder online freigegeben werden soll, ist es wichtig, dass das Bild mit den richtigen Ausrichtungsmetadaten codiert wird. Dadurch wird bei der Anzeige durch eine andere App oder ein anderes Gerät das Bild korrekt ausgerichtet. Die Bestimmung der korrekten Ausrichtungsdaten für eine Mediendatei kann eine komplexe Aufgabe sein, da verschiedene Variablen berücksichtigt werden müssen. Dazu zählen die Ausrichtung des Geräte-Chassis, die Ausrichtung der Anzeige und die Platzierung der Kamera (nach vorne oder nach hinten gerichtete Kamera).

Um die Handhabung der Ausrichtung zu vereinfachen, empfehlen wir die Verwendung der Hilfsklasse CameraRotationHelper, deren vollständige Definition am Ende dieses Artikels bereitgestellt wird. Sie können diese Klasse dem Projekt hinzufügen und dann die Schritte zum Hinzufügen von Ausrichtungsunterstützung zur Kamera-App in diesem Artikel ausführen. Die Hilfsklasse erleichtert auch das Drehen der Steuerelemente in der Kamera-UI, damit sie aus Sicht des Benutzers korrekt gerendert werden.

Hinweis

Dieser Artikel baut auf Konzepten und Code auf, die im Artikel Allgemeine Foto-, Video- und Audioaufnahme mit „MediaCapture“ erläutert werden. Es wird empfohlen, dass Sie sich mit den grundlegenden Konzepten der Verwendung der MediaCapture-Klasse vertraut machen, bevor Sie die Ausrichtungsunterstützung zur App hinzufügen.

In diesem Artikel verwendete Namespaces

Der Beispielcode in diesem Artikel verwendet APIs aus den folgenden Namespaces, die Sie in den Code einschließen sollten.

using Windows.Devices.Enumeration;
using Windows.UI.Core;

Der erste Schritt beim Hinzufügen der Ausrichtungsunterstützung zur App ist das Sperren der Anzeige, sodass diese sich beim Drehen des Geräts nicht automatisch dreht. Die automatische UI-Drehung eignet sich gut für die meisten Arten von Apps. Es ist jedoch für Benutzer nicht intuitiv, wenn sich die Kameravorschau dreht. Sperren Sie die Bildschirmausrichtung, indem Sie die DisplayInformation.AutoRotationPreferences-Eigenschaft auf DisplayOrientations.Landscape festlegen.

DisplayInformation.AutoRotationPreferences = DisplayOrientations.Landscape;

Nachverfolgen der Position des Kamerageräts

Um die richtige Ausrichtung für aufgenommene Medien zu berechnen, muss die App die Position des Kamerageräts am Chassis ermitteln. Fügen Sie eine boolesche Membervariable hinzu, um nachzuverfolgen, ob es sich um eine externe Kamera handelt, z. B. eine USB-Webcam. Fügen Sie eine weitere boolesche Variable hinzu, um nachzuverfolgen, ob die Vorschau gespiegelt werden soll. Dies ist bei der Verwendung einer nach vorne gerichteten Kamera der Fall. Fügen Sie außerdem eine Variable zum Speichern eines DeviceInformation-Objekts hinzu, das die ausgewählte Kamera darstellt.

private bool _externalCamera;
private bool _mirroringPreview;
DeviceInformation _cameraDevice;

Auswählen eines Kamerageräts und Initialisieren des MediaCapture-Objekts

Im Artikel Allgemeine Foto-, Video- und Audioaufnahme mit „MediaCapture“ wird beschrieben, wie Sie das MediaCapture-Objekt mit nur wenigen Codezeilen initialisieren. Um die Ausrichtung der Kamera zu unterstützen, fügen wir dem Initialisierungsprozess weitere Schritte hinzu.

Rufen Sie zuerst DeviceInformation.FindAllAsync auf, und übergeben Sie die Geräteauswahl DeviceClass.VideoCapture, um eine Liste aller verfügbaren Videoaufzeichnungsgeräte abzurufen. Wählen Sie dann das erste Gerät in der Liste aus, für das die Bereichsposition der Kamera bekannt ist und das mit dem angegebenen Wert übereinstimmt, in diesem Beispiel eine nach vorne gerichtete Kamera. Wird keine Kamera im gewünschten Bereich gefunden, wird die erste oder standardmäßig verfügbare Kamera verwendet.

Wenn ein Kameragerät gefunden wird, wird ein neues MediaCaptureInitializationSettings-Objekt erstellt, und die VideoDeviceId-Eigenschaft wird auf das ausgewählte Gerät festgelegt. Erstellen Sie als Nächstes das MediaCapture-Objekt, rufen Sie InitializeAsync auf, und übergeben Sie das Einstellungsobjekt, um dem System mitzuteilen, dass die ausgewählte Kamera verwendet werden soll.

Überprüfen Sie abschließend, ob der ausgewählte Gerätebereich null oder unbekannt ist. In diesem Fall ist die Kamera extern. Dies bedeutet, dass ihre Drehung ohne Bezug auf die Drehung des Geräts ist. Wenn der Bereich bekannt ist und sich an der Vorderseite des Geräte-Chassis befindet, wissen wir, dass die Vorschau gespiegelt werden soll. Die Variable zur Nachverfolgung ist also festgelegt.

var allVideoDevices = await DeviceInformation.FindAllAsync(DeviceClass.VideoCapture);
DeviceInformation desiredDevice = allVideoDevices.FirstOrDefault(x => x.EnclosureLocation != null 
    && x.EnclosureLocation.Panel == Windows.Devices.Enumeration.Panel.Front);
_cameraDevice = desiredDevice ?? allVideoDevices.FirstOrDefault();


if (_cameraDevice == null)
{
    System.Diagnostics.Debug.WriteLine("No camera device found!");
    return;
}

var settings = new MediaCaptureInitializationSettings { VideoDeviceId = _cameraDevice.Id };

mediaCapture = new MediaCapture();
mediaCapture.RecordLimitationExceeded += MediaCapture_RecordLimitationExceeded;
mediaCapture.Failed += MediaCapture_Failed;

try
{
    await mediaCapture.InitializeAsync(settings);
}
catch (UnauthorizedAccessException)
{
    System.Diagnostics.Debug.WriteLine("The app was denied access to the camera");
    return;
}

// Handle camera device location
if (_cameraDevice.EnclosureLocation == null || 
    _cameraDevice.EnclosureLocation.Panel == Windows.Devices.Enumeration.Panel.Unknown)
{
    _externalCamera = true;
}
else
{
    _externalCamera = false;
    _mirroringPreview = (_cameraDevice.EnclosureLocation.Panel == Windows.Devices.Enumeration.Panel.Front);
}

Initialisieren der CameraRotationHelper-Klasse

Wir beginnen nun mit der Verwendung der CameraRotationHelper-Klasse. Deklarieren Sie eine Klassenmembervariable zum Speichern des Objekts. Rufen Sie den Konstruktor auf, und übergeben Sie die Gehäuseposition der ausgewählten Kamera. Die Hilfsklasse verwendet diese Informationen, um die richtige Ausrichtung für aufgenommene Medien, den Vorschaustream und die Benutzeroberfläche zu berechnen. Registrieren Sie einen Handler für das OrientationChanged-Ereignis der Hilfsklasse, das ausgelöst wird, wenn die Ausrichtung der Benutzeroberfläche oder des Vorschaustreams aktualisiert werden muss.

private CameraRotationHelper _rotationHelper;
_rotationHelper = new CameraRotationHelper(_cameraDevice.EnclosureLocation);
_rotationHelper.OrientationChanged += RotationHelper_OrientationChanged;

Hinzufügen von Ausrichtungsdaten zum Kameravorschau-Stream

Das Hinzufügen der richtigen Ausrichtung zu den Metadaten des Vorschaustreams hat keinen Einfluss auf die Anzeige der Vorschau für Benutzer, erleichtert jedoch die richtige Codierung von aus dem Vorschaustream erfassten Frames durch das System.

Starten Sie die Kameravorschau durch Aufrufen von MediaCapture.StartPreviewAsync. Überprüfen Sie zuvor die Membervariable, um festzustellen, ob die Vorschau gespiegelt werden soll (für eine nach vorne gerichtete Kamera). Wenn dies der Fall ist, legen Sie die FlowDirection-Eigenschaft von CaptureElement, das in diesem Beispiel den Namen PreviewControl hat, auf FlowDirection.RightToLeft fest. Rufen Sie nach dem Starten der Vorschau die Hilfsmethode SetPreviewRotationAsync auf, um die Drehung der Vorschau festzulegen. Es folgt die Implementierung dieser Methode.

PreviewControl.Source = mediaCapture;
PreviewControl.FlowDirection = _mirroringPreview ? FlowDirection.RightToLeft : FlowDirection.LeftToRight;

await mediaCapture.StartPreviewAsync();
await SetPreviewRotationAsync();

Wir legen die Vorschaudrehung in einer separaten Methode fest, sodass sie bei Änderung der Telefonausrichtung aktualisiert werden kann, ohne den Vorschaustream zu initialisieren. Wenn es sich um eine externe Kamera handelt, wird keine Aktion ausgeführt. Andernfalls wird die CameraRotationHelper-Methode GetCameraPreviewOrientation aufgerufen und gibt die richtige Ausrichtung für den Vorschaustream zurück.

Zum Festlegen der Metadaten werden die Vorschaustreameigenschaften durch Aufrufen von VideoDeviceController.GetMediaStreamProperties abgerufen. Erstellen Sie als Nächstes die GUID, die das Media Foundation Transform (MFT)-Attribut für die Drehung des Videostreams darstellt. In C++ können Sie die Konstante MF_MT_VIDEO_ROTATION verwenden, während Sie in C# den GUID-Wert manuell angeben müssen.

Fügen Sie dem Datenstromeigenschaftenobjekt einen Eigenschaftswert hinzu, und geben Sie die GUID als Schlüssel und die Vorschaudrehung als Wert an. Diese Eigenschaft erwartet Werte in Einheiten von Grad entgegen dem Uhrzeigersinn, sodass mithilfe der CameraRotationHelper-Methode ConvertSimpleOrientationToClockwiseDegrees der einfache Ausrichtungswert konvertiert wird. Rufen Sie abschließend SetEncodingPropertiesAsync auf, um die neue Drehungseigenschaft auf den Stream anzuwenden.

private async Task SetPreviewRotationAsync()
{
    if (!_externalCamera)
    {
        // Add rotation metadata to the preview stream to make sure the aspect ratio / dimensions match when rendering and getting preview frames
        var rotation = _rotationHelper.GetCameraPreviewOrientation();
        var props = mediaCapture.VideoDeviceController.GetMediaStreamProperties(MediaStreamType.VideoPreview);
        Guid RotationKey = new Guid("C380465D-2271-428C-9B83-ECEA3B4A85C1");
        props.Properties.Add(RotationKey, CameraRotationHelper.ConvertSimpleOrientationToClockwiseDegrees(rotation));
        await mediaCapture.SetEncodingPropertiesAsync(MediaStreamType.VideoPreview, props, null);
    }
}

Fügen Sie als Nächstes den Handler für das CameraRotationHelper.OrientationChanged-Ereignis hinzu. Dieses Ereignis übergibt ein Argument, das Ihnen mitteilt, ob der Vorschaustream gedreht werden muss. Wenn die Ausrichtung des Geräts nach oben oder unten geändert wurde, ist dieser Wert „false“. Wenn die Vorschau gedreht werden muss, rufen Sie SetPreviewRotationAsync auf, die zuvor definiert wurde.

Aktualisieren Sie als Nächstes im OrientationChanged-Ereignishandler bei Bedarf die Benutzeroberfläche. Rufen Sie die aktuelle empfohlene UI-Ausrichtung durch Aufrufen von GetUIOrientation aus der Hilfsklasse ab, und konvertieren Sie den Wert in Grad im Uhrzeigersinn für XAML-Transformationen. Erstellen Sie eine RotateTransform aus dem Ausrichtungswert, und legen Sie die RenderTransform-Eigenschaft der XAML-Steuerelemente fest. Je nach dem UI-Layout müssen Sie möglicherweise zusätzliche Anpassungen zum einfachen Drehen der Steuerelemente vornehmen. Beachten Sie außerdem, dass alle Aktualisierungen der Benutzeroberfläche im UI-Thread vorgenommen werden müssen. Daher sollten Sie diesen Code innerhalb eines Aufrufs von RunAsync platzieren.

private async void RotationHelper_OrientationChanged(object sender, bool updatePreview)
{
    if (updatePreview)
    {
        await SetPreviewRotationAsync();
    }
    await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () => {
        // Rotate the buttons in the UI to match the rotation of the device
        var angle = CameraRotationHelper.ConvertSimpleOrientationToClockwiseDegrees(_rotationHelper.GetUIOrientation());
        var transform = new RotateTransform { Angle = angle };

        // The RenderTransform is safe to use (i.e. it won't cause layout issues) in this case, because these buttons have a 1:1 aspect ratio
        CapturePhotoButton.RenderTransform = transform;
        CapturePhotoButton.RenderTransform = transform;
    });
}

Aufnehmen eines Fotos mit Ausrichtungsdaten

Im Artikel Allgemeine Foto-, Video- und Audioaufnahme mit „MediaCapture“ wird erläutert, wie Sie ein Foto in einer Datei aufnehmen, indem Sie zuerst in einen In-Memory-Datenstrom aufnehmen und dann mithilfe eines Decoders die Bilddaten aus dem Datenstrom lesen und mithilfe eines Encoders die Bilddaten in eine Datei transcodieren. Aus der CameraRotationHelper-Klasse abgerufene Ausrichtungsdaten können während des Transcodierungsvorgangs der Bilddatei hinzugefügt werden.

Im folgenden Beispiel wird ein Foto in einem InMemoryRandomAccessStream mit einem Aufruf von CapturePhotoToStreamAsync aufgenommen, und ein BitmapDecoder wird aus dem Stream erstellt. Als Nächstes wird eine StorageFile erstellt und geöffnet, um einen IRandomAccessStream zum Schreiben in die Datei abzurufen.

Vor dem Transcodieren der Datei wird die Ausrichtung des Fotos aus der Hilfsklassenmethode GetCameraCaptureOrientation abgerufen. Diese Methode gibt ein SimpleOrientation-Objekt zurück, das mit der Hilfsmethode ConvertSimpleOrientationToPhotoOrientation in ein PhotoOrientation-Objekt konvertiert wird. Als Nächstes wird ein neues BitmapPropertySet-Objekt erstellt und eine Eigenschaft hinzugefügt, wobei der Schlüssel „System.Photo.Orientation“ und der Wert die Fotoausrichtung ist, ausgedrückt als BitmapTypedValue. „System.Photo.Orientation“ ist eine der vielen Windows-Eigenschaften, die als Metadaten einer Bilddatei hinzugefügt werden können. Eine Liste aller fotobezogenen Eigenschaften finden Sie unter Windows-Eigenschaften – Foto. Weitere Informationen zum Arbeiten mit Metadaten in Bildern finden Sie unter Bildmetadaten.

Schließlich wird der Eigenschaftensatz, der die Ausrichtungsdaten enthält, für den Encoder mit einem Aufruf von SetPropertiesAsync festgelegt, und das Bild wird mit einem Aufruf von FlushAsync transcodiert.

private async Task CapturePhotoWithOrientationAsync()
{
    var captureStream = new InMemoryRandomAccessStream();

    try
    {
        await mediaCapture.CapturePhotoToStreamAsync(ImageEncodingProperties.CreateJpeg(), captureStream);
    }
    catch (Exception ex)
    {
        System.Diagnostics.Debug.WriteLine("Exception when taking a photo: {0}", ex.ToString());
        return;
    }


    var decoder = await BitmapDecoder.CreateAsync(captureStream);
    var file = await KnownFolders.PicturesLibrary.CreateFileAsync("SimplePhoto.jpeg", CreationCollisionOption.GenerateUniqueName);

    using (var outputStream = await file.OpenAsync(FileAccessMode.ReadWrite))
    {
        var encoder = await BitmapEncoder.CreateForTranscodingAsync(outputStream, decoder);
        var photoOrientation = CameraRotationHelper.ConvertSimpleOrientationToPhotoOrientation(
            _rotationHelper.GetCameraCaptureOrientation());
        var properties = new BitmapPropertySet {
            { "System.Photo.Orientation", new BitmapTypedValue(photoOrientation, PropertyType.UInt16) } };
        await encoder.BitmapProperties.SetPropertiesAsync(properties);
        await encoder.FlushAsync();
    }
}

Aufnehmen eines Videos mit Ausrichtungsdaten

Die allgemeine Videoaufnahme wird im Artikel Allgemeine Foto-, Video- und Audioaufnahme mit „MediaCapture“ beschrieben. Das Hinzufügen von Ausrichtungsdaten zur Codierung des aufgenommenen Videos erfolgt mit dem gleichen Verfahrens wie weiter oben im Abschnitt zum Hinzufügen von Ausrichtungsdaten zum Vorschaustream beschrieben.

Im folgenden Beispiel wird eine Datei erstellt, in die das aufgenommene Video geschrieben wird. Ein MP4-Codierungsprofil wird mithilfe der statischen CreateMp4-Methode erstellt. Die richtige Ausrichtung für das Video wird aus der CameraRotationHelper-Klasse mit einem Aufruf von GetCameraCaptureOrientation abgerufen. Da die Drehungseigenschaft erfordert, dass die Ausrichtung in Grad gegen den Uhrzeigersinn ausgedrückt wird, wird die ConvertSimpleOrientationToClockwiseDegrees-Hilfsmethode zum Konvertieren des Ausrichtungswerts aufgerufen. Erstellen Sie als Nächstes die GUID, die das Media Foundation Transform (MFT)-Attribut für die Drehung des Videostreams darstellt. In C++ können Sie die Konstante MF_MT_VIDEO_ROTATION verwenden, während Sie in C# den GUID-Wert manuell angeben müssen. Fügen Sie dem Datenstromeigenschaftenobjekt einen Eigenschaftswert hinzu, und geben Sie die GUID als Schlüssel und die Drehung als Wert an. Rufen Sie schließlich StartRecordToStorageFileAsync auf, um die mit Ausrichtungsdaten codierte Videoaufnahme zu beginnen.

private async Task StartRecordingWithOrientationAsync()
{
    try
    {
        var videoFile = await KnownFolders.VideosLibrary.CreateFileAsync("SimpleVideo.mp4", CreationCollisionOption.GenerateUniqueName);

        var encodingProfile = MediaEncodingProfile.CreateMp4(VideoEncodingQuality.Auto);

        var rotationAngle = CameraRotationHelper.ConvertSimpleOrientationToClockwiseDegrees(
            _rotationHelper.GetCameraCaptureOrientation());
        Guid RotationKey = new Guid("C380465D-2271-428C-9B83-ECEA3B4A85C1");
        encodingProfile.Video.Properties.Add(RotationKey, PropertyValue.CreateInt32(rotationAngle));

        await mediaCapture.StartRecordToStorageFileAsync(encodingProfile, videoFile);
    }
    catch (Exception ex)
    {
        System.Diagnostics.Debug.WriteLine("Exception when starting video recording: {0}", ex.ToString());
    }
}

Vollständiger Code für CameraRotationHelper

Der folgende Codeausschnitt führt den vollständigen Code für die CameraRotationHelper-Klasse auf, mit der die Hardwareausrichtungssensoren verwaltet, die richtigen Ausrichtungswerte für Fotos und Videos berechnet und Hilfsmethoden zum Konvertieren zwischen den unterschiedlichen Darstellungsformen der Ausrichtung bereitgestellt werden, die von verschiedenen Windows-Features verwendet werden. Wenn Sie die im oben genannten Artikel gezeigten Anleitungen befolgen, können Sie diese Klasse dem Projekt in der vorliegenden Form hinzufügen, ohne Änderungen vornehmen zu müssen. Natürlich können Sie den folgenden Code an die Anforderungen des jeweiligen Szenarios anpassen.

Diese Hilfsklasse verwendet den SimpleOrientationSensor des Geräts zum Ermitteln der aktuellen Ausrichtung des Geräte-Chassis und die DisplayInformation-Klasse zum Ermitteln der aktuellen Ausrichtung der Anzeige. Alle diese Klassen bieten Ereignisse, die ausgelöst werden, wenn sich die aktuelle Ausrichtung ändert. Die Seite, auf der sich das Aufnahmegerät befindet – nach vorne gerichtet, nach hinten gerichtet oder extern –, wird verwendet, um festzustellen, ob der Vorschaustream gespiegelt werden soll. Außerdem wird die EnclosureLocation.RotationAngleInDegreesClockwise-Eigenschaft verwendet, die von einigen Geräten unterstützt wird, um die Ausrichtung zu bestimmen, in der die Kamera auf dem Chassis montiert ist.

Mit den folgenden Methoden können empfohlene Ausrichtungswerte für die angegebenen Kamera-App-Aufgaben abgerufen werden:

  • GetUIOrientation – Gibt die vorgeschlagene Ausrichtung für Kamera-UI-Elemente zurück.
  • GetCameraCaptureOrientation – Gibt die vorgeschlagene Ausrichtung für die Codierung in Bildmetadaten zurück.
  • GetCameraPreviewOrientation – Gibt die vorgeschlagene Ausrichtung für den Vorschaustream zum Bereitstellen einer natürlicheren Benutzererfahrung zurück.
class CameraRotationHelper
{
    private EnclosureLocation _cameraEnclosureLocation;
    private DisplayInformation _displayInformation = DisplayInformation.GetForCurrentView();
    private SimpleOrientationSensor _orientationSensor = SimpleOrientationSensor.GetDefault();
    public event EventHandler<bool> OrientationChanged;

    public CameraRotationHelper(EnclosureLocation cameraEnclosureLocation)
    {
        _cameraEnclosureLocation = cameraEnclosureLocation;
        if (!IsEnclosureLocationExternal(_cameraEnclosureLocation))
        {
            _orientationSensor.OrientationChanged += SimpleOrientationSensor_OrientationChanged;
        }
        _displayInformation.OrientationChanged += DisplayInformation_OrientationChanged;
    }

    private void SimpleOrientationSensor_OrientationChanged(SimpleOrientationSensor sender, SimpleOrientationSensorOrientationChangedEventArgs args)
    {
        if (args.Orientation != SimpleOrientation.Faceup && args.Orientation != SimpleOrientation.Facedown)
        {
            HandleOrientationChanged(false);
        }
    }

    private void DisplayInformation_OrientationChanged(DisplayInformation sender, object args)
    {
        HandleOrientationChanged(true);
    }

    private void HandleOrientationChanged(bool updatePreviewStreamRequired)
    {
        var handler = OrientationChanged;
        if (handler != null)
        {
            handler(this, updatePreviewStreamRequired);
        }
    }

    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 default
        return (_cameraEnclosureLocation.Panel == Windows.Devices.Enumeration.Panel.Front);
    }

    private SimpleOrientation GetCameraOrientationRelativeToNativeOrientation()
    {
        // Get the rotation angle of the camera enclosure
        return ConvertClockwiseDegreesToSimpleOrientation((int)_cameraEnclosureLocation.RotationAngleInDegreesClockwise);
    }

    // Gets the rotation to rotate ui elements
    public SimpleOrientation GetUIOrientation()
    {
        if (IsEnclosureLocationExternal(_cameraEnclosureLocation))
        {
            // Cameras that are not attached to the device do not rotate along with it, so apply no rotation
            return SimpleOrientation.NotRotated;
        }

        // Return the difference between the orientation of the device and the orientation of the app display
        var deviceOrientation = _orientationSensor.GetCurrentOrientation();
        var displayOrientation = ConvertDisplayOrientationToSimpleOrientation(_displayInformation.CurrentOrientation);
        return SubOrientations(displayOrientation, deviceOrientation);
    }

    // Gets the rotation of the camera to rotate pictures/videos when saving to file
    public SimpleOrientation GetCameraCaptureOrientation()
    {
        if (IsEnclosureLocationExternal(_cameraEnclosureLocation))
        {
            // Cameras that are not attached to the device do not rotate along with it, so apply no rotation
            return SimpleOrientation.NotRotated;
        }

        // Get the device orienation offset by the camera hardware offset
        var deviceOrientation = _orientationSensor.GetCurrentOrientation();
        var result = SubOrientations(deviceOrientation, GetCameraOrientationRelativeToNativeOrientation());

        // If the preview is being mirrored for a front-facing camera, then the rotation should be inverted
        if (IsCameraMirrored())
        {
            result = MirrorOrientation(result);
        }
        return result;
    }

    // Gets the rotation of the camera to display the camera preview
    public SimpleOrientation GetCameraPreviewOrientation()
    {
        if (IsEnclosureLocationExternal(_cameraEnclosureLocation))
        {
            // Cameras that are not attached to the device do not rotate along with it, so apply no rotation
            return SimpleOrientation.NotRotated;
        }

        // Get the app display rotation offset by the camera hardware offset
        var result = ConvertDisplayOrientationToSimpleOrientation(_displayInformation.CurrentOrientation);
        result = SubOrientations(result, GetCameraOrientationRelativeToNativeOrientation());

        // If the preview is being mirrored for a front-facing camera, then the rotation should be inverted
        if (IsCameraMirrored())
        {
            result = MirrorOrientation(result);
        }
        return result;
    }

    public static PhotoOrientation ConvertSimpleOrientationToPhotoOrientation(SimpleOrientation orientation)
    {
        switch (orientation)
        {
            case SimpleOrientation.Rotated90DegreesCounterclockwise:
                return PhotoOrientation.Rotate90;
            case SimpleOrientation.Rotated180DegreesCounterclockwise:
                return PhotoOrientation.Rotate180;
            case SimpleOrientation.Rotated270DegreesCounterclockwise:
                return PhotoOrientation.Rotate270;
            case SimpleOrientation.NotRotated:
            default:
                return PhotoOrientation.Normal;
        }
    }

    public static int ConvertSimpleOrientationToClockwiseDegrees(SimpleOrientation orientation)
    {
        switch (orientation)
        {
            case SimpleOrientation.Rotated90DegreesCounterclockwise:
                return 270;
            case SimpleOrientation.Rotated180DegreesCounterclockwise:
                return 180;
            case SimpleOrientation.Rotated270DegreesCounterclockwise:
                return 90;
            case SimpleOrientation.NotRotated:
            default:
                return 0;
        }
    }

    private SimpleOrientation ConvertDisplayOrientationToSimpleOrientation(DisplayOrientations orientation)
    {
        SimpleOrientation result;
        switch (orientation)
        {
            case DisplayOrientations.Landscape:
                result = SimpleOrientation.NotRotated;
                break;
            case DisplayOrientations.PortraitFlipped:
                result = SimpleOrientation.Rotated90DegreesCounterclockwise;
                break;
            case DisplayOrientations.LandscapeFlipped:
                result = SimpleOrientation.Rotated180DegreesCounterclockwise;
                break;
            case DisplayOrientations.Portrait:
            default:
                result = SimpleOrientation.Rotated270DegreesCounterclockwise;
                break;
        }

        // Above assumes landscape; offset is needed if native orientation is portrait
        if (_displayInformation.NativeOrientation == DisplayOrientations.Portrait)
        {
            result = AddOrientations(result, SimpleOrientation.Rotated90DegreesCounterclockwise);
        }

        return result;
    }

    private static SimpleOrientation MirrorOrientation(SimpleOrientation orientation)
    {
        // This only affects the 90 and 270 degree cases, because rotating 0 and 180 degrees is the same clockwise and counter-clockwise
        switch (orientation)
        {
            case SimpleOrientation.Rotated90DegreesCounterclockwise:
                return SimpleOrientation.Rotated270DegreesCounterclockwise;
            case SimpleOrientation.Rotated270DegreesCounterclockwise:
                return SimpleOrientation.Rotated90DegreesCounterclockwise;
        }
        return orientation;
    }

    private static SimpleOrientation AddOrientations(SimpleOrientation a, SimpleOrientation b)
    {
        var aRot = ConvertSimpleOrientationToClockwiseDegrees(a);
        var bRot = ConvertSimpleOrientationToClockwiseDegrees(b);
        var result = (aRot + bRot) % 360;
        return ConvertClockwiseDegreesToSimpleOrientation(result);
    }

    private static SimpleOrientation SubOrientations(SimpleOrientation a, SimpleOrientation b)
    {
        var aRot = ConvertSimpleOrientationToClockwiseDegrees(a);
        var bRot = ConvertSimpleOrientationToClockwiseDegrees(b);
        //add 360 to ensure the modulus operator does not operate on a negative
        var result = (360 + (aRot - bRot)) % 360;
        return ConvertClockwiseDegreesToSimpleOrientation(result);
    }

    private static VideoRotation ConvertSimpleOrientationToVideoRotation(SimpleOrientation orientation)
    {
        switch (orientation)
        {
            case SimpleOrientation.Rotated90DegreesCounterclockwise:
                return VideoRotation.Clockwise270Degrees;
            case SimpleOrientation.Rotated180DegreesCounterclockwise:
                return VideoRotation.Clockwise180Degrees;
            case SimpleOrientation.Rotated270DegreesCounterclockwise:
                return VideoRotation.Clockwise90Degrees;
            case SimpleOrientation.NotRotated:
            default:
                return VideoRotation.None;
        }
    }

    private static SimpleOrientation ConvertClockwiseDegreesToSimpleOrientation(int orientation)
    {
        switch (orientation)
        {
            case 270:
                return SimpleOrientation.Rotated90DegreesCounterclockwise;
            case 180:
                return SimpleOrientation.Rotated180DegreesCounterclockwise;
            case 90:
                return SimpleOrientation.Rotated270DegreesCounterclockwise;
            case 0:
            default:
                return SimpleOrientation.NotRotated;
        }
    }
}