Menangani orientasi perangkat dengan MediaCapture

Saat aplikasi Anda mengambil foto atau video untuk digunakan di luar aplikasi, seperti menyimpan ke file atau berbagi, Anda perlu mengodekan gambar dengan metadata orientasi yang benar sehingga konten ditampilkan dengan benar di aplikasi dan perangkat lain. Artikel ini memperlihatkan cara menggunakan kelas pembantu untuk mengelola orientasi kamera di aplikasi desktop WinUI 3.

Prasyarat

Sebelum mulai, pastikan bahwa Anda memiliki:

  • Proyek desktop WinUI 3 (dikemas atau tidak dikemas).
  • Kamera yang kompatibel tersambung ke perangkat Anda.
  • Akses kamera dideklarasikan untuk aplikasi Anda:
    • Aplikasi paketwebcam: Tambahkan kemampuan perangkat ke Package.appxmanifest:

      <Capabilities>
          <DeviceCapability Name="webcam" />
      </Capabilities>
      
    • Aplikasi yang tidak dikemas: Tidak ada kemampuan manifes untuk dideklarasikan. Akses kamera dikontrol oleh pengaturan Izinkan aplikasi desktop mengakses kamera Anda di bawah Pengaturan>Privasi &keamanan>Kamera pada perangkat pengguna.

Pengguna masih dapat menolak akses kamera di tingkat OS dalam kedua kasus. Periksa akses sebelum Anda menginisialisasi kamera dan menangani kasus yang ditolak dengan baik, seperti yang dijelaskan dalam Menangani pengaturan privasi kamera Windows.

Konsep orientasi

Aplikasi desktop biasanya berjalan di perangkat dengan layar tetap, sehingga Anda tidak perlu menangani rotasi berkelanjutan seperti yang dilakukan aplikasi seluler. Namun, Anda masih perlu memperhitungkan:

  • Orientasi sensor kamera — Sudut pemasangan fisik sensor kamera, yang bervariasi menurut perangkat.
  • Rotasi kamera eksternal — Webcam eksternal dapat diputar oleh pengguna.

Ide utamanya adalah menerapkan koreksi rotasi saat mengodekan foto atau video yang diambil sehingga output cocok dengan apa yang dilihat pengguna dalam pratinjau.

Membuat kelas CameraRotationHelper

Kelas pembantu berikut mengelola nilai rotasi berdasarkan orientasi sensor kamera dan sensor orientasi perangkat:

using Windows.Devices.Enumeration;
using Windows.Devices.Sensors;
using Windows.Media.Capture;
using Windows.Storage.FileProperties;

public class CameraRotationHelper
{
    private readonly EnclosureLocation _cameraEnclosureLocation;
    private readonly SimpleOrientationSensor _orientationSensor;
    private SimpleOrientation _deviceOrientation =
        SimpleOrientation.NotRotated;

    public event EventHandler<bool> OrientationChanged;

    public CameraRotationHelper(
        EnclosureLocation cameraEnclosureLocation)
    {
        _cameraEnclosureLocation = cameraEnclosureLocation;

        _orientationSensor =
            SimpleOrientationSensor.GetDefault();

        if (_orientationSensor != null)
        {
            _orientationSensor.OrientationChanged +=
                OrientationSensor_OrientationChanged;
        }
    }

