共用方式為


相片和視訊擷取的手動相機控制項

本文介紹如何使用手動裝置控制項來啟用增強的相片和影片擷取案例,包括光學影像防震和流暢縮放。

本文中討論的控制項全都會使用相同的模式新增至您的應用程式。 首先,請檢查您的應用程式執行所在的目前裝置是否支援控制項。 如果支援控制項,請設定控制項所需的模式。 一般而言,如果目前裝置不支援特定控制項,您應該停用或隱藏允許使用者啟用功能的 UI 元素。

本文中的程式碼是改編自相機手動控制項 SDK 範例。 您可以下載範例以查看內容中使用的程式碼,或使用該範例做為您自己的應用程式的起點。

注意

本文以使用 MediaCapture 進行基本相片、視訊和音訊的擷取中所討論的概念和程式碼為基礎,說明實作基本相片和視訊擷取的步驟。 我們建議您先熟悉該文章中的基本媒體擷取模式後,再繼續進行更進階的擷取案例。 本文中的程式碼假設您的應用程式已經有已正確初始化的 MediaCapture 執行個體。

本文所討論的所有裝置控制 API 都是 Windows.Media.Devices 命名空間的成員。

using Windows.Media.Devices;

曝光

ExposureControl 可讓您設定相片或影片擷取期間使用的快門速度。

此範例會使用 Slider 控制項來調整目前的曝光值和核取方塊來切換自動曝光調整。

<Slider Name="ExposureSlider" ValueChanged="ExposureSlider_ValueChanged"/>
<TextBlock Name="ExposureTextBlock" Text="{Binding ElementName=ExposureSlider,Path=Value}"/>
<CheckBox Name="ExposureAutoCheckBox" Content="Auto" Checked="ExposureCheckBox_CheckedChanged" Unchecked="ExposureCheckBox_CheckedChanged"/>

透過檢查 Supported 屬性來檢查目前擷取裝置是否支援 ExposureControl。 如果支援控制項,您可以顯示並啟用此功能的 UI。 設定核取方塊的核取狀態,以指出自動曝光調整目前是否為 Auto 屬性的值使用中。

曝光值必須位於裝置所支援的範圍內,而且必須是所支援步驟大小的遞增。 透過檢查 MinMaxStep 屬性來取得目前裝置支援的值,這些屬性用於設定滑桿控制項的對應屬性。

取消註冊 ValueChanged 事件處理常式之後,將滑桿控制項的值設定為 ExposureControl 的目前值,以便在設定值時不會觸發事件。

var exposureControl = _mediaCapture.VideoDeviceController.ExposureControl;

if (exposureControl.Supported)
{
    ExposureAutoCheckBox.Visibility = Visibility.Visible;
    ExposureSlider.Visibility = Visibility.Visible;

    ExposureAutoCheckBox.IsChecked = exposureControl.Auto;

    ExposureSlider.Minimum = exposureControl.Min.Ticks;
    ExposureSlider.Maximum = exposureControl.Max.Ticks;
    ExposureSlider.StepFrequency = exposureControl.Step.Ticks;

    ExposureSlider.ValueChanged -= ExposureSlider_ValueChanged;
    var value = exposureControl.Value;
    ExposureSlider.Value = value.Ticks;
    ExposureSlider.ValueChanged += ExposureSlider_ValueChanged;
}
else
{
    ExposureAutoCheckBox.Visibility = Visibility.Collapsed;
    ExposureSlider.Visibility = Visibility.Collapsed;
}

ValueChanged 事件處理常式中,取得控制項的目前值並透過呼叫 SetValueAsync 設定曝光值。

private async void ExposureSlider_ValueChanged(object sender, Windows.UI.Xaml.Controls.Primitives.RangeBaseValueChangedEventArgs e)
{
    var value = TimeSpan.FromTicks((long)(sender as Slider).Value);
    await _mediaCapture.VideoDeviceController.ExposureControl.SetValueAsync(value);
}

在自動曝光核取方塊的 CheckedChanged 事件處理常式中,透過呼叫 SetAutoAsync 並傳入布林值來開啟或關閉自動曝光調整。

private async void ExposureCheckBox_CheckedChanged(object sender, RoutedEventArgs e)
{
    if(! _isPreviewing)
    {
        // Auto exposure only supported while preview stream is running.
        return;
    }

    var autoExposure = ((sender as CheckBox).IsChecked == true);
    await _mediaCapture.VideoDeviceController.ExposureControl.SetAutoAsync(autoExposure);
}

重要

只有在預覽串流執行時,才支援自動曝光模式。 在開啟自動曝光之前,請先檢查預覽串流是否正在執行。

曝光補償

ExposureCompensationControl 可讓您設定相片或影片擷取期間所使用的曝光補償。

此範例會使用 Slider 控制項來調整目前的曝光補償值。

<Slider Name="EvSlider" ValueChanged="EvSlider_ValueChanged"/>
<TextBlock Text="{Binding ElementName=EvSlider,Path=Value}" Name="EvTextBlock"/>

透過檢查 Supported 屬性來檢查目前擷取裝置是否支援 ExposureCompensationControl。 如果支援控制項,您可以顯示並啟用此功能的 UI。

曝光補償值必須位於裝置所支援的範圍內,而且必須是所支援步驟大小的遞增。 透過檢查 MinMaxStep 屬性來取得目前裝置支援的值,這些屬性用於設定滑桿控制項的對應屬性。

取消註冊 ValueChanged 事件處理常式之後,將滑桿控制項的值設定為 ExposureCompensationControl 的目前值,以便在設定值時不會觸發事件。

var exposureCompensationControl = _mediaCapture.VideoDeviceController.ExposureCompensationControl;

if (exposureCompensationControl.Supported)
{
    EvSlider.Visibility = Visibility.Visible;
    EvSlider.Minimum = exposureCompensationControl.Min;
    EvSlider.Maximum = exposureCompensationControl.Max;
    EvSlider.StepFrequency = exposureCompensationControl.Step;

    EvSlider.ValueChanged -= EvSlider_ValueChanged;
    EvSlider.Value = exposureCompensationControl.Value;
    EvSlider.ValueChanged += EvSlider_ValueChanged;
}
else
{
    EvSlider.Visibility = Visibility.Collapsed;
}

