Flicker-Free Drawing Using Bounds Accumulation | CodeGuru

Flicker-Free Drawing Using Bounds Accumulation

Environment: VC++ 6.0/7.0, Windows95 (but read on…) and later. Off-Screen Drawing with QBufferDC To avoid annoying screen flicker while drawing with GDI or GDI+, using an off-screen bitmap buffer is a well-known technique. Basically, you do your drawing to a bitmap in memory, and then copy (‘bitblt’) the result to the screen. In applying this […]

Written By
CodeGuru Staff
CodeGuru Staff
Aug 15, 2003
5 minute read
CodeGuru content and product recommendations are editorially independent. We may make money when you click on links to our partners. Learn More

Environment: VC++ 6.0/7.0, Windows95 (but read on…) and later.

Off-Screen Drawing with QBufferDC

To avoid annoying screen flicker while drawing with GDI or GDI+, using an off-screen bitmap buffer is a well-known technique. Basically, you do your drawing to a bitmap in memory, and then copy (‘bitblt’) the result to the screen.

In applying this method, we need to know two things. First, we have to determine how big the off-screen bitmap should be. Second, we will want to know which part of the bitmap we have to copy to the screen.

The first problem is easy to solve. The device context maintains a clip box. It will not draw anything outside this rectangle. So, there is no point in making the off-screen bitmap bigger than the clip box.

You can get the clip box by using CDC’s member function GetClipBox, which is defined as:

virtual int CDC::GetClipBox(LPRECT lpRect) const;

Here, lpRect points to a RECT structure of a CRect object that receives the dimensions of the clip box, and thus the (minimum) size of the off-screen bitmap.

The second problem seems harder, at first sight. So hard, in fact, that it is often not solved at all. Many implementations, such as Keith Rule’s clever MFC class CMemDC, simply bitblt all of the bitmap to the screen, even the parts on which nothing has been drawn.

It would be more efficient to copy only the part of the bitmap on which some actual drawing has taken place. One solution would be to define a variable rectDirty, and update this after every graphic primitive. Something along these lines:

// ...

CRect rectSmall(100, 100, 150, 150);
dcBufferBitmap.Rectangle(rectSmall);
rectDirty |= rectSmall;

CRect rectBig(120, 120, 450, 450);
dcBufferBitmap.Rectangle(rectBig);
rectDirty |= rectBig;

// ...

After all drawing is done, rectDirty would contain the union of all graphic primitives’ bounding rectangles, and therefore the part of the bitmap that should be bitblt’ed to the screen.

This method would have been very inconvenient, to say the least. We would have to intersperse our drawing code with updating code for rectDirty. On top of that, this code often would not be as simple as shown above—think of TextOut calls, for instance. This hardly seems worth the trouble.

Funny enough, the Windows device context comes with built-in resources for just the things we tried to do. It is called ‘bounds accumulation‘. It’s not very well known and not often used—in fact, I couldn’t find any reference to it, apart from the official documentation. Without doubt, one of the main reasons lies in the poor documentation Microsoft delivers on this subject. You more or less have to guess how it works. I did so, and I hope I guessed right.

Windows Bounds Accumulation

Normally, bounds accumulation is turned off. To turn it on, you have to use the CDC member function SetBoundsRect (or its SDK equivalent):

UINT CDC::SetBoundsRect(LPCRECT lpRectBounds, UINT flags);

You then do all your drawing. After that, you can retrieve the part to which actual drawing took place with:

UINT CDC::GetBoundsRect(LPRECT lpRectBounds, UINT flags);

In the ‘Remarks’ section of Microsoft’s documentation it is stated that ‘The drawing bounds are useful for invalidating bitmap caches’, and that is almost all the clarification we get. After some experimentation, however, I think I know how to put these obscure functions to use.

First, call SetBoundsRect with lpRectBounds = NULL and the following flags. (The flag DCB_RESET is not mentioned in the MFC documentation, but it is in the Windows GDI docs.)

