How to capture a photo in your Windows Phone 8.1 Runtime app – Part III: capturing and saving the photo

This is the third and last post of this series. In the first two posts I showed you how to start the preview of MediaCapture and some modifications we can apply to it. In this post, we are finally capturing and saving the photo – including the modifications we made before.

The easiest way – capture as is:

The easiest way to capture the photo is to use MediaCapture’s CapturePhotoToStorageFileAsync() method. This method shows you how to do it:

            //declare image format
            ImageEncodingProperties format = ImageEncodingProperties.CreateJpeg();

            //generate file in local folder:
            StorageFile capturefile = await ApplicationData.Current.LocalFolder.CreateFileAsync("photo_" + DateTime.Now.Ticks.ToString(), CreationCollisionOption.ReplaceExisting);

            ////take & save photo
            await captureManager.CapturePhotoToStorageFileAsync(format, capturefile);

            //show captured photo
            BitmapImage img = new BitmapImage(new Uri(capturefile.Path));
            takenImage.Source = img;
            takenImage.Visibility = Visibility.Visible;

This way however does not respect any modifications we made to the preview. The only thing that gets respected is the camera device we are using.

Respecting rotation in the captured photo:

In our ongoing sample, we are using a 90 degree rotation to display the preview element in portrait mode. Naturally, we want to port over this orientation in our captured image.

There are two ways to achieve this. We could capture the photo to a WriteableBitmap and manipulate it, or we could manipulate the image stream directly with the BitmapDecoder and  BitmapEncoder classes. We will do the latter one.

First, we need to open an InMemoryRandomAccessStream for our the captured photo. We are capturing the photo to the stream with MediaCapture’s CapturePhotoToStreamAsync() method, specifing the stream name and the image format.

The next step is to decode the stream with our BitmapDecoder. If we are performing only rotation, we can directly re-encode the InMemoryRandomAccessStream we are using. Rotating the captured photo is very simple with just setting the BitmapTransform.Rotation property to be rotated by 90 degrees, pretty much as easy as rotating the preview.

The last steps are generating a file in the storage, followed by copying the transcoded image stream into the file stream. Here is the complete code that does all this:

            //declare string for filename
            string captureFileName = string.Empty;
            //declare image format
            ImageEncodingProperties format = ImageEncodingProperties.CreateJpeg();

            //rotate and save the image
            using (var imageStream = new InMemoryRandomAccessStream())
            {
                //generate stream from MediaCapture
                await captureManager.CapturePhotoToStreamAsync(format, imageStream);

                //create decoder and encoder
                BitmapDecoder dec = await BitmapDecoder.CreateAsync(imageStream);
                BitmapEncoder enc = await BitmapEncoder.CreateForTranscodingAsync(imageStream, dec);

                //roate the image
                enc.BitmapTransform.Rotation = BitmapRotation.Clockwise90Degrees;

                //write changes to the image stream
                await enc.FlushAsync();

                //save the image
                StorageFolder folder = KnownFolders.SavedPictures;
                StorageFile capturefile = await folder.CreateFileAsync("photo_" + DateTime.Now.Ticks.ToString() + ".jpg", CreationCollisionOption.ReplaceExisting);
                captureFileName = capturefile.Name;

                //store stream in file
                using (var fileStream = await capturefile.OpenStreamForWriteAsync())
                {
                    try
                    {
                        //because of using statement stream will be closed automatically after copying finished
                        await RandomAccessStream.CopyAsync(imageStream, fileStream.AsOutputStream());
                    }
                    catch 
                    {

                    }
                }
            }

Of course, we need to stop the preview after we captured the photo. It also makes all sense to load the saved image and display it to the user. This is the code to stop the preview:

        private async void CleanCapture()
        {

            if (captureManager != null)
            {
                if (isPreviewing == true)
                {
                    await captureManager.StopPreviewAsync();
                    isPreviewing = false;
                }
                captureManager.Dispose();

                previewElement.Source = null;
                previewElement.Visibility = Visibility.Collapsed;
                takenImage.Source = null;
                takenImage.Visibility = Visibility.Collapsed;
                captureButton.Content = "capture";
            }

        }

The result of above mentioned code (screenshot of preview left, captured photo right):

16by9Photo

Cropping the captured photo

Not all Windows Phone devices have an aspect ratio of 16:9. In fact, most devices in the market have an aspect ratio of 15:9, due to the fact that they are WVGA or WXGA devices (I talked a bit about this already in my second post). If we are just capturing the photo with the method above, we will have the same black bands in our image as we have in our preview. To get around this and capture a photo that has a true 15:9 resolution (makes sense for photos that get reused in apps, but less for real life photos), additional code is needed.