ValueChanged 事件處理常式中,取得控制項的目前值並透過呼叫 SetValueAsync 設定曝光值。

private async void EvSlider_ValueChanged(object sender, Windows.UI.Xaml.Controls.Primitives.RangeBaseValueChangedEventArgs e)
{
    var value = (sender as Slider).Value;
    await _mediaCapture.VideoDeviceController.ExposureCompensationControl.SetValueAsync((float)value);
}

Flash

FlashControl 可讓您啟用或停用快閃,或啟用自動閃爍,其中系統會動態判斷是否要使用快閃。 此控制項也可讓您在支援它的裝置上啟用自動紅眼縮小。 這些設定全都適用於擷取相片。 TorchControl 是用於開啟或關閉影片擷取手電筒的個別控制項。

此範例會使用一組選項按鈕,讓使用者在開啟、關閉和自動快閃設定之間切換。 也提供核取方塊,以允許切換紅眼縮小和影片手電筒。

<RadioButton Name="FlashOnRadioButton" Content="On" Checked="FlashOnRadioButton_Checked"/>
<RadioButton Name="FlashAutoRadioButton" Content="Auto" Checked="FlashAutoRadioButton_Checked"/>
<RadioButton Name="FlashOffRadioButton" Content="Off" Checked="FlashOffRadioButton_Checked"/>
<CheckBox Name="RedEyeFlashCheckBox" Content="Red Eye" Visibility="Collapsed" Checked="RedEyeFlashCheckBox_CheckedChanged" Unchecked="RedEyeFlashCheckBox_CheckedChanged"/>
<CheckBox Name="TorchCheckBox" Content="Video Light" Visibility="Collapsed" Checked="TorchCheckBox_CheckedChanged" Unchecked="TorchCheckBox_CheckedChanged"/>

透過檢查 Supported 屬性來檢查目前擷取裝置是否支援 FlashControl。 如果支援控制項,您可以顯示並啟用此功能的 UI。 如果支援 FlashControl,可能會或不支援自動縮小紅眼,因此請在啟用 UI 之前檢查 RedEyeReductionSupported 屬性。 由於 TorchControl 獨立於快閃控制項,因此您也必須在使用它之前先檢查其 Supported 屬性。

在每一個快閃選項按鈕的 Checked 事件處理常式中,啟用或停用適當的對應快閃設定。 請注意,若要將快閃設定為一律使用,您必須將 Enabled 屬性設定為 true,並將 Auto 屬性設定為 false。

var flashControl = _mediaCapture.VideoDeviceController.FlashControl;

if (flashControl.Supported)
{
    FlashAutoRadioButton.Visibility = Visibility.Visible;
    FlashOnRadioButton.Visibility = Visibility.Visible;
    FlashOffRadioButton.Visibility = Visibility.Visible;

    FlashAutoRadioButton.IsChecked = true;

    if (flashControl.RedEyeReductionSupported)
    {
        RedEyeFlashCheckBox.Visibility = Visibility.Visible;
    }

    // Video light is not strictly part of flash, but users might expect to find it there
    if (_mediaCapture.VideoDeviceController.TorchControl.Supported)
    {
        TorchCheckBox.Visibility = Visibility.Visible;
    }
}
else
{
    FlashAutoRadioButton.Visibility = Visibility.Collapsed;
    FlashOnRadioButton.Visibility = Visibility.Collapsed;
    FlashOffRadioButton.Visibility = Visibility.Collapsed;
}
private void FlashOnRadioButton_Checked(object sender, RoutedEventArgs e)
{
    _mediaCapture.VideoDeviceController.FlashControl.Enabled = true;
    _mediaCapture.VideoDeviceController.FlashControl.Auto = false;
}

private void FlashAutoRadioButton_Checked(object sender, RoutedEventArgs e)
{
    _mediaCapture.VideoDeviceController.FlashControl.Enabled = true;
    _mediaCapture.VideoDeviceController.FlashControl.Auto = true;
}

private void FlashOffRadioButton_Checked(object sender, RoutedEventArgs e)
{
    _mediaCapture.VideoDeviceController.FlashControl.Enabled = false;
}

在紅眼縮小核取方塊的處理常式中,將 RedEyeReduction 屬性設定為適當的值。

private void RedEyeFlashCheckBox_CheckedChanged(object sender, RoutedEventArgs e)
{
    _mediaCapture.VideoDeviceController.FlashControl.RedEyeReduction = (RedEyeFlashCheckBox.IsChecked == true);
}

最後,在影片手電筒核取方塊的處理常式中,將 Enabled 屬性設定為適當的值。

private void TorchCheckBox_CheckedChanged(object sender, RoutedEventArgs e)
{
    _mediaCapture.VideoDeviceController.TorchControl.Enabled = (TorchCheckBox.IsChecked == true);

    if(! (_isPreviewing && _isRecording))
    {
        System.Diagnostics.Debug.WriteLine("Torch may not emit light if preview and video capture are not running.");
    }
}

注意

在某些裝置上,手電筒不會發出光線,即使 TorchControl.Enabled 設為 true,除非裝置執行預覽串流且正在主動擷取影片。 建議的作業順序是開啟影片預覽、將 Enabled 設定為 true 來開啟手電筒,然後起始影片擷取。 在某些裝置上,預覽開始後手電筒會亮起。 在其他裝置上,在影片擷取啟動之前,手電筒可能不會亮起。

焦點

FocusControl 物件支援三種不同的常用方法來調整相機的焦點、連續自動對焦、點選對焦和手動對焦。 相機應用程式可能支援這三種方法,但為了可讀性,本文會分別討論每個技術。 本節也會討論如何啟用對焦輔助燈。

連續自動對焦

啟用連續自動對焦會指示相機動態調整焦點,以嘗試讓相片或影片的主旨保持焦點。 本範例會使用選項按鈕來開啟和關閉連續自動對焦。

<RadioButton Content="CAF" Name="CafFocusRadioButton" Checked="CafFocusRadioButton_Checked"/>

透過檢查 Supported 屬性來檢查目前擷取裝置是否支援 FocusControl。 接下來,藉由檢查 SupportedFocusModes 清單來判斷是否支援連續自動對焦,以查看它是否包含 FocusMode.Continuous 值,如果是,則顯示連續自動對焦選項按鈕。

