×

First time here?

You are looking at the most recent posts. You may also want to check out older archives. Please leave a comment, ask a question and consider subscribing to the latest posts via RSS or email. Thank you for visiting!

I was spelunking around playing with Silverlight in Windows Phone 7 and specifically the CameraCaptureTask.  The “tasks” are APIs that allow you to interact with phone-specific functionality like the camera, picture picker, phone dialer, etc.  A whole list of the available tasks in the Microsoft.Phone.Tasks namespace can be found in the developer documentation.

I was basically creating a simple application that would allow you to choose (PhotoChooserTask) or take a picture (CameraCaptureTask) and then display the picture (and later post it online or something).  Here was my basic XAML structure:

   1: <Grid x:Name="LayoutRoot" Background="Transparent">
   2:     <Grid.RowDefinitions>
   3:         <RowDefinition Height="Auto"/>
   4:         <RowDefinition Height="*"/>
   5:     </Grid.RowDefinitions>
   6:  
   7:     <!--TitlePanel contains the name of the application and page title-->
   8:     <StackPanel x:Name="TitlePanel" Grid.Row="0" Margin="12,17,0,28">
   9:         <TextBlock x:Name="ApplicationTitle" Text="PICTURE POSTER" Style="{StaticResource PhoneTextNormalStyle}"/>
  10:         <TextBlock x:Name="PageTitle" Text="take and post" Margin="9,-7,0,0" Style="{StaticResource PhoneTextTitle1Style}"/>
  11:     </StackPanel>
  12:  
  13:     <!--ContentPanel - place additional content here-->
  14:     <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0">
  15:         <Image Margin="8,8,8,159" x:Name="ChosenPicture" />
  16:         <Button Click="OnPostClicked" x:Name="PostPic" Content="POST" HorizontalAlignment="Right" VerticalAlignment="Bottom" Margin="0,0,8,47" Width="199"/>
  17:         <TextBlock x:Name="PostedUri" TextWrapping="Wrap" VerticalAlignment="Bottom" Margin="8,0,8,20"/></Grid>
  18: </Grid>
  19:  
  20: <!--Sample code showing usage of ApplicationBar-->
  21: <phone:PhoneApplicationPage.ApplicationBar>
  22:     <shell:ApplicationBar IsVisible="True" IsMenuEnabled="True">
  23:         <shell:ApplicationBar.MenuItems>
  24:             <shell:ApplicationBarMenuItem Text="take picture" Click="OnMenuTakeClicked"/>
  25:             <shell:ApplicationBarMenuItem Text="choose picture" Click="OnMenuChooseClicked"/>
  26:             <shell:ApplicationBarMenuItem Text="save picture" Click="OnSavePictureClicked" />
  27:         </shell:ApplicationBar.MenuItems>
  28:     </shell:ApplicationBar>
  29: </phone:PhoneApplicationPage.ApplicationBar>

And the initial code to trigger the task from a MenuItem:

   1: private void OnMenuTakeClicked(object sender, EventArgs e)
   2: {
   3:     CameraCaptureTask cam = new CameraCaptureTask();
   4:     cam.Completed += new EventHandler<PhotoResult>(OnCameraCaptureCompleted);
   5:     cam.Show();
   6: }
   7:  
   8: private void OnMenuChooseClicked(object sender, EventArgs e)
   9: {
  10:     PhotoChooserTask pix = new PhotoChooserTask();
  11:     pix.Completed += new EventHandler<PhotoResult>(OnCameraCaptureCompleted);
  12:     pix.ShowCamera = true;
  13:     pix.Show();
  14: }

As you can see it is pretty simple and brings up the OS-standard camera and/or photo chooser. 

NOTE: if you use the PhotoChooserTask you can also initiate taking a new picture from that task as well.

After the picture is chosen (from a new pic or from a picker) I put the item in the Image control in my XAML:

   1: void OnCameraCaptureCompleted(object sender, PhotoResult e)
   2: {
   3:     capturedImage = e.ChosenPhoto; // this is a member variable to store the last chosen pic
   4:  
   5:     BitmapImage bmp = new BitmapImage();
   6:     bmp.SetSource(e.ChosenPhoto);
   7:  
   8:     ChosenPicture.Source = bmp;
   9: }

