37

I found several patterns for optimizing Bitmaps handling in WPF. I do not, however, understand when to use each patterns. As I think this is a common problem, I summarized what I understand and what I guess and ask for your help. If you can add patterns, explain how they differ, explain if they use the CPU or the GPU, and teach when to use each and how to combine them, it’d be a tremendous help!

Context – the Images "Grid" Scenario:

My application has to display many bitmap images. The images are displayed on the screen in a rows-and-columns grid-like organization (not necessarily the Grid or UniformGrid classes, think Window Media Player’s Album view). Images might move between different grid cells. Some images at arbitrary cells may be replaced by others. Images should be clickable, should provide a context menu, should be selectable, drag-able etc. In other words, “combine the little buggers into one big bitmap” is not applicable, at least not naively.

Pattern 0: The Hack

Do combine the little buggers into a bitmap (how? drawing context?), and use this as the background. Overlay this with images with empty content that will handle the hits, context menus, events etc.

The advantage is that we're only speaking about two bitmaps here: The currently displayed one and the one that should replace it. This should be really fast. However, my years of experience raise the red flag of danger. Your comments?

Pattern 1: Reduce Image Size

This is a no-brainer when you know in advance the image size to resize to, and when you’re prepared to lose details (color) for performance:

  1. Reduce the bitmap size using BitmapImage.DecodePixelWidth
  2. Reduce the color information using FormatConvertedBitmap.DestinationFormat
  3. Set the control’s scaling behavior setting Image.Stretch to Stretch.None
  4. Set the SetBitmapScalingMode for the image to LowQuality.
  5. Freeze the bugger

See code here.

Pattern 2: Background pre-fetch

This pattern is applicable when you think you can take advantage of the user gazing at the images on the screen, and prepare ahead the next images to display. The cons for your project, in addition to the memory overhead, is that it has to support the .Net Framework 4 target and not just the client profile, so it might incur an installation on the client’s. You yourself will have to suffer the async programming pain.

In this pattern you create exactly the required number of Image controls. When bitmaps need to be added, moved or deleted you only modify the Image controls' BitmapSource(s). A BackgroundWorker task is responsible for pre-fetching the BitmapSource(s) (possibly using the “Reduce Image Size” pattern above) and inserting them into MemoryCache.

For this to work you have to set the BitmapImage’s CacheOption to OnLoad, so that the work is off-loaded to the background worker.

Pattern 3: Drawing Context

This was suggested by Sheldon Ziao from Microsoft Support on the MSDN WPF forum here. See page 494, Chapter 15 “2D Graphics” in Adam Nathan’s WPF 4 Unleashed for a description of DrawingContext. I can’t say I understand it. According to the answer here, I would assume this would improve handling of Geometry drawings, not bitmaps. Next, I don’t think this will support the focus and events requirements for the images (my bad for not explaining the requirements better at the forum) Moreover, I’m worried by the book’s summary sentence: “Note that the use of DrawingContext doesn’t change the fact that you’re operating within a retained-mode system. The specified drawing doesn’t happen immediately; the commands are persisted by WPF until they are needed.” This means that once our even handler re we can’t take advantage of parallelism as in “Background pre-fetch”.

Pattern 4: Writeable Bitmaps

The MSDN documentation here describes it as a dual buffer system: Your UI thread updates the buffer; the WPF’s render thread moves this to video memory.

The intended usage (see here) is for bitmaps that change a lot such in a video movie like display. I’m not sure, but possible this could be hacked and combined with the Background Pre-fetch pattern and used in the grid scenario.

Pattern 5: Cached Bitmap

Not much info on the MSDN (here). On the WPF forum archive (here) it’s explained that “The BitmapCache API is designed to cache your content (when rendering in hardware) in video memory, meaning it stays resident on your GPU. This saves you the cost of re-rendering that content when drawing it to the screen.” This seems like a great idea. I’m not sure, however, what are the pitfalls and how to use it.

Pattern 6: RenderTargetBitmap

The RenderTargetBitmap converts a Visual to a bitmap. I’m not sure if it’s relevant here. See here.

Edit: Regarding Paul Hoenecke question: I've written that "My application has to display many bitmap iages". I failed to mentions that I need to display about 800 images concurrently.

One can read about the performance issues involved at my SO questions WPF Bitmap performance and How can I make displaying images on WPF more “snappy”?

I've modified the description of pattern 1 to highlight the concept that the image controls aren't created or deleted (unless we want to display a larger or smaller grid). Only their Sources are set to different, new or null BitmapSources.

Edit: This question as posted on the WPF support forum, with some answers from MS personnel.