var focusControl = _mediaCapture.VideoDeviceController.FocusControl;

if (focusControl.Supported)
{
    CafFocusRadioButton.Visibility = focusControl.SupportedFocusModes.Contains(FocusMode.Continuous) 
        ? Visibility.Visible : Visibility.Collapsed;
}
else
{
    CafFocusRadioButton.Visibility = Visibility.Collapsed;
}

在連續自動對焦選項按鈕的 Checked 事件處理常式中,使用 VideoDeviceController.FocusControl 屬性來取得控制項的執行個體。 呼叫 UnlockAsync 即可解除鎖定控制項,以防您的應用程式先前呼叫 LockAsync 來啟用其中一個其他對焦模式。

建立新的 FocusSettings 物件,並將 Mode 屬性設定為 Continuous。 將 AutoFocusRange 屬性設定為適合您應用程式案例的值,或由使用者從 UI 選取的值。 將您的 FocusSettings 物件傳遞至 Configure 方法,然後呼叫 FocusAsync 以起始連續自動對焦。

private async void CafFocusRadioButton_Checked(object sender, RoutedEventArgs e)
{
    if(! _isPreviewing)
    {
        // Autofocus only supported while preview stream is running.
        return;
    }

    var focusControl = _mediaCapture.VideoDeviceController.FocusControl;
    await focusControl.UnlockAsync();
    var settings = new FocusSettings { Mode = FocusMode.Continuous, AutoFocusRange = AutoFocusRange.FullRange };
    focusControl.Configure(settings);
    await focusControl.FocusAsync();
}

重要

只有在預覽串流執行時,才支援自動對焦模式。 在開啟連續自動對焦之前,請先檢查預覽串流是否正在執行。

點選對焦

點選對焦技術會使用 FocusControlRegionsOfInterestControl 來指定擷取裝置應對焦的擷取畫面的子區域。 對焦區域是由使用者點選顯示預覽串流的畫面所決定。

此範例會使用選項按鈕來啟用和停用點選對焦模式。

<RadioButton Content="Tap" Name="TapFocusRadioButton" Checked="TapFocusRadioButton_Checked"/>

透過檢查 Supported 屬性來檢查目前擷取裝置是否支援 FocusControl。 必須支援 RegionsOfInterestControl,而且必須至少支援一個區域,才能使用這項技術。 檢查AutoFocusSupportedMaxRegions 屬性,以判斷要顯示或隱藏點選對焦的選項按鈕。

var focusControl = _mediaCapture.VideoDeviceController.FocusControl;

if (focusControl.Supported)
{
    TapFocusRadioButton.Visibility = (_mediaCapture.VideoDeviceController.RegionsOfInterestControl.AutoFocusSupported &&
                                      _mediaCapture.VideoDeviceController.RegionsOfInterestControl.MaxRegions > 0) 
                                      ? Visibility.Visible : Visibility.Collapsed;
}
else
{
    TapFocusRadioButton.Visibility = Visibility.Collapsed;
}

在點選對焦選項按鈕的 Checked 事件處理常式中,使用 VideoDeviceController.FocusControl 屬性來取得控制項的執行個體。 如果您的應用程式先前已呼叫 UnlockAsync 來啟用連續自動對焦,則呼叫 LockAsync 來鎖定控制項,然後等待使用者點選畫面來變更焦點。

private async void TapFocusRadioButton_Checked(object sender, RoutedEventArgs e)
{
    // Lock focus in case Continuous Autofocus was active when switching to Tap-to-focus
    var focusControl = _mediaCapture.VideoDeviceController.FocusControl;
    await focusControl.LockAsync();
    // Wait for user tap
}

本範例將焦點放在使用者點選畫面時的區域,然後在使用者再次點選時從該區域移除焦點,例如切換。 使用布林變數來追蹤目前的切換狀態。

bool _isFocused = false;

下一步是透過處理目前顯示擷取預覽串流的 CaptureElementTapped 事件來聆聽使用者點選畫面時的事件。 如果相機目前未預覽,或已停用點選對焦模式,則從處理常式傳回而不執行任何動作。

如果追蹤變數 _isFocused 切換為 false,且相機目前未處於對焦程序中 (由 FocusControlFocusState 屬性決定),則開始點選對焦程序。 從傳遞至處理常式的事件自自變數取得使用者點選的位置。 此範例還利用此機會選擇將對焦的區域大小。 在此情況下,大小是擷取元素最小維度的 1/4。 將點選位置和區域大小傳遞至下一節中定義的 TapToFocus 協助程式方法。

如果 _isFocused 切換設定為 true,使用者點選應該會清除上一個區域的焦點。 這會在 TapUnfocus 協助程式方法中完成,如下所示。

private async void PreviewControl_Tapped(object sender, TappedRoutedEventArgs e)
{
    if (!_isPreviewing || (TapFocusRadioButton.IsChecked != true)) return;

    if (!_isFocused && _mediaCapture.VideoDeviceController.FocusControl.FocusState != MediaCaptureFocusState.Searching)
    {
        var smallEdge = Math.Min(Window.Current.Bounds.Width, Window.Current.Bounds.Height);

        // Choose to make the focus rectangle 1/4th the length of the shortest edge of the window
        var size = new Size(smallEdge / 4, smallEdge / 4);
        var position = e.GetPosition(sender as UIElement);

        // Note that at this point, a rect at "position" with size "size" could extend beyond the preview area. The following method will reposition the rect if that is the case
        await TapToFocus(position, size);
    }
    else
    {
        await TapUnfocus();
    }
}

TapToFocus協助程式方法中,先將 _isFocused 切換設為 true,讓下一個畫面點選從點選區域釋放焦點。

這個協助程式方法中的下一個工作是決定將指派給對焦控制項之預覽串流內的矩形。 這需要兩個步驟。 第一個步驟是判斷預覽串流在 CaptureElement 控制項中佔用的矩形。 這取決於預覽串流的維度和裝置的方向。 本節末所示的協助程式方法 GetPreviewStreamRectInControl 會執行此工作並傳回包含預覽串的矩形。

