用于照片和视频捕获的手动相机控件

本文介绍如何使用手动设备控件实现增强的照片和视频捕获方案,包括光学图像防抖动和平滑缩放。

本文中讨论的控件全部使用相同模式添加到你的应用中。 首先,检查运行你的应用的当前设备是否支持该控件。 如果控件受支持,则为控件设置所需模式。 通常,如果特定控件在当前设备上不受支持,你应禁用或隐藏允许用户启用该功能的 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 属性,它们用于设置滑块控件的对应属性。

在将滑块控件的值设置为 ExposureControl 的当前值之前,需先注销 ValueChanged 事件处理程序,以便在设置该值时不触发该事件。

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 属性,它们用于设置滑块控件的对应属性。

在将滑块控件的值设置为 ExposureCompensationControl 的当前值之前,需先注销 ValueChanged 事件处理程序,以便在设置该值时不触发该事件。

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 属性可获取该控件的一个实例。 如果你的应用先前调用了 LockAsync,则调用 UnlockAsync 以解锁该控件,以便启用两种对焦模式中的另外一种。

创建新的 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 属性来检查当前捕获设备是否支持 FocusControlRegionsOfInterestControl 必须受支持,且必须至少支持一个区域才能使用该技术。 选中 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 对象,并在进行检查以确保 ModeAutoFocusRangeFocusControl 支持之后,将它们设置为所需值。 在 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 属性,它们用于设置滑块控件的对应属性。

在将滑块控件的值设置为 FocusControl 的当前值之前,需先注销 ValueChanged 事件处理程序,以便在设置该值时不触发该事件。

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 事件处理程序中,如果你的应用之前通过调用 UnlockAsync 解锁了对焦,则获取 FocusControl 对象并调用 LockAsync

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 属性,它们用于设置滑块控件的对应属性。

在将滑块控件的值设置为 IsoSpeedControl 的当前值之前,需先注销 ValueChanged 事件处理程序,以便在设置该值时不触发该事件。

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

Powerline frequency

某些相机设备支持防闪烁处理,该功能依赖于获知当前环境中的电力线 AC 频率。 某些设备支持自动确定电力线频率,而另一些设备需要手动设置该频率。 以下代码示例显示如何确定设备上的电力线频率支持以及如何手动设置该频率(如果需要)。

首先,调用 VideoDeviceController 方法 TryGetPowerlineFrequency,从而传入 PowerlineFrequency 类型的输出参数;如果此调用失败,则电力线频率控制在当前设备上不受支持。 如果该功能受支持,则你可以通过尝试设置自动模式来确定自动模式在设备上是否可用。 为此,请调用 TrySetPowerlineFrequency 并传入值 Auto。如果调用成功,则表示支持自动 powerline 频率。 如果设备上支持电力线频率控制器,但不支持自动频率检测,你仍然可以使用 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 属性,它们用于设置滑块控件的对应属性。 在启用手动控件前,检查以确保最小受支持值和最大支持值之差大于该步长。 如果不是这样,则表明当前设备不支持手动控件。

在将滑块控件的值设置为 WhiteBalanceControl 的当前值之前,需先注销 ValueChanged 事件处理程序,以便在设置该值时不触发该事件。

           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,因为不能保证该值是正确的。

缩放

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 属性,它们用于设置滑块控件的对应属性。

在将滑块控件的值设置为 ZoomControl 的当前值之前,需先注销 ValueChanged 事件处理程序,以便在设置该值时不触发该事件。

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 属性,确定数字缩放控件在当前设备上是否受支持。 接下来,通过检查 ZoomControl.SupportedModes 以查看它是否包含值 ZoomTransitionMode.Smooth,确定是否可以使用平滑缩放模式。

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

}