Manual camera controls for photo and video capture

This article shows you how to use manual device controls to enable enhanced photo and video capture scenarios including optical image stabilization and smooth zoom.

The controls discussed in this article are all added to your app using the same pattern. First, check to see if the control is supported on the current device on which your app is running. If the control is supported, set the desired mode for the control. Typically, if a particular control is unsupported on the current device, you should disable or hide the UI element that allows the user to enable the feature.

The code in this article was adapted from the Camera Manual Controls SDK sample. You can download the sample to see the code used in context or to use the sample as a starting point for your own app.

Note

This article builds on concepts and code discussed in Basic photo, video, and audio capture with MediaCapture, which describes the steps for implementing basic photo and video capture. We recommend that you familiarize yourself with the basic media capture pattern in that article before moving on to more advanced capture scenarios. The code in this article assumes that your app already has an instance of MediaCapture that has been properly initialized.

All of the device control APIs discussed in this article are members of the Windows.Media.Devices namespace.

using Windows.Media.Devices;

Exposure

The ExposureControl allows you to set the shutter speed used during photo or video capture.

This example uses a Slider control to adjust the current exposure value and a checkbox to toggle automatic exposure adjustment.

<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"/>

Check to see if the current capture device supports the ExposureControl by checking the Supported property. If the control is supported, you can show and enable the UI for this feature. Set the checked state of the checkbox to indicate if automatic exposure adjustment is currently active to the value of the Auto property.

The exposure value must be within the range supported by the device and must be an increment of the supported step size. Get the supported values for the current device by checking the Min, Max, and Step properties, which are used to set the corresponding properties of the slider control.

Set the slider control's value to the current value of the ExposureControl after unregistering the ValueChanged event handler so that the event is not triggered when the value is set.

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

In the ValueChanged event handler, get the current value of the control and the set the exposure value by calling 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);
}

In the CheckedChanged event handler of the auto exposure checkbox, turn automatic exposure adjustment on or off by calling SetAutoAsync and passing in a boolean value.

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

Important

Automatic exposure mode is only supported while the preview stream is running. Check to make sure that the preview stream is running before turning on automatic exposure.

Exposure compensation

The ExposureCompensationControl allows you to set the exposure compensation used during photo or video capture.

This example uses a Slider control to adjust the current exposure compensation value.

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

Check to see if the current capture device supports the ExposureCompensationControl by checking the Supported property. If the control is supported, you can show and enable the UI for this feature.

The exposure compensation value must be within the range supported by the device and must be an increment of the supported step size. Get the supported values for the current device by checking the Min, Max, and Step properties, which are used to set the corresponding properties of the slider control.

Set slider control's value to the current value of the ExposureCompensationControl after unregistering the ValueChanged event handler so that the event is not triggered when the value is set.

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

In the ValueChanged event handler, get the current value of the control and the set the exposure value by calling 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

The FlashControl allows you to enable or disable the flash or to enable automatic flash, where the system dynamically determines whether to use the flash. This control also allows you to enable automatic red eye reduction on devices that support it. These settings all apply to capturing photos. The TorchControl is a separate control for turning the torch on or off for video capture.

This example uses a set of radio buttons to allow the user to switch between on, off, and auto flash settings. A checkbox is also provided to allow toggling of red eye reduction and the video torch.

<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"/>

Check to see if the current capture device supports the FlashControl by checking the Supported property. If the control is supported, you can show and enable the UI for this feature. If the FlashControl is supported, automatic red eye reduction may or may not be supported, so check the RedEyeReductionSupported property before enabling the UI. Because the TorchControl is separate from the flash control, you must also check its Supported property before using it.

In the Checked event handler for each of the flash radio buttons, enable or disable the appropriate corresponding flash setting. Note that to set the flash to always be used, you must set the Enabled property to true and the Auto property to 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;
}

In the handler for the red eye reduction checkbox, set the RedEyeReduction property to the appropriate value.

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

Finally, in the handler for the video torch checkbox, set the Enabled property to the appropriate value.

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

Note

On some devices the torch will not emit light, even if TorchControl.Enabled is set to true, unless the device has a preview stream running and is actively capturing video. The recommended order of operations is to turn on the video preview, turn on the torch by setting Enabled to true, and then initiate video capture. On some devices the torch will light up after the preview is started. On other devices, the torch may not light up until video capture is started.

Focus

Three different commonly used methods for adjusting the focus of the camera are supported by the FocusControl object, continuous autofocus, tap to focus, and manual focus. A camera app may support all three of these methods, but for readability, this article discusses each technique separately. This section also discusses how to enable the focus assist light.

Continuous autofocus

Enabling continuous autofocus instructs the camera to adjust the focus dynamically to try to keep the subject of the photo or video in focus. This example uses a radio button to toggle continuous autofocus on and off.

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

