BeginBufferedPaint: It's not just for buffered painting any more

Raymond Chen

Raymond

I covered the BeginBufferedPaint function in

my 2008 PDC presentation
,
but one thing I didn’t mention is that the buffered paint functions
are very handy even if you have no intention of painting.

Since the buffered paint functions maintain a cache
(provided that you remembed to call
Buffered­Paint­Init),
you can use
Begin­Buffered­Paint to get a temporary bitmap
even if you have no intention of actually painting to the screen.
You might want a bitmap to do some off-screen composition,
or for some other temporary purpose,
in which case you can ask
Begin­Buffered­Paint to give
you a bitmap,
use the bitmap for whatever you like,
and then pass fUpdateTarget = FALSE when you call
End­Buffered­Paint
to say “Ha ha, just kidding.”

One thing to have to be aware of is that the bitmap provided
by Begin­Buffered­Paint
is not guaranteed to be exactly
the size you requested; it only promises that the bitmap will be
at least the size you requested.
Most of the time,
your code won’t care (there are just pixels out there that you aren’t
using),
but if you use the
Get­Buffered­Paint­Bits
function to obtain direct access to the bits,
don’t forget to take the stride into account.

Consider this artifical example of a program that uses
Create­DIB­Section to create a temporary 32bpp bitmap
for the purpose of updating a layered window.
Start with the

scratch program

and make these changes:

BOOL
OnCreate(HWND hwnd, LPCREATESTRUCT lpcs)
{
 BOOL fRc = FALSE;
 HDC hdcWin = GetDC(hwnd);
 if (hdcWin) {
  HDC hdcMem = CreateCompatibleDC(hdcWin);
  if (hdcMem) {
   const int cx = 200;
   const int cy = 200;
   RECT rc = { 0, 0, cx, cy };
   BITMAPINFO bmi = { 0 };
   bmi.bmiHeader.biSize = sizeof(bmi.bmiHeader);
   bmi.bmiHeader.biWidth = cx;
   bmi.bmiHeader.biHeight = cy;
   bmi.bmiHeader.biPlanes = 1;
   bmi.bmiHeader.biBitCount = 32;
   bmi.bmiHeader.biCompression = BI_RGB;
   RGBQUAD *prgbBits;
   HBITMAP hbm = CreateDIBSection(hdcWin, &bmi,
             DIB_RGB_COLORS, &reinterpret_cast<void*&>(prgbBits),
                                                        NULL, 0);
   if (hbm) {
    HBITMAP hbmPrev = SelectBitmap(hdcMem, hbm);
    // Draw a simple picture
    FillRect(hdcMem, &rc,
                     reinterpret_cast<HBRUSH>(COLOR_INFOBK + 1));
    rc.left = cx / 4;
    rc.right -= rc.left;
    rc.top = cy / 4;
    rc.bottom -= rc.top;
    FillRect(hdcMem, &rc,
                   reinterpret_cast<HBRUSH>(COLOR_INFOTEXT + 1));
    // Apply the alpha channel (and premultiply)
    for (int y = 0; y < cy; y++) {
     for (int x = 0; x < cx; x++) {
      RGBQUAD *prgb = &prgbBits[y * cx + x];
      BYTE bAlpha = static_cast<BYTE>(cx * x / cx);
      prgb->rgbRed = static_cast<BYTE>(prgb->rgbRed * bAlpha / 255);
      prgb->rgbBlue = static_cast<BYTE>(prgb->rgbBlue * bAlpha / 255);
      prgb->rgbGreen = static_cast<BYTE>(prgb->rgbGreen * bAlpha / 255);
      prgb->rgbReserved = bAlpha;
     }
    }
    // update the layered window
    POINT ptZero = { 0, 0 };
    SIZE siz = { cx, cy };
    BLENDFUNCTION bf =  { AC_SRC_OVER, 0, 255, AC_SRC_ALPHA };
    fRc = UpdateLayeredWindow(hwnd, NULL, &ptZero, &siz, hdcMem,
                              &ptZero, 0, &bf, ULW_ALPHA);
    SelectBitmap(hdcMem, hbmPrev);
    DeleteObject(hbm);
   }
   DeleteDC(hdcMem);
  }
  ReleaseDC(hwnd, hdcWin);
 }
 return fRc;
}

Pretty standard stuff.
But let’s convert this to use the buffered paint functions
to take advantage of the buffered paint bitmap cache.

