Since the first release, Windows Phone devices have got at least a camera on board but it remained only a beautiful gadget, useful only to take shots of your sons or during your favorite rockstar's concert, because in the OS7.0 there were few api to access the camera.
In the first release of the operating system there was only a CameraCaptureTask able to start the camera and wait for the user to take a picture, then return it to the application leaving to the user the choose of the resolution, of the focus and other things. It is for sure a beautiful feature but the best is being able to integrate the camera stream into your own application and manipulate it, controlling all the features to get the most useful images from it for the sake of the application.
With OS7.5 it is now possible to deep integrate the camera using a set of API that let you fine tune the image you get from the shot, and decide when to take a shot without asking the user to press the hardware button. There is two levels of integration with the camera. You can simply command the camera and take pictures or you can also directly access the stream and manipulate it for the most complicated elaborations. In this article I will cover the most simple case, showing how to replicate the most common camera features into your own application.
Source code
Connecting to the camera(s)
As you know for sure, Windows Phone must have at least a camera, positioned on the back, that is usually referred as the "main camera" because it have the most advanced features like higher resolution and so on. But, there are lot of models that benefit of a secondary camera that is positioned on top of the screen and used when you have to place a video call. This camera is called "front facing" and it is not required by the minimal requirements.
So, when you need to access the phone camera, the first thing you have to do is checking if the camera you want to use is available. Using the PhotoCamera class this check is quite simple. You have an IsCameraTypeSupported method you can use to test using a CameraType enumerator as input. So your code can choose if it need to use FrontFacing or Primary in this way:
1: private PhotoCamera Camera { get; set; }
2:
3: private void MainPage_Loaded(object sender, RoutedEventArgs e)
4: {
5: if (PhotoCamera.IsCameraTypeSupported(CameraType.FrontFacing))
6: this.Camera = new PhotoCamera(CameraType.FrontFacing);
7: else if (PhotoCamera.IsCameraTypeSupported(CameraType.Primary))
8: this.Camera = new PhotoCamera(CameraType.Primary);
9: else
10: {
11: MessageBox.Show("Cannot find a camera on this device");
12: return;
13: }
14:
15: // TODO: initialize here the camera
16: }
The PhotoCamera class is the starting point you use to access the camera and it represents the type you choosed into the application for the entire lifetime. Once you attached a camera you can attach an "Initialized" event that is useful to know when the information of the camera are available to the application. As I will show in a few it is required to access some important info about the capabilities of the camera.
Now, after connecting to the camera, the first ask is for sure how to present the incoming stream to the user interface. It is probably so simple as you cannot expect. The trick is made using the VideoBrush. If you do not know what it is, you have to experiment it immediately. As the name suggest the VideoBrush is a Brush like the one you use to specify a color or a gradient for a shape. So, with a VideoBrush you can choose to paint almost everything using a video as source. this mean you can paint a rectangle background, a border, a circle stroke or a text foreground with the live output of your camera. Ok, let me show how simple it is. First of all decide the shape you want to use - in my case it is a Border - and set the VideoBrush as Background:
1: <Border x:Name="cameraView" BorderBrush="Black" BorderThickness="5" Grid.Column="0">
2: <Border.Background>
3: <VideoBrush x:Name="cameraViewBrush" />
4: </Border.Background>
5: </Border>
Then, go to your codebehind and just after the hook up of the wanted camera connect the PhotoCamera instance to the output brush. It is a single line of code:
this.cameraViewBrush.SetSource(this.Camera);
This simply code automatically makes your rectangle showing the stream that is coming from the camera. If you try to run the application you will see it painted on the background of the border. If you want to enjoy try to change the output shape. I really cannot figure out how can you use a text painted with your camera but it is amazing to see and if you think how simple it is probably is becomes amazing the double.
Handling the camera orientation
So, please stop now experimenting the most strange shapes and restore the simple rectangle, then run the application and try to rotate the camera changing the orientation of the screen. What you will observe is almost so strange like a keyhole painted with the camera stream but mostly disturbing. In few words, when the orientation changes, the video orientation does not change in the same way.
The problem here is that the phone do not directly handle the orientation of the video stream so for example, if you take the camera in LandscapeLeft the video is presented in the correct orientation but if you rotate to LandscapeRight the video is upside down. The PhotoCamera class provides an Orientation property that helps setting the correct degrees to a CompositeTransform So you have to change the xaml for your video brush this way:
1: <VideoBrush x:Name="cameraViewBrush">
2: <VideoBrush.RelativeTransform>
3: <CompositeTransform x:Name="cameraViewBrushTransform" CenterX=".5" CenterY=".5" />
4: </VideoBrush.RelativeTransform>
5: </VideoBrush>
After this you have to attach the OrientationChanged event and apply a correction to the brush to make the video appear always in the right orientation. In the following box I show a CorrectViewFinderOrientation method that applies to Landscape orientations but it is for sure simple to extend to Portrait:
1: private void MainPage_Loaded(object sender, RoutedEventArgs e)
2: {
3: // TODO: camera initialization goes here
4:
5: this.CorrectViewFinderOrientation(this.Orientation);
6: this.OrientationChanged += new EventHandler<OrientationChangedEventArgs>(MainPage_OrientationChanged);
7: }
8:
9: private void MainPage_OrientationChanged(object sender, OrientationChangedEventArgs e)
10: {
11: this.CorrectViewFinderOrientation(e.Orientation);
12: }
13:
14: private void CorrectViewFinderOrientation(PageOrientation orientation)
15: {
16: if (orientation == PageOrientation.LandscapeLeft)
17: this.cameraViewBrushTransform.Rotation = this.Camera.Orientation - 90.0;
18: else if (orientation == PageOrientation.LandscapeRight)
19: this.cameraViewBrushTransform.Rotation = this.Camera.Orientation + 90.0;
20: }
Tune the camera settings
When you run the standard camera application there are a number of properties you can tune just before taking your shots. Depending of the features of the camera you can choose to use a set of resolutions, and you can also choose to set the flash light in defferent ways. When you have a PhotoCamera instance, just after the Initialization event have been raised, you can benefit of some information that give you a view of the features of the camera you choosed.
The PhotoCamera class exposes the AvailableSizes collection and a IsFlashModeSupported method to test the availability of a given flash mode. On my HTC HD7 the AvailableSizes collections states the following: 320x240, 640x480, 800x600, 1024x768, 1280x960, 1600x1200, 2048x1536 and 2592x1944 with the last resolution of about 4,8 MegaPixels.
On the side of the flash the FlashMode enumerator gives four modes available:
-
On: means the flashlight always illuminate the scene
-
Off: means that the flaslight is switched off
-
Auto: the camera automatically choose when to switch on the flashlight
-
RedEyeReduction: The camera does a preview flash to reduce the red-eye effect that is mostly evident when you take a picture of a child
On my phone the test for FlashMode.RedEyeReduction returns false stating this device does not have this feature but it returns true in the other cases.
Once you have decided the resolution and the flash mode you need you can set the relative property to change it on the phone camera. In this box I reported a snippet that does the test for both the collections an then change resolution and flash mode according to the press of a button in the user interface:
1: private void Camera_Initialized(object sender, CameraOperationCompletedEventArgs e)
2: {
3: this.AvailableFlashModes = new Queue<FlashMode>();
4:
5: if (this.Camera.IsFlashModeSupported(FlashMode.Auto))
6: this.AvailableFlashModes.Enqueue(FlashMode.Auto);
7: if (this.Camera.IsFlashModeSupported(FlashMode.On))
8: this.AvailableFlashModes.Enqueue(FlashMode.On);
9: if (this.Camera.IsFlashModeSupported(FlashMode.RedEyeReduction))
10: this.AvailableFlashModes.Enqueue(FlashMode.RedEyeReduction);
11: if (this.Camera.IsFlashModeSupported(FlashMode.Off))
12: this.AvailableFlashModes.Enqueue(FlashMode.Off);
13:
14: this.SetFlashMode();
15:
16: this.AvailableSizes = new Queue<Size>();
17:
18: foreach (Size size in this.Camera.AvailableResolutions)
19: this.AvailableSizes.Enqueue(size);
20:
21: this.SetSize();
22: }
23:
24: private void SetSize()
25: {
26: this.AvailableSizes.Enqueue(
27: this.AvailableSizes.Dequeue());
28:
29: Size size = this.AvailableSizes.ElementAt(0);
30:
31: this.Camera.Resolution = size;
32:
33: Deployment.Current.Dispatcher.BeginInvoke(() =>
34: {
35: this.bResolution.Content = string.Format("{0}x{1}", size.Width, size.Height);
36: });
37: }
38:
39: private void SetFlashMode()
40: {
41: this.AvailableFlashModes.Enqueue(
42: this.AvailableFlashModes.Dequeue());
43:
44: FlashMode mode = this.AvailableFlashModes.ElementAt(0);
45:
46: this.Camera.FlashMode = mode;
47:
48: Deployment.Current.Dispatcher.BeginInvoke(() =>
49: {
50: switch (mode)
51: {
52: case FlashMode.Off:
53: bFlashMode.Content = "OFF";
54: break;
55: case FlashMode.On:
56: bFlashMode.Content = "ON";
57: break;
58: case FlashMode.Auto:
59: bFlashMode.Content = "AUTO";
60: break;
61: case FlashMode.RedEyeReduction:
62: bFlashMode.Content = "R-E";
63: break;
64: }
65: });
66: }
Just after the initialization I copy the available sizes and flash modes in two Queue<T> collections. Then every time a user call SetSize or SetFlashMode I dequeue the last value and enqueue it on the tail of the collection. So the first element always indicates the current value and it rotates along the available values. According to this value I set the button text and the Resolution and FlashMode properties.
Another parameter you need to tune is the focus of the image. This operation is assisted by the autofocus feature of the camera and also it can be different on different devices. All the phones support the normal center-weighted focus but some other devices can focus on a give point on the frame. So also for focus you have to use the IsFocusAtPointSupported and IsFocusSupported to decide if and how to start focusing. Also you have to pay attention about not start focusing when the camera is capturing a picture.
1: private void MainPage_Loaded(object sender, RoutedEventArgs e)
2: {
3: // TODO: initialize here
4:
5: this.Camera.AutoFocusCompleted += new EventHandler<CameraOperationCompletedEventArgs>(Camera_AutoFocusCompleted);
6: }
7:
8: private void listener_Hold(object sender, Microsoft.Phone.Controls.GestureEventArgs e)
9: {
10: if (this.Camera.IsFocusAtPointSupported) // if supports focus at point calculates the percentage on the screen size
11: {
12: Point point = e.GetPosition(this.cameraView);
13: this.cameraView.BorderBrush = new SolidColorBrush(Colors.Black);
14: double focusX = point.X / this.cameraView.ActualWidth;
15: double focusY = point.Y / this.cameraView.ActualHeight;
16: this.Camera.FocusAtPoint(focusX, focusY);
17: }
18: else if (this.Camera.IsFocusSupported) // if it supports normal focus it simply start
19: {
20: this.cameraView.BorderBrush = new SolidColorBrush(Colors.Black);
21: this.Camera.Focus();
22: }
23: }
24:
25: private void Camera_AutoFocusCompleted(object sender, CameraOperationCompletedEventArgs e)
26: {
27: Deployment.Current.Dispatcher.BeginInvoke(() =>
28: {
29: this.cameraView.BorderBrush = new SolidColorBrush(Colors.Red);
30: });
31: }
When the focus is completed the AutoFocusCompleted event is raised but you have to be aware it is not on the UI Thread so you have to use the Dispatcher to change the UI aspects.
An finally capture a shot...
Now we are ready to take a shot and doing whatever we want with it. To capture an image you have to call the CaptureImage method. It is really important to not overlap focus and capture operation because the device will raise an exception if you try to do an operation while the other is in progress. The capture of an image is, as usual, an asynchronous operation. You start the capture and two events notifies when the capture has been completed.
With the CaptureThumbnailAvailable you get a notification when the camera completed the capture and prepared a thumbnail of the resulting picture and then with CaptureImageAvailable you are notified when the full image is ready. Here is a sample code:
1: private void MainPage_Loaded(object sender, RoutedEventArgs e)
2: {
3: // TODO: initialize here
4:
5: this.Camera.CaptureImageAvailable += new EventHandler<ContentReadyEventArgs>(Camera_CaptureImageAvailable);
6: this.Camera.CaptureThumbnailAvailable += new EventHandler<ContentReadyEventArgs>(Camera_CaptureThumbnailAvailable);
7: }
8:
9: private void listener_Tap(object sender, Microsoft.Phone.Controls.GestureEventArgs e)
10: {
11: this.Camera.CaptureImage();
12: }
13:
14: void Camera_CaptureThumbnailAvailable(object sender, ContentReadyEventArgs e)
15: {
16: Deployment.Current.Dispatcher.BeginInvoke(() =>
17: {
18: BitmapImage image = new BitmapImage();
19: image.SetSource(e.ImageStream);
20: this.lastShoot.Source = image;
21: this.lastShootFrame.Visibility = System.Windows.Visibility.Visible;
22: });
23: }
24:
25: private void Camera_CaptureImageAvailable(object sender, ContentReadyEventArgs e)
26: {
27: Deployment.Current.Dispatcher.BeginInvoke(() =>
28: {
29: MediaLibrary ml = new MediaLibrary();
30: ml.SavePictureToCameraRoll(
31: string.Format("{0:yyyyMMdd-HHmmss}.jpg", DateTime.Now), e.ImageStream);
32: });
33: }
As you can see the events are raised on the background thread so also in this case I need to marshal the resulting image to the UI thread before to do anything. When I get the thumbnail I show it on a corner fo the screen an finally when the full image is ready it is saved to the camera roll into the media library.
It's only the first step
The use of the PhotoCamera class gives you a deep control over the phone camera. Controlling the flash, the resolution and the focus, taking shots and presenting the live stream to the UI are for sure great capabilities to integrate into your own application. Nevertheless there is some cases where it may not suffice but as I've already said this is only the first step forward to the complete camera ownership. But about this I will speak in another time.