But this blog post is not about tombstoning, this post will show how to load an image from the picture library with the PhotoChooserTask. And most important how to save a picture to the library, which is not as obvious as the usage of the Task API. The last part will show how to get all images from the picture library without using the PhotoChooserTask.
Sample
I've built a Windows Phone 7 app that uses the WriteableBitmapEx library and lets the user draw on the WriteableBitmap surface. The app supports multi touch for drawing and the radius of the pen can be changed with the Slider control. An image from the phone's picture library can be chosen with the folder icon button and the floppy disk button saves the current image to the picture library. When the third button (download icon) is clicked, all pictures from the phone's library are loaded without further user interaction. The trash button clears the draw surface.
The video below demonstrates the features and shows how to use the app.
It would be great if anyone could try this sample app on a real device and give me some feedback. I'm especially interested how the multi touch drawing works.
Background music is Soft Shapes by Planet Boelex.
How it works
The raw touch points are handled through the Touch.FrameReported event. For each multi touch point a circle is being drawn with the WriteableBitmapEx' FillEllipseCentered method and a color map array that provides alternating colors:
private void Draw(IList<TouchPoint> points) { // Check if (!isManipulating) { return; } // Init some vars var bmp = Bitmap; var w = bmp.PixelWidth; var h = bmp.PixelHeight; var r = Radius; // Draw for (int i = 0; i < points.Count; i++) { var p = points[i].Position; if (p.X < w && p.Y < h) { bmp.FillEllipseCentered((int)p.X, (int)p.Y, r, r, ColorMap[(i + colorBase) % ColorMap.Length]); } } // Show Present(); }
Choose a photo
The PhotoChooserTask allows the user to select an image. The code for this task is pretty straight forward with the beta of the developer tools.
A member variable of the PhotoChooserTask is instantiated in the constructor and an event handler for the Completed event is attached.
public MainPage() { // ... // Init chooser photoChooserTask = new PhotoChooserTask(); photoChooserTask.Completed += PhotoChooserTaskCompleted; // ... }
private void PhotoChooserTaskCompleted(object sender, PhotoResult e) { if (e.TaskResult == TaskResult.OK) { // Load original image and invalidate bitmap so it gets newly rendered var bitmapImage = new BitmapImage(); bitmapImage.SetSource(e.ChosenPhoto); Viewport.Source = bitmapImage; bitmap = null; } }
The PhotoChooserTask.Show method is called when the corresponding Application Bar button was clicked:
private void ApplicationBarIconOpenButton_Click(object sender, EventArgs e) { photoChooserTask.Show(); }
The Show method launches the Windows Phone photo app. The current version of the Windows Phone operating system only allows one application to run at the same time and therefore our app gets terminated when the Chooser is started. Here's where the concept of tombstoning comes into play.
After the user selected an image or pressed the back button, the Chooser's Completed event is raised. You might have noticed the black screen in the video that appears after the choose operation. As a result of the app termination, the Visual Studio debugging session is stopped. The Windows Phone 7 emulator detected that the app was started in a debug context and the emulator now waits at the black screen for re-attaching. So just go back to Visual Studio and hit F5 (Start Debugging), then the debugger is being re-attached and the app continues. Of course it's also possible to Start Without Debugging in Visual Studio.
The Chooser's Completed event provides a PhotoResult that contains the TaskResult, the OriginalFileName and the ChosenPhoto as a Stream. If the user hasn't cancelled the operation, the stream is used as the source of a BitmapImage which is then assigned to the WriteableBitmap draw surface (Viewport). The user can now make some nice drawings on the photo.
Save a picture
After the user created his masterpiece he probably wants to save it back to the picture library / photo album. But how could this be done? There's no PhotoSaveTask available in the Silverlight SDK. Fortunately the Windows Phone's XNA MediaLibrary comes to the rescue. Only a reference to the Microsoft.Xna.Framework assembly is needed to use it in our Windows Phone Silverlight application. To make this task a bit easier I wrote some reusable extension methods for the WriteableBitmap. These are located in the file WriteableBitmapMediaLibraryExtensions.cs from the source code download. The signatures look like this:
// Saves the WriteableBitmap encoded as JPEG to the Media library. // The quality for JPEG encoding has to be in the range 0-100, // where 100 is the best quality with the largest size. void SaveToMediaLibrary(this WriteableBitmap bitmap, string name, int quality); // Saves the WriteableBitmap encoded as JPEG to the Media library // using the best quality of 100. void SaveToMediaLibrary(this WriteableBitmap bitmap, string name);
The methods use the MediaLibrary's SavePicture method internally. The SavePicture method expects a stream or a byte array as parameter that contains an image encoded in the JPEG format. For the earlier CTP versions I wrote a custom JEPG encoding functionality that used some parts of my Silverlight JPEG encoding blog post and an adapted FJCore version. Fortunately things got a bit easier with the Windows Phone Tools beta release. The Microsoft.Phone assembly now comes with two WriteableBitmap extension methods called LoadJpeg and most important SaveJpeg. The SaveJpeg method expects the targetStream, the width and height of the target, the orientation which is not used at the moment and the quality in a range from 0 to 100. The width and height parameters are useful when a scaled version of the WriteableBitmap should be saved as JPEG.
The current bitmap surface is saved as JPEG when the disk floppy button was clicked:
private void ApplicationBarIconSaveButton_Click(object sender, EventArgs e) { var name = String.Format("MediaLibSample_{0:yyyy-MM-dd_hh-mm-ss-tt}.jpg", DateTime.Now); Bitmap.SaveToMediaLibrary(name); }
Load all pictures
The last thing I'd like to cover in this post is the Pictures property of the MediaLibrary class. This property returns a collection of Picture instances. This class provides all the necessary meta information about the picture and streams of the image and thumbnail data.
I use this in the sample code to create a mosaic of all Windows Phone media library pictures:
private void ApplicationBarIconOpenAllButton_Click(object sender, EventArgs e) { using(var mediaLib = new MediaLibrary()) { // Pictures also includes saved pics, // mediaLib.SavedPictures returns the same collection items var allPics = mediaLib.Pictures; // Combine the pics to a single WriteableBitmap mosaic var bmp = Bitmap; bmp.Clear(); int x = 0; int y = 0; foreach (var picture in allPics) { // Load thumbnail stream to WriteableBitmap var wb = new WriteableBitmap(0, 0); wb.SetSource(picture.GetThumbnail()); // Blit thumbnail to background bitmap var w = wb.PixelWidth; var h = wb.PixelHeight; bitmap.Blit(new Rect(x, y, w, h), wb, new Rect(0, 0, w, h)); x += w; // Check bounds and move to next row if (x >= bitmap.PixelWidth) { x = 0; y += h; } // Bitmap filled if (y >= bitmap.PixelHeight) { break; } } } Present(); }
The GetThumbnail method returns the stream of an image thumbnail sized 99 x 99 pixels. This bitmap is then combined with the surface bitmap by using the WriteableBitmapEx' Blit method.
Beside the Pictures property the MediaLibrary also has the SavedPictures property, but I encountered that the SavedPictures collection returns all pictures in the emulator and not only saved pictures like the name and documentation implies. But as a little bird told me, the SavedPictures property works as expected on a real device.
The MediaLibrary has some more members that might become interesting in the future, esp. when combined with the MediaPlayer class.
Conclusion
This blog post covered the complete workflow of loading, manipulating and saving a picture to the Windows Phone's picture library / photo album. I also showed how to load all images from the library and mentioned some gotchas.
Source code
Download the complete Visual Studio 2010 Windows Phone solution from here.
Hi - I'm the lead pm for camera & photo in windows phone. Love to see you using our code =)
ReplyDeleteThanks KC! Glad you like it.
ReplyDeleteFantastic. I've been looking for an example of using some of this stuff for a few days, and this is the first non "hello world" thing i've found useful.
ReplyDeleteThank you for this post.
ReplyDeleteCan you view my Macro photos? http://art.hqcyber.info/
This is great. I am thinking of a way to extend this to take a screen shot of an active screen. Anyway to include other information like textbox, fields buttons etc.
ReplyDeleteHi techfail,
ReplyDeletesure, that's easy with the Writeablebitmap, just render the parent element. For example if you want to save the whole page, just use the LayoutRoot:
var wb = new WriteableBitmap(LayoutRoot, null);
wb.SaveToMediaLibrary(name);
Thanks a lot for the additional information. That was a lot easier than I thought it would be.
ReplyDeleteDo you know how to save image in png format?
ReplyDeleteI was trying to use Joe Stegman's PNG encoder from here: http://blogs.msdn.com/b/jstegman/archive/2008/04/21/dynamic-image-generation-in-silverlight.aspx but when i tried to save a stream like this:
new MediaLibrary().SavePicture(path, pngStream);
i get an exception
Hi max,
ReplyDeleteThe Windows Phone SDK supports only JPEG for saving. See the MSDN documentation.
http://msdn.microsoft.com/en-us/library/ff604987.aspx
Just giving you a heads up that this works fine on hardware. Thanks, this is really useful!
ReplyDeleteHi,
ReplyDeleteThanks for the Coding.after drawing when am navigating to the next page and am coming back to the same am getting error like parameter is not valid in the method Touch_FrameReported