During this, however, I found that no matter how I held the phone when I took the picture (portrait or landscape), the API always assumed landscape.  I tried looking at some of the device orientation data, but it wasn’t providing the right information at the time I needed it.  It turns out after some internal discussions that others were seeing this as well.  On the device I have (Samsung) the picture snapshot button is in a natural place if you were to hold it landscape.  However, in my current experience with my mobile devices (Android and iPhone) I actually take more pictures in portrait mode.

After some discussion with folks internally, one of our test leads for WP7 reminded everyone that the phone does provide the EXIF information for each picture taken.  One of the attributes of EXIF is orientation (or rotation).  Now all we needed was a method to read the EXIF data in .NET…enter ExifLib.  This is a cool Code Project article and source code download that does a great job providing a simple EXIF reading experience.

The ExifLib as it stood wouldn’t work with the Stream that is provided as a result of the CameraCaptureTask, so a slight modification (or in my case I just created an override) to the function was needed for the library.  Here’s the additional override I added:

   1: public static JpegInfo ReadJpeg(Stream FileStream, string FileName)
   2: {
   3:     DateTime now = DateTime.Now;
   4:     ExifReader reader = new ExifReader(FileStream);
   5:     reader.info.FileSize = (int)FileStream.Length;
   6:     reader.info.FileName = string.Format("{0}.jpg", FileName);
   7:     reader.info.LoadTime = (TimeSpan)(DateTime.Now - now);
   8:     return reader.info;
   9: }

Now with that in place, I could accomplish taking my picture and read the EXIF data and apply the appropriate transform based on the orientation data.  The first thing I had to do was to create a RotateTransform on my Image element as well as set the RenderTransformOrigin on the Image element:

   1: <Image Margin="8,8,8,159" x:Name="ChosenPicture" RenderTransformOrigin="0.5,0.5">
   2:     <Image.RenderTransform>
   3:         <RotateTransform x:Name="ImageRotate" />
   4:     </Image.RenderTransform>
   5: </Image>

Now in code in my completed handler for the task I modified it to look at the EXIF orientation data and apply the correct rotation to show the image:

   1: void OnCameraCaptureCompleted(object sender, PhotoResult e)
   2: {
   3:     capturedImage = e.ChosenPhoto;
   4:  
   5:     BitmapImage bmp = new BitmapImage();
   6:     bmp.SetSource(e.ChosenPhoto);
   7:  
   8:     ChosenPicture.Source = bmp;
   9:  
  10:     // figure out the orientation from EXIF data
  11:     e.ChosenPhoto.Position = 0;
  12:     JpegInfo info = ExifReader.ReadJpeg(e.ChosenPhoto, e.OriginalFileName);
  13:     PostedUri.Text = info.Orientation.ToString();
  14:  
  15:     switch (info.Orientation)
  16:     {
  17:         case ExifOrientation.TopLeft:
  18:         case ExifOrientation.Undefined:
  19:             ImageRotate.Angle = 0d;
  20:             break;
  21:         case ExifOrientation.TopRight:
  22:             ImageRotate.Angle = 90d;
  23:             break;
  24:         case ExifOrientation.BottomRight:
  25:             ImageRotate.Angle = 180d;
  26:             break;
  27:         case ExifOrientation.BottomLeft:
  28:             ImageRotate.Angle = 270d;
  29:             break;
  30:     }
  31: }

Now I’ve got my flexibility in my application and don’t have to worry about the orientation. 

Now of course this only helps for the display of the information.  If you were to use the libraries to save the image you’d still have the issue of an incorrect orientation on the picture.  Again, iterating with our test team internally (thanks Stefan!!!) here’s a modified view of the world.