Community
  • 1
  • 1
Avi
  • 15,696
  • 9
  • 39
  • 54
  • 1
    How many images are you anticipating? I have made an app that shows a grid of thousands of images before. We used a virtual-mode listbox; as the user scrolls down, the images are loaded in a background thread, frozen and set to the Source of the Image. Clearly, you have put a lot of thought into this... but, maybe knowing more about exactly what you want to accomplish would be better. For example, what problems have you had that make optimizing such a priority? – Paul Hoenecke Feb 17 '12 at 04:57
  • @PaulHoenecke Hi Paul, thank you for your answer. The virtual collections help you when you want to display a small number of items out of a large collection. Here, I want to display about 1K items concurrently. Additionally, I'm not sure there exists a virtualizing grid. Lastly, pattern 1 is essentially virtualizing - I'll add en edit. – Avi Feb 17 '12 at 09:10
  • Avi, your question looks inappropriate for the described problem. You may want to improve compression ratio for some reason, but then you need to know exactly what kind of images you are displaying. Or, on the other hand, you may want give the user an impression of all the images available, then you might define some sets and display thumbnails or alike. But both things don't have necessarily to do with each other. Maybe you want something like a similarity metric to define sets of look alike images? –  Feb 27 '12 at 06:39
  • @artsenay Sorry, I did not understand your comments. I want to display a lot of bitmaps on the screen, and I want to display them in a grid, not at arbitrary screen positions. And it's not fast enough, so I want it to appear faster. – Avi Feb 27 '12 at 08:20
  • @Avi Is there any reason not to use existing algorithms, e.g. JPEG with low quality settings? It depends on the images you want to compress, of course, but if you have images like diagrams, you could try with PNG and there with one of the numbered algorithms. It doesn't make any sense to discuss how one could shuffle with the binary data in memory if you don't know what you want to do. Probably server caching is your least problem, since the bottle neck will be on the client side or in the transmission. –  Feb 27 '12 at 09:06
  • @artsenay I think I undertand where the misunderstanding lies. I'm not talking about a web server senario. I'm talking WPF. THere's no transmission of images, they are all on the local computer. And displaying them is slow. Please re-read the interoduction and the context sections. – Avi Feb 27 '12 at 11:02

1 Answers1

21

I'm unable to find a specific question in your post, other than asking for comments on the approaches below. I won't claim to know everything above but I'll tell you what I do know having worked for a while developing high-performance UIs using WPF and Silverlight.

Pattern 0: The Hack. Combining all into one image

I'd avoid this if possible. It sounds like you want to display a large wrap-panel of thousands of small images. Each image is therefore a thumbnail (as you cannot display 1000s of large images at once). As a result, I'd advocate caching/resize over combination.

Pattern 1: Reduce Image Size

If you are displaying 1,000 images on screen at once, consider the available screen real-estate. The average monitor is 1280x1024 pixels, or just over 1.3MPixels. 1000 images suggests you will get a maximum size of 1300 pixels per image, or 36*36. Lets say your images are 32*32 in size. You should definitely be creating a thumbnail of that image size to render on screen, then on click (or other action) show the full size image.

Also consider not only the render overhead of resizing a large image, but of sending a large image to the GPU to resize. That data requires bandwidth to send. A large image can be several megabytes whereas a thumbnail of size 32*32 could be a few kilobytes.

If you require dynamic sizing, fine, but you'll need to experiment with creating multiple thumbnails or generating them on the fly.

Pattern 2: Background pre-fetch

This is a technique I've not heard of, however it seems plausible. What is the overhead in your application? is it updating the Image.Source property or creating a new Image, tessellating, performing Layout and sending the information to render it to the GPU?

All the above occur on the CPU except for the final render. By reducing the overhead on the CPU side and updating the source you might be on to something. Combine this with WriteableBitmap as a source and you could further gain a performance improvement (see below).

Pattern 3: Drawing Context

Ok, all this does is allow you to queue up retained mode drawing calls using an "OnPaint" style syntax which is nothing like the old GDI OnPaint. In my experience OnRender doesn't improve performance, but it does allow for fine grained flexibility over what is drawn and when. OnRender provides you with a context, which has a DrawImage function, allowing a BitmapSource to be drawn to the rendering pipeline without the need for an Image control. This is good as it removes some overhead, however introduces problems similar to those seen in Pattern0 (you will lose layout and have to compute position of all your images). If you do this, you might as well invoke Pattern 0, which I advised against.

Pattern 4: Writeable Bitmaps

