MediaCapture を使ってデバイスの向きを処理する

アプリ外での表示を目的とする写真やビデオ (ユーザーのデバイスにファイルを保存する場合や、オンラインで共有する場合など) をアプリでキャプチャする際は、別のアプリやデバイスで画像を表示するときに正しい向きで表示されるよう、適切な向きのメタデータを使って画像をエンコーディングすることが重要です。 メディア ファイルにどの向きのデータを含めれば良いか特定するのは複雑な作業です。これは、デバイス シャーシの向き、ディスプレイの向き、シャーシ上のカメラの位置 (全面カメラか背面カメラか) など、考慮すべき変数が複数あるためです。

そこで、向きの処理のプロセスを簡略化するために、CameraRotationHelper というヘルパー クラスを使うことをお勧めします。完全な定義についてはこの記事の最後に記載しています。 このクラスをプロジェクトに追加し、本記事の手順に従えば、カメラ アプリに向きのサポートを追加することができます。 また、このヘルパー クラスによって、カメラ UI のコントロールの回転が容易になり、ユーザーの視点から正しくレンダリングされるようになります。

注意

この記事の内容は、「MediaCapture を使った基本的な写真、ビデオ、およびオーディオのキャプチャ」で取り上げたコードや概念に基づいています。 MediaCapture クラスの使用についての基本概念を理解してから、向きのサポートをアプリに追加することをお勧めします。

この記事で使われている名前空間

この記事のコード例では、次の名前空間の API を使っています。自分でコードを記述する際はこれらを含める必要があります。

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

アプリに向きのサポートを追加するにはまず、ディスプレイをロックして、デバイスの回転時にディスプレイが自動的に回転しないようにします。 UI の自動回転はほとんどの種類のアプリに適していますが、カメラ プレビューについては、回転するとユーザーが操作しにくくなります。 DisplayInformation.AutoRotationPreferences プロパティを DisplayOrientations.Landscape に設定してディスプレイの向きをロックします。

DisplayInformation.AutoRotationPreferences = DisplayOrientations.Landscape;

カメラ デバイスの位置情報を追跡する

キャプチャしたメディアの正しい向きを計算するには、シャーシ上のカメラ デバイスの位置情報をアプリで特定する必要があります。 ブール値メンバー変数を追加して、(USB 接続型 Web カメラなどのように) カメラがデバイスの外部にあるかどうかを追跡します。 プレビューを左右反転すべきかどうかを追跡するための別のブール変数を追加します。左右反転は、正面カメラが使われている場合に必要になります。 また、選択されたカメラを表す DeviceInformation オブジェクトを格納するための変数を追加します。

private bool _externalCamera;
private bool _mirroringPreview;
DeviceInformation _cameraDevice;

カメラ デバイスを選択し MediaCapture オブジェクトを初期化する

MediaCapture を使った基本的な写真、ビデオ、およびオーディオのキャプチャ」では、数行のコードで MediaCapture オブジェクトを初期化する方法について説明しています。 カメラの向きをサポートするために、少しだけこの初期化プロセスに手順を追加します。

まず、DeviceClass.VideoCapture というデバイス セレクターを渡して DeviceInformation.FindAllAsync を呼び出し、利用可能なすべてのビデオ キャプチャ デバイスの一覧を取得します。 次に、カメラのパネル位置が認識されていて、かつ、指定した値とそのパネル位置が一致するものの中で、一覧の最も上にあるデバイスを選択します。この例では、正面カメラになります。 目的のパネルでカメラが見つからない場合は、先頭にあるカメラか、既定の利用可能なカメラが使用されます。

カメラ デバイスが見つかった場合、新しい MediaCaptureInitializationSettings オブジェクトを作成し、選択したデバイスに VideoDeviceId プロパティを設定します。 次に、MediaCapture オブジェクトを作成し、設定オブジェクトを渡して InitializeAsync を呼び出し、選択したカメラを使うようシステムに指示します。

最後に、選択したデバイスのパネルが null または unknown になっているかどうかを確認します。 このいずれかであれば、カメラは外付けのものなので、カメラの回転はデバイスの回転とは無関係ということになります。 パネルが認識されていてデバイス シャーシの全面にある場合は、プレビューを左右反転する必要があるため、プレビューを追跡する変数を設定します。

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 を呼び出してカメラ プレビューを開始します。 その前に、(正面カメラのために) プレビューを左右反転する必要があるかどうか、メンバー変数を確認してください。 左右反転する必要があれば、CaptureElementFlowDirection プロパティ (この例では PreviewControl という名前になっています) を FlowDirection.RightToLeft に設定します。 プレビューを開始したら、SetPreviewRotationAsync というヘルパー メソッドを呼び出してプレビューの回転を設定します。 以下に、このメソッドの実装を示します。

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

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

スマートフォンの向きが変わったときプレビュー ストリームを再初期化することなく更新できるように、プレビューの回転を別個のメソッドに設定します。 外付けのカメラの場合、処理は行われませんが、 それ以外の場合は CameraRotationHelper メソッドの GetCameraPreviewOrientation が呼び出され、プレビュー ストリームの正しい向きが返されます。

メタデータを設定するには VideoDeviceController.GetMediaStreamProperties を呼び出して、プレビュー ストリームのプロパティを取得します。 次に、ビデオ ストリームの回転状態のメディア ファンデーション トランスフォーム (MFT) 属性を表す 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 を作成しています。 次に、StorageFile を作成して、ファイルに書き込む IRandomAccessStream を取得するために開きます。

ファイルをコード変換する前に、GetCameraCaptureOrientation というヘルパー クラス メソッドから写真の向きを取得します。 このメソッドは、ConvertSimpleOrientationToPhotoOrientation というヘルパー メソッドによって PhotoOrientation オブジェクトに変換される SimpleOrientation オブジェクトを返します。 次に、新しい BitmapPropertySet オブジェクトを作成し、キーが "System.Photo.Orientation" で、値が (BitmapTypedValue として表される) 写真の向きであるプロパティを追加します。 "System.Photo.Orientation" は、メタデータとして画像ファイルに追加できる多くの Windows プロパティのうちの 1 つです。 写真に関連するプロパティの全一覧については、「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 を使った基本的な写真、ビデオ、およびオーディオのキャプチャ」で説明しています。 キャプチャしたビデオのエンコーディングに向きのデータを追加するには、向きのデータをプレビュー ストリームに追加するセクションで説明したのと同じ手法を用います。

次の例では、キャプチャしたビデオを書き込むファイルを作成します。 CreateMp4 という静的メソッドを使用して、MP4 エンコード プロファイルを作成します。 ビデオの正しい向きは、GetCameraCaptureOrientation を呼び出すことで CameraRotationHelper クラスから取得します。回転プロパティでは、向きを反時計回りの角度で表す必要があるため、ConvertSimpleOrientationToClockwiseDegrees ヘルパー メソッドを呼び出して向きの値を変換します。 次に、ビデオ ストリームの回転状態のメディア ファンデーション トランスフォーム (MFT) 属性を表す 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 の完全なコード

以下のコード スニペットに、CameraRotationHelper クラスの完全なコードを示します。このクラスは、ハードウェアの方位センサーを管理したり、写真やビデオの正しい向きの値を計算したりします。また、各種 Windows 機能で使われる、向きのさまざまな表示間で変換を実行するヘルパー メソッドを提供します。 上記の記事の手順に従えば、変更を加えることなくプロジェクトにこのクラスを追加することができます。 もちろん、特定のシナリオに合わせて、以下のコードを自由にカスタマイズすることも可能です。

このヘルパー クラスは、デバイスの 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;
        }
    }
}