dcBufferBitmap.SetBoundsRect(NULL, DCB_ENABLE | DCB_ACCUMULATE |
                                   DCB_RESET);

Next, do your drawing like you used to. Behind its back, the device context keeps track of the rectangular area to which it is drawn.

Finally, prepare a CRect rcBounds and call GetBoundsRect:

dcBufferBitmap.GetBoundsRect(rcBounds, DCB_RESET);

After that, rcBounds will contain the ‘dirty rectangle’ in logical coordinates. Only this part of the buffer bitmap needs to be bitblt’ed to the screen.

Advertisement

QBufferDC

I put all this together in a small class, called QBufferDC. You use it in the same way as you would use CMemDC:

void CWhateverView::OnDraw(CDC* pDC)
{
  QBufferDC dcBuffer(pDC);

  dcBuffer.MoveTo...
  dcBuffer.LineTo...

  // ...other drawing code to dcBuffer...

}

That’s all there is to it. If dcBuffer gets out of scope, its destructor automatically copies the result of all drawing code to the screen DC. It could hardly be simpler.

QBufferDC has some nice features:

  • It is compatible with all Windows mapping modes, even the user-defined ones (MM_ISOTROPIC and MM_ANISOTROPIC).
  • It handles MFC printing and print preview. In those situations, no buffer bitmap is used.
  • If no buffer bitmap can be created because of memory lack (highly unlikely, these days), it reverts to normal DC operation.
  • To avoid frequent creation and destruction of bitmaps, one static bitmap is maintained.
  • An optional second constructor parameter lets you define another ‘ternary raster operation’ (rop-code) than the default SRCCOPY. This is useful for rubber banding and other advanced applications.

The source code for QBufferDC is quite heavily commented. It compiles in Visual C++ 6.0 and 7.0.

Demonstration Project

QBufferDemo is a small demonstration of QBufferDC. It’s a silly program, just showing some silly graphics. It starts up in a small window because the interesting part is to scroll it.

If Demo Mode is switched on (the default), the background color is slightly modified to a random off-white color. This makes it possible to see which part of the screen is updated. Scroll the window, or overlay it temporarely with another window, to see which parts are updated. Note that the area around the graphics stays white, indicating that QBufferDC is not involved. No ’empty’ bitmap is copied there.

The source code for the demonstration project is also included.

Advertisement

The Windows 98 Issue

Although according to the Windows GDI documentation, bounds accumulation is available in Windows 98 (even in Windows 95), the bad news is that it doesn’t work. There are no crashes or other nasty things, but GetBoundsRect simply returns the same rectangle as GetClipBox. The result is that QBufferDC operates like an ‘ordinary’ bitmap buffer DC, without bounds accumulation. I tested QBufferDC successfully in Windows 2000 and Windows XP.

It almost seems that Get/SetBoundsRect in Windows 98 are empty functions, not doing what is claimed in the documentation. I find that rather hard to believe. If anyone knows more about this, I’ll be glad to hear it.

Of course, there is always the possibility that I made a stupid mistake…

Download

Download demo project and source – 48 Kb

CodeGuru Logo

CodeGuru covers topics related to Microsoft-related software development, mobile development, database management, and web application programming. In addition to tutorials and how-tos that teach programmers how to code in Microsoft-related languages and frameworks like C# and .Net, we also publish articles on software development tools, the latest in developer news, and advice for project managers. Cloud services such as Microsoft Azure and database options including SQL Server and MSSQL are also frequently covered.

Property of TechnologyAdvice. © 2026 TechnologyAdvice. All Rights Reserved

Advertiser Disclosure: Some of the products that appear on this site are from companies from which TechnologyAdvice receives compensation. This compensation may impact how and where products appear on this site including, for example, the order in which they appear. TechnologyAdvice does not include all companies or all types of products available in the marketplace.