As with getting the right camera solution, I generated an Enumeration that holds all possible values as well as a helper method to detect which aspect ratio the currently used device has:

        public enum DisplayAspectRatio
        {
            Unknown = -1,

            FifteenByNine = 0,

            SixteenByNine = 1
        }

        private DisplayAspectRatio GetDisplayAspectRatio()
        {
            DisplayAspectRatio result = DisplayAspectRatio.Unknown;

            //WP8.1 uses logical pixel dimensions, we need to convert this to raw pixel dimensions
            double logicalPixelWidth = Windows.UI.Xaml.Window.Current.Bounds.Width;
            double logicalPixelHeight = Windows.UI.Xaml.Window.Current.Bounds.Height;

            double rawPerViewPixels = DisplayInformation.GetForCurrentView().RawPixelsPerViewPixel;
            double rawPixelHeight = logicalPixelHeight * rawPerViewPixels;
            double rawPixelWidth = logicalPixelWidth * rawPerViewPixels;

            //calculate and return screen format
            double relation = Math.Max(rawPixelWidth, rawPixelHeight) / Math.Min(rawPixelWidth, rawPixelHeight);
            if (Math.Abs(relation - (15.0 / 9.0)) < 0.01)
            {
                result = DisplayAspectRatio.FifteenByNine;
            }
            else if (Math.Abs(relation - (16.0 / 9.0)) < 0.01)
            {
                result = DisplayAspectRatio.SixteenByNine;
            }

            return result;
        }

In Windows Phone 8.1, all Elements use logical pixel size. To get the values that most of us are used to, we need to calculate the raw pixels from the logical pixels. After that, we use the same math operations I used already for detecting the ratio of the camera resolution (see post 2). I tried to calculate the values with the logical pixels as well, but this ended up in some strange rounding behavior and not the results I wanted. That’s why I use the raw pixel sizes.

Before we continue with capturing the photo, we are going to add a border that is displayed and shows the area which is captured to the user in XAML:

            

When we are cropping our photo, we need to treaten the BitmapEncoder and the BitmapDecoder separately. To crop an image, we  need to set the Bounds and the new Width and Height of the photo via the BitmapTransform.Bounds property. We also need to read the PixelData via the GetPixelDataAsync() method, apply the changed Bounds to it and pass them to BitmapEncoder via the SetPixelData() method.

At the end, we are flushing the changed stream data directly into the file stream of our StorageFile. Here is how:

            //declare string for filename
            string captureFileName = string.Empty;
            //declare image format
            ImageEncodingProperties format = ImageEncodingProperties.CreateJpeg();

            using (var imageStream = new InMemoryRandomAccessStream())
            {
                //generate stream from MediaCapture
                await captureManager.CapturePhotoToStreamAsync(format, imageStream);

                //create decoder and transform
                BitmapDecoder dec = await BitmapDecoder.CreateAsync(imageStream);
                BitmapTransform transform = new BitmapTransform();

                //roate the image
                transform.Rotation = BitmapRotation.Clockwise90Degrees;
                transform.Bounds = GetFifteenByNineBounds();

                //get the conversion data that we need to save the cropped and rotated image
                BitmapPixelFormat pixelFormat = dec.BitmapPixelFormat;
                BitmapAlphaMode alpha = dec.BitmapAlphaMode;

                //read the PixelData
                PixelDataProvider pixelProvider = await dec.GetPixelDataAsync(
                    pixelFormat,
                    alpha,
                    transform,
                    ExifOrientationMode.RespectExifOrientation,
                    ColorManagementMode.ColorManageToSRgb
                    );
                byte[] pixels = pixelProvider.DetachPixelData();

                //generate the file
                StorageFolder folder = KnownFolders.SavedPictures;
                StorageFile capturefile = await folder.CreateFileAsync("photo_" + DateTime.Now.Ticks.ToString() + ".jpg", CreationCollisionOption.ReplaceExisting);
                captureFileName = capturefile.Name;

                //writing directly into the file stream
                using (IRandomAccessStream convertedImageStream = await capturefile.OpenAsync(FileAccessMode.ReadWrite))
                {
                    //write changes to the BitmapEncoder
                    BitmapEncoder enc = await BitmapEncoder.CreateAsync(BitmapEncoder.JpegEncoderId, convertedImageStream);
                    enc.SetPixelData(
                        pixelFormat,
                        alpha,
                        transform.Bounds.Width,
                        transform.Bounds.Height,
                        dec.DpiX,
                        dec.DpiY,
                        pixels
                        );

                    await enc.FlushAsync();
                }
            }

