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

アプリ外での表示を目的とする写真やビデオ (ユーザーのデバイスにファイルを保存する場合や、オンラインで共有する場合など) をアプリでキャプチャする際は、別のアプリやデバイスで画像を表示するときに正しい向きで表示されるよう、適切な向きのメタデータを使って画像をエンコーディングすることが重要です。When your app captures a photo or video that is intended to be viewed outside of your app, such as saving to a file on the user's device or sharing online, it's important that you encode the image with the proper orientation metadata so that when another app or device displays the image, it is oriented correctly. メディア ファイルにどの向きのデータを含めれば良いか特定するのは複雑な作業です。これは、デバイス シャーシの向き、ディスプレイの向き、シャーシ上のカメラの位置 (全面カメラか背面カメラか) など、考慮すべき変数が複数あるためです。Determining the correct orientation data to include in a media file can be a complex task because there are several variables to consider, including the orientation of the device chassis, the orientation of the display, and the placement of the camera on the chassis (whether it is a front or back-facing camera).

そこで、向きの処理のプロセスを簡略化するために、CameraRotationHelper というヘルパー クラスを使うことをお勧めします。完全な定義についてはこの記事の最後に記載しています。To simplify the process of handling orientation, we recommend using a helper class, CameraRotationHelper, for which the full definition is provided at the end of this article. このクラスをプロジェクトに追加し、本記事の手順に従えば、カメラ アプリに向きのサポートを追加することができます。You can add this class to your project and then follow the steps in this article to add orientation support to your camera app. また、このヘルパー クラスによって、カメラ UI のコントロールの回転が容易になり、ユーザーの視点から正しくレンダリングされるようになります。The helper class also makes it easier for you to rotate the controls in your camera UI so that they are rendered correctly from the user's point of view.

注意

この記事の内容は、「MediaCapture を使った基本的な写真、ビデオ、およびオーディオのキャプチャ」で取り上げたコードや概念に基づいています。This article builds on the code and concepts discussed in the article Basic photo, video, and audio capture with MediaCapture. MediaCapture クラスの使用についての基本概念を理解してから、向きのサポートをアプリに追加することをお勧めします。We recommend that you familiarize yourself with the basic concepts of using the MediaCapture class before adding orientation support to your app.

この記事で使われている名前空間Namespaces used in this article

この記事のコード例では、次の名前空間の API を使っています。自分でコードを記述する際はこれらを含める必要があります。The example code in this article uses APIs from the following namespaces that you should include in your code.

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

アプリに向きのサポートを追加するにはまず、ディスプレイをロックして、デバイスの回転時にディスプレイが自動的に回転しないようにします。The first step in adding orientation support to your app is to lock the display so that it doesn't automatically rotate when the device is rotated. UI の自動回転はほとんどの種類のアプリに適していますが、カメラ プレビューについては、回転するとユーザーが操作しにくくなります。Automatic UI rotation works well for most types of apps, but it is unintuitive for users when the camera preview rotates. DisplayInformation.AutoRotationPreferences プロパティを DisplayOrientations.Landscape に設定してディスプレイの向きをロックします。Lock the display orientation by setting the DisplayInformation.AutoRotationPreferences property to DisplayOrientations.Landscape.

DisplayInformation.AutoRotationPreferences = DisplayOrientations.Landscape;

カメラ デバイスの位置情報を追跡するTracking the camera device location

キャプチャしたメディアの正しい向きを計算するには、シャーシ上のカメラ デバイスの位置情報をアプリで特定する必要があります。To calculate the correct orientation for captured media, you app must determine the location of the camera device on the chassis. ブール値メンバー変数を追加して、(USB 接続型 Web カメラなどのように) カメラがデバイスの外部にあるかどうかを追跡します。Add a boolean member variable to track whether the camera is external to the device, such as a USB web cam. プレビューを左右反転すべきかどうかを追跡するための別のブール変数を追加します。左右反転は、正面カメラが使われている場合に必要になります。Add another boolean variable to track whether the preview should be mirrored, which is the case if a front-facing camera is used. また、選択されたカメラを表す DeviceInformation オブジェクトを格納するための変数を追加します。Also, add a variable for storing a DeviceInformation object that represents the selected camera.