TapToFocus 中的下一個工作,是將 CaptureElement.Tapped 事件處理常式中確定的點選位置和所需焦點矩形大小轉換為擷取串流中的座標。 本節稍後顯示的 ConvertUiTapToPreviewRect 協助程式方法會執行此轉換,並在擷取串流座標中傳回矩形,其中會要求焦點。

現在已經獲得了目標矩形,建立一個新的 RegionOfInterest 物件,將 Bounds 屬性設定為前面步驟中獲得的目標矩形。

取得擷取裝置的 FocusControl。 建立一個新的 FocusSettings 物件,並將 ModeAutoFocusRange 設為您想要的值,然後檢查以確保 FocusControl 支援它們。 呼叫 FocusControl 上的 Configure 以使您的設定處於使用中狀態,並指示裝置開始聚焦於指定區域。

接下來,取得擷取裝置的 RegionsOfInterestControl,並呼叫 SetRegionsAsync 來設定使用中的區域。 您可以在支援它的裝置上設定多個感興趣的區域,但本範例只會設定單一區域。

最後,在 FocusControl 上呼叫 FocusAsync 以起始對焦。

重要

實作點選對焦時,作業順序很重要。 您應該依照下列順序呼叫這些 API:

  1. FocusControl.Configure
  2. RegionsOfInterestControl.SetRegionsAsync
  3. FocusControl.FocusAsync
public async Task TapToFocus(Point position, Size size)
{
    _isFocused = true;

    var previewRect = GetPreviewStreamRectInControl();
    var focusPreview = ConvertUiTapToPreviewRect(position, size, previewRect);

    // Note that this Region Of Interest could be configured to also calculate exposure 
    // and white balance within the region
    var regionOfInterest = new RegionOfInterest
    {
        AutoFocusEnabled = true,
        BoundsNormalized = true,
        Bounds = focusPreview,
        Type = RegionOfInterestType.Unknown,
        Weight = 100,
    };


    var focusControl = _mediaCapture.VideoDeviceController.FocusControl;
    var focusRange = focusControl.SupportedFocusRanges.Contains(AutoFocusRange.FullRange) ? AutoFocusRange.FullRange : focusControl.SupportedFocusRanges.FirstOrDefault();
    var focusMode = focusControl.SupportedFocusModes.Contains(FocusMode.Single) ? FocusMode.Single : focusControl.SupportedFocusModes.FirstOrDefault();
    var settings = new FocusSettings { Mode = focusMode, AutoFocusRange = focusRange };
    focusControl.Configure(settings);

    var roiControl = _mediaCapture.VideoDeviceController.RegionsOfInterestControl;
    await roiControl.SetRegionsAsync(new[] { regionOfInterest }, true);

    await focusControl.FocusAsync();
}

TapUnfocus 協助程式方法中,取得 RegionsOfInterestControl 並呼叫 ClearRegionsAsync,以清除在 TapToFocus 協助程式方法內向控制項註冊的區域。 然後,取得 FocusControl 並呼叫 FocusAsync,讓裝置在沒有感興趣的區域的情況下重新對焦。

private async Task TapUnfocus()
{
    _isFocused = false;

    var roiControl = _mediaCapture.VideoDeviceController.RegionsOfInterestControl;
    await roiControl.ClearRegionsAsync();

    var focusControl = _mediaCapture.VideoDeviceController.FocusControl;
    await focusControl.FocusAsync();
}

GetPreviewStreamRectInControl 協助程式方法會使用預覽串流的解析度和裝置的方向,來判斷包含預覽串流的預覽元素內的矩形,並修剪控制項可能提供的任何字母填補,以維持串流的外觀比例。 此方法使用在使用 MediaCapture 進行基本相片、影片和音訊擷取中,找到的基本媒體擷取範例程式碼中定義的類別成員變數。

public Rect GetPreviewStreamRectInControl()
{
    var result = new Rect();

    var previewResolution = _mediaCapture.VideoDeviceController.GetMediaStreamProperties(MediaStreamType.VideoPreview) as VideoEncodingProperties;

    // In case this function is called before everything is initialized correctly, return an empty result
    if (PreviewControl == null || PreviewControl.ActualHeight < 1 || PreviewControl.ActualWidth < 1 ||
        previewResolution == null || previewResolution.Height == 0 || previewResolution.Width == 0)
    {
        return result;
    }

    var streamWidth = previewResolution.Width;
    var streamHeight = previewResolution.Height;

    // For portrait orientations, the width and height need to be swapped
    if (_displayOrientation == DisplayOrientations.Portrait || _displayOrientation == DisplayOrientations.PortraitFlipped)
    {
        streamWidth = previewResolution.Height;
        streamHeight = previewResolution.Width;
    }

    // Start by assuming the preview display area in the control spans the entire width and height both (this is corrected in the next if for the necessary dimension)
    result.Width = PreviewControl.ActualWidth;
    result.Height = PreviewControl.ActualHeight;

    // If UI is "wider" than preview, letterboxing will be on the sides
    if ((PreviewControl.ActualWidth / PreviewControl.ActualHeight > streamWidth / (double)streamHeight))
    {
        var scale = PreviewControl.ActualHeight / streamHeight;
        var scaledWidth = streamWidth * scale;

        result.X = (PreviewControl.ActualWidth - scaledWidth) / 2.0;
        result.Width = scaledWidth;
    }
    else // Preview stream is "wider" than UI, so letterboxing will be on the top+bottom
    {
        var scale = PreviewControl.ActualWidth / streamWidth;
        var scaledHeight = streamHeight * scale;

        result.Y = (PreviewControl.ActualHeight - scaledHeight) / 2.0;
        result.Height = scaledHeight;
    }

    return result;
}

ConvertUiTapToPreviewRect 協助程式方法會將點選事件的位置、所需的焦點區域大小,以及包含從 GetPreviewStreamRectInControl 協助程式方法取得的預覽串流的矩形做為引數。 這個方法會使用這些值和裝置的目前方向來計算包含所需區域的預覽串流內的矩形。 此方法再次使用在使用 MediaCapture 擷取相片和影片中,找到的基本媒體擷取範例程式碼中定義的類別成員變數。