Check to see if the current capture device supports the FocusControl by checking the Supported property. Next, determine if continuous autofocus is supported by checking the SupportedFocusModes list to see if it contains the value FocusMode.Continuous, and if so, show the continuous autofocus radio button.

var focusControl = _mediaCapture.VideoDeviceController.FocusControl;

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

In the Checked event handler for the continuous autofocus radio button, use the VideoDeviceController.FocusControl property to get an instance of the control. Call UnlockAsync to unlock the control in case your app has previously called LockAsync to enable one of the other focus modes.

Create a new FocusSettings object and set the Mode property to Continuous. Set the AutoFocusRange property to a value appropriate for your app scenario or selected by the user from your UI. Pass your FocusSettings object into the Configure method, and then call FocusAsync to initiate continuous autofocus.

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

Important

Autofocus mode is only supported while the preview stream is running. Check to make sure that the preview stream is running before turning on continuous autofocus.

Tap to focus

The tap-to-focus technique uses the FocusControl and the RegionsOfInterestControl to specify a subregion of the capture frame where the capture device should focus. The region of focus is determined by the user tapping on the screen displaying the preview stream.

This example uses a radio button to enable and disable tap-to-focus mode.

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

Check to see if the current capture device supports the FocusControl by checking the Supported property. The RegionsOfInterestControl must be supported, and must support at least one region, in order to use this technique. Check the AutoFocusSupported and MaxRegions properties to determine whether to show or hide the radio button for tap-to-focus.

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

In the Checked event handler for the tap-to-focus radio button, use the VideoDeviceController.FocusControl property to get an instance of the control. Call LockAsync to lock the control in case your app has previously called UnlockAsync to enable continuous autofocus, and then wait for the user to tap the screen to change the focus.

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
}

This example focuses on a region when the user taps the screen, and then removes the focus from that region when the user taps again, like a toggle. Use a boolean variable to track the current toggled state.

bool _isFocused = false;

The next step is to listen for the event when the user taps the screen by handling the Tapped event of the CaptureElement that is currently displaying the capture preview stream. If the camera isn't currently previewing, or if tap-to-focus mode is disabled, return from the handler without doing anything.

If the tracking variable _isFocused is toggled to false, and if the camera isn't currently in the process of focus (determined by the FocusState property of the FocusControl), begin the tap-to-focus process. Get the position of the user's tap from the event args passed into the handler. This example also uses this opportunity to pick the size of the region that will be focused upon. In this case, the size is 1/4 of the smallest dimension of the capture element. Pass the tap position and the region size into the TapToFocus helper method that is defined in the next section.

If the _isFocused toggle is set to true, the user tap should clear the focus from the previous region. This is done in the TapUnfocus helper method shown below.

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

In the TapToFocus helper method, first set the _isFocused toggle to true so that the next screen tap will release the focus from the tapped region.

The next task in this helper method is to determine the rectangle within the preview stream that will be assigned to the focus control. This requires two steps. The first step is to determine the rectangle that the preview stream takes up within the CaptureElement control. This depends on the dimensions of the preview stream and the orientation of the device. The helper method GetPreviewStreamRectInControl, shown at the end of this section, performs this task and returns the rectangle containing the preview stream.

The next task in TapToFocus is to convert the tap location and desired focus rectangle size, which were determined within the CaptureElement.Tapped event handler, into coordinates within capture stream. The ConvertUiTapToPreviewRect helper method, shown later in this section, performs this conversion and returns the rectangle, in capture stream coordinates, where the focus will be requested.

Now that the target rectangle has been obtained, create a new RegionOfInterest object, setting the Bounds property to the target rectangle obtained in the previous steps.

Get the capture device's FocusControl. Create a new FocusSettings object and set the Mode and AutoFocusRange to your desired values, after checking to make sure that they are supported by the FocusControl. Call Configure on the FocusControl to make your settings active and signal the device to begin focusing on the specified region.

Next, get the capture device's RegionsOfInterestControl and call SetRegionsAsync to set the active region. Multiple regions of interest can be set on devices that support it, but this example only sets a single region.

Finally, call FocusAsync on the FocusControl to initiate focusing.

Important

When implementing tap to focus, the order of operations is important. You should call these APIs in the following order:

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

In the TapUnfocus helper method, obtain the RegionsOfInterestControl and call ClearRegionsAsync to clear the region that was registered with the control within the TapToFocus helper method. Then, get the FocusControl and call FocusAsync to cause the device to refocus without a region of interest.

private async Task TapUnfocus()
{
    _isFocused = false;

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

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

The GetPreviewStreamRectInControl helper method uses the resolution of the preview stream and the orientation of the device to determine the rectangle within the preview element that contains the preview stream, trimming off any letterboxed padding that the control may provide to maintain the stream's aspect ratio. This method uses class member variables defined in the basic media capture example code found in Basic photo, video, and audio capture with 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;
}

