Painting Performance and Form Background Images
I'm blogging to:
Erykah Badu
Mama's Gun
Today I'm not going to talk about Crossbow stuff. Instead, I'm going to focus on an issue related to general Windows Forms applications. A customer that I met at PDC was talking to me about an issue that they were having and he was looking for some advice. It seems that they had decided to use a background image on one of their main forms and then set the background for all of the controls to Transparent so that the form would look really nice. They created a good looking .jpg file that had high transparency and away they went. When they run the app, they noticed that painting performance was less than desirable. The individual rendering of the controls was noticable and quite annoying. The first thing we talked about was to make sure that they were using double-buffering and ensuring that the AllPaintingInWmPaint style was set to true (we'll talk more about these later). He tried both of those and still no joy. So I asked him to send me the repro project and told him that we would try to see what was going on.
Sure enough, when we took a look at the application, the painting was less than stellar. The first thing we did was check out what value he had chosen for the BackgroundImageLayout property of the Form. He had the default value of Tile. This was the first thing we addressed. Using Tile for this value is much less performant than any of the other values and since he was not really intending to tile the image across the form since the image occupied the entire form using this value was not necessary. Tile layout is more expensive because painting is performed using a TextureBrush and creation of a TextureBrush is heavy-weight since it involves scanning the entire image. So we changed that value to Stretch.
Okay, the painting performance is a bit better now, but still not what we would like to see. Let's keep noodling on this one a bit... Another technique that can usually help here is to use a pre-computed bitmap for the image. What we do here is to load the image from file and then render it into a Bitmap object using the PixelFormat.Format32bppPArgb value. So what we did was to override the BackgroundImage property of the form and use the following code:
private Bitmap renderBmp;
public override Image BackgroundImage
{
set
{
Image baseImage = value;
renderBmp = new Bitmap(Width, Height, System.Drawing.Imaging.PixelFormat.Format32bppPArgb);
Graphics g = Graphics.FromImage(renderBmp);
g.DrawImage(baseImage, 0, 0, Width, Height);
g.Dispose();
}
get
{
return renderBmp;
}
}
After making these two changes, the painting performance was quite acceptable. Now, let's go back and talk a bit more about some of these issues. We mentioned double buffering earlier, so what is this all about? Double buffering is a technique whereby you render someting to chunk of offscreen memory and then once the rendering is complete you simply plunk the completly rendered image onto the display. This makes rendering appear smoother and typically reduces flicker. To fully enable double buffering, you also need to set the AllPaintingInWmPaint control style to TRUE. When this style is set, the windows message WM_ERASEBKGRND is ignored and both OnPaintBackground and OnPaint are called directly from the windows message WM_PAINT. This means that these two messages will be called with the same buffered graphics object and they will paint off screen together and update all at once. Note that when you set the DoubleBuffered property of a control to TRUE, the AllPaintingInWmPaint style is automatically set for you.
Does that mean that double buffering is a panacea? Should I always use it? No and no. You should understand that this will depend on your situation. One of the major drawbacks of this technique is that it may require a significant chunk of memory to buffer the image depending on the size of the image. This may result in deminished returns due to the memory overhead. To help control this, make sure that you minimizing what you need to repaint by properly manaing your invalidation.
That's all for today.
Comments
- Anonymous
October 12, 2005
Don't forget that double-buffering can be especially evil in a Terminal Server environment. Instead of being able to draw just whatever changes on your window, RDP will have to send your whole window over the network as a bitmap every time it changes.
If you do decide to use this feature, make sure you allow it to be disabled. - Anonymous
October 12, 2005
Outstanding! I had attempted to do that on another project and finally had to take it back out because of the same problem. Thank you so much, more developers are wanting this type of information than you know!
JMH. - Anonymous
November 17, 2005
Hello there
Very interesting article. I'm determined to have a background image in my app but, up to now, performance has vetoed that idea.
Anyway, regarding your article, I've tried using the BackgroundImageLayout property of the form but it doesn't seem to exist in .net 1.1 Is this only for 2.0?
I also tried your code and it made a huge difference, the painting delays were virtually non-existant. However, using it caused a problem. My background image is 900 by 700, the same size as the form. However, when using your code, it is tiled 3 times, as if the image is a third of the actual size. I was wondering if this was to do with the Format32bppPArgb format or something but I just can't work it out. I've tried creating an image with a bit depth of 24 and using Format24bppRgb or trying to down-sample the image in various formats and trying various format settings but the results are always the same. Help!