private Rect ConvertUiTapToPreviewRect(Point tap, Size size, Rect previewRect)
{
    // Adjust for the resulting focus rectangle to be centered around the position
    double left = tap.X - size.Width / 2, top = tap.Y - size.Height / 2;

    // Get the information about the active preview area within the CaptureElement (in case it's letterboxed)
    double previewWidth = previewRect.Width, previewHeight = previewRect.Height;
    double previewLeft = previewRect.Left, previewTop = previewRect.Top;

    // Transform the left and top of the tap to account for rotation
    switch (_displayOrientation)
    {
        case DisplayOrientations.Portrait:
            var tempLeft = left;

            left = top;
            top = previewRect.Width - tempLeft;
            break;
        case DisplayOrientations.LandscapeFlipped:
            left = previewRect.Width - left;
            top = previewRect.Height - top;
            break;
        case DisplayOrientations.PortraitFlipped:
            var tempTop = top;

            top = left;
            left = previewRect.Width - tempTop;
            break;
    }

    // For portrait orientations, the information about the active preview area needs to be rotated
    if (_displayOrientation == DisplayOrientations.Portrait || _displayOrientation == DisplayOrientations.PortraitFlipped)
    {
        previewWidth = previewRect.Height;
        previewHeight = previewRect.Width;
        previewLeft = previewRect.Top;
        previewTop = previewRect.Left;
    }

    // Normalize width and height of the focus rectangle
    var width = size.Width / previewWidth;
    var height = size.Height / previewHeight;

    // Shift rect left and top to be relative to just the active preview area
    left -= previewLeft;
    top -= previewTop;

    // Normalize left and top
    left /= previewWidth;
    top /= previewHeight;

    // Ensure rectangle is fully contained within the active preview area horizontally
    left = Math.Max(left, 0);
    left = Math.Min(1 - width, left);

    // Ensure rectangle is fully contained within the active preview area vertically
    top = Math.Max(top, 0);
    top = Math.Min(1 - height, top);

    // Create and return resulting rectangle
    return new Rect(left, top, width, height);
}

手動對焦

手動對焦技術會使用 Slider 控制項來設定擷取裝置目前的焦點深度。 選項按鈕可用來開啟和關閉手動對焦。

<Slider Name="FocusSlider" IsEnabled="{Binding ElementName=ManualFocusRadioButton,Path=IsChecked}" ValueChanged="FocusSlider_ValueChanged"/>
<TextBlock Text="{Binding ElementName=FocusSlider,Path=Value,FallbackValue='0'}"/>
<RadioButton Content="Manual" Name="ManualFocusRadioButton" Checked="ManualFocusRadioButton_Checked" IsChecked="False"/>

透過檢查 Supported 屬性來檢查目前擷取裝置是否支援 FocusControl。 如果支援控制項,您可以顯示並啟用此功能的 UI。

對焦值必須位於裝置所支援的範圍內,而且必須是所支援步驟大小的遞增。 透過檢查 MinMaxStep 屬性來取得目前裝置支援的值,這些屬性用於設定滑桿控制項的對應屬性。

取消註冊 ValueChanged 事件處理常式之後,將滑桿控制項的值設定為 FocusControl 的目前值,以便在設定值時不會觸發事件。

var focusControl = _mediaCapture.VideoDeviceController.FocusControl;

if (focusControl.Supported)
{
    FocusSlider.Visibility = Visibility.Visible;
    ManualFocusRadioButton.Visibility = Visibility.Visible;

    FocusSlider.Minimum = focusControl.Min;
    FocusSlider.Maximum = focusControl.Max;
    FocusSlider.StepFrequency = focusControl.Step;
    

    FocusSlider.ValueChanged -= FocusSlider_ValueChanged;
    FocusSlider.Value = focusControl.Value;
    FocusSlider.ValueChanged += FocusSlider_ValueChanged;
}
else
{
    FocusSlider.Visibility = Visibility.Collapsed;
    ManualFocusRadioButton.Visibility = Visibility.Collapsed;
}

在手動對焦選項按鈕的 Checked 事件處理常式中,取得 FocusControl 物件並呼叫 LockAsync,以防您的應用程式之前透過呼叫 UnlockAsync 解鎖對焦。

private async void ManualFocusRadioButton_Checked(object sender, RoutedEventArgs e)
{
    var focusControl = _mediaCapture.VideoDeviceController.FocusControl;
    await focusControl.LockAsync();
}

在手動對焦滑桿的 ValueChanged 事件處理常式中,呼叫 SetValueAsync 來取得控制項的目前值和設定對焦值。

private async void FocusSlider_ValueChanged(object sender, Windows.UI.Xaml.Controls.Primitives.RangeBaseValueChangedEventArgs e)
{
    var value = (sender as Slider).Value;
    await _mediaCapture.VideoDeviceController.FocusControl.SetValueAsync((uint)value);
}

啟用對焦燈

在支援它的裝置上,您可以啟用對焦輔助燈來協助裝置對焦。 此範例會使用核取方塊來啟用或停用對焦輔助燈。

<CheckBox Content="Assist Light" Name="FocusLightCheckBox" IsEnabled="{Binding ElementName=TapFocusRadioButton,Path=IsChecked}"
                  Checked="FocusLightCheckBox_CheckedChanged" Unchecked="FocusLightCheckBox_CheckedChanged"/>

透過檢查 Supported 屬性來檢查目前擷取裝置是否支援 FlashControl。 也檢查 AssistantLightSupported,以確保也支援輔助燈。 如果兩者均支援,您可以顯示並啟用此功能的 UI。

var focusControl = _mediaCapture.VideoDeviceController.FocusControl;

if (focusControl.Supported)
{

    FocusLightCheckBox.Visibility = (_mediaCapture.VideoDeviceController.FlashControl.Supported &&
                                     _mediaCapture.VideoDeviceController.FlashControl.AssistantLightSupported) ? Visibility.Visible : Visibility.Collapsed;
}
else
{
    FocusLightCheckBox.Visibility = Visibility.Collapsed;
}

CheckedChanged 事件處理常式中,取得擷取裝置 FlashControl 物件。 設定 AssistantLightEnabled 屬性以啟用或停用對焦燈。