First, instead of rotating the Image element, let’s just rotate the actual Pixels themselves:

   1: void OnCameraCaptureCompleted(object sender, PhotoResult e)
   2: {
   3:     // figure out the orientation from EXIF data
   4:     e.ChosenPhoto.Position = 0;
   5:     JpegInfo info = ExifReader.ReadJpeg(e.ChosenPhoto, e.OriginalFileName);
   6:  
   7:     _width = info.Width;
   8:     _height = info.Height;
   9:     _orientation = info.Orientation;
  10:  
  11:     PostedUri.Text = info.Orientation.ToString();
  12:  
  13:     switch (info.Orientation)
  14:     {
  15:         case ExifOrientation.TopLeft:
  16:         case ExifOrientation.Undefined:
  17:             _angle = 0;
  18:             break;
  19:         case ExifOrientation.TopRight:
  20:             _angle = 90;
  21:             break;
  22:         case ExifOrientation.BottomRight:
  23:             _angle = 180;
  24:             break;
  25:         case ExifOrientation.BottomLeft:
  26:             _angle = 270;
  27:             break;
  28:     }
  29:  
  30:     if (_angle > 0d)
  31:     {
  32:         capturedImage = RotateStream(e.ChosenPhoto, _angle);
  33:     }
  34:     else
  35:     {
  36:         capturedImage = e.ChosenPhoto;
  37:     }
  38:  
  39:     BitmapImage bmp = new BitmapImage();
  40:     bmp.SetSource(capturedImage);
  41:  
  42:     ChosenPicture.Source = bmp;           
  43: }
  44:  
  45: private Stream RotateStream(Stream stream, int angle)
  46: {
  47:     stream.Position = 0;
  48:     if (angle % 90 != 0 || angle < 0) throw new ArgumentException();
  49:     if (angle % 360 == 0) return stream;
  50:  
  51:     BitmapImage bitmap = new BitmapImage();
  52:     bitmap.SetSource(stream);
  53:     WriteableBitmap wbSource = new WriteableBitmap(bitmap);
  54:  
  55:     WriteableBitmap wbTarget = null;
  56:     if (angle % 180 == 0)
  57:     {
  58:         wbTarget = new WriteableBitmap(wbSource.PixelWidth, wbSource.PixelHeight);
  59:     }
  60:     else
  61:     {
  62:         wbTarget = new WriteableBitmap(wbSource.PixelHeight, wbSource.PixelWidth);
  63:     }
  64:  
  65:     for (int x = 0; x < wbSource.PixelWidth; x++)
  66:     {
  67:         for (int y = 0; y < wbSource.PixelHeight; y++)
  68:         {
  69:             switch (angle % 360)
  70:             {
  71:                 case 90:
  72:                     wbTarget.Pixels[(wbSource.PixelHeight - y - 1) + x * wbTarget.PixelWidth] = wbSource.Pixels[x + y * wbSource.PixelWidth];
  73:                     break;
  74:                 case 180:
  75:                     wbTarget.Pixels[(wbSource.PixelWidth - x - 1) + (wbSource.PixelHeight - y - 1) * wbSource.PixelWidth] = wbSource.Pixels[x + y * wbSource.PixelWidth];
  76:                     break;
  77:                 case 270:
  78:                     wbTarget.Pixels[y + (wbSource.PixelWidth - x - 1) * wbTarget.PixelWidth] = wbSource.Pixels[x + y * wbSource.PixelWidth];
  79:                     break;
  80:             }
  81:         }
  82:     }
  83:     MemoryStream targetStream = new MemoryStream();
  84:     wbTarget.SaveJpeg(targetStream, wbTarget.PixelWidth, wbTarget.PixelHeight, 0, 100);
  85:     return targetStream;
  86: }

Notice here that OnCameraCaptureCompleted is different in that we first check the orientation and if needed rotate the pixels using the newly introduced RotateStream method.  The resulting stream is what we actually set on our Image element and no need for RotateTransform at this point.  I can then even have a menu item save the picture to the media library on the device:

   1: private void OnSavePictureClicked(object sender, EventArgs e)
   2: {
   3:     if (capturedImage != null)
   4:     {
   5:         capturedImage.Seek(0, 0); // necessary to initiate the stream correctly before save
   6:  
   7:         MediaLibrary ml = new MediaLibrary();
   8:         try
   9:         {
  10:             Picture p = ml.SavePicture(Guid.NewGuid().ToString(), capturedImage);
  11:             PostedUri.Text += ":" + p.Name;
  12:         }
  13:         catch (Exception ex)
  14:         {
  15:             PostedUri.Text = ex.Message;
  16:         }
  17:     }
  18: }

