Drawing a monochrome bitmap with transparency


Last time, I left you with a brief puzzle. Here are two approaches. I am not a GDI expert, so there may be even better solutions out there. To emphasize the transparency, I'll change the window background color to the application workspace color.

 BOOL WinRegisterClass(WNDCLASS *pwc)
 {
  pwc->hbrBackground = (HBRUSH)(COLOR_APPWORKSPACE + 1);
  return __super::WinRegisterClass(pwc);
 }

Method 1: A big MaskBlt.

void RootWindow::PaintContent(PAINTSTRUCT *pps)
{
 HDC hdcMem = CreateCompatibleDC(pps->hdc);
 if (hdcMem) {
  int cxCheck = GetSystemMetrics(SM_CXMENUCHECK);
  int cyCheck = GetSystemMetrics(SM_CYMENUCHECK);
  HBITMAP hbmMono = CreateBitmap(cxCheck, cyCheck, 1, 1, NULL);
  if (hbmMono) {
   HBITMAP hbmPrev = SelectBitmap(hdcMem, hbmMono);
   if (hbmPrev) {
    RECT rc = { 0, 0, cxCheck, cyCheck };
    DrawFrameControl(hdcMem, &rc, DFC_MENU, DFCS_MENUCHECK);
    COLORREF clrTextPrev = SetTextColor(pps->hdc,
                                     GetSysColor(COLOR_MENUTEXT));
    // COLORREF clrBkPrev = SetBkColor(pps->hdc,
    //                                  GetSysColor(COLOR_MENU));
    MaskBlt(pps->hdc, 0, 0, cxCheck, cyCheck,
           hdcMem, 0, 0, hbmMono, 0, 0
           MAKEROP4(0x00AA0029, SRCCOPY));
    // SetBkColor(pps->hdc, clrBkPrev);
    SetTextColor(pps->hdc, clrTextPrev);
    SelectBitmap(hdcMem, hbmPrev);
   }
   DeleteObject(hbmMono);
  }
  DeleteDC(hdcMem);
 }
}

This has the least amount of typing but feels like overkill to me, using a quaternary raster operation as if were a ternary, just because I didn't want to create a pattern brush. (The raster operation 0x00AA0029 is the NOP operator; it leaves the destination alone. I didn't have this memorized; I looked it up in the documentation.) The MAKEROP4 says that for each white pixel in the mask, do nothing (NOP), and for each black pixel, do a SRCCOPY.