BOOL
OnCreate(HWND hwnd, LPCREATESTRUCT lpcs)
{
 BOOL fRc = FALSE;
 HDC hdcWin = GetDC(hwnd);
 if (hdcWin) {
  HDC hdcMem;
  // HDC hdcMem = CreateCompatibleDC(hdcWin);
  // if (hdcMem) {
   const int cx = 200;
   const int cy = 200;
   RECT rc = { 0, 0, cx, cy };
   // BITMAPINFO bmi = { 0 };
   // bmi.bmiHeader.biSize = sizeof(bmi.bmiHeader);
   // bmi.bmiHeader.biWidth = cx;
   // bmi.bmiHeader.biHeight = cy;
   // bmi.bmiHeader.biPlanes = 1;
   // bmi.bmiHeader.biBitCount = 32;
   // bmi.bmiHeader.biCompression = BI_RGB;
   RGBQUAD *prgbBits;
   BP_PAINTPARAMS params = { sizeof(params), BPPF_NOCLIP };
   HPAINTBUFFER hbp = BeginBufferedPaint(hdcWin, &rc,
                              BPBF_TOPDOWNDIB, &params, &hdcMem);
   if (hbp) {
    int cxRow;
    if (SUCCEEDED(GetBufferedPaintBits(hpb, &prgbBits, &cxRow))) {
   // HBITMAP hbm = CreateDIBSection(hdcWin, &bmi,
   //        DIB_RGB_COLORS, &reinterpret_cast<void*&>(prgbBits),
   //                                                   NULL, 0);
   // if (hbm) {
    // HBITMAP hbmPrev = SelectBitmap(hdcMem, hbm);
    // Draw a simple picture
    FillRect(hdcMem, &rc,
                     reinterpret_cast<HBRUSH>(COLOR_INFOBK + 1));
    rc.left = cx / 4;
    rc.right -= rc.left;
    rc.top = cy / 4;
    rc.bottom -= rc.top;
    FillRect(hdcMem, &rc,
                   reinterpret_cast<HBRUSH>(COLOR_INFOTEXT + 1));
    // Apply the alpha channel (and premultiply)
    for (int y = 0; y < cy; y++) {
     for (int x = 0; x < cx; x++) {
      RGBQUAD *prgb = &prgbBits[y * cxRow + x];
      BYTE bAlpha = static_cast<BYTE>(cx * x / cx);
      prgb->rgbRed = static_cast<BYTE>(prgb->rgbRed * bAlpha / 255);
      prgb->rgbBlue = static_cast<BYTE>(prgb->rgbBlue * bAlpha / 255);
      prgb->rgbGreen = static_cast<BYTE>(prgb->rgbGreen * bAlpha / 255);
      prgb->rgbReserved = bAlpha;
     }
    }
    // update the layered window
    POINT ptZero = { 0, 0 };
    SIZE siz = { cx, cy };
    BLENDFUNCTION bf =  { AC_SRC_OVER, 0, 255, AC_SRC_ALPHA };
    fRc = UpdateLayeredWindow(hwnd, NULL, &ptZero, &siz, hdcMem,
                              &ptZero, 0, &bf, ULW_ALPHA);
    // SelectBitmap(hdcMem, hbmPrev);
    // DeleteObject(hbm);
   }
   EndBufferedPaint(hpb, FALSE);
   // DeleteDC(hdcMem);
  }
  ReleaseDC(hwnd, hdcWin);
 }
 return fRc;
}
// changes to WinMain
 if (SUCCEEDED(BufferedPaintInit())) {
 // if (SUCCEEDED(CoInitialize(NULL))) {/* In case we use COM */
  hwnd = CreateWindowEx(WS_EX_LAYERED,
  // hwnd = CreateWindow(
  ...
  BufferedPaintUnInit();
  // CoUninitialize();
  ...

We’re using the buffered paint API not for buffered painting
but just as a convenient way to get a bitmap and a DC at one shot.
It saves some typing (you don’t have to create the bitmap and the DC
and select the bitmap in and out),
and when you return the paint buffer to the cache, some other
window that calls Begin­Buffered­Paint may be able
to re-use that bitmap.

There are a few tricky parts here.
First, if you’re going to be accessing
the bits directly, you need to call
Get­Buffered­Paint­Bits
and use the cxRow to determine the bitmap stride.
Next, when we’re done, we pass FALSE to
End­Buffered­Paint to say,
“Yeah, um, thanks for the bitmap, but don’t Bit­Blt the
results back into the DC we passed to
Begin­Buffered­Paint.
Sorry for the confusion.”

A less obvious trick is that we used BPPF_NOCLIP
to get a full bitmap.
By default, Begin­Buffered­Paint
returns you a bitmap which
is clipped to the DC you pass as the first parameter.
This is an optimization to avoid allocating memory for pixels that
can’t be seen anyway when End­Buffered­Paint
goes to copy
the bits back to the original DC.
We don’t want this optimization, however, since we have no intention
of blitting the results back to the original DC.
The clip region of the original DC is irrelevant to us because
we just want a temporary bitmap for some internal calculations.

Anyway, there you have it, an example of using
Begin­Buffered­Paint to obtain a temporary bitmap.
It doesn’t win much in this example (since we call it only once,
at window creation time),
but if you have code which creates a lot of DIB sections for temporary
use, you can use this trick to take advantage of the buffered paint
cache and reduce the overhead of bitmap creation and deletion.

Pre-emptive snarky comment:
How dare you show us an alternative method that isn’t
available on Windows 2000
!”

Raymond Chen
Raymond Chen

Follow Raymond