The MediaLibrary is a part of Microsoft.Xna.Framework.Media and provides the easy functionality of saving to the device.  And upon sync (or sharing) my image is what I expected when I took the picture using the CameraCaptureTask.

Here is my final project sample (lots of debug code in there, but you should get the point): WindowsPhoneApplication63.zip (requires the Windows Phone Developer Tools)

Hope this helps!


This work is licensed under a Creative Commons Attribution By license.


9/23/2010 9:03 PM | # re: Handling picture orientation in CameraCaptureTask in Windows Phone 7
Thought that might be the route to take when we discussed in one of Pete Brown's WP7 sessions. Still it would be nice if in v.Next if the event args class had orientation information to cut out the hoops
9/24/2010 11:24 AM | # re: Handling picture orientation in CameraCaptureTask in Windows Phone 7
Hello Tim,

Very interesting post and I hadn't seen the CodeProject article before. Does this mean that if you're dealing with the metadata from phone generated images that Exif is only route or are IPTC and XMP options as well?

Thanks

John
10/8/2010 9:02 PM | # re: Handling picture orientation in CameraCaptureTask in Windows Phone 7
That saved a non trivial and unavoidable time spend.

Thanks heaps Tim, Stefan and supporting crew, you guys rock!

For those trying to test this in the emulator you could make buttons to manually tell it what orientation you are using and mock map them to exif results as follows:

A. With phone Upright - TopRight
B. Turn phone CCW 90% - TopLeft (h/w cam button on top right)
C. Turn phone CCW 180% - BottomRight (phone upside down)
D. Turn phone CCW 270% - BottomLeft (h/w cam button on bottom left)

I also profiled the OnCameraCaptureCompleted and RotateStream methods for the 4 orientations taking a normal outdoor picture on a 650Mhz prototype device for peoples reference (OnCameraCaptureCompleted, RotateStream - ss.mmm format).

A. 59.201-01.437, 59.280-01.206
B. 33.634-34.342
C. 01.710-04.091, 01.782-03.858
D. 35.350-37.551, 35.417-37.307

So the rotation is taking 1-2s extra on what is normally only .2-.7s.

Keep this in mind if doing this amongst other processing, you might want to do some UI magic if you have a few stages of work to do.

ps - make sure to set your image control height/width to auto (or exact match of photo size) or you'll get screwy results at the render transform stage of Tim's post.
10/17/2010 8:27 AM | # re: Handling picture orientation in CameraCaptureTask in Windows Phone 7
Hey thanks. I tried this but it crashes on the call to ReadJpeg. Weird
11/11/2010 6:40 AM | # re: Handling picture orientation in CameraCaptureTask in Windows Phone 7
Very helpful article, really liked it!!

wordpress tutorials
11/13/2010 3:33 PM | # re: Handling picture orientation in CameraCaptureTask in Windows Phone 7
Very nice.. I find that the Bitmap in RotateStream and the JpegInfo have different widths and heights. Why would this happen?
2/3/2011 9:32 AM | # re: Handling picture orientation in CameraCaptureTask in Windows Phone 7
Nice article.
But you should to speed-up RotateStream method. For example, just using local variables instead of properties can increase speed by 50%.

int width = wbSource.PixelWidth;
int height = wbSource.PixelHeight;
int targetWidth = wbTarget.PixelWidth;
int[] sourcePixels = wbSource.Pixels;
int[] targetPixels = wbTarget.Pixels;

