Tratar a orientação do dispositivo com MediaCapture

Quando seu aplicativo captura uma foto ou vídeo que deve ser exibido fora do aplicativo, como salvar um arquivo no dispositivo do usuário ou compartilhar online, é importante que você codifique a imagem com os metadados de orientação adequados para que quando outro aplicativo ou dispositivo exibir a imagem, ela fique na posição correta. Determinar os dados de orientação corretos para incluir em um arquivo de mídia pode ser uma tarefa complexa porque há várias variáveis a serem consideradas, inclusive a orientação do chassi do dispositivo, a orientação da tela e o posicionamento da câmera no chassi (se é uma câmera frontal ou traseira).

Para simplificar o processo de manipulação da orientação, recomendamos usar uma classe auxiliar, CameraRotationHelper, cuja definição completa está no final deste artigo. Você pode adicionar essa classe ao seu projeto e seguir as etapas neste artigo para adicionar suporte para orientação ao seu aplicativo de câmera. A classe auxiliar também torna mais fácil para você girar os controles na interface do usuário da câmera para que eles sejam renderizados corretamente do ponto de vista do usuário.

Observação

Este artigo se baseia no código e nos conceitos abordados no artigo Captura básica de fotos, áudio e vídeo com o MediaCapture. Recomendamos que você se familiarize com os conceitos básicos do uso da classe MediaCapture antes de adicionar suporte para orientação ao seu aplicativo.

Namespaces usados neste artigo

O exemplo de código neste artigo usa APIs dos namespaces a seguir que você deve incluir em seu código.

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

A primeira etapa da adição de suporte para orientação ao seu aplicativo é bloquear a tela para que ela não gire automaticamente quando o dispositivo for girado. A rotação automática da interface do usuário funciona muito bem para a maioria dos tipos de aplicativos, mas ele não é intuitiva para os usuários quando a visualização da câmera gira. Bloqueie a orientação da tela definindo a propriedade DisplayInformation.AutoRotationPreferences como DisplayOrientations.Landscape.

DisplayInformation.AutoRotationPreferences = DisplayOrientations.Landscape;

Rastreando a localização do dispositivo de câmera

Para calcular a orientação correta para a mídia capturada, seu aplicativo deve determinar a localização do dispositivo de câmera no chassi. Adicione uma variável de membro booleano para controlar se a câmera é externa para o dispositivo, como uma webcam USB. Adicione outra variável booleana para controlar se a visualização deve ser espelhada, que é o caso quando uma câmera frontal é usada. Além disso, adicione uma variável para armazenar um objeto DeviceInformation que representa a câmera selecionada.

private bool _externalCamera;
private bool _mirroringPreview;
DeviceInformation _cameraDevice;

Selecionar um dispositivo de câmera e inicializar o objeto MediaCapture

O artigo Captura básica de fotos, áudio e vídeo com o MediaCapture mostra como inicializar o objeto MediaCapture com apenas algumas linhas de código. Para dar suporte à orientação da câmera, adicionaremos mais algumas etapas ao processo de inicialização.

Primeiro, chame Findallasync passando o seletor de dispositivo DeviceClass.VideoCapture para obter uma lista de todos os dispositivos de captura de vídeo disponíveis. Em seguida, selecione o primeiro dispositivo na lista onde a localização do painel da câmera é conhecida e onde corresponde ao valor fornecido, que, neste exemplo, é uma câmera frontal. Se nenhuma câmera for encontrada no painel desejado, a primeira ou a câmera disponível padrão será usada.

Se um dispositivo de câmera for encontrado, um novo objeto MediaCaptureInitializationSettings é criado e a propriedade VideoDeviceId é definida como o dispositivo selecionado. Em seguida, crie o objeto MediaCapture e chame InitializeAsync, passando o objeto de configurações para dizer para o sistema usar a câmera selecionada.

Por fim, verifique se o painel de dispositivo selecionado é nulo ou desconhecido. Se for, a câmera é externa, o que significa que sua rotação não está relacionada à rotação do dispositivo. Se o painel for conhecido e estiver na parte frontal do chassi do dispositivo, sabemos que a visualização deve ser espelhada, então, a variável que controla isso é definida.

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

