High Dynamic Range Imaging (HDR) is a technique that uses multiple exposures of the same shot in order to reproduce a greater dynamic range of luminosity than is possible with standard digital imaging or photographic techniques. The Universal Windows Platform (UWP) empowers you to build apps that take HDR photographs and videos, using digital compositing to overcome some of the limitations of standard photography and videography.
Unlike the human eye, standard cameras are not able to capture a great dynamic range (the difference between your darkest and brightest tones). You can use a high exposure in order to allow more detail to enter your picture, or a lower exposure in order to remove noise under bright lighting conditions. When your composition contains both bright and dark areas in high contrast, HDR provides a way to capture the richness of the items in shadow as well as the vibrancy of the highlights.
In the past, HDR was typically done in post-processing with photo-imaging software. Today, it can be done in real-time on a camera phone or tablet as the photo or video is being taken. This post will walk you through how to add HDR functionality to your own custom camera app in UWP:
- MediaCapture API basics: photo and video capture
- Using the AdvancedPhotoCapture class for High Dynamic Range photos
- Managing hardware buttons and lock screens
- Deciding if HDR would help with the SceneAnalysisEffect class
- Enhanced video with the HDRVideo control
MediaCapture API basics: photos and videos
HDR photography and videography are extensions to the core functionality of the MediaCapture class. To learn how to use HDR, consequently, it’s important to first understand how to capture standard photos and videos in UWP.
The first thing you want to do when starting a new Universal Windows project that uses MediaCapture class is to double click the app manifest file in Visual Studio and configure your app’s capabilities in the Capabilities tab. You are going to want to enable a lot of features related to photos and videos for the code in this post. Select Microphone, Pictures Library, Videos Library and Webcam in order to gain access to the sensors and storage locations you will need. Also, to keep this sample code easy, go to the Application tab and select Landscape as the only supported rotation. In a full camera app, you will want to keep track of device rotation—but we can cut down on a lot of code by not doing it here.
If you run into mysterious errors while testing your app, always come back here and check that you have the correct capabilities checked off.
Since you will need to access your media capture instance from many spots in your code, you should create a private member to hold it.
[code lang=”csharp”]
private MediaCapture _mediaCapture;
[/code]
To use the media capture object, you need to instantiate it and also call InitializeAsync on it. It is also standard practice to create a view finder for your app. To create this, you will place a CaptureElement control in your XAML. Then set the capture element’s source to your media capture object. In order to kick off a video preview in the view finder, call StartPreviewAsync.
The InitializeCameraAsync method below is typically called from your Page’s OnNavigatedTo method. A corresponding cleanup method should be called in OnNavigatingFrom. In order to manage app lifecycle events, it is good practice to also call the cleanup method during the Suspended event and InitializeCameraAsync from your event handler for the Resuming lifecycle event.
[code lang=”csharp”]
private async Task InitializeCameraAsync()
{
if (_mediaCapture == null)
{
// instantiate media capture object
_mediaCapture = new MediaCapture();
try
{
// init media capture
await _mediaCapture.InitializeAsync();
// set the capture element’s source in the UI to the media capture object
ViewFinder.Source = _mediaCapture;
await _mediaCapture.StartPreviewAsync();
}
catch (Exception ex)
{
Debug.WriteLine("Exception initializing camera {0}", ex.ToString());
}
}
}
[/code]
With the media capture object initialized, it is pretty easy to capture a photo. You just have to call the CapturePhotoToStreamAsync method to grab the photo stream.
[code lang=”csharp”]
private async Task TakeStandardPhotoAsync()
{
var photoStream = new InMemoryRandomAccessStream();
try
{
// write photo to random access stream
await _mediaCapture.CapturePhotoToStreamAsync(ImageEncodingProperties.CreateJpeg(), photoStream);
var fileName = "Sample_Photo.jpeg";
// write to file image
await WriteJpeg(photoStream, fileName);
}
catch (Exception ex)
{
Debug.WriteLine("Exception when taking a photo: {0}", ex.ToString());
}
}
[/code]
The sample code below provides a helper method called WriteJpeg that will encode the stream and write it to a file in your Pictures Library.
(Note also that, for simplicity, the jpeg’s orientation is being set to landscape.)
[code lang=”csharp”]
private static async Task WriteJpeg(IRandomAccessStream stream, string fileName)
{
// create image format decoder; init with photo stream
var decoder = await BitmapDecoder.CreateAsync(stream);
// create image file with unique name
var file = await KnownFolders.PicturesLibrary.CreateFileAsync(fileName, CreationCollisionOption.GenerateUniqueName);
// write to file
using (var outputStream = await file.OpenAsync(FileAccessMode.ReadWrite))
{
var encoder = await BitmapEncoder.CreateForTranscodingAsync(outputStream, decoder);
// set orientation to landscape
var properties = new BitmapPropertySet { { "System.Photo.Orientation", new BitmapTypedValue(PhotoOrientation.Rotate90, PropertyType.UInt16) } };
await encoder.BitmapProperties.SetPropertiesAsync(properties);
// write image data to the end of the stream
await encoder.FlushAsync();
}
}
[/code]
Capturing video is, if anything, even easier than taking a picture. To do so, you call the media capture instance’s StartRecordToStorageFileAsync method. To stop your video capture, just call StopRecordAsync.
[code lang=”csharp”]
private async Task TakeVideoAsync()
{
// create mp4 encoder
var encodingProfile = MediaEncodingProfile.CreateMp4(VideoEncodingQuality.Auto);
// create video file with unique name
var videoFile = await KnownFolders.VideosLibrary.CreateFileAsync("Sample_Video.mp4", CreationCollisionOption.GenerateUniqueName);
// start recording
await _mediaCapture.StartRecordToStorageFileAsync(encodingProfile, videoFile);
}
private async Task StopRecordingVideoAsync()
{
// stop recording
await _mediaCapture.StopRecordAsync();
}
[/code]
You will see later in this post how easy it is to transform these two methods into HDR video capture methods.
Using AdvancedPhotoCapture for High Dynamic Range photos
If a device supports HDR photos, you will be able to use the AdvancedPhotoCapture class to grab them. Create a private member to hold your instance of the class.
[code lang=”csharp”]
private AdvancedPhotoCapture _advancedCapture;
[/code]
To determine if a device supports this feature, you need to check the VideoDeviceController.AdvancedPhotoControl property off of your MediaCapture instance.
[code lang=”csharp”]
private bool IsHdrPhotoSupported()
{
var supportedModes = _mediaCapture.VideoDeviceController.AdvancedPhotoControl.SupportedModes;
return supportedModes.Contains(Windows.Media.Devices.AdvancedPhotoMode.Hdr);
}
[/code]
If it does, you create an instance of AdvancedPhotoCaptureSettings to configure the AdvancedPhotoControl for HDR. Initialize your AdvancedPhotoCapture object by calling the media capture method PrepareAdvancedPhotoCaptureAsync. Finally, you can call the CaptureAsync method on the AdvancedPhotoCapture instance in order to retrieve an HDR photo stream which can be passed to the WriteJpeg helper method we created for the TakeStandardPhotoAsync method earlier.
[code lang=”csharp”]
private async Task TakeHDRPhotoAsync()
{
if (!IsHdrPhotoSupported()) return;
// select HDR mode
var settings = new AdvancedPhotoCaptureSettings { Mode = AdvancedPhotoMode.Hdr };
// Configure media capture object for HDR
_mediaCapture.VideoDeviceController.AdvancedPhotoControl.Configure(settings);
// Prepare for an advanced capture
_advancedCapture = await _mediaCapture.PrepareAdvancedPhotoCaptureAsync(ImageEncodingProperties.CreateJpeg());
try
{
// start capture
AdvancedCapturedPhoto capture = await _advancedCapture.CaptureAsync();
using (var frame = capture.Frame)
{
var fileName = "Sample_HDR_Photo.jpeg";
// write to file image
await WriteJpeg(frame, fileName);
}
}
catch (Exception ex)
{
Debug.WriteLine("Exception when taking an HDR photo: {0}", ex.ToString());
}
}
[/code]
For a final bit of flexibility, add a member level flag to track whether the user wants to take high dynamic range photos or just standard ones.
[code lang=”csharp”]
bool _isHDR;
[/code]
With this, you can create a simple wrapper around both the HDR and standard photo capture calls.
[code lang=”csharp”]
private async Task TakePhotoAsync()
{
// take HDR photo if HDR is flagged
if (_isHDR)
{
await TakeHDRPhotoAsync();
}
else
{
await TakeStandardPhotoAsync();
}
}
[/code]
Intercepting the hardware button and blocking the lock screen
Here are two optional features that will make your camera app dramatically better. First, Windows 10 mobile devices have a hardware button that you can intercept and then appropriate for picture taking in your own app. To do this, you first need to add a reference to the Windows Mobile Extensions for the UWP, a Universal Windows extension. Once the extensions are referenced, you can use ApiInformation to determine if the hardware button is available on the current device and then add an event handler if it is.
[code lang=”csharp”]
private void SubscribeToCameraHardwareButton()
{
// subscribe to camera hardware button click if available
if (ApiInformation.IsTypePresent("Windows.Phone.UI.Input.HardwareButtons"))
{
HardwareButtons.CameraPressed += HardwareButtons_CameraPressed;
}
}
private async void HardwareButtons_CameraPressed(object sender, CameraEventArgs e)
{
// snap picture
await TakePhotoAsync();
}
[/code]
This code can be dropped in the InitializeCameraAsync method and should have a corresponding event unregistration call in the cleanup method.
Another useful bit of code, the DisplayRequest.RequestActive call, can also be dropped into InitializeCameraAsync. It will prevent the lock screen from activating while someone is trying to take a picture.
[code lang=”csharp”]
bool _isDisplayRequestActivated;
private readonly DisplayRequest _displayRequest = new DisplayRequest();
private void ActivateDisplayRequest()
{
if (_isDisplayRequestActivated)
return;
// prevent display from going into lock screen
_displayRequest.RequestActive();
_isDisplayRequestActivated = true;
}
[/code]
The RequestActive call will automatically become inert if the app is suspended and reactivated when the app is resumed, so you do not have to actively manage it. Please read the post How to prevent screen locks for a fuller explanation of how this feature works.
Deciding if HDR would actually help
High contrast photos (top), especially landscapes, will typically benefit from HDR mode, while low contrast images (bottom) will produce limited results.
The MediaCapture class allows you to hook into the video preview pipeline in order to analyze the current image and determine if HDR would improve the current frame. The following code can be placed into the InitializeCameraAsync method right after StartPreviewAsync is called. It creates a definition object in order to tell the media capture instance what it requires. An event handler is registered for the SceneAnalyzed event. Finally, the HighDynamicRangeAnalyzer is enabled.
[code lang=”csharp”]
private async Task TurnOnSceneAnalysis()
{
// create definition
var definition = new SceneAnalysisEffectDefinition();
// intercept the view finder image
_sceneAnalysisEffect = (SceneAnalysisEffect)await _mediaCapture.AddVideoEffectAsync(definition, MediaStreamType.VideoPreview);
// register
_sceneAnalysisEffect.SceneAnalyzed += SceneAnalysisEffect_SceneAnalyzed;
_sceneAnalysisEffect.HighDynamicRangeAnalyzer.Enabled = true;
}
private async void SceneAnalysisEffect_SceneAnalyzed(SceneAnalysisEffect sender, SceneAnalyzedEventArgs args)
{
await Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
{
// Certainty is a double between 0 and 1; multiply by 100
var certainty = args.ResultFrame.HighDynamicRange.Certainty * 100;
// display certainty value in UI
HDRCertainty.Text = Math.Floor(certainty).ToString();
});
}
[/code]
The event handler shown here takes the certainty value returned by the analyzer and simply displays it as information for the user. If you have a threshold value you want to use, however, you could instead compare this to the certainty value and flip the _isHDR flag when the threshold is reached, effectively controlling whether photos will be HDR or not.
[code lang=”csharp”]
// turn HDR on if threshold is reached
If(args.ResultFrame.HighDynamicRange.Certainty >= CERTAINTY_THRESHOLD){
_isHDR = true;
}
[/code]
This would be equivalent to creating your own “auto HDR” feature.
Taking HDR Video
If your device supports it, HDR video using the HDRVideoControl is extremely easy to implement. You basically just need to enable it. This occurs in two steps. First, you must check to see if the HDRVideoControl is available on the device your app is running on. Then, set the HdrVideoControl mode to On. HdrVideoMode is an enumeration with three possible values: On, Off, Auto.
[code lang=”csharp”]
private bool TrySetVideoHDR(HdrVideoMode mode)
{
if (!_mediaCapture.VideoDeviceController.HdrVideoControl.Supported)
{
return false;
}
var hdrVideoModes = _mediaCapture.VideoDeviceController.HdrVideoControl.SupportedModes;
if (!hdrVideoModes.Contains(mode))
{
return false;
}
_mediaCapture.VideoDeviceController.HdrVideoControl.Mode = mode;
return true;
}
[/code]
Using the _isHDR flag you created above, you can simply insert the TrySetVideoHDR check into the video code you’ve already written. If the user has requested HDR and it is available, videos recordings will just automatically be recorded in this mode.
[code lang=”csharp”]
private async Task TakeVideoAsync()
{
if (_isHDR)
TrySetVideoHDR(HdrVideoMode.On);
try
{
// create mp4 encoder
var encodingProfile = MediaEncodingProfile.CreateMp4(VideoEncodingQuality.Auto);
// create video file with unique name
var videoFile = await KnownFolders.VideosLibrary.CreateFileAsync("Sample_Video.mp4", CreationCollisionOption.GenerateUniqueName);
// start recording
await _mediaCapture.StartRecordToStorageFileAsync(encodingProfile, videoFile);
}
catch (Exception ex)
{
Debug.WriteLine("Exception when starting video recording: {0}", ex.ToString());
}
}
private async Task StopRecordingVideoAsync()
{
try
{
// stop recording
await _mediaCapture.StopRecordAsync();
if (_isHDR)
TrySetVideoHDR(HdrVideoMode.Off);
}
catch (Exception ex)
{
Debug.WriteLine("Exception when stopping video recording: {0}", ex.ToString());
}
}
[/code]
The rest of the code for recording video in UWP is the same in both HDR and standard modes.
Wrapping up
As you can see, creating a camera app for UWP is not as intimidating as it might appear. Not only is the basic functionality easy to implement, but adding on complex features like high dynamic range support is also fairly trivial. HDR is also just the tip of the iceberg. The basic architecture is in place to create additional computational photography extensions.
At this point in time, you can already implement Face detection analysis and low light capture mode. And if you want to implement your own HDR mode, you can even use the variable photo sequence media capture feature in UWP to analyze multiple frames and composite a custom image. To learn more about media capture and the camera capabilities built into UWP, see the following MSDN articles:
- High Dynamic Range photo capture
- Scene analysis for media capture
- Capture device controls for media capture
- Variable photo sequence
- Camera Starter Kit on GitHub
- CameraHdr sample for UWP on GitHub