MediaCapture를 사용하여 디바이스 방향 처리

앱이 사용자 디바이스의 파일에 저장하거나 온라인 공유와 같이 앱 외부에서 볼 수 있는 사진 또는 비디오를 캡처하는 경우 다른 앱 또는 디바이스가 이미지를 표시할 때 올바르게 지향되도록 적절한 방향 메타데이터로 이미지를 인코딩하는 것이 중요합니다. 미디어 파일에 포함할 올바른 방향 데이터를 결정하는 것은 디바이스 섀시의 방향, 디스플레이 방향 및 섀시에 카메라 배치(전면 또는 후면 카메라인지 여부)를 포함하여 고려해야 할 몇 가지 변수가 있기 때문에 복잡한 작업이 될 수 있습니다.

방향 처리 프로세스를 간소화하려면 이 문서의 끝에 전체 정의가 제공되는 도우미 클래스 CameraRotationHelper를 사용하는 것이 좋습니다. 이 클래스를 프로젝트에 추가한 다음 이 문서의 단계에 따라 카메라 앱에 방향 지원을 추가할 수 있습니다. 도우미 클래스를 사용하면 카메라 UI의 컨트롤을 더 쉽게 회전하여 사용자의 관점에서 올바르게 렌더링할 수 있습니다.

참고 항목

이 문서는 MediaCapture를 사용하여 기본 사진, 비디오 및 오디오 캡처 문서에서 설명하는 코드 및 개념을 기반으로 합니다. 앱에 방향 지원을 추가하기 전에 MediaCapture 클래스를 사용하는 기본 개념을 숙지하는 것이 좋습니다.

이 문서에서 사용되는 네임스페이스

이 문서의 예제 코드는 코드에 포함해야 하는 다음 네임스페이스의 API를 사용합니다.

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

앱에 방향 지원을 추가하는 첫 번째 단계는 디바이스가 회전될 때 자동으로 회전하지 않도록 디스플레이를 잠그는 것입니다. 자동 UI 회전은 대부분의 앱 유형에서 잘 작동하지만 카메라 미리 보기가 회전할 때 사용자에게는 적합하지 않습니다. DisplayInformation.AutoRotationPreferences 속성을 DisplayOrientations.Landscape로 설정하여 표시 방향을 잠급니다.

DisplayInformation.AutoRotationPreferences = DisplayOrientations.Landscape;

카메라 디바이스 위치 추적

캡처된 미디어에 대한 올바른 방향을 계산하려면 앱이 섀시에서 카메라 디바이스의 위치를 결정해야 합니다. 부울 멤버 변수를 추가하여 카메라가 USB 웹 캠과 같은 디바이스 외부에 있는지 여부를 추적합니다. 미리 보기를 미러 여부를 추적하는 또 다른 부울 변수를 추가합니다. 즉, 전면 카메라를 사용하는 경우입니다. 또한 선택한 카메라를 나타내는 DeviceInformation 개체를 저장하기 위한 변수를 추가합니다.

private bool _externalCamera;
private bool _mirroringPreview;
DeviceInformation _cameraDevice;

카메라 디바이스를 선택하고 MediaCapture 개체 초기화

MediaCapture를 사용한 기본 사진, 비디오 및 오디오 캡처 문서에서는 몇 줄의 코드만으로 MediaCapture 개체를 초기화하는 방법을 보여줍니다. 카메라 방향을 지원하기 위해 초기화 프로세스에 몇 가지 단계를 더 추가합니다.

먼저 디바이스 선택기 내 DeviceClass.VideoCapture에 전달되는 DeviceInformation.FindAllAsync를 호출하여 사용 가능한 모든 비디오 캡처 디바이스 목록을 가져옵니다. 다음으로, 카메라의 패널 위치가 알려진 목록에서 첫 번째 디바이스를 선택하고 제공된 값과 일치하는 위치를 선택합니다. 이 예제에서는 전면 카메라입니다. 원하는 패널에 카메라가 없으면 사용 가능한 첫 번째 또는 기본 카메라가 사용됩니다.

카메라 디바이스가 발견되면 새 MediaCaptureInitializationSettings 개체가 생성되고 VideoDeviceId 속성이 선택한 디바이스로 설정됩니다. 다음으로 MediaCapture 개체를 만들고 InitializeAsync를 호출하여 설정 개체를 전달하여 선택한 카메라를 사용하도록 시스템에 지시합니다.

마지막으로 검사 선택한 디바이스 패널이 null인지 알 수 없는지 확인합니다. 이 경우 카메라가 외부에 있으므로 회전은 디바이스 회전과 관련이 없습니다. 패널이 알려져 있고 디바이스 섀시 전면에 있는 경우 미리 보기를 미러 알고 있으므로 이를 추적하는 변수가 설정됩니다.

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

CameraRotationHelper 클래스 초기화