private bool _externalCamera;
private bool _mirroringPreview;
DeviceInformation _cameraDevice;

カメラ デバイスを選択し MediaCapture オブジェクトを初期化するSelect a camera device and initialize the MediaCapture object

MediaCapture を使った基本的な写真、ビデオ、およびオーディオのキャプチャ」では、数行のコードで MediaCapture オブジェクトを初期化する方法について説明しています。The article Basic photo, video, and audio capture with MediaCapture shows you how to initialize the MediaCapture object with just a couple of lines of code. カメラの向きをサポートするために、少しだけこの初期化プロセスに手順を追加します。To support camera orientation, we will add a few more steps to the initialization process.

まず、DeviceClass.VideoCapture というデバイス セレクターを渡して DeviceInformation.FindAllAsync を呼び出し、利用可能なすべてのビデオ キャプチャ デバイスの一覧を取得します。First, call DeviceInformation.FindAllAsync passing in the device selector DeviceClass.VideoCapture to get a list of all available video capture devices. 次に、カメラのパネル位置が認識されていて、かつ、指定した値とそのパネル位置が一致するものの中で、一覧の最も上にあるデバイスを選択します。この例では、正面カメラになります。Next, select the first device in the list where the panel location of the camera is known and where it matches the supplied value, which in this example is a front-facing camera. 目的のパネルでカメラが見つからない場合は、先頭にあるカメラか、既定の利用可能なカメラが使用されます。If no camera is found on the desired panel, the first or default available camera is used.

カメラ デバイスが見つかった場合、新しい MediaCaptureInitializationSettings オブジェクトを作成し、選択したデバイスに VideoDeviceId プロパティを設定します。If a camera device is found, a new MediaCaptureInitializationSettings object is created and the VideoDeviceId property is set to the selected device. 次に、MediaCapture オブジェクトを作成し、設定オブジェクトを渡して InitializeAsync を呼び出し、選択したカメラを使うようシステムに指示します。Next, create the MediaCapture object and call InitializeAsync, passing in the settings object to tell the system to use the selected camera.

最後に、選択したデバイスのパネルが null または unknown になっているかどうかを確認します。Finally, check to see if the selected device panel is null or unknown. このいずれかであれば、カメラは外付けのものなので、カメラの回転はデバイスの回転とは無関係ということになります。If so, the camera is external, which means that its rotation is unrelated to the rotation of the device. パネルが認識されていてデバイス シャーシの全面にある場合は、プレビューを左右反転する必要があるため、プレビューを追跡する変数を設定します。If the panel is known and is on the front of the device chassis, we know the preview should be mirrored, so the variable tracking this is set.

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 クラスを初期化するInitialize the CameraRotationHelper class

ここから CameraRotationHelper クラスを使い始めます。Now we begin using the CameraRotationHelper class. オブジェクトを格納するためのクラス メンバー変数を宣言し、Declare a class member variable to store the object. 選択されたカメラの筐体の位置を渡して、コンストラクターを呼び出します。Call the constructor, passing in the enclosure location of the selected camera. このヘルパー クラスでは、この情報を使用して、キャプチャしたメディア、プレビュー ストリーム、および UI の正しい向きを計算します。The helper class uses this information to calculate the correct orientation for captured media, the preview stream, and the UI. そして、このヘルパー クラスの OrientationChanged イベントのハンドラーを登録します。このイベントは、UI やプレビュー ストリームの向きを更新する必要がある場合に発生します。Register a handler for the helper class's OrientationChanged event, which will be raised when we need to update the orientation of the UI or the preview stream.

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

カメラのプレビュー ストリームに向きのデータを追加するAdd orientation data to the camera preview stream

プレビュー ストリームのメタデータに正しい向きを追加しても、ユーザーに表示されるプレビューには影響しませんが、プレビュー ストリームからキャプチャされるフレームをシステムが正しくエンコーディングしやすくなります。Adding the correct orientation to the metadata of the preview stream does not affect how the preview appears to the user, but it helps the system encode any frames captured from the preview stream correctly.