You may have notice the GetFifteenByNineBounds() method in the above code. As we need to calculate some values for cropping the image, I decided to separate them. They are not only providing values for the image to be cropped, but also size values for our earlier added Border that is used in my sample (download link at the end of the project) to show the size that the photo will have after our cropping (which is an automatic process in our case,). Here is the code:

        private BitmapBounds GetFifteenByNineBounds()
        {
            BitmapBounds bounds = new BitmapBounds();

            //image size is raw pixels, so we need also here raw pixels
            double logicalPixelWidth = Windows.UI.Xaml.Window.Current.Bounds.Width;
            double logicalPixelHeight = Windows.UI.Xaml.Window.Current.Bounds.Height;

            double rawPerViewPixels = DisplayInformation.GetForCurrentView().RawPixelsPerViewPixel;
            double rawPixelHeight = logicalPixelHeight * rawPerViewPixels;
            double rawPixelWidth = logicalPixelWidth * rawPerViewPixels;

            //calculate scale factor of UniformToFill Height (remember, we rotated the preview)
            double scaleFactorVisualHeight = maxResolution().Width / rawPixelHeight;

            //calculate the visual Width
            //(because UniFormToFill scaled the previewElement Width down to match the previewElement Height)
            double visualWidth = maxResolution().Height / scaleFactorVisualHeight;
            
            //calculate cropping area for 15:9
            uint scaledBoundsWidth = maxResolution().Height;
            uint scaledBoundsHeight = (scaledBoundsWidth / 9) * 15;

            //we are starting at the top of the image
            bounds.Y = 0;
            //cropping the image width
            bounds.X = 0;
            bounds.Height = scaledBoundsHeight;
            bounds.Width = scaledBoundsWidth;

            //set finalPhotoAreaBorder values that shows the user the area that is captured
            finalPhotoAreaBorder.Width = (scaledBoundsWidth / scaleFactorVisualHeight) / rawPerViewPixels;
            finalPhotoAreaBorder.Height = (scaledBoundsHeight / scaleFactorVisualHeight) / rawPerViewPixels;
            finalPhotoAreaBorder.Margin = new Thickness(
                                            Math.Floor(((rawPixelWidth - visualWidth) / 2) / rawPerViewPixels), 
                                            0,
                                            Math.Floor(((rawPixelWidth - visualWidth) / 2) / rawPerViewPixels), 
                                            0);
            finalPhotoAreaBorder.Visibility = Visibility.Visible;

            return bounds;
        }

Again, we need to apply raw pixels to achieve the best results here (I just pasted those lines in for this sample). To calculate the correct values for our Border, we need the scale factor between the screen and the preview resolution we used (which is the scaleFactorVisualHeight double).  Before we’re calculating the border values, we are setting the Width to resolution’s Height (we rotated, remember?) and calculate the matching 15:9 Height.

The Border values are based on the Width and Height of the cropped image, but scaled down by scaleFactorVisualHeight’s value and converted in raw pixel. The Margin positions the border accordingly on top of the preview element.

This is the result of above mentioned code (screenshot of preview left, captured photo right):

15by9Photo

That’s all you need to know to get started with basic photo capturing from within your Windows Phone 8.1 Runtime app. Of course, there are also other modifications that you can apply, and I mentioned already most of the classes that lead you to the matching methods and properties (click on the links to get to the documentation)

By the way, most of the code can be adapted in a Windows 8.1 app as well (with some differences, of course).

Sample project

As promised, you can download the sample here. It contains all code snippets I showed you and is able to run as you build and deploy it.

As always, feedback is welcome and I hope this post is helpful for some of  you.

Until the next time, happy coding!

