다음을 통해 공유


Image Tips for Windows Phone 7

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:

 

 

Image Caching

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.

BitmapCreateOptions

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.

 

Custom Decoding

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 320x320 images from a service only to display them as 32x32 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.

 

 

Auto Downsampling

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 2048x2048 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 2048x2048. This is different from desktop Silverlight where this limitation does not exist.

In the sample app I am consuming an image of size 3000x2102, which subsequently gets downsamples to 1500x1051.

 

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.

ImageTips.zip

Comments

  • Anonymous
    April 08, 2011
    Please put this information into MSDN! This is really good stuff.

  • Anonymous
    April 23, 2011
    Nice job, well written and in depth.

  • Anonymous
    April 26, 2011
    Great Article,, I have one question How can we save/download the image in to internal memory of winphone and open/clear when required??

  • Anonymous
    June 28, 2011
    In your Auto Downsampling sample app, did you hard-code 3000x2102?  are we still able to get the original size before the auto clipping?  

  • Anonymous
    August 15, 2011
    Very nice for me , but can you tell me how to use the WCF service to get image from database to phone?

  • Anonymous
    February 21, 2012
    great post! very helpful, thank you!

  • Anonymous
    April 04, 2012
    Hi, Stefan. I have user control, which contain Image. Images are loaded from internet (not local -- like in your example). And every time after loading different images --- memory increases and never decreases after destructor for user control and after destructor for page.... I try to fix memory leak with images and use code like: BitmapImage bitmapImage = image.Source as BitmapImage; bitmapImage.UriSource = null; image.Source = null; But it does not work for me... Can you help me --- how can I clear image cache. Thanks in advance.

  • Anonymous
    April 08, 2012
    Hi, Stefan once more. I have tested your example -- project (ImageTips.zip) --- and I can say that code:      BitmapImage bitmapImage = image.Source as BitmapImage;      bitmapImage.UriSource = null;      image.Source = null; does not release memory for cached images and does not delete cached images for your Application. When you set UriSource to null -- you only releases memory on your page Caching.xaml, but if you will navigate back to MainPage.xaml --- you will see that memory does not released --- memory has increased !!! To reproduce my observation --- you can:

  1. Add code to MainPage.xaml for viewing current memory value like on your  Caching.xaml page:            DispatcherTimer timer = new DispatcherTimer();            timer.Interval = TimeSpan.FromMilliseconds(500);            timer.Start();            timer.Tick += delegate            {                GC.Collect();                tbMemory.Text = string.Format("Memory: {0} bytes", DeviceExtendedProperties.GetValue("ApplicationCurrentMemoryUsage"));            };
  2. Run your application. When we navigate to MainPage.xaml in first time --- value of memory equals nearly 9676448 bytes
  3. Click to navigate to Caching.xaml ( Image caching) --- value of memory equals nearly    9011200 bytes
  4. Click on button Show to show image -- value of memory equals nearly 12996608 bytes
  5. Click on Avoid Image Cache (to clear cached image) and after that press button clear --- 10090144 bytes .... So, where have dissapeared 413696 bytes ??? (10090144 - 9676448 = 413696 bytes )
  6. Click on button back to navigate back to MainPage.xaml --- 11828248 bytes... But previous value before navigating to Caching.xaml was 9676448 bytes... So, where dissapeared 2151800 bytes???? I encountered with this problem in my Application and does not know how to resolve it. If someone knows how to delete images cache and  how to release memory of cached images --- please write how to do this.... Thanks.
  • Anonymous
    May 04, 2012
    this blog post by David Anson

the post link doesn't work,please fix it man

  • Anonymous
    August 03, 2012
    The comment has been removed

  • Anonymous
    December 18, 2013
    Great post! lots of interesting things

  • Anonymous
    January 19, 2014
    Great post!!! Really helpful Memory issues caused if you don't handle bitmapeimage is huge... thanks

  • Anonymous
    July 18, 2014
    If I want to display the image control with no source, how do I do this? For example, I have an image control, when i tap on it, I can select which image to set as source. If I hold on the control, I want the image to be blank. When I tap again, I get to select again. If I set the source to null, then I dont get to tap again.

  • Anonymous
    November 22, 2015
    Please attach the working sample. The sample currently needs TFS connection which doesnt work.