MediaCapture.StartPreviewAsync を呼び出してカメラ プレビューを開始します。You start the camera preview by calling MediaCapture.StartPreviewAsync. その前に、(正面カメラのために) プレビューを左右反転する必要があるかどうか、メンバー変数を確認してください。Before you do this, check the member variable to see if the preview should be mirrored (for a front-facing camera). 左右反転する必要があれば、CaptureElementFlowDirection プロパティ (この例では PreviewControl という名前になっています) を FlowDirection.RightToLeft に設定します。If so, set the FlowDirection property of the CaptureElement, named PreviewControl in this example, to FlowDirection.RightToLeft. プレビューを開始したら、SetPreviewRotationAsync というヘルパー メソッドを呼び出してプレビューの回転を設定します。After starting the preview, call the helper method SetPreviewRotationAsync to set the preview rotation. 以下に、このメソッドの実装を示します。Following is the implementation of this method.

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

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

スマートフォンの向きが変わったときプレビュー ストリームを再初期化することなく更新できるように、プレビューの回転を別個のメソッドに設定します。We set the preview rotation in a separate method so that it can be updated when the phone orientation changes without reinitializing the preview stream. 外付けのカメラの場合、処理は行われませんが、If the camera is external to the device, no action is taken. それ以外の場合は CameraRotationHelper メソッドの GetCameraPreviewOrientation が呼び出され、プレビュー ストリームの正しい向きが返されます。Otherwise, the CameraRotationHelper method GetCameraPreviewOrientation is called and returns the proper orientation for the preview stream.

メタデータを設定するには VideoDeviceController.GetMediaStreamProperties を呼び出して、プレビュー ストリームのプロパティを取得します。To set the metadata, the preview stream properties are retrieved by calling VideoDeviceController.GetMediaStreamProperties. 次に、ビデオ ストリームの回転状態のメディア ファンデーション トランスフォーム (MFT) 属性を表す GUID を作成します。Next, create the GUID representing the Media Foundation Transform (MFT) attribute for video stream rotation. C++ では MF_MT_VIDEO_ROTATION 定数を使用できますが、C# では GUID 値を手動で指定する必要があります。In C++ you can use the constant MF_MT_VIDEO_ROTATION, but in C# you must manually specify the GUID value.

キーに GUID を、値にプレビューの回転を指定して、ストリーム プロパティ オブジェクトにプロパティ値を追加します。Add a property value to the stream properties object, specifying the GUID as the key and the preview rotation as the value. このプロパティは、値が反時計回りの角度の単位であることを想定しているため、単なる向きの値を変換するのに CameraRotationHelper メソッドの ConvertSimpleOrientationToClockwiseDegrees を使用します。This property expects values to be in units of counterclockwise degrees, so the CameraRotationHelper method ConvertSimpleOrientationToClockwiseDegrees is used to convert the simple orientation value. 最後に、SetEncodingPropertiesAsync を呼び出して新しい回転プロパティをストリームに適用します。Finally, call SetEncodingPropertiesAsync to apply the new rotation property to the stream.

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 イベントのハンドラーを追加します。Next, add the handler for the CameraRotationHelper.OrientationChanged event. このイベントは、プレビュー ストリームが回転する必要があるかどうかを通知する引数を渡します。This event passes in an argument that lets you know whether the preview stream needs to be rotated. デバイスの向きが表向きまたは裏向きに変わった場合、この値は false になります。If the orientation of the device was changed to face up or face down, this value will be false. プレビューを回転する必要がある場合は、先ほど定義した SetPreviewRotationAsync を呼び出します。If the preview does need to be rotated, call SetPreviewRotationAsync which was defined previously.

次に、OrientationChanged イベント ハンドラーで、必要に応じて UI を更新します。Next, in the OrientationChanged event handler, update your UI if needed. GetUIOrientation を呼び出して、現在推奨される UI の向きをヘルパー クラスから取得し、値を時計回りに変換します。この値は XAML の変換に使われます。Get the current recommended UI orientation from the helper class by calling GetUIOrientation and convert the value to clockwise degrees, which is used for XAML transforms. 向きの値から RotateTransform を作成し、XAML コントロールの RenderTransform プロパティを設定します。Create a RotateTransform from the orientation value and set the RenderTransform property of your XAML controls. UI レイアウトによっては、単なるコントロールの回転に加え、追加の調整が必要になる場合があります。Depending on your UI layout, you may need to make additional adjustments here in addition to simply rotating the controls. また、UI へのすべての更新は UI スレッドで実行される必要があるため、このコードを RunAsync の呼び出しの中に配置する必要があることに注意してください。Also, remember that all updates to your UI must be made on the UI thread, so you should place this code inside a call to 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;
    });
}