Comments 21
  1. Hi – great stuff.
    I am just starting to develop Windows Phone app’s. And the sample is of great help.
    As a start, I use the Emulator in Visual Studio Community 2013. When I run the sample code, it starts up just fine. But the screen is Black, there are no sample video. Is this correct? Or should the emulator be showing some sort of video?

    1. Hi,
      glad my sample is helping you to get started. The emulator is not able to show any other than a black screen or some colored borders. If you truly want to test it, you’ll need to use a device.
      Best,
      Marco

  2. Great sample. Helped me a lot. There is just one issue: You pass the same memory stream used for the decoder to the encoder. That just does not work all the time. In cases where the decoder is still reading the stream and the encoder already writing we get the unhelpful exception “The component is not installed”. Why not simply open the file stream before and pass this one to the encoder? I do it this way and it works.

    1. Hi Jürgen,

      I will have a look into that, however I never had a problem with this. Thanks for the hint.

      Marco

  3. I don’t want to save the captured photo to a file, I just want to output it to the image section in the XAML. How would I do this (including all the bitmap transforms etc.)?

    If I do bitmapEncoder.FlushAsync(); then seek the stream back to the beginning the changes don’t seem to take affect when the image preview appears. It’s almost like the stream isn’t being affected, and if not, how would I go about doing so.

    1. you could save it to the apps local storage folder, open it from there and delete it at the end.

  4. Hi, how can i convert to byte array from the bitmapImage to send it to a service to save it online, also can i configure resolution of the picture ?, NICE POST

    1. you need to convert the image stream to a byte array to upload it. Sadly, I do not have a sample at hand, but searching for converting stream to byte array should help you finding a sample. The resolution can be modified with the BitmapEncoder class as well.

  5. On my Phones Lumia 930 and Lumia 1520 using MediaStreamType.VideoPreview
    {
    GetAvailableMediaStreamProperties(MediaStreamType.VideoPreview);
    SetMediaStreamPropertiesAsync( MediaStreamType.VideoPreview)
    CapturePhotoToStreamAsync(ImageEncodingProperties.CreateJpeg(), ….)
    }
    is returning valid perfect Images (camera resolution up to 1920 x 1080 available)

    however using MediaStreamType.Photo
    {
    GetAvailableMediaStreamProperties(MediaStreamType.Photo);
    SetMediaStreamPropertiesAsync( MediaStreamType.Photo)
    CapturePhotoToStreamAsync(ImageEncodingProperties.CreateJpeg(), ….)
    }
    is returning valid images with some solid blocks of grey and white (camera resolutions up to 5376 x 3024 available)

    What can I do to solve the problem?

    Best Regards
    Alois

  6. Hi, thank you for this series. I flollow this three post,but I got the same problems as Alois with a Lumia 830. “Stripes at the top and on the bottom of the image.
    Best Regards,
    Mike

  7. Thanks for the great posts on getting started with the camera. Very helpful!

    I am trying to display the captured image and give the user open to retry if they aren’t happy with the picture. When running in debug mode on the device, I am getting a nearly 2+ second delay after calling the CapturePhotoToStreamAsync and then a little more after rotating, saving, and display the image for review. If the user isn’t happy, they click a button and the Storage File is deleted and preview starts again.

    i have tried using the PhotoConfirmationCaptured event, which makes the image available much quicker. However, I can’t figure out how to get the Captured Frame object into the right kind of stream to create the Bitmap Decoder/Encoder and rotate for the captured image preview.I can get a faster preview, but the image is landscape.

    Have you done anything with the Photo Confirmation Captured event for a faster preview? Or is that 2+ second delay between shutter and preview seem off? I’m using default image encoding properties, so they come out 1619 x 2158.

    1. Hi,

      glad that my posts are helpful for you.

      I did not use the PhotoConfirmationCaptured event. The most time gets lost while manipulating the image with the BitmapEncoder, this is where you can improve the time. In my experience, it also heavily depends on which kind of device you are using. The camera of the Lumia 1020 is slower in comparison to the camera of a Lumia 640 XL (sounds strange, but since WP8.1 the camera of the 1020 is a mess). There might be similar experiences with a Lumia 830 (which uses similar technologies). I was not able to find a real fix for this until now.

      What could help is if you delay saving the photo to file and display the image immediately from the captured stream, casting it into the source of a WritableBitmap (I had some performance “boost” in a Windows 10 app I am working on for work).

      Hope this helps as well.

  8. Damn, from the developer point of view this platform really sucks!

    I had to translate your code from C# into JavaScript (I’m using Cordova) and, despite the poor, c*appy MSDN JavaScript documentation and examples, after a though day of work I finally succeded in getting pics rotated correctly.
    This post was for me a light in a deep dark tunnel of windows phone c*ap.
    I don’t get why those egg heads at M$ did not implement the CameraCaptureUI available for Windows also for stoopid windows phones… Then they complain nobody wants to develop apps for it: It’s worse than a trip to hell!

    Hope with Win 10 things will get better.

    Anyway thank you very much, my friend!

  9. Thanks so much. I was trying for two days to rotate the image in google, StackOverflow, Microsoft examples but only by your site I have found the exact answers and clearly exposed. Compliments!

Join the discussion right now!

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Prev
How to capture a photo in your Windows Phone 8.1 Runtime app-Part II: some common modifications

How to capture a photo in your Windows Phone 8.1 Runtime app-Part II: some common modifications

Next
Editorial: We are all humans (why racism sucks)

Editorial: We are all humans (why racism sucks)

You May Also Like

This website uses cookies. By continuing to use this site, you accept the use of cookies.  Learn more