private void FocusLightCheckBox_CheckedChanged(object sender, RoutedEventArgs e)
{
    var flashControl = _mediaCapture.VideoDeviceController.FlashControl;

    flashControl.AssistantLightEnabled = (FocusLightCheckBox.IsChecked == true);
}

ISO 速度

IsoSpeedControl 可讓您設定相片或影片擷取期間所使用的 ISO 速度。

此範例會使用 Slider 控制項來調整目前的曝光補償值和核取方塊來切換自動 ISO 速度調整。

<Slider Name="IsoSlider" ValueChanged="IsoSlider_ValueChanged"/>
<TextBlock Text="{Binding ElementName=IsoSlider,Path=Value}" Visibility="{Binding ElementName=IsoSlider,Path=Visibility}"/>
<CheckBox Name="IsoAutoCheckBox" Content="Auto" Checked="IsoAutoCheckBox_CheckedChanged" Unchecked="IsoAutoCheckBox_CheckedChanged"/>

透過檢查 Supported 屬性來檢查目前擷取裝置是否支援 IsoSpeedControl。 如果支援控制項,您可以顯示並啟用此功能的 UI。 設定核取方塊的核取狀態,以指出自動 ISO 速度調整目前是否為 Auto 屬性的值使用中。

ISO 速度值必須位於裝置所支援的範圍內,而且必須是所支援步驟大小的遞增。 透過檢查 MinMaxStep 屬性來取得目前裝置支援的值,這些屬性用於設定滑桿控制項的對應屬性。

取消註冊 ValueChanged 事件處理常式之後,將滑桿控制項的值設定為 IsoSpeedControl 的目前值,以便在設定值時不會觸發事件。

private void UpdateIsoControlCapabilities()
{
    var isoSpeedControl = _mediaCapture.VideoDeviceController.IsoSpeedControl;

    if (isoSpeedControl.Supported)
    {
        IsoAutoCheckBox.Visibility = Visibility.Visible;
        IsoSlider.Visibility = Visibility.Visible;

        IsoAutoCheckBox.IsChecked = isoSpeedControl.Auto;
        
        IsoSlider.Minimum = isoSpeedControl.Min;
        IsoSlider.Maximum = isoSpeedControl.Max;
        IsoSlider.StepFrequency = isoSpeedControl.Step;

        IsoSlider.ValueChanged -= IsoSlider_ValueChanged;
        IsoSlider.Value = isoSpeedControl.Value;
        IsoSlider.ValueChanged += IsoSlider_ValueChanged;
    }
    else
    {
        IsoAutoCheckBox.Visibility = Visibility.Collapsed;
        IsoSlider.Visibility = Visibility.Collapsed;
    }
}

ValueChanged 事件處理常式中,取得控制項的目前值並透過呼叫 SetValueAsync 設定 ISO 速度值。

private async void IsoSlider_ValueChanged(object sender, Windows.UI.Xaml.Controls.Primitives.RangeBaseValueChangedEventArgs e)
{
    var value = (sender as Slider).Value;
    await _mediaCapture.VideoDeviceController.IsoSpeedControl.SetValueAsync((uint)value);
}

在自動 ISO 速度核取方塊的 CheckedChanged 事件處理常式中,透過呼叫 SetAutoAsync 開啟自動 ISO 速度調整。 透過呼叫 SetValueAsync 並傳入滑桿控制項目前的值,以關閉自動 ISO 速度調整。

private async void IsoAutoCheckBox_CheckedChanged(object sender, RoutedEventArgs e)
{
    var autoIso = (sender as CheckBox).IsChecked == true;

    if (autoIso)
    {
        await _mediaCapture.VideoDeviceController.IsoSpeedControl.SetAutoAsync();
    }
    else
    {
        await _mediaCapture.VideoDeviceController.IsoSpeedControl.SetValueAsync((uint)IsoSlider.Value);
    }
}

光學影像防震

光學影像穩定 (OIS )通過機械操縱硬件捕獲裝置來穩定捕獲的視頻流,這可以提供比數字穩定更優越的結果。 在不支援 OIS 的裝置上,您可以使用 VideoStabilizationEffect 在所擷取的視訊上執行數位防震。 有關詳細資訊,請參閱影片擷取的效果

透過檢查 OpticalImageStabilizationControl.Supported 屬性,判斷目前裝置是否支援 OIS。

OIS 控制項支援三種模式:開啟、關閉和自動,這表示裝置會動態判斷 OIS 是否會改善媒體擷取,如果是的話,則啟用 OIS。 若要判斷裝置上是否支援特定模式,請檢查以了解 OpticalImageStabilizationControl.SupportedModes 集合是否包含所需的模式。

透過將 OpticalImageStabilizationControl.Mode 設定為所需模式來啟用或停用 OIS。

private void SetOpticalImageStabilizationMode(OpticalImageStabilizationMode mode)
{
    if (!_mediaCapture.VideoDeviceController.OpticalImageStabilizationControl.Supported)
    {
        ShowMessageToUser("Optical image stabilization not available");
        return;
    }

    var stabilizationModes = _mediaCapture.VideoDeviceController.OpticalImageStabilizationControl.SupportedModes;

    if (!stabilizationModes.Contains(mode))
    {
        ShowMessageToUser("Optical image stabilization setting not supported");
        return;
    }

    _mediaCapture.VideoDeviceController.OpticalImageStabilizationControl.Mode = mode;
}

電力線路頻率

某些相機裝置支援反閃爍處理,這取決於知道目前環境中的電力線路的 AC 頻率。 某些裝置能支援自動判斷電力線路頻率,而其他裝置則要求手動設定頻率。 下列程式碼範例示範如何判斷裝置上的電力線路頻率支援,並視需要如何手動設定頻率。