The ConvertUiTapToPreviewRect helper method takes as arguments the location of the tap event, the desired size of the focus region, and the rectangle containing the preview stream obtained from the GetPreviewStreamRectInControl helper method. This method uses these values and the device's current orientation to calculate the rectangle within the preview stream that contains the desired region. Once again, this method uses class member variables defined in the basic media capture example code found in Capture Photos and Video with 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);
}

Manual focus

The manual focus technique uses a Slider control to set the current focus depth of the capture device. A radio button is used to toggle manual focus on and off.

<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"/>

Check to see if the current capture device supports the FocusControl by checking the Supported property. If the control is supported, you can show and enable the UI for this feature.

The focus value must be within the range supported by the device and must be an increment of the supported step size. Get the supported values for the current device by checking the Min, Max, and Step properties, which are used to set the corresponding properties of the slider control.

Set the slider control's value to the current value of the FocusControl after unregistering the ValueChanged event handler so that the event is not triggered when the value is set.

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

In the Checked event handler for the manual focus radio button, get the FocusControl object and call LockAsync in case your app had previously unlocked the focus with a call to UnlockAsync.

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

In the ValueChanged event handler of the manual focus slider, get the current value of the control and the set the focus value by calling 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);
}

Enable the focus light

On devices that support it, you can enable a focus assist light to help the device focus. This example uses a checkbox to enable or disable the focus assist light.

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

Check to see if the current capture device supports the FlashControl by checking the Supported property. Also check the AssistantLightSupported to make sure the assist light is also supported. If these are both supported, you can show and enable the UI for this feature.

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

In the CheckedChanged event handler, get the capture devices FlashControl object. Set the AssistantLightEnabled property to enable or disable the focus light.

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

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

ISO speed

The IsoSpeedControl allows you to set the ISO speed used during photo or video capture.

This example uses a Slider control to adjust the current exposure compensation value and a checkbox to toggle automatic ISO speed adjustment.

<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"/>

Check to see if the current capture device supports the IsoSpeedControl by checking the Supported property. If the control is supported, you can show and enable the UI for this feature. Set the checked state of the checkbox to indicate if automatic ISO speed adjustment is currently active to the value of the Auto property.

The ISO speed value must be within the range supported by the device and must be an increment of the supported step size. Get the supported values for the current device by checking the Min, Max, and Step properties, which are used to set the corresponding properties of the slider control.

Set the slider control's value to the current value of the IsoSpeedControl after unregistering the ValueChanged event handler so that the event is not triggered when the value is set.

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

In the ValueChanged event handler, get the current value of the control and the set the ISO speed value by calling SetValueAsync.

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

In the CheckedChanged event handler of the auto ISO speed checkbox, turn on automatic ISO speed adjustment by calling SetAutoAsync. Turn automatic ISO speed adjustment off by calling SetValueAsync and passing in the current value of the slider control.

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

Optical image stabilization

Optical image stabilization (OIS) stabilizes a captured video stream by mechanically manipulating the hardware capture device, which can provide a superior result than digital stabilization. On devices that don't support OIS, you can use the VideoStabilizationEffect to perform digital stabilization on your captured vide. For more information, see Effects for video capture.

Determine if OIS is supported on the current device by checking the OpticalImageStabilizationControl.Supported property.

The OIS control supports three modes: on, off, and automatic, which means that the device dynamically determines if OIS would improve the media capture and, if so, enables OIS. To determine if a particular mode is supported on a device, check to see if the OpticalImageStabilizationControl.SupportedModes collection contains the desired mode.

Enable or disable OIS by setting the OpticalImageStabilizationControl.Mode to the desired mode.

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

Some camera devices support anti-flicker processing that depends on knowing the AC frequency of the powerlines in the current environment. Some devices support automatic determination of the powerline frequency, while others require that the frequency be set manually. The following code example shows how to determine powerline frequency support on the device and, if needed, how to set the frequency manually.

First, call the VideoDeviceController method TryGetPowerlineFrequency, passing in an output parameter of type PowerlineFrequency; if this call fails, the powerline frequency control is not supported on the current device. If the feature is supported, you can determine if automatic mode is available on the device by trying to set auto mode. Do this by calling TrySetPowerlineFrequency and passing in the value Auto. If the call succeeds, that means that your auto powerline frequency is supported. If the powerline frequency controller is supported on the device but automatic frequency detection is not, you can still manually set the frequency by using TrySetPowerlineFrequency. In this example, MyCustomFrequencyLookup is a custom method that you implement to determine the correct frequency for the device's current location.

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

White balance

The WhiteBalanceControl allows you to set the white balance used during photo or video capture.

