Last time, we saw that you can use the SetDIBitsToDevice function to draw a DIB with an alternate color table without having to modify the HBITMAP. In that version of the function, we selected the HBITMAP into a device context in preparation for drawing from it, but in fact that step isn't necessary for drawing. It was merely necessary to get the original color table so we could build our grayscale color table. If you don't care what the original colors are, then you can skip that step. And even if you care what the old colors are, and if you assume that the colors don't change, then you only need to ask once.

To demonstrate, that all the work of building the BITMAPINFO structure could have been done ahead of time, let's use this alternate version of our program:

HBITMAP g_hbm;
struct BITMAPINFO256 {
 BITMAPINFOHEADER bmiHeader;
   RGBQUAD bmiColors[256];
} g_bmiGray;
void *g_pvBits;

BOOL
OnCreate(HWND hwnd, LPCREATESTRUCT lpcs)
{
 // change path as appropriate
 g_hbm = (HBITMAP)LoadImage(g_hinst,
                      TEXT("C:\\Windows\\Gone Fishing.bmp"),
                      IMAGE_BITMAP, 0, 0,
                      LR_CREATEDIBSECTION | LR_LOADFROMFILE);
 if (g_hbm) {
  BITMAP bm;
  if (GetObject(g_hbm, sizeof(bm), &bm) == sizeof(bm) &&
                bm.bmBits != NULL &&
                bm.bmPlanes * bm.bmBitsPixel <= 8) {
   ZeroMemory(&g_bmiGray, sizeof(g_bmiGray));
   HDC hdc = CreateCompatibleDC(NULL);
   if (hdc) {
    HBITMAP hbmPrev = SelectBitmap(hdc, g_hbm);
    UINT cColors = GetDIBColorTable(hdc, 0, 256, g_bmiGray.bmiColors);
    for (UINT iColor = 0; iColor < cColors; iColor++) {
     BYTE b = (BYTE)((30 * g_bmiGray.bmiColors[iColor].rgbRed +
                      59 * g_bmiGray.bmiColors[iColor].rgbGreen +
                      11 * g_bmiGray.bmiColors[iColor].rgbBlue) / 100);
     g_bmiGray.bmiColors[iColor].rgbRed   = b;
     g_bmiGray.bmiColors[iColor].rgbGreen = b;
     g_bmiGray.bmiColors[iColor].rgbBlue  = b;
    }
    g_bmiGray.bmiHeader.biSize        = sizeof(g_bmiGray.bmiHeader);
    g_bmiGray.bmiHeader.biWidth       = bm.bmWidth;
    g_bmiGray.bmiHeader.biHeight      = bm.bmHeight;
    g_bmiGray.bmiHeader.biPlanes      = bm.bmPlanes;
    g_bmiGray.bmiHeader.biBitCount    = bm.bmBitsPixel;
    g_bmiGray.bmiHeader.biCompression = BI_RGB;
    g_bmiGray.bmiHeader.biClrUsed     = cColors;
    g_pvBits                          = bm.bmBits;
    DeleteDC(hdc);
   }
 }
 return TRUE;
}

void
PaintContent(HWND hwnd, PAINTSTRUCT *pps)
{
 if (g_pvBits) {
    SetDIBitsToDevice(pps->hdc, 0, 0,
                  g_bmiGray.bmiHeader.biWidth,
                  g_bmiGray.bmiHeader.biHeight, 0, 0,
                  0, g_bmiGray.bmiHeader.biHeight,
                  g_pvBits,
                  (BITMAPINFO*)&g_bmiGray, DIB_RGB_COLORS);
 }
}

I moved the blue code from PaintContent to OnCreate to demonstrate that pretty much all of the work we used to do in PaintContent could have been done ahead of time. The only other thing we had to do was save the pointer to the bits so we could pass them to SetDIBitsToDevice. (Of course, that pointer becomes invalid once the controlling HBITMAP is destroyed, so be careful! In practice, you probably would be better off calling GetObject immediately before drawing to protect against the case that somebody deleted the bitmap out from under you.)

Next time, we'll look at another operation we can perform when we have a BITMAPINFO and a collection of pixels.

(Note that there are issues with this technique which will be taken up on Friday.)