首先,呼叫 VideoDeviceController 方法 TryGetPowerlineFrequency,傳入一個 PowerlineFrequency 類型的輸出參數;如果呼叫失敗,表示目前裝置不支援電力線路頻率控制項。 如果支援此功能,您可以嘗試設定自動模式來判斷裝置上是否可使用自動模式。 透過呼叫 TrySetPowerlineFrequency 並傳入 Auto 值來執行此動作。如果呼叫成功,這表示支援您的自動電源頻率。 如果裝置上支援電力線路頻率控制器,但未進行自動頻率偵測,您仍然可以使用 TrySetPowerlineFrequency 手動設定頻率。 在此範例中,MyCustomFrequencyLookup 是您實作的自訂方法,用來判斷裝置目前位置的正確頻率。

 PowerlineFrequency getFrequency;

 if (! _mediaCapture.VideoDeviceController.TryGetPowerlineFrequency(out getFrequency))
 {
     // Powerline frequency is not supported on this device.
     return;
 }

 if (! _mediaCapture.VideoDeviceController.TrySetPowerlineFrequency(PowerlineFrequency.Auto))
 {
     // Set the frequency manually
     PowerlineFrequency setFrequency = MyCustomFrequencyLookup();
     if (_mediaCapture.VideoDeviceController.TrySetPowerlineFrequency(setFrequency))
     {
         System.Diagnostics.Debug.WriteLine(String.Format("Powerline frequency manually set to {0}.", setFrequency));
     }
 }

白平衡

WhiteBalanceControl 可讓您設定在相片或影片擷取期間使用的白平衡。

此範例會使用 ComboBox 控制項從內建色彩溫度預設值和 Slider 控制項中選取,以進行手動白平衡調整。

<Slider Name="WbSlider" ValueChanged="WbSlider_ValueChanged"/>
<TextBlock Name="WbTextBox" Text="{Binding ElementName=WbSlider,Path=Value}" Visibility="{Binding ElementName=WbSlider,Path=Visibility}"/>
<ComboBox Name="WbComboBox" SelectionChanged="WbComboBox_SelectionChanged"/>

透過檢查 Supported 屬性來檢查目前擷取裝置是否支援 WhiteBalanceControl。 如果支援控制項,您可以顯示並啟用此功能的 UI。 將下拉式方塊的項目設定為 ColorTemperaturePreset 列舉的值。 並將選取的項目設定為 Preset 屬性的目前值。

針對手動控制,白平衡值必須位於裝置所支援的範圍內,而且必須是支援步驟大小的遞增。 透過檢查 MinMaxStep 屬性來取得目前裝置支援的值,這些屬性用於設定滑桿控制項的對應屬性。 啟用手動控制之前,請檢查以確定最小和最大支援值之間的範圍大於步驟大小。 如果不是,目前裝置不支援手動控制。

取消註冊 ValueChanged 事件處理常式之後,將滑桿控制項的值設為 WhiteBalanceControl 的目前值,以便在設定值時不會觸發該事件。

           var whiteBalanceControl = _mediaCapture.VideoDeviceController.WhiteBalanceControl;

           if (whiteBalanceControl.Supported)
           {
               WbSlider.Visibility = Visibility.Visible;
               WbComboBox.Visibility = Visibility.Visible;

               if (WbComboBox.ItemsSource == null)
               {
                   WbComboBox.ItemsSource = Enum.GetValues(typeof(ColorTemperaturePreset)).Cast<ColorTemperaturePreset>();
               }

               WbComboBox.SelectedItem = whiteBalanceControl.Preset;

               if (whiteBalanceControl.Max - whiteBalanceControl.Min > whiteBalanceControl.Step)
               {

                   WbSlider.Minimum = whiteBalanceControl.Min;
                   WbSlider.Maximum = whiteBalanceControl.Max;
                   WbSlider.StepFrequency = whiteBalanceControl.Step;

                   WbSlider.ValueChanged -= WbSlider_ValueChanged;
                   WbSlider.Value = whiteBalanceControl.Value;
                   WbSlider.ValueChanged += WbSlider_ValueChanged;
               }
               else
               {
                   WbSlider.Visibility = Visibility.Collapsed;
               }
           }
           else
           {
               WbSlider.Visibility = Visibility.Collapsed;
               WbComboBox.Visibility = Visibility.Collapsed;
           }

在色彩溫度預設下拉式方塊的 SelectionChanged 事件處理常式中,取得目前已選取的預設並透過呼叫 SetPresetAsync 設定控制項的值。 如果已選取的預設值不是 Manual,請停用手動白平衡滑桿。

private async void WbComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
    if(!_isPreviewing)
    {
        // Do not set white balance values unless the preview stream is running.
        return;
    }

    var selected = (ColorTemperaturePreset)WbComboBox.SelectedItem;
    WbSlider.IsEnabled = (selected == ColorTemperaturePreset.Manual);
    await _mediaCapture.VideoDeviceController.WhiteBalanceControl.SetPresetAsync(selected);

}

ValueChanged 事件處理常式中,取得控制項的目前值並透過呼叫 SetValueAsync 設定白平衡。

private async void WbSlider_ValueChanged(object sender, Windows.UI.Xaml.Controls.Primitives.RangeBaseValueChangedEventArgs e)
{
    if (!_isPreviewing)
    {
        // Do not set white balance values unless the preview stream is running.
        return;
    }

    var value = (sender as Slider).Value;
    await _mediaCapture.VideoDeviceController.WhiteBalanceControl.SetValueAsync((uint)value);
}

重要

只有在預覽串流執行時,才支援調整白平衡。 請先檢查預覽串流是否正在執行,再設定白平衡值或預設值。

重要

ColorTemperaturePreset.Auto 預設值會指示系統自動調整白平衡層級。 在某些案例下,例如擷取相片順序,其中每個畫面的白平衡層級應該相同,您可能想要將控制項鎖定為目前的自動值。 若要這樣做,請呼叫 SetPresetAsync 並指定 Manual 預設值,而不使用 SetValueAsync 在控制項上設定值。 這會導致裝置鎖定目前的值。 請勿嘗試讀取目前的控制項值,然後將傳回的值傳遞至 SetValueAsync,因為此值不保證正確。

Zoom

ZoomControl 可讓您設定相片或影片擷取期間所使用的縮放層級。

此範例會使用 Slider 控制項來調整目前的縮放層級。 下一節說明如何根據畫面上的捏合手勢調整縮放。

<Slider Name="ZoomSlider" Grid.Row="0" Orientation="Vertical" HorizontalAlignment="Center" VerticalAlignment="Stretch" ValueChanged="ZoomSlider_ValueChanged"/>
<TextBlock Grid.Row="1" HorizontalAlignment="Center" Text="{Binding ElementName=ZoomSlider,Path=Value}"/>

