If you are developing for Windows Phone you have probably been using the the Image element in various places of your UI already. Using the Image element is very easy and wouldn’t warrent a blog post: just set the Source property to the URI of your image and you are done, right? Yeah, pretty much – however, there are a couple subtle details that are very useful to know, especially if you want to optimize your app for best performance and memory usage. Most of these little tips aren’t specific to the phone – they apply also to Silverlight desktop – but on the phone it is so much more important to pay attention to the little performance details that can turn a good app into an awesome app. To help illustrate the points, I have attached a sample app that demonstrates the results you can achieve by applying these tips.
JPG vs. PNG
If you can make a choice between using JPG and PNG, choose JPG for all your opaque images. The JPG decoder in the Windows Phone 7 operating system is considerably faster than the PNG decoder. If you need to use images that have transparency, PNG is your only choice though.
Resource vs. Content
Images (or any files for that matter) that are part of your Windows Phone XAP can have their “Build Type” set to either “Resource” or “Content” in Visual Studio. At first sight it doesn’t seem to make any difference what you choose here, as in both cases your files end up getting packaged up in the XAP. The difference becomes visible when you crack open your XAP (rename it to .zip and unzip it): now you can see all your files of type “Content”, but you won’t find the files of type “Resource” because those are embedded in the assemblies (.dll files). OK, why do I care? You care because the larger your application’s DLL, the slower your app will launch. On the other hand, once they are loaded the images will show up quicker compared to having them load from disk later. Summary: include your images as “Content” to optimize for quick start up, include them as “Resource” to optimize for quick access.
The other thing to note as that there is a difference in URI syntax for images included as “Content” vs. “Resource”. Check out the attached sample and navigate to the “Content vs. Resource” page for more details on this.
Content: <Image Source=”/ImagesAsContent/smiley1.png”/>
Resource: <Image Source=”..\ImagesAsResource\smiley3.png”/>
Async vs. Sync Loading of images
If you drill a little deeper into the Imaging APIs you will come across the BitmapImage class, which is doing most of the work behind the scenes when you create an Image. It can load things in two different modes:
BitmapImage.UriSource = uriSource; // loads the image via URI, asynchronously
BitmapImage.SetSource(stream); // loads the image from stream, synchronously
Note that loading the image asynchronously doesn’t mean it will happen completely off-thread. This is because currently the image decode is happening on the UI thread. For some more information about asynchronous loading of images while keeping the UI thread responsive check out this blog post by David Anson.
Some more trivia about async vs. sync loading:
If you load an invalid image file synchronously, you will get an exception
If you load an invalid image file asynchronously, the ImageFailed event fires (if you are subscribed)
If you load an valid image file asynchronously, the ImageOpened event fires when it’s done loading
If you load an valid image file synchronously, the ImageOpened event does not fire
To these differences in action check out the attached sample and navigate to the “Loading” page:
This is an important one, and MSDN is currently fairly silent about it. If you were ever wondering why your image memory didn’t get released after clearing the Source and removing the Image from the tree, you were most likely seeing Image caching in action. This is an intended performance optimization, to avoid (down)loading and decoding the same image over and over again. Instead we keep a cache in memory that we can easily and quickly reuse. This is not to confuse with the browser cache for downloaded files.
While this is a nice and free performance optimization, at times it can blow your memory unnecessarily, especially when you cycle through many images that you will never come back to. Their cache will use up memory for the lifetime of your app. The good news is that you can delete the cache when you decided that you no longer need it:
BitmapImage bitmapImage = image.Source as BitmapImage;
bitmapImage.UriSource = null;
image.Source = null;
Being smart about this can save you quite a bit of memory usage, which is a precious resource on a phone device. In the sample app, go to the “Caching” page and monitor the memory usage as you show/clear the image. Then check the box and try again. You will see a difference of ~3MB in the example case.
This is a quick, yet interesting one. If you create BitmapImage objects by hand, you might wonder why it doesn’t report the size of the image after you think it’s done creating the image. This is because by default the CreateOptions property is set to “DelayCreation”, which means that the object really gets created only when it’s indeed needed, meaning the BitmapImage is assigned as the source of an Image element or an ImageBrush that are in the live tree. This allows creating collections of BitmapImages up front on start up, without having to pay all the cost (CPU & memory) until the app actually needs the respective images. If you want to force immediate creation, set the CreateOptions to “None”. There is another option (IgnoreImageCache) that isn’t terribly useful for many practical purposes, but it’s needed by the design tools (e.g. Blend).
In the sample app, if you go to the “CreateOptions” page, go through the Create/Show/Clear sequence once with CreateOptions=DelayCreation and once with CreateOptions=None and monitor the Size value to see the difference in action.
By default the Imaging APIs will decode images in their natural resolution (except when auto-downsampling, more about that later). However, in many cases this is more than you actually need on a phone display. If you are downloading a collection of 320×320 images from a service only to display them as 32×32 thumbnails, you would be wasting a lot of memory if you decode them at their natural resolution.
On Windows Phone (specifically, not on desktop) the platform provides the PictureDecoder API that allows to specify the resolution you want to decode to:
image.Source = PictureDecoder.DecodeJpeg(jpgStream, 192, 256);
Note: in the current release there is an unfortunate bug in this API as the values for width and height are swapped. To workaround it just pass your width into the height parameter and vice-versa. This will be future compatible, as we will fix this with a quirks mode for existing apps.
To see the memory gains from doing this, open the sample app and navigate to the “Custom Decoding” page. Switch between the two resolutions and monitor the memory usage.
This is the last piece of need-to-know trivia when dealing with images in Silverlight for Windows Phone 7. The background is that individual elements are limited to 2048×2048 size on Windows Phone 7. Anything beyond that will be clipped. This means that if you want to consume an image that is larger than 2048 pixels in either dimension, it will not show correctly/completely. To avoid this potential confusion, the platform automatically downsamples large images so that they fit into 2048×2048. This is different from desktop Silverlight where this limitation does not exist.
In the sample app I am consuming an image of size 3000×2102, which subsequently gets downsamples to 1500×1051.
Phew, this post was longer than I thought it would be. Turns out there are a lot of little things to learn about Images in Silverlight for Windows Phone 7. Thanks for reading this far and I hope you found this post and the attached sample app helpful.