Notice that the background color is never used (since it's supposed to be transparent); consequently, we can delete the code that sets and restores the DC's background color.

Method 2: The traditional two-step.

void RootWindow::PaintContent(PAINTSTRUCT *pps)
{
 HDC hdcMem = CreateCompatibleDC(pps->hdc);
 if (hdcMem) {
  int cxCheck = GetSystemMetrics(SM_CXMENUCHECK);
  int cyCheck = GetSystemMetrics(SM_CYMENUCHECK);
  HBITMAP hbmMono = CreateBitmap(cxCheck, cyCheck, 1, 1, NULL);
  if (hbmMono) {
   HBITMAP hbmPrev = SelectBitmap(hdcMem, hbmMono);
   if (hbmPrev) {
    RECT rc = { 0, 0, cxCheck, cyCheck };
    DrawFrameControl(hdcMem, &rc, DFC_MENU, DFCS_MENUCHECK);
    COLORREF clrTextPrev = SetTextColor(pps->hdc, RGB(0,0,0));
    COLORREF clrBkPrev = SetBkColor(pps->hdc, RGB(255,255,255));
    BitBlt(pps->hdc, cxCheck, 0, cxCheck, cyCheck,
           hdcMem, 0, 0, SRCAND);
    SetTextColor(pps->hdc, GetSysColor(COLOR_MENUTEXT));
    SetBkColor(pps->hdc, RGB(0,0,0));
    BitBlt(pps->hdc, cxCheck, 0, cxCheck, cyCheck,
           hdcMem, 0, 0, SRCPAINT);
    SetBkColor(pps->hdc, clrBkPrev);
    SetTextColor(pps->hdc, clrTextPrev);
    SelectBitmap(hdcMem, hbmPrev);
   }
   DeleteObject(hbmMono);
  }
  DeleteDC(hdcMem);
 }
}

This is the traditional two-step blit. The first erases the pixels that are about to be overwritten by setting the foreground to black and background to white, then using SRCAND. This has the effect of erasing all the foreground pixels to zero while leaving the background intact. The second blit does the same, but with SRCPAINT. This means that the background pixels need to be treated as black, so that when they are "or"d with the destination, the destination pixels are unchanged. The foreground pixels get the desired foreground color.

This method can be shortened by negating the first blit, reversing the sense of foreground and background, so that the color black doesn't have to move between the background color and the text color.

void RootWindow::PaintContent(PAINTSTRUCT *pps)
{
 HDC hdcMem = CreateCompatibleDC(pps->hdc);
 if (hdcMem) {
  int cxCheck = GetSystemMetrics(SM_CXMENUCHECK);
  int cyCheck = GetSystemMetrics(SM_CYMENUCHECK);
  HBITMAP hbmMono = CreateBitmap(cxCheck, cyCheck, 1, 1, NULL);
  if (hbmMono) {
   HBITMAP hbmPrev = SelectBitmap(hdcMem, hbmMono);
   if (hbmPrev) {
    RECT rc = { 0, 0, cxCheck, cyCheck };
    DrawFrameControl(hdcMem, &rc, DFC_MENU, DFCS_MENUCHECK);
    COLORREF clrTextPrev = SetTextColor(pps->hdc, RGB(255,255,255));
    COLORREF clrBkPrev = SetBkColor(pps->hdc, RGB(0,0,0));
    BitBlt(pps->hdc, cxCheck, 0, cxCheck, cyCheck,
           hdcMem, 0, 0, 0x00220326); // DSna
    SetTextColor(pps->hdc, GetSysColor(COLOR_MENUTEXT));
    BitBlt(pps->hdc, cxCheck, 0, cxCheck, cyCheck,
           hdcMem, 0, 0, SRCPAINT);
    SetBkColor(pps->hdc, clrBkPrev);
    SetTextColor(pps->hdc, clrTextPrev);
    SelectBitmap(hdcMem, hbmPrev);
   }
   DeleteObject(hbmMono);
  }
  DeleteDC(hdcMem);
 }
}

Whether this shortening is actually an overall improvement is difficult to tell. It's possible that some display drivers have a highly optimized SRCAND handler whereas they are less likely to have an optimized 0x00220326 handler.

(Exercise: Why can't you instead reverse the second blit, converting it to a MERGEPAINT?)

Comments (6)
  1. Moi says:

    Why is it that I have a hard time taking seriously a webpage (as linked in the article) whose filename is "pantdraw"? :-)

  2. Chump says:

    I’m scared to click the link in case I get busted by the "URL cops" in my company. 8))

  3. Mike Dimmick says:

    On CE (therefore with very limited font options) I needed to emulate a monospaced character cell display, but with colour – the font also needs to be user-modifiable (the platform emulated permitted this). The font is designed as a monochrome bitmap – black represents text, white is background.

    I used to have a horrible sequence of BitBlts with a temporary DC to do the composition in, for coloured (not black or white) text and/or backgrounds. Thanks to your article on ternary ROPs I managed to work out a single ROP code that would do most of the work in one go. I forget exactly what it is – I think the official RPN definition involves XORs, which I’d never have figured out myself. I’d always assumed that the only supported ROP3s were the ones listed in the BitBlt documentation.

    I’m still using the ‘special case’ code that does black-on-white and white-on-black (SRCCOPY and NOTSRCCOPY respectively). In those cases I don’t need to do a background fill first, nor select different brushes.

  4. D. Philippe says:

    Mike D: Not sure why your CE font options are so limited, unless you’re distributing to existing PocketPC devices. Using CE 4.2 (not even 5.0) we have the option of including any TTF font in our platform.

    Still, I’d be interested in knowing what ROP codes you used to paint a b/w modifiable font.

  5. Neil says:

    Mike D: Sounds like all you needed to do was to set the destination DC’s colours before using SRCCOPY.

  6. The text foreground and background colors play a role.

Comments are closed.

Skip to main content