Display-optimal DIB section bitmaps

by Damon Chandler

In the article "Fast bitmap zooming and scrolling," I discussed the advantages of drawing only the required portions of a bitmap. That approach is half of the equation necessary to drawing bitmaps as fast as possible. In this article, I’ll discuss the other half.

Prevent format conversions

Recall that the BitBlt() GDI function is used to render a bitmap to a display device. Yet, contrary to its name, BitBlt() does not always perform a simple Bit-Block transfer of memory. If the color format of the display device is not the same as the color format of the bitmap, the BitBlt() function (and its close cousin StretchBlt()) will have to perform a potentially costly color format conversion.

Suppose, for example, that you use BitBlt() to display a 24 bits-per-pixel bitmap on a system whose display also uses 24 bits per pixel. In this case, because the color format of the bitmap is the same as that of the display device, the BitBlt() function will effectively perform a bit-for-bit copy from the memory associated with the bitmap to the memory associated with the screen. On the other hand, if the display device uses, say, 15 bits-per-pixel, BitBlt() will have to convert from 24 bpp to 15 bpp by discarding the three least-significant bits from each pixel. Depending on the size of the bitmap, this conversion can severely reduce the performance of the BitBlt() function.

Of course, there’s a tradeoff between the cost of the conversion and the cost of copying so many bytes. In some cases, it’s faster to convert a smaller block of memory than it is to just copy a larger block. In general, however, if you want to display a bitmap as fast as possible, you need to make sure a format conversion isn’t required.

Creating a device-optimal bitmap

As I mentioned in the previous articles on printing bitmaps, there are three main types of bitmaps: device-dependent bitmaps (DDBs), device-independent bitmaps (DIBs) and DIB section bitmaps. Because the device itself maintains device-dependent bitmaps, this variety of bitmap should theoretically be the fastest to render. Unfortunately, device-dependent bitmaps are severely limited in size, and they don’t provide direct access to their underlying pixels.

On the other hand, Windows maintains DIB section bitmaps. Because of this fact, not only can you access the pixels directly, you can also specify the color format of the DIB section bitmap. However, in order to display a DIB section bitmap as fast as possible, you must make sure that this format matches that of the display device. Here’s a function to do that:

unsigned char MakeBitmapOptimal(
   Graphics::TBitmap& Bitmap)
{
  // will hold a handle to the display-
  // optimal DIB section bitmap
  HBITMAP hDIBSection = NULL;
  // will hold the optimal color depth
  unsigned char opt_bpp = 0;

  // grab the dimensions of Bitmap and a
  // handle to its associated memory DC
  const SIZE SBmp =
    {Bitmap.Width, Bitmap.Height};
  const HDC hBmpDC =
    Bitmap.Canvas->Handle;
  
  // allocate memory for a BITMAPINFO
  // (this structure is a DIB w/o its
  // pixels; this structure will be
  // initialized with the optimal format)
  const std::size_t dib_size =
    sizeof(BITMAPINFOHEADER) +
    256 * sizeof(RGBQUAD);
  BITMAPINFO* pDIB =
    reinterpret_cast<BITMAPINFO*>(
      new unsigned char[dib_size]);

  // zero-out all fields and then
  // specify the header size
  memset(pDIB, 0, dib_size);
  pDIB->bmiHeader.biSize =
    sizeof(BITMAPINFOHEADER);

  // grab a handle to the screen's DC
  const HDC hScnDC = GetDC(NULL);

  // create a 1x1 DDB which we'll use to
  // determine the format of the device's
  // internal memory surface
  const HBITMAP hDDB =
    CreateCompatibleBitmap(hScnDC, 1, 1);

  // use the DDB and the GetDIBits()
  // function with a NULL lpvBits
  // parameter to query the format of
  // the device-managed surface
  bool got_ok = GetDIBits(hScnDC, hDDB, 
    0, 1, NULL, pDIB, DIB_RGB_COLORS);
  if (got_ok)
  {
    // get the optimal bit-field info
    // (for 15/16- or 32-bpp bitmaps)
    got_ok = GetDIBits(hScnDC, hDDB, 0,
      1, NULL, pDIB, DIB_RGB_COLORS);
  }
  // delete the DDB
  DeleteObject(hDDB);
  
  if (got_ok)
  {
    // store the optimal color depth
    opt_bpp = pDIB->bmiHeader.biBitCount;

    // set the proper dimensions; create
    // a top-down DIB section by setting
    // the biHeight data member negative
    pDIB->bmiHeader.biWidth = SBmp.cx;
    pDIB->bmiHeader.biHeight = -SBmp.cy;

    // create the optimal DIB section
    // bitmap based on the information
    // in pDIB (which the GetDIBits()
    // calls should have filled in)
    hDIBSection =
      CreateDIBSection(hScnDC, pDIB, 
        DIB_RGB_COLORS, NULL, NULL, 0);
  }
  // release the screen's DC
  ReleaseDC(NULL, hScnDC);

  // free the BITMAPINFO
  delete [] reinterpret_cast
    <unsigned char*>(pDIB);

  // copy (and convert) the pixels of
  // Bitmap to the optimal format DIB
  // section bitmap...
  if (hDIBSection != NULL)
  {
    HDC hMemDC = NULL;
    HBITMAP hOldBmp = NULL;
    bool blt_ok = false;
    try {
      // associate DIB section bitmap
      // with a memory DC (for drawing)
      hMemDC = CreateCompatibleDC(NULL);
      hOldBmp = static_cast<HBITMAP>(
        SelectObject(hMemDC,hDIBSection)
        );

      // sync. the color tables
      if (opt_bpp <= 8)
      {
        // get Bitmap's color table
        RGBQUAD rgbQ[256];
        UINT num_entries =
          GetDIBColorTable(
            hBmpDC, 0, 256, rgbQ);

        // if Bitmap has no color table,
        // let's use its Palette instead
        if (num_entries == 0)
        {
          // grab a handle to Bitmap's
          // logical palette
          const HPALETTE hPal =
            Bitmap.Palette;

          // get the number of Bitmap's
          // palette entries
          num_entries =
            GetPaletteEntries(
              hPal, 0, 0, NULL);              
          // create a LOGPALETTE buffer
          const std::size_t pal_size =
            sizeof(LOGPALETTE) +
            (num_entries - 1) *
            sizeof(PALETTEENTRY);
          LOGPALETTE* pLogPal =
            reinterpret_cast
              <LOGPALETTE*>(
              new unsigned char[pal_size]
              );
          // copy the palette data
          const UINT num_got =
            GetPaletteEntries(
              hPal, 0, num_entries,
              pLogPal->palPalEntry);
          for (UINT i=0; i<num_got; ++i)
          {
            rgbQ[i].rgbRed = pLogPal->
              palPalEntry[i].peRed;
            rgbQ[i].rgbGreen = pLogPal->
              palPalEntry[i].peGreen;
            rgbQ[i].rgbBlue = pLogPal->
              palPalEntry[i].peBlue;
          }

          // free the LOGPALETTE buffer
          delete [] reinterpret_cast
            <unsigned char*>(pLogPal);
        }
        if (num_entries > 0)
        {
          // copy the colors in rgbQ to
          // hDIBSection's color table
          SetDIBColorTable(hMemDC, 0, 
            num_entries, rgbQ);
        }
      }

      // copy and convert the pixels of
      // Bitmap to DIB section bitmap
      blt_ok = BitBlt(
        hMemDC, 0, 0, SBmp.cx, SBmp.cy,
        Bitmap.Canvas->Handle, 0, 0,
        SRCCOPY);

      // assign the optimal DIB section
      // bitmap to Bitmap (the TBitmap
      // object will take ownership)
      if (blt_ok)
      {
        Bitmap.Handle = hDIBSection;
      }
    }
    __finally {
      // clean up
      SelectObject(hMemDC, hOldBmp);
      DeleteDC(hMemDC);
      if (!blt_ok)
      {
        DeleteObject(hDIBSection);
      }
    }
  }
  // return the optimal color depth
  return opt_bpp;
}

This function is fairly well commented, so I won’t discuss its specific steps. The basic idea, however, is to use the GetDIBits() function with a display DDB to query the format of the display device. The GetDIBits() function will copy this information into a BITMAPINFO structure, which is then used with the CreateDIBSection() function to create the display-optimal DIB section bitmap.

Conclusion

There are a few things you should keep in mind when using the MakeBitmapOptimal() function. First, if the display device uses fewer bits per pixel than your bitmap does, there will be a loss of color information. There’s just no way to avoid this. The second point is a bit obvious, but I need to mention it anyway: If the MakeBitmapOptimal() function changes the color format of your bitmap, and you later need to access and work with the bitmap’s pixels, you’ll need to modify your code to work with the new format.

You can download the MakeBitmapOptimal() function along with a sample project that demonstrates its use from www.bridgespublishing.com.