for (int x = 0; x < width; x++)
{
for (int y = 0; y < height; y++)
{
switch (angle % 360)
{
case 90:
targetPixels[(height - y - 1) + x * targetWidth] = sourcePixels[x + y * width];
//wbTarget.Pixels[(wbSource.PixelHeight - y - 1) + x * wbTarget.PixelWidth] = wbSource.Pixels[x + y * wbSource.PixelWidth];
break;
case 180:
targetPixels[(width - x - 1) + (height - y - 1) * width] = sourcePixels[x + y * width];
//wbTarget.Pixels[(wbSource.PixelWidth - x - 1) + (wbSource.PixelHeight - y - 1) * wbSource.PixelWidth] = wbSource.Pixels[x + y * wbSource.PixelWidth];
break;
case 270:
targetPixels[y + (width - x - 1) * targetWidth] = sourcePixels[x + y * width];
//wbTarget.Pixels[y + (wbSource.PixelWidth - x - 1) * wbTarget.PixelWidth] = wbSource.Pixels[x + y * wbSource.PixelWidth];
break;
}
}
}
7/20/2011 9:47 PM | # re: Handling picture orientation in CameraCaptureTask in Windows Phone 7
It is really help to us. eid text messages its give us lots of interest and pleasure. Its opportunity are so fantastic and working style so speedy. Its really a good article. It gives me lots of pleasure and interest.
Gravatar
10/12/2011 2:18 PM | # re: Handling picture orientation in CameraCaptureTask in Windows Phone 7
hi,

Is there any API available for wp7, in which when any notification send to the device it capture picture by default, without any user interaction ?

waiting for responses
Gravatar
1/21/2012 8:06 PM | # re: Handling picture orientation in CameraCaptureTask in Windows Phone 7
Worked a charm, thanks Tim!
7/8/2012 8:53 AM | # re: Handling picture orientation in CameraCaptureTask in Windows Phone 7
Hello
I have one question regarding using ExifLib with Picture Viewer Extension.

Namely, If we use PhotoChooserTask to take the picture from Picture Hub then ExifReader.ReadJpeg work with OriginalFileName (e.g. “\\Applications\\Data\\A5504C32-FC1B-43ED-9C40-575D57D2703F\\Data\\PlatformData\\PhotoChooser-4c555317-4828-43d4-8b42-a6292dfe98e6.jpg.jpg”).

But what if we want to use ExifReader.ReadJpeg in app where we use Picture Viewer Extension (using apps.. link. in Picture hub on specific image). In that case we get image by provided token to app as follows:

MediaLibrary library = new MediaLibrary();
Picture picture = library.GetPictureFromToken(queryStrings["token"]);

I can get picture name from “picture.Name” but in that case value is “WP_001284.jpg”.
Hot to get full OriginalFileName to work with ExifReader.ReadJpeg (second parameter)?

Best regards
6/16/2013 2:19 AM | # re: Handling picture orientation in CameraCaptureTask in Windows Phone 7
My link above describes a problem I ran across with my app on WP8, where a portrait image is delivered from the CameraCaptureTask

It strikes me that your code will probably now incorrectly rotate a portrait image on WP8.
10/11/2013 2:58 AM | # re: Handling picture orientation in CameraCaptureTask in Windows Phone 7
HELLO TIM
im using wp7 and use isolated storage to store images, i too have the problem with orientation and some how hen i use ur code it gives me syntax errors i tried to add the

public static JpegInfo ReadJpeg(Stream FileStream, string FileName)
2: {
3: DateTime now = DateTime.Now;
4: ExifReader reader = new ExifReader(FileStream);
5: reader.info.FileSize = (int)FileStream.Length;
6: reader.info.FileName = string.Format("{0}.jpg", FileName);
7: reader.info.LoadTime = (TimeSpan)(DateTime.Now - now);
8: return reader.info;
9: }

this gave me errors and caused my project not to build. all i want to do is change the orientation when the picture has been taken is the a need for me to add all the code you provided or is the a way of just rotating the image oncaptureCompleted? please help as i really dont see why i have to add over a huindred lines of code just to rotate one picture....
12/4/2013 9:07 AM | # re: Handling picture orientation in CameraCaptureTask in Windows Phone 7
Thanks For the Post ....

Well works just fine for me. And well done. It was been quite help full for me.



 
Please add 3 and 8 and type the answer here:

DISCLAIMER:

The opinions/content expressed on this blog are provided "ASIS" with no warranties and are my own personal opinions/content (unless otherwise noted) and do not represent my employer's view in any way.