    private void OrientationSensor_OrientationChanged(
        SimpleOrientationSensor sender,
        SimpleOrientationSensorOrientationChangedEventArgs args)
    {
        if (args.Orientation != SimpleOrientation.Faceup &&
            args.Orientation != SimpleOrientation.Facedown)
        {
            _deviceOrientation = args.Orientation;
            OrientationChanged?.Invoke(this, true);
        }
    }

    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 convention
        return _cameraEnclosureLocation?.Panel == Windows.Devices.Enumeration.Panel.Front;
    }

    private SimpleOrientation GetCameraOrientation()
    {
        if (IsEnclosureLocationExternal(_cameraEnclosureLocation))
        {
            return SimpleOrientation.NotRotated;
        }

        // Get the sensor orientation from the device
        return _deviceOrientation;
    }

    /// <summary>
    /// Gets the rotation to apply to the camera preview stream.
    /// </summary>
    public VideoRotation GetCameraPreviewOrientation()
    {
        if (IsEnclosureLocationExternal(_cameraEnclosureLocation))
        {
            return VideoRotation.None;
        }

        return ConvertSimpleOrientationToVideoRotation(
            GetCameraOrientation());
    }

    /// <summary>
    /// Gets the rotation to apply when encoding a photo.
    /// </summary>
    public PhotoOrientation GetCapturePhotoOrientation()
    {
        if (IsEnclosureLocationExternal(_cameraEnclosureLocation))
        {
            return PhotoOrientation.Normal;
        }

        int encodingRotation = ConvertDeviceOrientationToDegrees(
            GetCameraOrientation());

        if (IsCameraMirrored())
        {
            encodingRotation = (360 - encodingRotation) % 360;
        }

        return ConvertDegreesToPhotoOrientation(encodingRotation);
    }

    /// <summary>
    /// Gets the clockwise rotation to apply when encoding a video.
    /// </summary>
    public int GetCaptureVideoOrientation()
    {
        if (IsEnclosureLocationExternal(_cameraEnclosureLocation))
        {
            return 0;
        }

        int rotation = ConvertDeviceOrientationToDegrees(
            GetCameraOrientation());

        if (IsCameraMirrored())
        {
            rotation = (360 - rotation) % 360;
        }

        return rotation;
    }

    public void Dispose()
    {
        if (_orientationSensor != null)
        {
            _orientationSensor.OrientationChanged -=
                OrientationSensor_OrientationChanged;
        }
    }

    private static int ConvertDeviceOrientationToDegrees(
        SimpleOrientation orientation)
    {
        // TODO: This mapping from counterclockwise SimpleOrientation values
        // to clockwise degree values (for example, mapping
        // Rotated90DegreesCounterclockwise to 90) was carried over from the
        // original UWP sample this article is based on. Verify this mapping
        // against physical devices before relying on it in production; do
        // not change these values without hardware verification.
        return orientation switch
        {
            SimpleOrientation.Rotated90DegreesCounterclockwise => 90,
            SimpleOrientation.Rotated180DegreesCounterclockwise => 180,
            SimpleOrientation.Rotated270DegreesCounterclockwise => 270,
            _ => 0,
        };
    }

    private static VideoRotation ConvertSimpleOrientationToVideoRotation(
        SimpleOrientation orientation)
    {
        // TODO: See the verification note on ConvertDeviceOrientationToDegrees
        // above — this CCW-to-CW mapping needs the same device verification
        // before the values are changed.
        return orientation switch
        {
            SimpleOrientation.Rotated90DegreesCounterclockwise =>
                VideoRotation.Clockwise90Degrees,
            SimpleOrientation.Rotated180DegreesCounterclockwise =>
                VideoRotation.Clockwise180Degrees,
            SimpleOrientation.Rotated270DegreesCounterclockwise =>
                VideoRotation.Clockwise270Degrees,
            _ => VideoRotation.None,
        };
    }

    private static PhotoOrientation ConvertDegreesToPhotoOrientation(
        int degrees)
    {
        return degrees switch
        {
            90 => PhotoOrientation.Rotate90,
            180 => PhotoOrientation.Rotate180,
            270 => PhotoOrientation.Rotate270,
            _ => PhotoOrientation.Normal,
        };
    }
}

Gunakan kelas pembantu

Inisialisasi helper setelah Anda membuat instans MediaCapture dan mengetahui lokasi casing kamera:

private CameraRotationHelper _rotationHelper;
private MediaCapture _mediaCapture;

private async Task InitializeCameraAsync()
{
    _mediaCapture = new MediaCapture();
    await _mediaCapture.InitializeAsync();

    var cameraDevice = _mediaCapture.MediaCaptureSettings;

    // Find the camera device info to get enclosure location
    var devices = await DeviceInformation.FindAllAsync(
        DeviceClass.VideoCapture);
    var deviceInfo = devices.FirstOrDefault(
        d => d.Id == cameraDevice.VideoDeviceId);

    _rotationHelper = new CameraRotationHelper(
        deviceInfo?.EnclosureLocation);

    _rotationHelper.OrientationChanged += (s, e) =>
    {
        // Update preview rotation when device orientation changes
        DispatcherQueue.TryEnqueue(UpdatePreviewRotation);
    };
}

Terapkan rotasi pada pratinjau

Atur rotasi pratinjau pada instans MediaCapture Anda saat orientasi berubah:

private void UpdatePreviewRotation()
{
    var rotation = _rotationHelper.GetCameraPreviewOrientation();
    _mediaCapture.SetPreviewRotation(rotation);
}

Menerapkan rotasi saat mengambil foto

Atur metadata orientasi saat Anda menyimpan foto yang diambil:

using System.Collections.Generic;
using Windows.Storage;
using Windows.Storage.FileProperties;

private async Task CapturePhotoWithOrientationAsync()
{
    var file = await ApplicationData.Current.LocalFolder
        .CreateFileAsync("photo.jpg",
            CreationCollisionOption.GenerateUniqueName);

    await _mediaCapture.CapturePhotoToStorageFileAsync(
        ImageEncodingProperties.CreateJpeg(), file);

    // Set the orientation metadata. ImageProperties.Orientation is
    // read-only, so save the EXIF orientation value directly through
    // the file's property store instead.
    var photoOrientation =
        _rotationHelper.GetCapturePhotoOrientation();

    var propertiesToSave = new List<KeyValuePair<string, object>>
    {
        new KeyValuePair<string, object>(
            "System.Photo.Orientation", photoOrientation)
    };
    await file.Properties.SavePropertiesAsync(propertiesToSave);
}

Important

ApplicationData.Current.LocalFolder memerlukan identitas paket (MSIX). Aplikasi yang tidak dikemas tidak dapat digunakan ApplicationData tanpa identitas paket. Untuk aplikasi yang tidak dikemas, gunakan Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) atau jalur file Win32 lainnya sebagai gantinya.

Note

SimpleOrientationSensor tidak tersedia di semua perangkat desktop. null Periksa pengembalian dari SimpleOrientationSensor.GetDefault() dan tangani kasus di mana tidak ada sensor orientasi. Untuk kamera eksternal, rotasinya biasanya NotRotated.