Inicializar a classe CameraRotationHelper

Agora podemos começar a usar a classe CameraRotationHelper. Declare uma variável de membro de classe para armazenar o objeto. Chame o construtor, passando a localização do compartimento da câmera selecionada. A classe auxiliar usa essas informações para calcular a orientação correta para a mídia capturada, o fluxo de visualização e a interface do usuário. Registre um manipulador para o evento OrientationChanged da classe auxiliar, que será gerado quando precisarmos atualizar a orientação da interface do usuário ou o fluxo de visualização.

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

Adicionar dados de orientação ao fluxo de visualização da câmera

A adição da orientação correta aos metadados do fluxo de visualização não afeta como a visualização é exibida ao usuário, mas ajuda o sistema a codificar os quadros capturados do fluxo de visualização corretamente.

Você inicia a visualização da câmera chamando MediaCapture.StartPreviewAsync. Antes de fazer isso, verifique na variável de membro se a visualização deve ser espelhada (para uma câmera frontal). Nesse caso, defina a propriedade FlowDirection do CaptureElement, denominado PreviewControl neste exemplo, como RightToLeft. Depois de iniciar a visualização, chame o método auxiliar SetPreviewRotationAsync para definir a rotação da visualização. Depois vem a implementação desse método.

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

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

Definimos a rotação da visualização em um método separado para que ele possa ser atualizado quando a orientação do telefone muda sem reinicializar o fluxo de visualização. Se a câmera for externa para o dispositivo, nenhuma ação será executada. Caso contrário, o método CameraRotationHelperGetCameraPreviewOrientation é chamado e retorna a orientação adequada para o fluxo de visualização.

Para definir os metadados, as propriedades do fluxo de visualização são recuperadas chamando VideoDeviceController.GetMediaStreamProperties. Em seguida, crie o GUID que representa o atributo MFT (Media Foundation Transform) para a rotação do fluxo de vídeo. Em C++, você pode usar a constante MF_MT_VIDEO_ROTATION, mas em C#, você deve especificar manualmente o valor de GUID.

Adicione um valor de propriedade ao objeto de propriedades do fluxo, especificando o GUID como a chave e a rotação da visualização como o valor. Essa propriedade espera valores em unidades de graus no sentido anti-horário, então, o método CameraRotationHelperConvertSimpleOrientationToClockwiseDegrees é usado para converter o valor de orientação simples. Por fim, chame SetEncodingPropertiesAsync para aplicar a nova propriedade de rotação ao fluxo.

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

Em seguida, adicione o manipulador ao evento CameraRotationHelper.OrientationChanged. Esse evento passa um argumento que permite que você saiba se o fluxo de visualização precisa ser girado. Se a orientação do dispositivo for alterada para virado para cima ou virado para baixo, esse valor será false. Se a visualização precisar ser girada, chame o SetPreviewRotationAsync que foi definido anteriormente.

Em seguida, no manipulador de eventos OrientationChanged, atualize sua interface do usuário, se necessário. Obtenha a atual orientação da interface do usuário recomendada da classe auxiliar chamando GetUIOrientation e converta o valor em graus no sentido horário, que é usado para transformações de XAML. Crie um RotateTransform do valor de orientação e defina a propriedade RenderTransform dos controles XAML. Dependendo do layout da interface do usuário, talvez seja necessário fazer ajustes adicionais aqui além de simplesmente girar os controles. Além disso, lembre-se de que todas as atualizações da interface do usuário devem ser feitas no thread da interface do usuário, então, você deve colocar esse código dentro de uma chamada para 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;
    });
}

Capturar uma foto com dados de orientação

O artigo Captura básica de fotos, áudio e vídeo com o MediaCapture mostra como capturar uma foto em um arquivo capturando primeiro em fluxo de memória e, em seguida, usando um decodificador para ler os dados da imagem do fluxo e um codificador para transcodificar os dados de imagem para um arquivo. Os dados de orientação, obtidos da classe CameraRotationHelper, podem ser adicionados ao arquivo de imagem durante a operação de transcodificação.

No exemplo a seguir, uma foto é capturada em um InMemoryRandomAccessStream com uma chamada para CapturePhotoToStreamAsync e um BitmapDecoder é criado a partir do fluxo. Depois, um StorageFile é criado e aberto para recuperar um IRandomAccessStream para gravar no arquivo.

