LowProfileImageLoader with GIF support for WP7

LowProfileImageLoader

I’ve recently bumped into the need to display images from various RSS feeds in a Windows Phone 7 app. UI binding and loading of images can easily degrade the performance of any wp7 app, which led me to the excellent LowProfileImageLoader (LPIL) by David Anson. The LPIL attaches a property, UriSource, to an Image and offloads as much of the image handling work as possible to a background thread, very nice. But, all is not well yet..

Update 2012-01-10

According to the comment by nati, a better way of doing this would be to check the first bytes of the actual image. I have not tested this, but please give it a try – looks better to me! /A

GIF

Windows Phone 7 does not have built-in support for GIF. JPG and PNG are supported, and the JPG handling is much faster so that is preferred. However, when consuming web data, you will very soon find yourself needing to handle gif images. ImageTools to the rescue! There are other decoders for gif out there, read more in “Working with GIF” by Jaime Rodriguez.

Using the gif decoder is easy. This code shows how to download and image, use the gif decoder, and create an ImageSource that an Image can bind to. The alternative way of using the ImageTools is to first register the decoders that you need, then use the ExtendedImage in a more indirect way. This method also allows you to decode BMP and the other formats supported by ImageTools.

Alternative 1 – Using the GIF decoder directly

public ImageSource GifImageSource { get; set; }

private void CreateGifImageSource(string gifImageUrl)
{
    var client = new WebClient();
    client.OpenReadCompleted += (sender, e) =>
        {
            try
            {
                var decoder = new GifDecoder();
                var image = new ExtendedImage();
                decoder.Decode(image, e.Result);

                var gifImageSource = image.ToBitmap();

                GifImageSource = gifImageSource;
            }
            catch (Exception exc)
            {
                ErrorMessage = exc.Message;
            }
        };
    client.OpenReadAsync(new Uri(gifImageUrl));
}

 

 

Alternative 2 – Using the ExtendedImage class

This method can utilize more than one decoder. The decoders must be registered first, then the ExtendedImage can be used.

ImageTools.IO.Decoders.AddDecoder();
ExtendedImage desert = new ExtendedImage();
desert.UriSource = new Uri("/Images/Desert.gif", UriKind.Relative);

 

The combination

So, how do we mix these two nice things together? Let me show you, it’s really not a lot of code. The LPIL processes image tasks in batches for maximum efficiency, very clever. Shown below is the unmodified code from LPIL that processes all downloaded streams and creates a BitmapImage for each one, this is all done on the UI thread (which it needs to be).

The original code of LowProfileImageLoader

 

// Process pending completions
if (0 < pendingCompletions.Count)
{
    // Get the Dispatcher and process everything that needs
		// to happen on the UI thread in one batch
    Deployment.Current.Dispatcher.BeginInvoke(() =>
    {
        while (0 < pendingCompletions.Count)
        {
            // Decode the image and set the source
            var pendingCompletion = pendingCompletions.Dequeue();
            if (GetUriSource(pendingCompletion.Image)
								== pendingCompletion.Uri)
            {
                var bitmap = new BitmapImage();
                try
                {
                    bitmap.SetSource(pendingCompletion.Stream);
                }
                catch
                {
                    // Ignore image decode exceptions
										// (ex: invalid image)
                }
                pendingCompletion.Image.Source = bitmap;
            }
            else
            {
                // Uri mis-match; do nothing
            }
            // Dispose of response stream
            pendingCompletion.Stream.Dispose();

 

That was the hard part – all thanks to David Anson. Here comes the easy part. What I do is simply to check if the file is a GIF file – in that case:

  • Create a gif decoder
  • Decode the stream
  • Extract the resulting bitmap using the ToBitmap method on ExtendedImage

That does it! GIF images are decoded and pops up in the UI.

The modified LowProfileImageLoader

// Process pending completions
if (0 < pendingCompletions.Count)
{
    // Get the Dispatcher and process everything that needs
    // to happen on the UI thread in one batch
    Deployment.Current.Dispatcher.BeginInvoke(() =>
    {
        while (0 < pendingCompletions.Count)
        {
            // Decode the image and set the source
            var pendingCompletion = pendingCompletions.Dequeue();
            if (GetUriSource(pendingCompletion.Image) == pendingCompletion.Uri)
            {
                try
                {
                    ImageSource bitmap;
                    if (pendingCompletion.Uri.AbsolutePath
												.ToLower().Contains(".gif"))
                    {
                        var decoder = new GifDecoder();
                        ExtendedImage image = new ExtendedImage();
                        decoder.Decode(image, pendingCompletion.Stream);
                        bitmap = image.ToBitmap();

                    }
                    else
                    {
                        var bitmapImage = new BitmapImage();
                        bitmapImage.SetSource(pendingCompletion.Stream);
                        bitmap = bitmapImage;
                    }
                    pendingCompletion.Image.Source = bitmap;
                }
                catch
                {
                    // Ignore image decode exceptions (ex: invalid image)
                }
            }
            else
            {
                // Uri mis-match; do nothing
            }
            // Dispose of response stream
            pendingCompletion.Stream.Dispose();

 

Room for improvement

Not a lot right? The code above worked well for me in the app I was building. Something that is nagging me though, is the fact that it’s all being done in the batch “all-background-work-done-now-just-populate-the-gui”, hence the GIF decoding is done on the UI thread. I have not found this to be a big problem, but it would be nicer to split the decoding and image creation into two different tasks, with the first task being done in one of the background batches of LPIL.

I once took a quick stab at this and bumped into some kind of problem, but I can’t remember what that was…

Anyway, I hope you find this piece of code useful. Please – see it as display of how GIF support can be accomplished, not a production-ready package.

Thank you for listening.

This Post Has 6 Comments

  1. pj

    Hi~
    May I ask one question?
    I would like to bind a .png with Listbox
    and the properties set as
    Build Action=content
    Copy to Output Directory = copy always.

    But it was failure when loading the application.

    Would u please help? thanks very much

  2. Chris

    Superb code… But one thing I’ve found from writing my twitter client is that, just because it ends in GIF, its not always a GIF image… (likewise PNG etc..). It’s a crazy world out there! :)

  3. Andreas Hammar

    Thanks Chris! That is very true! I haven’t had big problems with that luckily.. yet…

  4. nati

    Thanks, that was really helpful!

    One small improvement:
    You identify gif files by looking for “.gif” in the URL but that’s not always the case…

    One can replace the following if statement:
    if (pendingCompletion.Uri.AbsolutePath.ToLower().Contains(“.gif”))
    with this one:
    if (StreamHeaderMatch(pendingCompletion.Stream, new byte[] { 71, 73, 70 }))

    where StreamHeaderMatch is defined:

    private static bool StreamHeaderMatch(Stream stream, byte[] value)
    {
    var header = new byte[value.Length];

    var x = stream.Read(header, 0, header.Length);
    stream.Seek(0, SeekOrigin.Begin);

    return (x == value.Length) && header.SequenceEqual(value);
    }

    Gif files has these magic bytes at the beginning (actually, 71,73,70 is ‘G’,’I’,’F’…), so I used that to identify these files.

    Of course, this method could be used to identify any file type, as long as you know the type’s magic bytes…

  5. Rob

    Thanks a lot!! Had same issues of Gifs not showing in my list of tweets.Your solution resolved this issue. Update 2012-01-10 seems to work as well

  6. Andreas Hammar

    Hi Rob! Thanks for sharing – great to know that the update tip worked.

Leave a Reply