이제 CameraRotationHelper 클래스를 사용하기 시작합니다. 개체를 저장할 클래스 멤버 변수를 선언합니다. 선택한 카메라의 엔클로저 위치를 전달하여 생성자를 호출합니다. 도우미 클래스는 이 정보를 사용하여 캡처된 미디어, 미리 보기 스트림 및 UI에 대한 올바른 방향을 계산합니다. 도우미 클래스의 OrientationChanged 이벤트에 대한 처리기를 등록합니다. 이 이벤트는 UI 또는 미리 보기 스트림의 방향을 업데이트해야 할 때 발생합니다.

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

카메라 미리 보기 스트림에 방향 데이터 추가

미리 보기 스트림의 메타데이터에 올바른 방향을 추가해도 사용자에게 미리 보기가 표시되는 방식에는 영향을 주지 않지만 시스템이 미리 보기 스트림에서 캡처한 프레임을 올바르게 인코딩하는 데 도움이 됩니다.

MediaCapture.StartPreviewAsync를 호출하여 카메라 미리 보기를 시작합니다. 이렇게 하기 전에 멤버 변수를 검사 미리 보기를 미러(전면 카메라용)해야 하는지 확인합니다. 이 경우 이 예제에서 PreviewControl이라는 CaptureElementFlowDirection 속성을 FlowDirection.RightToLeft로 설정합니다. 미리 보기를 시작한 후 도우미 메서드 SetPreviewRotationAsync를 호출하여 미리 보기 회전을 설정합니다. 다음은 이 방법의 구현한 것입니다.

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

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

미리 보기 스트림을 다시 초기화하지 않고 전화 방향이 변경될 때 업데이트할 수 있도록 미리 보기 회전을 별도의 방법으로 설정합니다. 카메라가 디바이스 외부에 있는 경우 아무 작업도 수행되지 않습니다. 그렇지 않으면 CameraRotationHelper 메서드 GetCameraPreviewOrientation이 호출되고 미리 보기 스트림에 대한 적절한 방향을 반환합니다.

메타데이터를 설정하기 위해 VideoDeviceController.GetMediaStreamProperties를 호출하여 미리 보기 스트림 속성을 검색합니다. 다음으로, 비디오 스트림 회전에 대한 MFT(Media Foundation Transform) 특성을 나타내는 GUID를 만듭니다. C++에서는 상수 MF_MT_VIDEO_ROTATION을 사용할 수 있지만 C#에서는 GUID 값을 수동으로 지정해야 합니다.

GUID를 키로 지정하고 미리 보기 회전을 값으로 지정하여 스트림 속성 개체에 속성 값을 추가합니다. 이 속성은 값이 시계 반대 방향 각도 단위로 예상되므로 CameraRotationHelper 메서드 ConvertSimpleOrientationToClockwiseDegrees를 사용하여 단순 방향 값을 변환합니다. 마지막으로 SetEncodingPropertiesAsync를 호출하여 스트림에 새 회전 속성을 적용합니다.

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

다음으로 CameraRotationHelper.OrientationChanged 이벤트에 대한 처리기를 추가합니다. 이 이벤트는 미리 보기 스트림을 회전해야 하는지 여부를 알려주는 인수를 전달합니다. 디바이스의 방향을 위로 향하거나 아래로 향하도록 변경한 경우 이 값은 false입니다. 미리 보기를 회전해야 하는 경우 이전에 정의된 SetPreviewRotationAsync를 호출합니다.

다음으로 OrientationChanged 이벤트 처리기에서 필요한 경우 UI를 업데이트합니다. GetUIOrientation을 호출하여 도우미 클래스에서 현재 권장되는 UI 방향을 얻고 값을 XAML 변환에 사용되는 시계 방향으로 변환합니다. 방향 값에서 RotateTransform을 만들고 XAML 컨트롤의 RenderTransform 속성을 설정합니다. UI 레이아웃에 따라 단순히 컨트롤을 회전하는 것 외에도 여기에서 추가 조정을 수행해야 할 수 있습니다. 또한 UI에 대한 모든 업데이트는 UI 스레드에서 수행되어야 하므로 이 코드를 RunAsync 호출 내에 배치해야 합니다.

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

방향 데이터를 사용하여 사진 캡처

MediaCapture를 사용한 기본 사진, 비디오 및 오디오 캡처 문서에서는 먼저 메모리 내 스트림에 캡처한 다음 디코더를 사용하여 스트림에서 이미지 데이터를 읽고 인코더를 사용하여 이미지 데이터를 파일로 트랜스코딩하여 파일에 사진을 캡처하는 방법을 보여줍니다. CameraRotationHelper 클래스에서 가져온 방향 데이터는 코드 변환 작업 중에 이미지 파일에 추가할 수 있습니다.

다음 예제에서는 CapturePhotoToStreamAsync를 호출하여 사진을 InMemoryRandomAccessStream에 캡처하고 스트림에서 BitmapDecoder를 만듭니다. 그런 다음, 파일에 쓰기 위해 IRandomAccessStream을 다시 처리하기 위해StorageFile이 만들어지고 열립니다.

