Drawing a monochrome bitmap with transparency

Date:August 3, 2005 / year-entry #210
Tags:code
Orig Link:https://blogs.msdn.microsoft.com/oldnewthing/20050803-16/?p=34733
Comments:    6
Summary: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); }...

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.


*DISCLAIMER: I DO NOT OWN THIS CONTENT. If you are the owner and would like it removed, please contact me. The content herein is an archived reproduction of entries from Raymond Chen's "Old New Thing" Blog (most recent link is here). It may have slight formatting modifications for consistency and to improve readability.

WHY DID I DUPLICATE THIS CONTENT HERE? Let me first say this site has never had anything to sell and has never shown ads of any kind. I have nothing monetarily to gain by duplicating content here. Because I had made my own local copy of this content throughout the years, for ease of using tools like grep, I decided to put it online after I discovered some of the original content previously and publicly available, had disappeared approximately early to mid 2019. At the same time, I present the content in an easily accessible theme-agnostic way.

The information provided by Raymond's blog is, for all practical purposes, more authoritative on Windows Development than Microsoft's own MSDN documentation and should be considered supplemental reading to that documentation. The wealth of missing details provided by this blog that Microsoft could not or did not document about Windows over the years is vital enough, many would agree an online "backup" of these details is a necessary endeavor. Specifics include:

<-- Back to Old New Thing Archive Index