« WPF Rocks | Main | Universal web application pattern vs. Experience first pattern »

Brrrr ... it's cold out here Freezing my WPF off ...

I posted a few days ago about a technique for loading a BitmapImage in the background. Charles  suggested I try loading (the image) in a background thread and then Freeze the BitmapImage; finally, handing it off to the UI thread.

If you'd like to know more about freezing and WPF, there's a decent article available on MSDN here.

Success! It works. But I discovered something sinister going on. Downright evil.

The new implementation was slower. Way slower. How could that be? I was still using a background thread. The code was simpler. I did notice that I had changed from using the MemoryStream to using the UriSource property. A little bit of experimentation later, I discovered that the StreamSource property was also slow when I passed it a FileStream object. Confused thoroughly at this point, I switched back to the MemoryStream in a last ditched attempt knowing it wouldn't help -- but it did!!!! It was fast again! Here's what I ended up with:

 

        private void DoLoadImage(object o)
        {
            if (!_loading) { return; }

byte[] buffer = File.ReadAllBytes(_filename);
// by reading the data into an in-memory buffer,
we prevent the file from being read in the UI thread -- which speeds up

// access dramatically!
MemoryStream mem = new MemoryStream(buffer);

if (!_loading)
{
mem.Dispose();
return;
}

BitmapImage bi = new BitmapImage();
bi.BeginInit();
bi.DecodePixelWidth = 80;
bi.DecodePixelHeight = 60;
//bi.UriSource = new Uri(_filename); slow slow slow
bi.StreamSource = mem;
bi.EndInit();
bi.Freeze();

_imageSource = bi;
_loading = false;
RaisePropetyChanged("IsLoading");
RaisePropetyChanged("ImageSource");

// if you dispose of the memory stream here, the image will be toast (burnt toast)
// (as the dispatcher won't have run yet).
}


For some reason, the MemoryStream technique blows away the performance of any other option. It's at least 5 - 10 times faster than the other options! If anyone knows why, I'd love to hear it! As you can see though, if you compare to my original post, I've gotten rid of the Dispatcher reference and just directly create the BitmapImage. The key though is that I "Freeze" the BitmapImage so that it can be used outside of the creating thread and back in the UI thread.

Comments

I'm still trying to figure out how to get an image from a database. I need to 'draw' on the image (that, I know how to do), but I've yet to grok exactly how images get loaded into WPF correctly.

Any tips? (I'm reading through your archives right now, just in case ;)

Great Tip - the golden rule is to not make changes except on the UI thread, but this is the exception that is never really considered.

Robb -- a stream is probably the best way to go. Just load the bytes into a memorystream and using the technique above, it should work fine. (Depends on how you stored the image, but if you can get the bytes out it should work).

I'm finding myself in a similar boat - however my image is on the web (http) so I'm trying to keep the slow Uri method. The problem is the BitmapImage doesn't download the image at EndInit() and therefore cannot be frozen. I've beeen trying to find a way to "force" the download, but so far no luck.

Are you using this in XAML? That is, how are you using this for something like a ListView or ComboBox items template? Converter?

As for the loading images from a database comment, check this out: http://cromwellhaus.com/blogs/ryanc/archive/2007/07/26/binding-to-the-byte-of-an-image-in-wpf.aspx

If you set bi.CacheOption = BitmapCacheOption.OnLoad, does this address the URI-based slowness?

Ryan -- I'm using it in a variety of ways, as a bound object from Xaml mostly.

Adam -- never had luck with any of the Cache settings dramatically changing the actual or perceived performance that I recall (It was march when I did this...).

> Adam -- never had luck with any of the Cache settings dramatically changing the actual or perceived performance that I recall (It was march when I did this...).

Setting the CacheOption to OnLoad, it should force the bitmap to immediately load and decode the file on EndInit(), which should avoid the hit of doing this on the UI thread. This may not be the issue that you're seeing, but given that mem stream is affecting the performance so dramatically, I'd expect that the OnLoad behavior should be comparable.

From a comment I made on Rob Relyea's blog - http://rrelyea.spaces.live.com/Blog/cns!167AD7A5AB58D5FE!2294.entry

Trying to figure out the optimal strategy for implementing an Explorer/Photo Gallery style list view of thumbnail images for my WPF app in terms of performance and memory usage.

Reading say a directory of 100 images of say 3MB each using Aaron's approach of reading the whole file into a memory stream to then extract the thumbnail doesn't sound promising in terms of paging in 300MB from disk, decompressing the jpeg and then using DecodePixelWidth/Height etc.

Using BitmapDecoder.Frames[0].Thumbnail would surely be more effecient with maybe a fallback to DecodePixelWidth/Height if the image doesn't include an embedded thumbnail.

Ideal would probably be to use the system thumbnail cache especially if the user has viewed the directory in Explorer/Photo Gallery already.

Although it sounds like we'd need to interop to the shell's IThumbnailCache or IShellItemImageFactory since BitmapImage doesn't do this for us.

Do you know if the Windows Photo Gallery uses the system thumbnail cache or does it build it's own thumbnail cache?

Cheers

Sean --

Have you checked out my other sample?

http://www.wiredprairie.us/journal/2007/04/photoscroll_the_worst_named_wp.html

I certainly agree that reading in an entire image just to make a thumbnail sucks -- and you should go with the thumbnail that exists in many JPEGs already (depending on the digital camera model).

Regarding the thumbnail cache.

A tiny experiment would probably indicate whether they share or not (by creating a new folder with lots of images, then browsing it in one vs the other ... how fast is the second?). The behavior from my experience is that they don't share.

Just looking at your PhotoScroll sample and reading your http://www.wiredprairie.us/journal/2007/04/extracting_thumbnails_from_a_j.html article.

I noticed in your thumbnail article you also read the whole file into a memory stream and then get the embedded thumbnail from: imgDecoder.Frames[0].Thumbnail.

So although you're extracting the embedded thumbnail you're still reading the full file from disk. The only thing you're saving is the decompression of the full image and the downscaling to your requested thumbnail size.

Surely/ideally if you just set the UriSource property and then only requested the embedded thumbnail then instead of reading in say a full 3MB image the BitmapDecoder would be smart enough to seek into the file and only read out the say 100KB embedded thumbnail.

I Need to try this out and use Filemon to see exactly how much file IO there is to the image file.

But I still think the most optimal solution would be to use IThumbnailCache, if the thumbnail exists in the system cache then you immediately get it with very little IO and if not it gets created and added to the cache making subsequent requests for it optimal in terms of IO.

Cheers

Help support my web site by searching and buying through Amazon.com (in assocation with Amazon.com).