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.