This example uses a ComboBox control to select from built-in color temperature presets and a Slider control for manual white balance adjustment.

<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"/>

Check to see if the current capture device supports the WhiteBalanceControl by checking the Supported property. If the control is supported, you can show and enable the UI for this feature. Set the items of the combo box to the values of the ColorTemperaturePreset enumeration. And set the selected item to the current value of the Preset property.

For manual control, the white balance value must be within the range supported by the device and must be an increment of the supported step size. Get the supported values for the current device by checking the Min, Max, and Step properties, which are used to set the corresponding properties of the slider control. Before enabling manual control, check to make sure that the range between the minimum and maximum supported values is greater than the step size. If it is not, manual control is not supported on the current device.

Set the slider control's value to the current value of the WhiteBalanceControl after unregistering the ValueChanged event handler so that the event is not triggered when the value is set.

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

In the SelectionChanged event handler of the color temperature preset combo box, get the currently selected preset and set the value of the control by calling SetPresetAsync. If the selected preset value is not Manual, disable the manual white balance slider.

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

}

In the ValueChanged event handler, get the current value of the control and the set the white balance value by calling 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);
}

Important

Adjusting the white balance is only supported while the preview stream is running. Check to make sure that the preview stream is running before setting the white balance value or preset.

Important

The ColorTemperaturePreset.Auto preset value instructs the system to automatically adjust the white balance level. For some scenarios, such as capturing a photo sequence where the white balance levels should be the same for each frame, you may want to lock the control to the current automatic value. To do this, call SetPresetAsync and specify the Manual preset and do not set a value on the control using SetValueAsync. This will cause the device to lock the current value. Do not attempt to read the current control value and then pass the returned value into SetValueAsync because this value is not guaranteed to be correct.

Zoom

The ZoomControl allows you to set the zoom level used during photo or video capture.

This example uses a Slider control to adjust the current zoom level. The following section shows how to adjust zoom based on a pinch gesture on the screen.

<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}"/>

Check to see if the current capture device supports the ZoomControl by checking the Supported property. If the control is supported, you can show and enable the UI for this feature.

The zoom level value must be within the range supported by the device and must be an increment of the supported step size. Get the supported values for the current device by checking the Min, Max, and Step properties, which are used to set the corresponding properties of the slider control.

Set the slider control's value to the current value of the ZoomControl after unregistering the ValueChanged event handler so that the event is not triggered when the value is set.

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

In the ValueChanged event handler, create a new instance of the ZoomSettings class, setting the Value property to the current value of the zoom slider control. If the SupportedModes property of the ZoomControl contains ZoomTransitionMode.Smooth, it means the device supports smooth transitions between zoom levels. Since this modes provides a better user experience, you will typically want to use this value for the Mode property of the ZoomSettings object.

Finally, change the current zoom settings by passing your ZoomSettings object into the Configure method of the ZoomControl object.

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

Smooth zoom using pinch gesture

As discussed in the previous section, on devices that support it, smooth zoom mode allows the capture device to smoothly transition between digital zoom levels, allowing the user to dynamically adjust the zoom level during the capture operation without discrete and jarring transitions. This section describes how to adjust the zoom level in response to a pinch gesture.

First, determine if the digital zoom control is supported on the current device by checking the ZoomControl.Supported property. Next, determine if smooth zoom mode is available by checking the ZoomControl.SupportedModes to see if it contains the value 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;
}

On a multi-touch enabled device, a typical scenario is to adjust the zoom factor based on a two-finger pinch gesture. Set the ManipulationMode property of the CaptureElement control to ManipulationModes.Scale to enable the pinch gesture. Then, register for the ManipulationDelta event which is raised when the pinch gesture changes size.

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

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

In the handler for the ManipulationDelta event, update the zoom factor based on the change in the user's pinch gesture. The ManipulationDelta.Scale value represents the change in scale of the pinch gesture such that a small increase in the size of the pinch is a number slightly larger than 1.0 and a small decrease in the pinch size is a number slightly smaller than 1.0. In this example, the current value of the zoom control is multiplied by the scale delta.

Before setting the zoom factor, you must make sure that the value is not less than the minimum value supported by the device as indicated by the ZoomControl.Min property. Also, make sure that the value is less than or equal to the ZoomControl.Max value. Finally, you must make sure that the zoom factor is a multiple of the zoom step size supported by the device as indicated by the Step property. If your zoom factor does not meet these requirements, an exception will be thrown when you attempt to set the zoom level on the capture device.

Set the zoom level on the capture device by creating a new ZoomSettings object. Set the Mode property to ZoomTransitionMode.Smooth and then set the Value property to your desired zoom factor. Finally, call ZoomControl.Configure to set the new zoom value on the device. The device will smoothly transition to the new zoom value.

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

}