WriteableBitmaps are a little used and extraordinarily powerful subsystem within WPF. I use them to great effect to create a charting component capable of rendering large amounts of data in real-time. I would suggest checking out the WriteableBitmapEx codeplex project Disclosure, I have contributed to this once and seeing if you can combine it with other patterns. Specifically the Blit function which would let you write a cached bitmap to a bitmap source on an image.

For instance, a good technique might be Pattern 1 + 2 + 4.

You could have a grid of N Image controls on the screen at set locations in a grid control. Each of these is static and doesn't get scrolled out of view so there are no creations/deletions going on. Now, on top of this, resize your image and write to a WriteableBitmap which is set as the Source property on each image. As you scroll, get the next/previous thumbnails and update the sources using WriteableBitmapEx.Blit. Pow! virtualized, cached, multi-threaded imaging goodness.

Pattern 5: Cached Bitmap

This is an attempt by microsoft to do 1+2+4 as I discussed above. What it tries to do is after layout (CPU side), tessellation (CPU Side), sending retained mode render instructions to the GPU (CPU side) and rendering (GPU side) it caches a raster image of the rendered element which is re-used on the next rendering pass. Yes a little known fact about WPF is that wonderful GPU powered engine is terribly slow as it does most of its work on the CPU :P

I would experiment with BitmapCache and see how it performs. There are caveats, and they are that when you update your UIElement it has to recreate the cache so static elements will perform far better than dynamic. Also I've not seen a significant improvement in performance from using this whereas WriteableBitmap style techniques can give an order of magnitude improvement.

Pattern 6: RenderTargetBitmap

This final technique lets you render a UIElement to a bitmap - you know that - but what is interesting is this can perform a poor-mans thumbnail generator (or resize). For instance, set an Image with BitmapSource of your full size image. Now set the size of the Image control to 32*32 and render to bitmap. Voila! You have your BitmapSource thumbnail to use in conjunction with some swapping (Pattern 2) and/or writeable bitmaps.

Ok finally, just wanted to say the requirement you have will push WPF to its limits, however there are ways to get it to perform. Like I said, I have build systems which rendered thousands or millions of elements on the screen at once by using the wonderful workaround that is WriteableBitmap. Going down the standard WPF route will result in performance hell so you will have to do something exotic to solve this.

As I said my recommendation is 1+2+4. You must resize a thumbnail, of that I have no doubt. The idea of having a static grid of Image controls and updating the sources is very good. The idea of using WriteableBitmap (specifically WriteableBitmapEx blit function) to update the sources is also one worth exploring.

Good luck!

Dr. Andrew Burnett-Thompson
  • 20,980
  • 8
  • 88
  • 178
  • Thank you for your answer! An ancient Jewish Passover story describes four sons: Wise, evil, naive and one that doesn't even know which questions to ask. I feel I'm so lacking in background I'm not even capable to ask the correct questions: "tessellating"? Are you aware of any book or article describing the (2D) graphics "pipeline" in WPF? What are the activities, what does the CPU do, what does the GPU do? – Avi Feb 27 '12 at 17:15
  • 1
    Yes I do! See this article: http://jeremiahmorrill.com/2011/02/14/a-critical-deep-dive-into-the-wpf-rendering-system/ This is pretty esoteric stuff, don't feel bad. Most will tell you "Just override OnRender" for performance, or "WPF uses the GPU", but that's the wrong answer. When you realise that WPF does most of its work CPU side (lame!!) then you can begin to optimize for performance. – Dr. Andrew Burnett-Thompson Feb 27 '12 at 17:28
  • Going through the trail you sent me on, I start getting the feeling that WPF is inherently slow and that one should wait for Direct2D or WinRT... – Avi Feb 28 '12 at 22:15
  • @Avi WPF is inherently slow, yes. However WinRT/Xaml apparently is no better. Direct2D is available now, you shouldn't need to wait to integrate this to your application. However if you want rich databinding and quick creation of screens you won't find it in Direct2D! – Dr. Andrew Burnett-Thompson Feb 28 '12 at 22:56
  • @Avi I should add, there are ways to make WPF perform, just takes a little coaxing ;-) Check out http://bit.ly/xv5cVv - which uses a combination of efficient WPF techniques and immediate mode techniques such as Bitmaps/Direct GPU access to get the desired effect – Dr. Andrew Burnett-Thompson Feb 28 '12 at 23:03
  • 2
    That Article link on the WPF Rendering Pipeline is now dead, please see http://jeremiahmorrill.wordpress.com/2011/02/14/a-critical-deep-dive-into-the-wpf-rendering-system/ – Dr. Andrew Burnett-Thompson Jun 02 '14 at 14:54