파일을 코드 변환하기 전에 도우미 클래스 메서드 GetCameraCaptureOrientation에서 사진의 방향을 검색합니다. 이 메서드는 도우미 메서드 ConvertSimpleOrientationToPhotoOrientation을 사용하여 PhotoOrientation 개체로 변환되는 SimpleOrientation 개체를 반환합니다. 그런 다음 새 BitmapPropertySet 개체가 만들어지고 키가 "System.Photo.Orientation"이고 값이 BitmapTypedValue로 표현된 사진 방향인 속성이 추가됩니다. "System.Photo.Orientation"은 이미지 파일에 메타데이터로 추가할 수 있는 많은 Windows 속성 중 하나입니다. 모든 사진 관련 속성 목록은 Windows 속성 - 사진을 참조하세요. 이미지에서 메타데이터로 작업하는 방법에 대한 자세한 내용은 이미지 메타데이터를 참조하세요.

마지막으로, 방향 데이터를 포함하는 속성 집합은 SetPropertiesAsync를 호출하여 인코더에 대해 설정되고 이미지는 FlushAsync에 대한 호출로 트랜스코딩됩니다.

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

방향 데이터를 사용하여 비디오 캡처

기본 비디오 캡처는 MediaCapture를 사용한 기본 사진, 비디오 및 오디오 캡처 문서에 설명되어 있습니다. 캡처된 비디오의 인코딩에 방향 데이터를 추가하는 작업은 미리 보기 스트림에 방향 데이터를 추가하는 방법에 대한 섹션의 앞부분에서 설명한 것과 동일한 기술을 사용하여 수행됩니다.

다음 예제에서는 캡처된 비디오를 쓸 파일이 만들어집니다. MP4 인코딩 프로필은 정적 메서드 CreateMp4를 사용하여 만들어집니다. 비디오의 적절한 방향은 GetCameraCaptureOrientation 호출을 사용하여 CameraRotationHelper 클래스에서 가져옵니다. 회전 속성은 방향이 시계 반대 방향으로 표현되어야 하기 때문에 ConvertSimpleOrientationToClockwiseDegrees 도우미 메서드를 호출하여 방향 값을 변환합니다. 다음으로, 비디오 스트림 회전에 대한 MFT(Media Foundation Transform) 특성을 나타내는 GUID를 만듭니다. C++에서는 상수 MF_MT_VIDEO_ROTATION을 사용할 수 있지만 C#에서는 GUID 값을 수동으로 지정해야 합니다. GUID를 키로 지정하고 회전을 값으로 지정하여 스트림 속성 개체에 속성 값을 추가합니다. 마지막으로 StartRecordToStorageFileAsync를 호출하여 방향 데이터로 인코딩된 비디오 녹화를 시작합니다.

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

CameraRotationHelper 전체 코드 목록

다음 코드 조각은 하드웨어 방향 센서를 관리하고, 사진과 비디오에 대한 적절한 방향 값을 계산하고, 다른 Windows 기능에서 사용되는 방향의 다양한 표현 간에 변환하는 도우미 메서드를 제공하는 CameraRotationHelper 클래스에 대한 전체 코드를 나열합니다. 위의 문서에 나와 있는 지침을 따르는 경우 변경할 필요 없이 프로젝트에 이 클래스를 있는 그대로 추가할 수 있습니다. 물론 특정 시나리오의 요구 사항에 맞게 다음 코드를 자유롭게 사용자 지정할 수 있습니다.

이 도우미 클래스는 디바이스의 SimpleOrientationSensor를 사용하여 디바이스 섀시의 현재 방향을 확인하고 DisplayInformation 클래스를 사용하여 디스플레이의 현재 방향을 결정합니다. 이러한 각 클래스는 현재 방향이 변경될 때 발생하는 이벤트를 제공합니다. 캡처 디바이스가 탑재된 패널(전면, 후면 또는 외부)은 미리 보기 스트림을 미러 여부를 결정하는 데 사용됩니다. 또한 일부 디바이스에서 지원하는 EnclosureLocation.RotationAngleInDegreesClockwise 속성은 카메라가 섀시에 탑재되는 방향을 결정하는 데 사용됩니다.

다음 메서드를 사용하여 지정된 카메라 앱 작업에 권장되는 방향 값을 가져올 수 있습니다.

  • GetUIOrientation - 카메라 UI 요소에 대해 제안된 방향을 반환합니다.
  • GetCameraCaptureOrientation - 이미지 메타데이터로 인코딩하기 위해 제안된 방향을 반환합니다.
  • GetCameraPreviewOrientation - 자연스러운 사용자 환경을 제공하기 위해 미리 보기 스트림에 대해 제안된 방향을 반환합니다.
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;
        }
    }
}