Separating the metadata from the DIB pixels: Precalculating the BITMAPINFO


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.)

Comments (4)
  1. Nathan_works says:

    Should the pointer (red text) in PaintContent be "g_pvBits" and not "pvBits" ?

  2. Nick Tompson says:

    "I moved the blue code moved from PaintContent "

    I believe it should read "I moved the blue code from PaintContent" (without the second ‘moved’)

    ;)

  3. Tom says:

    @Nathan works:  I think you’re right about the g_pvBits.  Looking over the code, I can’t see any other reference to that variable.

    In addition, it looks like the reference to bmiGray in the PaintContent() function should also be g_bmiGray.  In all, I think these are just cut & paste errors from the code yesterday.

    I can’t get over the expression g_pvBits.bm = bm.bmBits.  I know it’s just an example and a warning is clearly expressed in the text about why it is bad, but that just looks evil.

  4. Joseph Koss says:

    SetDIBitsToDevice() truely is a nice function.

    I’ve used it for some animation stuff (eye candy) where I didnt really want (or need) to mess with any other rendering capabilities (software rendering straight to a simple pixel buffer)

    "I have a pointer to some pixel data, please put it on screen.. thanks"

    All rendering API’s should support this sort of functionality, but sadly most do not.

    ..its all fine and dandy until you work with a language that does not support direct pointer operations (ex, visual basic) and are forced to deal with making secondary copy operations with those API’s. The value of something like SetDIBitsToDevice really begins to show in these languages.

Comments are closed.