向きのデータを使って写真をキャプチャするCapture a photo with orientation data

MediaCapture を使った基本的な写真、ビデオ、およびオーディオのキャプチャ」では、写真をファイルにキャプチャする方法について説明しています。このためには、まず写真をインメモリ ストリームにキャプチャしたら、デコーダーを使ってストリームから画像データを読み取り、エンコーダーを使って画像データをファイルにコード変換します。The article Basic photo, video, and audio capture with MediaCapture shows you how to capture a photo to a file by capturing first to an in-memory stream and then using a decoder to read the image data from the stream and an encoder to transcode the image data to a file. CameraRotationHelper クラスから取得した向きのデータを、変換操作中に画像ファイルに追加することができます。Orientation data, obtained from the CameraRotationHelper class, can be added to the image file during the transcoding operation.

次の例では、CapturePhotoToStreamAsync を呼び出すことで写真を InMemoryRandomAccessStream にキャプチャし、ストリームから BitmapDecoder を作成しています。In the following example, a photo is captured to an InMemoryRandomAccessStream with a call to CapturePhotoToStreamAsync and a BitmapDecoder is created from the stream. 次に、StorageFile を作成して、ファイルに書き込む IRandomAccessStream を取得するために開きます。Next a StorageFile is created and opened to retreive an IRandomAccessStream for writing to the file.

ファイルをコード変換する前に、GetCameraCaptureOrientation というヘルパー クラス メソッドから写真の向きを取得します。Before transcoding the file, the photo's orientation is retrieved from the helper class method GetCameraCaptureOrientation. このメソッドは、ConvertSimpleOrientationToPhotoOrientation というヘルパー メソッドによって PhotoOrientation オブジェクトに変換される SimpleOrientation オブジェクトを返します。This method returns a SimpleOrientation object which is converted to a PhotoOrientation object with the helper method ConvertSimpleOrientationToPhotoOrientation. 次に、新しい BitmapPropertySet オブジェクトを作成し、キーが "System.Photo.Orientation" で、値が (BitmapTypedValue として表される) 写真の向きであるプロパティを追加します。Next, a new BitmapPropertySet object is created and a property is added where the key is "System.Photo.Orientation" and the value is the photo orientation, expressed as a BitmapTypedValue. "System.Photo.Orientation" は、メタデータとして画像ファイルに追加できる多くの Windows プロパティのうちの 1 つです。"System.Photo.Orientation" is one of many Windows properties that can be added as metadata to an image file. 写真に関連するプロパティの全一覧については、「Windows プロパティ - 写真」をご覧ください。For a list of all of the photo-related properties, see Windows Properties - Photo. 画像のメタデータの使い方について詳しくは、「画像のメタデータ」をご覧ください。For more information about workine with metadata in images, see Image metadata.

最後に、SetPropertiesAsync を呼び出すことで、向きのデータを含むプロパティ セットをエンコーダーに設定し、FlushAsync を呼び出すことで、画像をコード変換します。Finally, the property set which includes the orientation data is set for the encoder by with a call to SetPropertiesAsync and the image is transcoded with a call to 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();
    }
}

向きのデータを使ってビデオをキャプチャするCapture a video with orientation data

基本的なビデオ キャプチャについては、「MediaCapture を使った基本的な写真、ビデオ、およびオーディオのキャプチャ」で説明しています。Basic video capture is described in the article Basic photo, video, and audio capture with MediaCapture. キャプチャしたビデオのエンコーディングに向きのデータを追加するには、向きのデータをプレビュー ストリームに追加するセクションで説明したのと同じ手法を用います。Adding orientation data to the encoding of the captured video is done using the same technique as described earlier in the section about adding orientation data to the preview stream.