Antes de transcodificar o arquivo, a orientação da foto é recuperada do método de classe auxiliar GetCameraCaptureOrientation. Esse método retorna um objeto SimpleOrientation que é convertido em um objeto PhotoOrientation com o método auxiliar ConvertSimpleOrientationToPhotoOrientation. Em seguida, um novo objeto BitmapPropertySet é criado e uma propriedade é adicionada em que a chave é "System.Photo.Orientation" e o valor é a orientação da foto, expressa como BitmapTypedValue. "System.Photo.Orientation" é uma das muitas propriedades do Windows que podem ser adicionadas como metadados a um arquivo de imagem. Para obter uma lista de todas as propriedades relacionadas a fotos, consulte Propriedades do Windows – Foto. Para saber mais sobre como trabalhar com metadados de imagens, consulte Metadados de imagem.

Por fim, o conjunto de propriedades que inclui os dados de orientação é definido para o codificador com uma chamada para SetPropertiesAsync e a imagem é transcodificada com uma chamada para 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();
    }
}

Capturar um vídeo com dados de orientação

A captura básica de vídeo é descrita no artigo Captura básica de fotos, áudio e vídeo com o MediaCapture. A adição de dados de orientação à codificação do vídeo capturado é feita usando a mesma técnica descrita anteriormente na seção sobre adição de dados de orientação ao fluxo de visualização.

No exemplo a seguir, cria-se um arquivo no qual o vídeo capturado será gravado. Um perfil de codificação MP4 é criado usando o método estático CreateMp4. A orientação adequada para o vídeo é obtida da classe CameraRotationHelper com uma chamada para GetCameraCaptureOrientation porque a propriedade de rotação requer que a orientação seja expressa em graus no sentido anti-horário, o método auxiliar ConvertSimpleOrientationToClockwiseDegrees é chamado para converter o valor da orientação. Em seguida, crie o GUID que representa o atributo MFT (Media Foundation Transform) para a rotação do fluxo de vídeo. Em C++, você pode usar a constante MF_MT_VIDEO_ROTATION, mas em C#, você deve especificar manualmente o valor de GUID. Adicione um valor de propriedade ao objeto de propriedades do fluxo, especificando o GUID como a chave e a rotação como o valor. Por fim, chame StartRecordToStorageFileAsync para começar a gravar um vídeo codificado com os dados de orientação.

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

Listagem do código completo CameraRotationHelper

O trecho de código a seguir lista o código completo para a classe CameraRotationHelper que gerencia os sensores de orientação de hardware, calcula os valores de orientação adequados para fotos e vídeos e fornece métodos auxiliares para converter entre as diferentes representações de orientação que são usadas pelos diferentes recursos do Windows. Se você seguir as diretrizes mostradas no artigo acima, poderá adicionar essa classe ao seu projeto como está, sem precisar fazer alterações. Obviamente, fique à vontade para personalizar o código a seguir de acordo com as necessidades de seu cenário específico.

Essa classe auxiliar usa o SimpleOrientationSensor do dispositivo para determinar a orientação atual do chassi do dispositivo e a classe DisplayInformation para determinar a orientação atual da tela. Cada uma dessas classes fornece eventos que são gerados quando a orientação atual muda. O painel no qual o dispositivo de captura está montado – frontal, traseiro ou externo – é usado para determinar se o fluxo de visualização deve ser espelhado. Além disso, a propriedade EnclosureLocation.RotationAngleInDegreesClockwise, compatível com alguns dispositivos, é usada para determinar a orientação em que a câmera está montada no chassi.

Os métodos a seguir podem ser usados para obter valores de orientação recomendados para as tarefas especificadas do aplicativo de câmera:

  • GetUIOrientation – Retorna a orientação sugerida para os elementos da interface do usuário da câmera.
  • GetCameraCaptureOrientation – Retorna a orientação sugerida para codificar os metadados de imagem.
  • GetCameraPreviewOrientation – Retorna a orientação sugerida para o fluxo de visualização para oferecer uma experiência natural ao usuário.
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;
        }
    }
}