透過檢查 Supported 屬性來檢查目前擷取裝置是否支援 ZoomControl。 如果支援控制項,您可以顯示並啟用此功能的 UI。

縮放層級值必須位於裝置所支援的範圍內,而且必須是所支援步驟大小的遞增。 透過檢查 MinMaxStep 屬性來取得目前裝置支援的值,這些屬性用於設定滑桿控制項的對應屬性。

取消註冊 ValueChanged 事件處理常式之後,將滑桿控制項的值設定為 ZoomControl 的目前值,以便在設定值時不會觸發事件。

var zoomControl = _mediaCapture.VideoDeviceController.ZoomControl;

if (zoomControl.Supported)
{
    ZoomSlider.Visibility = Visibility.Visible;

    ZoomSlider.Minimum = zoomControl.Min;
    ZoomSlider.Maximum = zoomControl.Max;
    ZoomSlider.StepFrequency = zoomControl.Step;
    
    ZoomSlider.ValueChanged -= ZoomSlider_ValueChanged;
    ZoomSlider.Value = zoomControl.Value;
    ZoomSlider.ValueChanged += ZoomSlider_ValueChanged;
}
else
{
    ZoomSlider.Visibility = Visibility.Collapsed;
}

ValueChanged 事件處理常式中,建立 ZoomSettings 類別的新執行個體,將 Value 屬性設定為縮放滑桿控制項的目前值。 如果 ZoomControlSupportedModes 屬性包含 ZoomTransitionMode.Smooth,則表示裝置支援縮放層級之間的流暢轉換。 由於此模式提供較佳的使用者體驗,因此您通常會想要針對 ZoomSettings 物件的 Mode 屬性使用此值。

最後,透過將 ZoomSettings 物件傳遞到 ZoomControl 物件的 Configure 方法來變更目前的縮放設定。

private void ZoomSlider_ValueChanged(object sender, Windows.UI.Xaml.Controls.Primitives.RangeBaseValueChangedEventArgs e)
{
    var level = (float)ZoomSlider.Value;
    var settings = new ZoomSettings { Value = level };

    var zoomControl = _mediaCapture.VideoDeviceController.ZoomControl;
    if (zoomControl.SupportedModes.Contains(ZoomTransitionMode.Smooth))
    {
        settings.Mode = ZoomTransitionMode.Smooth;
    }
    else
    {
        settings.Mode = zoomControl.SupportedModes.First();
    }

    zoomControl.Configure(settings);
}

使用捏合手勢流暢縮放

如上一節所述,在支援它的裝置上,流暢縮放模式可讓擷取裝置在數位縮放層級之間流暢轉換,進而讓使用者在擷取作業期間動態調整縮放層級,而不會出現離散和不和諧的轉換。 本節說明如何調整縮放層級以回應捏合手勢。

首先,透過檢查 ZoomControl.Supported 屬性來確定目前裝置是否支援數位縮放控制項。 接下來,透過檢查 ZoomTransitionMode.Smooth 來確定流暢縮放模式是否可用,看看它是否包含值 ZoomControl.SupportedModes

private bool IsSmoothZoomSupported()
{
    if (!_mediaCapture.VideoDeviceController.ZoomControl.Supported)
    {
        ShowMessageToUser("Digital zoom is not supported on this device.");
        return false;
    }

    var zoomModes = _mediaCapture.VideoDeviceController.ZoomControl.SupportedModes;

    if (!zoomModes.Contains(ZoomTransitionMode.Smooth))
    {
        ShowMessageToUser("Smooth zoom not supported");
        return false;
    }

    return true;
}

在啟用多點觸控的裝置上,典型的案例是根據雙指捏合手勢來調整縮放係數。 將 CaptureElement 控制項的 ManipulationMode 屬性設為 ManipulationModes.Scale 以啟用捏合手勢。 然後,註冊 ManipulationDelta 事件,該事件在捏合手勢改變大小時引發。

private void RegisterPinchGestureHandler()
{
    if (!IsSmoothZoomSupported())
    {
        return;
    }

    // Enable pinch/zoom gesture for the preview control
    PreviewControl.ManipulationMode = ManipulationModes.Scale;
    PreviewControl.ManipulationDelta += PreviewControl_ManipulationDelta;
}

ManipulationDelta 事件的處理常式中,根據使用者捏合手勢的變更來更新縮放係數。 ManipulationDelta.Scale 值表示捏合手勢的縮放變化,捏合大小的小幅增加是略大於 1.0 的數字,捏合大小的小幅減少是略小於 1.0 的數字。 在此範例中,縮放控制項的目前值會乘以比例差異。

在設定縮放係數之前,必須確保該值不小於 ZoomControl.Min 屬性所指示的裝置支援的最小值。 另外,請確保該值小於或等於 ZoomControl.Max 值。 最後,您必須確保縮放係數是裝置支援的縮放步驟的倍數 (如 Step 屬性所示)。 如果您的縮放係數不符合這些需求,當您嘗試在擷取裝置上設定縮放層級時,將會擲回例外狀況。

透過建立新的 ZoomSettings 物件來設定擷取裝置上的縮放層級。 將 Mode 屬性設為 ZoomTransitionMode.Smooth,然後將 Value 屬性設定為所需的縮放係數。 最後,呼叫 ZoomControl.Configure 在裝置上設定新的縮放值。 裝置會流暢轉換為新的縮放值。

private void PreviewControl_ManipulationDelta(object sender, ManipulationDeltaRoutedEventArgs e)
{
    var zoomControl = _mediaCapture.VideoDeviceController.ZoomControl;

    // Example zoom factor calculation based on size of scale gesture
    var zoomFactor = zoomControl.Value * e.Delta.Scale;

    if (zoomFactor < zoomControl.Min) zoomFactor = zoomControl.Min;
    if (zoomFactor > zoomControl.Max) zoomFactor = zoomControl.Max;
    zoomFactor = zoomFactor - (zoomFactor % zoomControl.Step);

    var settings = new ZoomSettings();
    settings.Mode = ZoomTransitionMode.Smooth;
    settings.Value = zoomFactor;

    _mediaCapture.VideoDeviceController.ZoomControl.Configure(settings);

}