次の例では、キャプチャしたビデオを書き込むファイルを作成します。In the following example, a file is created to which the captured video will be written. CreateMp4 という静的メソッドを使用して、MP4 エンコード プロファイルを作成します。An MP4 encoding profile is create using the static method CreateMp4. ビデオの正しい向きは、GetCameraCaptureOrientation を呼び出すことで CameraRotationHelper クラスから取得します。回転プロパティでは、向きを反時計回りの角度で表す必要があるため、ConvertSimpleOrientationToClockwiseDegrees ヘルパー メソッドを呼び出して向きの値を変換します。The proper orientation for the video is obtained from the CameraRotationHelper class with a call to GetCameraCaptureOrientation Because the rotation property requires the orientation to be expressed in counterclockwise degrees, the ConvertSimpleOrientationToClockwiseDegrees helper method is called to convert the orientation value. 次に、ビデオ ストリームの回転状態のメディア ファンデーション トランスフォーム (MFT) 属性を表す GUID を作成します。Next, create the GUID representing the Media Foundation Transform (MFT) attribute for video stream rotation. C++ では MF_MT_VIDEO_ROTATION 定数を使用できますが、C# では GUID 値を手動で指定する必要があります。In C++ you can use the constant MF_MT_VIDEO_ROTATION, but in C# you must manually specify the GUID value. キーに GUID を、値に回転状態を指定して、ストリーム プロパティ オブジェクトにプロパティ値を追加します。Add a property value to the stream properties object, specifying the GUID as the key and the rotation as the value. 最後に、StartRecordToStorageFileAsync を呼び出して、向きのデータによってエンコーディングされたビデオの録画を開始します。Finally call StartRecordToStorageFileAsync to begin recording video encoded with orientation data.

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 full code listing

以下のコード スニペットに、CameraRotationHelper クラスの完全なコードを示します。このクラスは、ハードウェアの方位センサーを管理したり、写真やビデオの正しい向きの値を計算したりします。また、各種 Windows 機能で使われる、向きのさまざまな表示間で変換を実行するヘルパー メソッドを提供します。The following code snippet lists the full code for the CameraRotationHelper class that manages the hardware orientation sensors, calculates the proper orientation values for photos and videos, and provides helper methods to convert between the different representations of orientation that are used by different Windows features. 上記の記事の手順に従えば、変更を加えることなくプロジェクトにこのクラスを追加することができます。If you follow the guidance shown in the article above, you can add this class to your project as-is without having to make any changes. もちろん、特定のシナリオに合わせて、以下のコードを自由にカスタマイズすることも可能です。Of course, you can feel free to customize the following code to meet the needs of your particular scenario.

このヘルパー クラスは、デバイスの SimpleOrientationSensor を使用してデバイス シャーシの現在の向きを特定し、DisplayInformation クラスを使用してディスプレイの現在の向きを特定します。This helper class uses the device's SimpleOrientationSensor to determine the current orientation of the device chassis and the DisplayInformation class to determine the current orientation of the display. これらの各クラスは、現在の向きが変更されたときに発生するイベントを提供します。Each of these classes provide events that are raised when the current orientation changes. キャプチャ デバイスが取り付けられている、正面、背面、または外部のパネルは、プレビュー ストリームを左右反転する必要があるかどうかを特定するために使われます。The panel on which the capture device is mounted - front-facing, back-facing, or external - is used to determine whether the preview stream should be mirrored. また、一部のデバイスでサポートされる EnclosureLocation.RotationAngleInDegreesClockwise プロパティは、シャーシに取り付けられているカメラの向きを特定するために使われます。Also, the EnclosureLocation.RotationAngleInDegreesClockwise property, supported by some devices, is used to determine the orientation in which the camera is mounted on the chasses.

以下のメソッドを使えば、特定のカメラ アプリのタスクに推奨される向きの値を取得することができます。The following methods can be used to get recommended orientation values for the specified camera app tasks:

  • GetUIOrientation - カメラの UI 要素に対する向きの候補が返されます。GetUIOrientation - Returns the suggested orientation for camera UI elements.
  • GetCameraCaptureOrientation - 画像のメタデータへのエンコーディングに対する向きの候補が返されます。GetCameraCaptureOrientation - Returns the suggested orientation for encoding into image metadata.
  • GetCameraPreviewOrientation - 自然なユーザー エクスペリエンスを実現するための、プレビュー ストリームの向きの候補が返されます。GetCameraPreviewOrientation - Returns the suggested orientation for the preview stream to provide a natural user experience.
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;
        }
    }
}