Get a site

VC++ 6.0 ebook chapter index
homepage aranna.altervista.org
Free counters!

A Library for DIBs

It is only now—after our long journey learning about GDI bitmap objects, the device-independent bitmap, the DIB section, and the Windows Palette Manager—that we're ready to devise some set of functions that help us in working with bitmaps.

The PACKEDIB files shown earlier illustrate one possible approach: A packed DIB in memory is represented solely by a pointer to it. All the information that a program needs about the DIB can be obtained by functions that access the header information structure. In practice, however, this method has serious performance problems when it comes to "get pixel" and "set pixel" routines. Image-processing tasks routinely require bitmap bits to be accessed, and these functions should ideally be as fast as possible.

A possible C++ solution involves creating a DIB class where a pointer to the packed DIB is just one of several member variables. Other member variables and member functions can help implement fast routines for obtaining and setting pixels in the DIB. However, since I indicated in the first chapter that you'd only need to know C for this particular book, the use of C++ will have to remain a solution for some other book.

Of course, just about anything that can be done in C++ can also be done in C. A good example of this are the multitude of Windows functions that use handles. What does an application program know about a handle other than the fact that it's a numeric value? It knows that the handle references a particular object and that functions for working with the object exist. Obviously, the operating system uses the handle to somehow reference internal information about the object. A handle could be as simple as a pointer to a structure.

For example, suppose there exists a collection of functions that use a handle called an HDIB. What's an HDIB? Well, it might be defined in a header file like so:

typedef void * HDIB ;

This definition answers the question "What's an HDIB?" with "None of your business!"

In reality, however, an HDIB might be a pointer to a structure that contains not only a pointer to a packed DIB but also some other information:

typedef struct
{
     BITMAPINFO * pPackedDib ;
     int          cx, cy, cBitsPerPixel, cBytesPerRow ;
     BYTE       * pBits ;
{
DIBSTRUCTURE, * PDIBSTRUCTURE ;

The other five fields of this structure contain information that is derivable from the packed DIB, of course, but the presence of these values in the structure allows them to be accessed more quickly. The various DIB library functions could work with this structure rather than the pPackedDib pointer. A DibGetPixelPointer function could be implemented like so:

BYTE * DibGetPixelPointer (HDIB hdib, int x, int y)
{
     PDIBSTRUCTURE pdib = hdib ;

     return pdib->pBits + y * pdib->cBytesPerRow + 
                             x * pdib->cBitsPerPixel / 8 ;
}

This is, of course, much faster than a "get pixel" routine that might be implemented in PACKEDIB.C.

While this approach is quite reasonable, I have decided to abandon the packed DIB and instead base my DIB library on the DIB section. This gives us virtually all of the flexibility involved with packed DIBs (that is, being able to manipulate DIB pixel bits in a somewhat device-independent manner) but is also more efficient when running under Windows NT.

The DIBSTRUCT Structure

The DIBHELP.C file—so named because it provides help for working with DIBs—is over a thousand lines long and will be shown shortly in several parts. But let's first take a close look at the structure that the DIBHELP functions work with. The structure is defined in DIBHELP.C like so:

typedef struct
{
     PBYTE    * ppRow ;       // array of row pointers
     int        iSignature ;  // = "Dib "
     HBITMAP    hBitmap ;     // handle returned from CreateDIBSection
     BYTE     * pBits ;       // pointer to bitmap bits
     DIBSECTION ds ;          // DIBSECTION structure
     int        iRShift[3] ;  // right-shift values for color masks
     int        iLShift[3] ;  // left-shift values for color masks
}
DIBSTRUCT, * PDIBSTRUCT ;

Let me skip the first field for now. There's a reason why it's the first field—it makes some macros easier—but it'll be easier to understand after I discuss the other fields first.

When this structure is first set up by one of the DIB creation functions in DIBHELP.C, the second field is set to the binary equivalent of the text string "Dib." This is used as a check of the validity of a pointer to the structure by some of the DIBHELP functions.

The third field—hBitmap—is the bitmap handle returned from the CreateDIBSection function. You'll recall that this handle can in many ways be used like the handles to GDI bitmap objects that we encountered in Chapter 14. However, the handle returned from CreateDIBSection references a bitmap that remains in a device-independent format until it is rendered on an output device by calls to BitBlt and StretchBlt.

The fourth field of DIBSTRUCT is a pointer to the bitmap bits. This is a value also set by the CreateDIBSection function. You'll recall that the operating system controls this memory block but that an application has access to it. The block is automatically freed when the bitmap handle is deleted.

The fifth field of DIBSTRUCT is a DIBSECTION structure. You'll recall that if you have a bitmap handle returned from CreateDIBSection, you can pass that to the GetObject function to obtain information about the bitmap in the DIBSECTION structure:

GetObject (hBitmap, sizeof (DIBSECTION), &ds) ;

As a reminder, the DIBSECTION structure is defined in WINGDI.H like so:

typedef struct tagDIBSECTION {
    BITMAP           dsBm ;
    BITMAPINFOHEADER dsBmih ;
    DWORD            dsBitfields[3] ;     // Color masks
    HANDLE           dshSection ;
    DWORD            dsOffset ;
}
DIBSECTION, * PDIBSECTION ;

The first field is the BITMAP structure that's used with CreateBitmapIndirect to create a bitmap object and used with GetObject to return information about a DDB. The second field is a BITMAPINFOHEADER structure. Regardless of the bitmap information structure passed to the CreateDIBSection function, the DIBSECTION structure will always have a BITMAPINFOHEADER structure and not, for example, a BITMAPCOREHEADER structure. This means that a lot of the functions in DIBHELP.C need not check for OS/2-compatible DIBs when accessing this structure.

You'll recall that for 16-bit and 32-bit DIBs, if the biCompression field of the BITMAPINFOHEADER structure is BI_BITFIELDS, then three mask values normally follow the information header structure. These mask values determine how to convert 16-bit and 32-bit pixel values to RGB colors. The masks are stored in the third field of the DIBSECTION structure.

The final two fields of the DIBSECTION structure refer to a DIB section created with a file-mapping object. DIBHELP does not use this feature of CreateDIBSection, so these fields can be ignored.

Finally, the last two fields of DIBSTRUCT store left and right shift values that are used with the color masks for 16-bit and 32-bit DIBs. These shift values were discussed in Chapter 15.

Let's go back to the first field of DIBSTRUCT. As we'll see, when a DIB is first created, this field is set to a pointer that references an array of pointers, each of which points to a row of pixels in the DIB. These pointers allow an even faster method to get at DIB pixel bits and are defined so that the DIB pixel bits can be referenced top row first. The last element of this array—referencing the bottom row of the DIB image—will usually be equal to the pBits field of DIBSTRUCT.

The Information Functions

DIBHELP.C begins by defining the DIBSTRUCT structure and then providing a collection of functions that let an application obtain information about the DIB section. The first part of DIBHELP.C is shown in Figure 16-20.

Figure 16-20. The first part of the DIBHELP.C file.

DIBHELP.C (first part)


/*------------------------------------------
   DIBHELP.C -- DIB Section Helper Routines 
                (c) Charles Petzold, 1998
  ------------------------------------------*/

#include <windows.h>
#include "dibhelp.h"

#define HDIB_SIGNATURE (* (int *) "Dib ")

typedef struct
{
     PBYTE    * ppRow ;            // must be first field for macros!
     int        iSignature ;
     HBITMAP    hBitmap ;
     BYTE     * pBits ;
     DIBSECTION ds ;
     int        iRShift[3] ;
     int        iLShift[3] ;
}
DIBSTRUCT, * PDIBSTRUCT ;

/*---------------------------------------------------------------
   DibIsValid:  Returns TRUE if hdib points to a valid DIBSTRUCT
  ---------------------------------------------------------------*/

BOOL DibIsValid (HDIB hdib)
{
     PDIBSTRUCT pdib = hdib ;

     if (pdib == NULL)
          return FALSE ;

     if (IsBadReadPtr (pdib, sizeof (DIBSTRUCT)))
          return FALSE ;

     if (pdib->iSignature != HDIB_SIGNATURE)
          return FALSE ;

     return TRUE ;
}

/*-----------------------------------------------------------------------
   DibBitmapHandle:  Returns the handle to the DIB section bitmap object
  -----------------------------------------------------------------------*/

HBITMAP DibBitmapHandle (HDIB hdib)
{
     if (!DibIsValid (hdib))
          return NULL ;
     
     return ((PDIBSTRUCT) hdib)->hBitmap ;
}

/*-------------------------------------------
   DibWidth:  Returns the bitmap pixel width
  -------------------------------------------*/

int DibWidth (HDIB hdib)
{
     if (!DibIsValid (hdib))
          return 0 ;
     
     return ((PDIBSTRUCT) hdib)->ds.dsBm.bmWidth ;
}

/*---------------------------------------------
   DibHeight:  Returns the bitmap pixel height
  ---------------------------------------------*/

int DibHeight (HDIB hdib)
{
     if (!DibIsValid (hdib))
          return 0 ; 
     
     return ((PDIBSTRUCT) hdib)->ds.dsBm.bmHeight ;
}

/*----------------------------------------------------
   DibBitCount:  Returns the number of bits per pixel
  ----------------------------------------------------*/

int DibBitCount (HDIB hdib)
{
     if (!DibIsValid (hdib))
          return 0 ;
     
     return ((PDIBSTRUCT) hdib)->ds.dsBm.bmBitsPixel ;
}

/*--------------------------------------------------------------
   DibRowLength:  Returns the number of bytes per row of pixels
  --------------------------------------------------------------*/

int DibRowLength (HDIB hdib)
{
     if (!DibIsValid (hdib))
          return 0 ;
     
     return 4 * ((DibWidth (hdib) * DibBitCount (hdib) + 31) / 32) ;
}

/*----------------------------------------------------------------
   DibNumColors:  Returns the number of colors in the color table
  ----------------------------------------------------------------*/

int DibNumColors (HDIB hdib)
{
     PDIBSTRUCT pdib = hdib ;

     if (!DibIsValid (hdib))
          return 0 ;

     if (pdib->ds.dsBmih.biClrUsed != 0)
     {
          return pdib->ds.dsBmih.biClrUsed ;
     }
     else if (DibBitCount (hdib) <= 8)
     {
          return 1 << DibBitCount (hdib) ;
     }
     return 0 ;
}

/*------------------------------------------
   DibMask:  Returns one of the color masks
  ------------------------------------------*/

DWORD DibMask (HDIB hdib, int i)
{
     PDIBSTRUCT pdib = hdib ;

     if (!DibIsValid (hdib) || i < 0 || i > 2)
          return 0 ;
     
     return pdib->ds.dsBitfields[i] ;
}

/*---------------------------------------------------
   DibRShift:  Returns one of the right-shift values
  ---------------------------------------------------*/

int DibRShift (HDIB hdib, int i)
{
     PDIBSTRUCT pdib = hdib ;

     if (!DibIsValid (hdib) || i < 0 || i > 2)
          return 0 ;
     
     return pdib->iRShift[i] ;
}

/*--------------------------------------------------
   DibLShift:  Returns one of the left-shift values
  --------------------------------------------------*/

int DibLShift (HDIB hdib, int i)
{
     PDIBSTRUCT pdib = hdib ;

     if (!DibIsValid (hdib) || i < 0 || i > 2)
          return 0 ;
     
     return pdib->iLShift[i] ;
}

/*---------------------------------------------------------------
   DibCompression:  Returns the value of the biCompression field
  ---------------------------------------------------------------*/
int DibCompression (HDIB hdib)
{
     if (!DibIsValid (hdib))
          return 0 ;

     return ((PDIBSTRUCT) hdib)->ds.dsBmih.biCompression ;
}

/*--------------------------------------------------------------
   DibIsAddressable:  Returns TRUE if the DIB is not compressed
  --------------------------------------------------------------*/

BOOL DibIsAddressable (HDIB hdib)
{
     int iCompression ;

     if (!DibIsValid (hdib))
          return FALSE ;

     iCompression = DibCompression (hdib) ;

     if (iCompression == BI_RGB || iCompression == BI_BITFIELDS)
         return TRUE ;

     return FALSE ;
}

/*---------------------------------------------------------------------------
   These functions return the sizes of various components of the DIB section
     AS THEY WOULD APPEAR in a packed DIB. These functions aid in converting
     the DIB section to a packed DIB and in saving DIB files.
  ---------------------------------------------------------------------------*/

DWORD DibInfoHeaderSize (HDIB hdib)
{
     if (!DibIsValid (hdib))
          return 0 ;

     return ((PDIBSTRUCT) hdib)->ds.dsBmih.biSize ;
}

DWORD DibMaskSize (HDIB hdib)
{
     PDIBSTRUCT pdib = hdib ;

     if (!DibIsValid (hdib))
          return 0 ;

     if (pdib->ds.dsBmih.biCompression == BI_BITFIELDS)
          return 3 * sizeof (DWORD) ;

     return 0 ;
}

DWORD DibColorSize (HDIB hdib)
{
     return DibNumColors (hdib) * sizeof (RGBQUAD) ;
} 

DWORD DibInfoSize (HDIB hdib)
{
     return DibInfoHeaderSize(hdib) + DibMaskSize(hdib) + DibColorSize(hdib) ;
}

DWORD DibBitsSize (HDIB hdib)
{
     PDIBSTRUCT pdib = hdib ;

     if (!DibIsValid (hdib))
          return 0 ;

     if (pdib->ds.dsBmih.biSizeImage != 0)
     {
          return pdib->ds.dsBmih.biSizeImage ;
     }
     return DibHeight (hdib) * DibRowLength (hdib) ;
}

DWORD DibTotalSize (HDIB hdib)
{
     return DibInfoSize (hdib) + DibBitsSize (hdib) ;
}

/*----------------------------------------------------------------------
   These functions return pointers to the various components of the DIB 
     section.
  ----------------------------------------------------------------------*/
BITMAPINFOHEADER * DibInfoHeaderPtr (HDIB hdib)
{
     if (!DibIsValid (hdib))
          return NULL ;
     
     return & (((PDIBSTRUCT) hdib)->ds.dsBmih) ;
}

DWORD * DibMaskPtr (HDIB hdib)
{
     PDIBSTRUCT pdib = hdib ;

     if (!DibIsValid (hdib))
          return 0 ;

     return pdib->ds.dsBitfields ;
}

void * DibBitsPtr (HDIB hdib)
{
     if (!DibIsValid (hdib))
          return NULL ;
     
     return ((PDIBSTRUCT) hdib)->pBits ;
}

/*------------------------------------------------------
   DibSetColor:  Obtains entry from the DIB color table
  ------------------------------------------------------*/

BOOL DibGetColor (HDIB hdib, int index, RGBQUAD * prgb)
{
     PDIBSTRUCT pdib = hdib ;
     HDC        hdcMem ;
     int        iReturn ;

     if (!DibIsValid (hdib))
          return 0 ;

     hdcMem = CreateCompatibleDC (NULL) ;
     SelectObject (hdcMem, pdib->hBitmap) ;
     iReturn = GetDIBColorTable (hdcMem, index, 1, prgb) ;
     DeleteDC (hdcMem) ;

     return iReturn ? TRUE : FALSE ;
}

/*----------------------------------------------------
   DibGetColor:  Sets an entry in the DIB color table
  ----------------------------------------------------*/
 
BOOL DibSetColor (HDIB hdib, int index, RGBQUAD * prgb)
{
     PDIBSTRUCT pdib = hdib ;
     HDC        hdcMem ;
     int        iReturn ;

     if (!DibIsValid (hdib))
          return 0 ;

     hdcMem = CreateCompatibleDC (NULL) ;
     SelectObject (hdcMem, pdib->hBitmap) ;
     iReturn = SetDIBColorTable (hdcMem, index, 1, prgb) ;
     DeleteDC (hdcMem) ;

     return iReturn ? TRUE : FALSE ;
}

Most of the functions in this part of DIBHELP.C are self-explanatory. The DibIsValid function helps keep the whole system fairly bulletproof. The other functions call DibIsValid before attempting to reference information in DIBSTRUCT. All these functions have a first, and usually only, parameter of HDIB, which (as we'll see shortly) is defined in DIBHELP.H as a void pointer. The functions can cast this parameter to a PDIBSTRUCT and then access the fields in the structure.

Note the DibIsAddressable function, which returns a BOOL value. This function could also be called the DibIsNotCompressed function. The return value indicates whether the individual pixels of the DIB can be addressed.

A collection of functions beginning with DibInfoHeaderSize obtain the sizes of various components of the DIB Section as they would appear in a packed DIB. As we shall see, these functions help in converting a DIB section to a packed DIB and in saving DIB files. These are followed by a collection of functions that obtain pointers to the various components of the DIB.

Although DIBHELP.C contains a function named DibInfoHeaderPtr that obtains a pointer to the BITMAPINFOHEADER structure, there is no function that obtains a pointer to the BITMAPINFO structure—that is, the information structure followed by the DIB color table. That's because when working with DIB sections, applications don't have direct access to a structure of this type. While the BITMAPINFOHEADER structure and the color masks are both available in the DIBSECTION structure, and the pointer to the pixel bits is returned from the CreateDIBSection function, the DIB color table is accessible only indirectly, by calling GetDIBColorTable and SetDIBColorTable. These functions are encapsulated in DIBHELP's DibGetColor and DibSetColor functions.

Later in DIBHELP.C, a file named DibCopyToInfo allocates a pointer to a BITMAPINFO structure and fills it with information, but that's not exactly the same as getting a pointer to an existing structure in memory.

Reading and Writing Pixels

One compelling advantage in maintaining a packed DIB or a DIB section by an application is being able to directly manipulate the pixel bits of the DIB. The second section of DIBHELP.C, shown in Figure 16-21, shows the functions provided for this purpose.

Figure 16-21. The second part of the DIBHELP.C file.

DIBHELP.C (second part)

/*-----------------------------------------------------------------
   DibPixelPtr:  Returns a pointer to the pixel at position (x, y)
  -----------------------------------------------------------------*/

BYTE * DibPixelPtr (HDIB hdib, int x, int y)
{
     if (!DibIsAddressable (hdib))
          return NULL ;

     if (x < 0 || x >= DibWidth (hdib) || y < 0 || y >= DibHeight (hdib))
          return NULL ;

     return (((PDIBSTRUCT) hdib)->ppRow)[y] + (x * DibBitCount (hdib) >> 3) ;
}

/*-----------------------------------------------
   DibGetPixel:  Obtains a pixel value at (x, y)
  -----------------------------------------------*/

DWORD DibGetPixel (HDIB hdib, int x, int y)
{
     PBYTE pPixel ;

     if (!(pPixel = DibPixelPtr (hdib, x, y)))
          return 0 ;

     switch (DibBitCount (hdib))
     {
     case  1:  return 0x01 & (* pPixel >> (7 - (x & 7))) ;
     case  4:  return 0x0F & (* pPixel >> (x & 1 ? 0 : 4)) ;
     case  8:  return * pPixel ;
     case 16:  return * (WORD *) pPixel ;
     case 24:  return 0x00FFFFFF & * (DWORD *) pPixel ; 
     case 32:  return * (DWORD *) pPixel ;
     }
     return 0 ;
}

/*--------------------------------------------
   DibSetPixel:  Sets a pixel value at (x, y)
  --------------------------------------------*/

BOOL DibSetPixel (HDIB hdib, int x, int y, DWORD dwPixel)
{
     PBYTE pPixel ;

     if (!(pPixel = DibPixelPtr (hdib, x, y)))
          return FALSE ;

     switch (DibBitCount (hdib))
     {
     case  1:  * pPixel &= ~(1     << (7 - (x & 7))) ;
               * pPixel |= dwPixel << (7 - (x & 7)) ;
               break ;

     case  4:  * pPixel &= 0x0F    << (x & 1 ? 4 : 0) ;
               * pPixel |= dwPixel << (x & 1 ? 0 : 4) ;
               break ;

     case  8:  * pPixel = (BYTE) dwPixel ;
               break ;

     case 16:  * (WORD *) pPixel = (WORD) dwPixel ;
               break ;

     case 24:  * (RGBTRIPLE *) pPixel = * (RGBTRIPLE *) &dwPixel ; 
               break ;

     case 32:  * (DWORD *) pPixel = dwPixel ;
               break ;
     default:
          return FALSE ;
     }
     return TRUE ;
}

/*------------------------------------------------------
   DibGetPixelColor:  Obtains the pixel color at (x, y)
  ------------------------------------------------------*/

BOOL DibGetPixelColor (HDIB hdib, int x, int y, RGBQUAD * prgb)
{
     DWORD      dwPixel ;
     int        iBitCount ;
     PDIBSTRUCT pdib = hdib ;

          // Get bit count; also use this as a validity check

     if (0 == (iBitCount = DibBitCount (hdib)))
          return FALSE ;

          // Get the pixel value

     dwPixel = DibGetPixel (hdib, x, y) ;

          // If the bit-count is 8 or less, index the color table

     if (iBitCount <= 8)
          return DibGetColor (hdib, (int) dwPixel, prgb) ;

          // If the bit-count is 24, just use the pixel

     else if (iBitCount == 24)
     {
          * (RGBTRIPLE *) prgb = * (RGBTRIPLE *) & dwPixel ;
          prgb->rgbReserved = 0 ;
     }

          // If the bit-count is 32 and the biCompression field is BI_RGB,
          //   just use the pixel

     else if (iBitCount == 32 && 
               pdib->ds.dsBmih.biCompression == BI_RGB)

     {
          * prgb = * (RGBQUAD *) & dwPixel ;
     }

          // Otherwise, use the mask and shift values
          //   (for best performance, don't use DibMask and DibShift functions)
     else
     {
          prgb->rgbRed   = (BYTE) (((pdib->ds.dsBitfields[0] & dwPixel) 
                                   >> pdib->iRShift[0]) << pdib->iLShift[0]) ;

          prgb->rgbGreen = (BYTE) (((pdib->ds.dsBitfields[1] & dwPixel) 
                                   >> pdib->iRShift[1]) << pdib->iLShift[1]) ;

          prgb->rgbBlue  = (BYTE) (((pdib->ds.dsBitfields[2] & dwPixel) 
                                   >> pdib->iRShift[2]) << pdib->iLShift[2]) ;
     }
     return TRUE ;
}

/*---------------------------------------------------
   DibSetPixelColor:  Sets the pixel color at (x, y) 
  ---------------------------------------------------*/

BOOL DibSetPixelColor (HDIB hdib, int x, int y, RGBQUAD * prgb)
{
     DWORD      dwPixel ;
     int        iBitCount ;
     PDIBSTRUCT pdib = hdib ;

          // Don't do this function for DIBs with color tables

     iBitCount = DibBitCount (hdib) ;

     if (iBitCount <= 8)
          return FALSE ;

          // The rest is just the opposite of DibGetPixelColor

     else if (iBitCount == 24)
     {
          * (RGBTRIPLE *) & dwPixel = * (RGBTRIPLE *) prgb ;
          dwPixel &= 0x00FFFFFF ;
     }
     else if (iBitCount == 32 &&
               pdib->ds.dsBmih.biCompression == BI_RGB)
     {
          * (RGBQUAD *) & dwPixel = * prgb ;
     }

     else
     {
          dwPixel  = (((DWORD) prgb->rgbRed >> pdib->iLShift[0]) 
                         << pdib->iRShift[0]) ; 

          dwPixel |= (((DWORD) prgb->rgbGreen >> pdib->iLShift[1])
                         << pdib->iRShift[1]) ; 

          dwPixel |= (((DWORD) prgb->rgbBlue >> pdib->iLShift[2])
                         << pdib->iRShift[2]) ; 
     }

     DibSetPixel (hdib, x, y, dwPixel) ;
     return TRUE ;
}

This section of DIBHELP.C begins with a DibPixelPtr function that obtains a pointer to the byte where a particular pixel is stored (or partially stored). Recall that the ppRow field of the DIBSTRUCT structure is a pointer to the addresses of the rows of pixels in the DIB, beginning with the top row. Thus,

((PDIBSTRUCT) hdib)->pprow)[0]

is a pointer to the leftmost pixel of the top row of the DIB and

(((PDIBSTRUCT) hdib)->ppRow)[y] + (x * DibBitCount (hdib) >> 3)

is a pointer to the pixel at position (x,y). Notice that the function returns a NULL value if the DIB is not addressable (that is, if it's compressed) or if the x and y parameters to the function are negative or reference an area outside the DIB. This checking slows the function (and any function that relies on DibPixelPtr), but I'll describe some faster routines soon.

The DibGetPixel and DibSetPixel functions that follow in the file make use of DibPixelPtr. For 8-bit, 16-bit, 24-bit, and 32-bit DIBs, these functions need only cast the pointer to the proper data size and access the pixel value. For 1-bit and 4-bit DIBs, some masking and shifting is required.

The DibGetColor function obtains the pixel color as an RGBQUAD structure. For 1bit, 4-bit, and 8-bit DIBs, this involves using the pixel value to get a color from the DIB color table. For 16-bit, 24-bit, and 32-bit DIBs, in general the pixel value must be masked and shifted to derive an RGB color. The DibSetPixel function is opposite, and it allows setting a pixel value from an RGBQUAD structure. This function is defined only for 16-bit, 24-bit, and 32-bit DIBs.

Creating and Converting

The third and final section of DIBHELP, shown in Figure 16-22, shows how DIB sections are created and how they can be converted to and from packed DIBs.

Figure 16-22. The third and final part of the DIBHELP.C file.

DIBHELP.C (third part)

/*--------------------------------------------------------------
   Calculating shift values from color masks is required by the 
     DibCreateFromInfo function.
  --------------------------------------------------------------*/

static int MaskToRShift (DWORD dwMask)
{
     int iShift ;

     if (dwMask == 0)
          return 0 ;

     for (iShift = 0 ; !(dwMask & 1) ; iShift++)
          dwMask >>= 1 ;

     return iShift ;
}

static int MaskToLShift (DWORD dwMask)
{
     int iShift ;

     if (dwMask == 0)
          return 0 ;

     while (!(dwMask & 1))
          dwMask >>= 1 ;

     for (iShift = 0 ; dwMask & 1 ; iShift++)
          dwMask >>= 1 ;

     return 8 - iShift ;
}

/*-------------------------------------------------------------------------
   DibCreateFromInfo: All DIB creation functions ultimately call this one.
     This function is responsible for calling CreateDIBSection, allocating
     memory for DIBSTRUCT, and setting up the row pointer.
  -------------------------------------------------------------------------*/

HDIB DibCreateFromInfo (BITMAPINFO * pbmi)
{
     BYTE      * pBits ;
     DIBSTRUCT * pdib ;
     HBITMAP     hBitmap ;
     int         i, iRowLength, cy, y ;
     
     hBitmap = CreateDIBSection (NULL, pbmi, DIB_RGB_COLORS, &pBits, NULL, 0) ;

     if (hBitmap == NULL)
          return NULL ;

     if (NULL == (pdib = malloc (sizeof (DIBSTRUCT))))
     {
          DeleteObject (hBitmap) ;
          return NULL ;
     }

     pdib->iSignature = HDIB_SIGNATURE ;
     pdib->hBitmap    = hBitmap ;
     pdib->pBits      = pBits ;

     GetObject (hBitmap, sizeof (DIBSECTION), &pdib->ds) ;

          // Notice that we can now use the DIB information functions 
          //   defined above.

          // If the compression is BI_BITFIELDS, calculate shifts from masks

     if (DibCompression (pdib) == BI_BITFIELDS)
     {
          for (i = 0 ; i < 3 ; i++)
          {
               pdib->iLShift[i] = MaskToLShift (pdib->ds.dsBitfields[i]) ;
               pdib->iRShift[i] = MaskToRShift (pdib->ds.dsBitfields[i]) ;
          }
     }

          // If the compression is BI_RGB, but bit-count is 16 or 32,
          //   set the bitfields and the masks

     else if (DibCompression (pdib) == BI_RGB)
     {
          if (DibBitCount (pdib) == 16)
          {
               pdib->ds.dsBitfields[0] = 0x00007C00 ;
               pdib->ds.dsBitfields[1] = 0x000003E0 ;
               pdib->ds.dsBitfields[2] = 0x0000001F ;

               pdib->iRShift[0] = 10 ;
               pdib->iRShift[1] =  5 ;
               pdib->iRShift[2] =  0 ; 

               pdib->iLShift[0] =  3 ;
               pdib->iLShift[1] =  3 ;
               pdib->iLShift[2] =  3 ;
          }
          else if (DibBitCount (pdib) == 24 || DibBitCount (pdib) == 32)
          {
               pdib->ds.dsBitfields[0] = 0x00FF0000 ;
               pdib->ds.dsBitfields[1] = 0x0000FF00 ;
               pdib->ds.dsBitfields[2] = 0x000000FF ;

               pdib->iRShift[0] = 16 ;
               pdib->iRShift[1] =  8 ;
               pdib->iRShift[2] =  0 ; 

               pdib->iLShift[0] =  0 ;
               pdib->iLShift[1] =  0 ;
               pdib->iLShift[2] =  0 ;
          }
     }
          // Allocate an array of pointers to each row in the DIB

     cy = DibHeight (pdib) ;

     if (NULL == (pdib->ppRow = malloc (cy * sizeof (BYTE *))))
     {
          free (pdib) ;
          DeleteObject (hBitmap) ;
          return NULL ;
     }

          // Initialize them.

     iRowLength = DibRowLength (pdib) ; 
     if (pbmi->bmiHeader.biHeight > 0)       // ie, bottom up
     {
          for (y = 0 ; y < cy ; y++)
               pdib->ppRow[y] = pBits + (cy - y - 1) * iRowLength ;
     }
     else                                    // top down
     {
          for (y = 0 ; y < cy ; y++)
               pdib->ppRow[y] = pBits + y * iRowLength ;
     }
     return pdib ;
}

/*--------------------------------------------------
   DibDelete:  Frees all memory for the DIB section
  --------------------------------------------------*/

BOOL DibDelete (HDIB hdib)
{
     DIBSTRUCT * pdib = hdib ;

     if (!DibIsValid (hdib))
          return FALSE ;

     free (pdib->ppRow) ;
     DeleteObject (pdib->hBitmap) ;
     free (pdib) ;
     return TRUE ;
}

/*----------------------------------------------------
   DibCreate: Creates an HDIB from explicit arguments
  ----------------------------------------------------*/ 

HDIB DibCreate (int cx, int cy, int cBits, int cColors)
{
     BITMAPINFO * pbmi ;
     DWORD        dwInfoSize ;
     HDIB         hDib ;
     int          cEntries ;

     if (cx <= 0 || cy <= 0 || 
         ((cBits !=  1) && (cBits !=  4) && (cBits !=  8) && 
          (cBits != 16) && (cBits != 24) && (cBits != 32)))

     {
          return NULL ;
     }

     if (cColors != 0)
          cEntries = cColors ;
     else if (cBits <= 8)
          cEntries = 1 << cBits ;

     dwInfoSize = sizeof (BITMAPINFOHEADER) + (cEntries - 1) * sizeof (RGBQUAD);

     if (NULL == (pbmi = malloc (dwInfoSize)))
     {
          return NULL ;
     }

     ZeroMemory (pbmi, dwInfoSize) ;

     pbmi->bmiHeader.biSize          = sizeof (BITMAPINFOHEADER) ;
     pbmi->bmiHeader.biWidth         = cx ;
     pbmi->bmiHeader.biHeight        = cy ;
     pbmi->bmiHeader.biPlanes        = 1 ;
     pbmi->bmiHeader.biBitCount      = cBits ;
     pbmi->bmiHeader.biCompression   = BI_RGB ;
     pbmi->bmiHeader.biSizeImage     = 0 ;
     pbmi->bmiHeader.biXPelsPerMeter = 0 ;
     pbmi->bmiHeader.biYPelsPerMeter = 0 ;
     pbmi->bmiHeader.biClrUsed       = cColors ;
     pbmi->bmiHeader.biClrImportant  = 0 ;

     hDib = DibCreateFromInfo (pbmi) ;
     free (pbmi) ;

     return hDib ;
}

/*--------------------------------------------------
   DibCopyToInfo:  Builds BITMAPINFO structure.
                   Used by DibCopy and DibCopyToDdb
  --------------------------------------------------*/

static BITMAPINFO * DibCopyToInfo (HDIB hdib)
{
     BITMAPINFO * pbmi ;
     int          i, iNumColors ;
     RGBQUAD    * prgb ;
     if (!DibIsValid (hdib))
          return NULL ;

          // Allocate the memory

     if (NULL == (pbmi = malloc (DibInfoSize (hdib))))
          return NULL ;

          // Copy the information header

     CopyMemory (pbmi, DibInfoHeaderPtr (hdib), 
                                        sizeof (BITMAPINFOHEADER));
          
          // Copy the possible color masks

     prgb = (RGBQUAD *) ((BYTE *) pbmi + sizeof (BITMAPINFOHEADER)) ;

     if (DibMaskSize (hdib))
     {
          CopyMemory (prgb, DibMaskPtr (hdib), 3 * sizeof (DWORD)) ;

          prgb = (RGBQUAD *) ((BYTE *) prgb + 3 * sizeof (DWORD)) ;
     }
          // Copy the color table

     iNumColors = DibNumColors (hdib) ;

     for (i = 0 ; i < iNumColors ; i++)
          DibGetColor (hdib, i, prgb + i) ;

     return pbmi ;
}

/*-------------------------------------------------------------------
   DibCopy:  Creates a new DIB section from an existing DIB section,
     possibly swapping the DIB width and height.
  -------------------------------------------------------------------*/

HDIB DibCopy (HDIB hdibSrc, BOOL fRotate)
{
     BITMAPINFO * pbmi ;
     BYTE       * pBitsSrc, * pBitsDst ;
     HDIB         hdibDst ;

     if (!DibIsValid (hdibSrc))
          return NULL ;

     if (NULL == (pbmi = DibCopyToInfo (hdibSrc)))
          return NULL ;

     if (fRotate)
     {
          pbmi->bmiHeader.biWidth = DibHeight (hdibSrc) ;
          pbmi->bmiHeader.biHeight = DibWidth (hdibSrc) ;
     }

     hdibDst = DibCreateFromInfo (pbmi) ;
     free (pbmi) ;

     if (hdibDst == NULL)
          return NULL ;

          // Copy the bits

     if (!fRotate)
     {
          pBitsSrc = DibBitsPtr (hdibSrc) ;
          pBitsDst = DibBitsPtr (hdibDst) ;

          CopyMemory (pBitsDst, pBitsSrc, DibBitsSize (hdibSrc)) ;
     }
     return hdibDst ;
}

/*----------------------------------------------------------------------
   DibCopyToPackedDib is generally used for saving DIBs and for 
     transferring DIBs to the clipboard. In the second case, the second 
     argument should be set to TRUE so that the memory is allocated
     with the GMEM_SHARE flag.
  ----------------------------------------------------------------------*/

BITMAPINFO * DibCopyToPackedDib (HDIB hdib, BOOL fUseGlobal)
{
     BITMAPINFO * pPackedDib ;
     BYTE       * pBits ;
     DWORD        dwDibSize ;
     HDC          hdcMem ;
     HGLOBAL      hGlobal ;
     int          iNumColors ;
     PDIBSTRUCT   pdib = hdib ;
     RGBQUAD    * prgb ;

     if (!DibIsValid (hdib))
          return NULL ;
          // Allocate memory for packed DIB

     dwDibSize = DibTotalSize (hdib) ;

     if (fUseGlobal)
     {
          hGlobal = GlobalAlloc (GHND | GMEM_SHARE, dwDibSize) ;
          pPackedDib = GlobalLock (hGlobal) ;
     }
     else
     {
          pPackedDib = malloc (dwDibSize) ;
     }

     if (pPackedDib == NULL) 
          return NULL ;

          // Copy the information header

     CopyMemory (pPackedDib, &pdib->ds.dsBmih, sizeof (BITMAPINFOHEADER)) ;

     prgb = (RGBQUAD *) ((BYTE *) pPackedDib + sizeof (BITMAPINFOHEADER)) ;

          // Copy the possible color masks

     if (pdib->ds.dsBmih.biCompression == BI_BITFIELDS)
     {
          CopyMemory (prgb, pdib->ds.dsBitfields, 3 * sizeof (DWORD)) ;

          prgb = (RGBQUAD *) ((BYTE *) prgb + 3 * sizeof (DWORD)) ;
     }
          // Copy the color table

     if (iNumColors = DibNumColors (hdib))
     {
          hdcMem = CreateCompatibleDC (NULL) ;
          SelectObject (hdcMem, pdib->hBitmap) ;
          GetDIBColorTable (hdcMem, 0, iNumColors, prgb) ;
          DeleteDC (hdcMem) ;
     }

     pBits = (BYTE *) (prgb + iNumColors) ;

          // Copy the bits

     CopyMemory (pBits, pdib->pBits, DibBitsSize (pdib)) ;

          // If last argument is TRUE, unlock global memory block and 
          //   cast it to pointer in preparation for return

     if (fUseGlobal)
     {
          GlobalUnlock (hGlobal) ;
          pPackedDib = (BITMAPINFO *) hGlobal ;     
     }
     return pPackedDib ;
}

/*------------------------------------------------------------------
   DibCopyFromPackedDib is generally used for pasting DIBs from the 
     clipboard.
  ------------------------------------------------------------------*/

HDIB DibCopyFromPackedDib (BITMAPINFO * pPackedDib)
{
     BYTE     * pBits ;     
     DWORD      dwInfoSize, dwMaskSize, dwColorSize ;
     int        iBitCount ;
     PDIBSTRUCT pdib ;

          // Get the size of the information header and do validity check
     
     dwInfoSize = pPackedDib->bmiHeader.biSize ;

     if (dwInfoSize != sizeof (BITMAPCOREHEADER) &&
         dwInfoSize != sizeof (BITMAPINFOHEADER) &&
         dwInfoSize != sizeof (BITMAPV4HEADER) &&
         dwInfoSize != sizeof (BITMAPV5HEADER))
     {
          return NULL ;
     }
          // Get the possible size of the color masks

     if (dwInfoSize == sizeof (BITMAPINFOHEADER) &&
          pPackedDib->bmiHeader.biCompression == BI_BITFIELDS)
     {
          dwMaskSize = 3 * sizeof (DWORD) ;
     }
     else
     {
          dwMaskSize = 0 ;
     }
          // Get the size of the color table
     if (dwInfoSize == sizeof (BITMAPCOREHEADER))
     {
          iBitCount = ((BITMAPCOREHEADER *) pPackedDib)->bcBitCount ;

          if (iBitCount <= 8)
          {
               dwColorSize = (1 << iBitCount) * sizeof (RGBTRIPLE) ;
          }
          else
               dwColorSize = 0 ;
     }
     else           // all non-OS/2 compatible DIBs
     {
          if (pPackedDib->bmiHeader.biClrUsed > 0)
          {
               dwColorSize = pPackedDib->bmiHeader.biClrUsed * sizeof (RGBQUAD);
          }
          else if (pPackedDib->bmiHeader.biBitCount <= 8)
          {
               dwColorSize = (1 << pPackedDib->bmiHeader.biBitCount) * 
                                                       sizeof (RGBQUAD) ;
          }
          else
          {
               dwColorSize = 0 ;
          }
     }
          // Finally, get the pointer to the bits in the packed DIB

     pBits = (BYTE *) pPackedDib + dwInfoSize + dwMaskSize + dwColorSize ;

          // Create the HDIB from the packed-DIB pointer

     pdib = DibCreateFromInfo (pPackedDib) ;

          // Copy the pixel bits

     CopyMemory (pdib->pBits, pBits, DibBitsSize (pdib)) ;

     return pdib ;
}

/*-----------------------------------------------------
   DibFileLoad:  Creates a DIB section from a DIB file
  -----------------------------------------------------*/

HDIB DibFileLoad (const TCHAR * szFileName)
{
     BITMAPFILEHEADER bmfh ;
     BITMAPINFO     * pbmi ;
     BOOL             bSuccess ;
     DWORD            dwInfoSize, dwBitsSize, dwBytesRead ;
     HANDLE           hFile ;
     HDIB             hDib ;

          // Open the file: read access, prohibit write access

     hFile = CreateFile (szFileName, GENERIC_READ, FILE_SHARE_READ, NULL, 
                         OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL) ;

     if (hFile == INVALID_HANDLE_VALUE)
          return NULL ;

          // Read in the BITMAPFILEHEADER

     bSuccess = ReadFile (hFile, &bmfh, sizeof (BITMAPFILEHEADER), 
                          &dwBytesRead, NULL) ;

     if (!bSuccess || (dwBytesRead != sizeof (BITMAPFILEHEADER))         
                   || (bmfh.bfType != * (WORD *) "BM"))
     {
          CloseHandle (hFile) ;
          return NULL ;
     }
          // Allocate memory for the information structure & read it in

     dwInfoSize = bmfh.bfOffBits - sizeof (BITMAPFILEHEADER) ;

     if (NULL == (pbmi = malloc (dwInfoSize)))
     {
          CloseHandle (hFile) ;
          return NULL ;
     }

     bSuccess = ReadFile (hFile, pbmi, dwInfoSize, &dwBytesRead, NULL) ;

     if (!bSuccess || (dwBytesRead != dwInfoSize))
     {
          CloseHandle (hFile) ;
          free (pbmi) ;
          return NULL ;
     }
          // Create the DIB

     hDib = DibCreateFromInfo (pbmi) ;
     free (pbmi) ;

     if (hDib == NULL)
     {
          CloseHandle (hFile) ;
          return NULL ;
     }
          // Read in the bits

     dwBitsSize = bmfh.bfSize - bmfh.bfOffBits ;

     bSuccess = ReadFile (hFile, ((PDIBSTRUCT) hDib)->pBits, 
                          dwBitsSize, &dwBytesRead, NULL) ;
     CloseHandle (hFile) ;

     if (!bSuccess || (dwBytesRead != dwBitsSize))
     {
          DibDelete (hDib) ;
          return NULL ;
     }
     return hDib ;
}

/*---------------------------------------------
   DibFileSave:  Saves a DIB section to a file
  ---------------------------------------------*/

BOOL DibFileSave (HDIB hdib, const TCHAR * szFileName)
{
     BITMAPFILEHEADER bmfh ;
     BITMAPINFO     * pbmi ;
     BOOL             bSuccess ;
     DWORD            dwTotalSize, dwBytesWritten ;
     HANDLE           hFile ;

     hFile = CreateFile (szFileName, GENERIC_WRITE, 0, NULL,
                         CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL) ;

     if (hFile == INVALID_HANDLE_VALUE)
          return FALSE ;

     dwTotalSize  = DibTotalSize (hdib) ;
     bmfh.bfType      = * (WORD *) "BM" ;
     bmfh.bfSize      = sizeof (BITMAPFILEHEADER) + dwTotalSize ;
     bmfh.bfReserved1 = 0 ;
     bmfh.bfReserved2 = 0 ;
     bmfh.bfOffBits   = bmfh.bfSize - DibBitsSize (hdib) ;

          // Write the BITMAPFILEHEADER

     bSuccess = WriteFile (hFile, &bmfh, sizeof (BITMAPFILEHEADER), 
                           &dwBytesWritten, NULL) ;

     if (!bSuccess || (dwBytesWritten != sizeof (BITMAPFILEHEADER)))
     {
          CloseHandle (hFile) ;
          DeleteFile (szFileName) ;
          return FALSE ;
     }
          // Get entire DIB in packed-DIB format

     if (NULL == (pbmi = DibCopyToPackedDib (hdib, FALSE)))
     {
          CloseHandle (hFile) ;
          DeleteFile (szFileName) ;
          return FALSE ;
     }
          // Write out the packed DIB

     bSuccess = WriteFile (hFile, pbmi, dwTotalSize, &dwBytesWritten, NULL) ;
     CloseHandle (hFile) ;
     free (pbmi) ;

     if (!bSuccess || (dwBytesWritten != dwTotalSize))
     {
          DeleteFile (szFileName) ;
          return FALSE ;
     }
     return TRUE ;
}

/*---------------------------------------------------
   DibCopyToDdb:  For more efficient screen displays
  ---------------------------------------------------*/
HBITMAP DibCopyToDdb (HDIB hdib, HWND hwnd, HPALETTE hPalette)
{
     BITMAPINFO * pbmi ;
     HBITMAP      hBitmap ;
     HDC          hdc ;

     if (!DibIsValid (hdib))
          return NULL ;

     if (NULL == (pbmi = DibCopyToInfo (hdib)))
          return NULL ;

     hdc = GetDC (hwnd) ;

     if (hPalette)
     {
          SelectPalette (hdc, hPalette, FALSE) ;
          RealizePalette (hdc) ;
     }
     
     hBitmap = CreateDIBitmap (hdc, DibInfoHeaderPtr (hdib), CBM_INIT,
                               DibBitsPtr (hdib), pbmi, DIB_RGB_COLORS) ;

     ReleaseDC (hwnd, hdc) ;
     free (pbmi) ;

     return hBitmap ;
}

This part of the DIBHELP.C file begins with two little functions that derive left-shift and right-shift values from color masks for 16-bit and 32-bit DIBs. These functions were described in the Color Masking section in Chapter 15.

The DibCreateFromInfo function is the only function in DIBHELP that calls CreateDIBSection and allocates memory for the DIBSTRUCT structure. All other creation and copy functions go through this function. The single parameter to DibCreateFromInfo is a pointer to a BITMAPINFO structure. The color table of this structure must exist, but it doesn't necessarily have to be filled with valid values. After calling CreateDIBSection, the function initializes all the fields of the DIBSTRUCT structure. Notice that when setting the values of the ppRow field of the DIBSTRUCT structure (the pointers to the DIB row addresses), separate logic exists for bottom-up and top-down DIBs. The first element of ppRow is always the top row of the DIB.

DibDelete deletes the bitmap created in DibCreateFromInfo and also frees the memory allocated in that function.

DibCreate is probably a more likely function than DibCreateFromInfo to be called from application programs. The first three arguments provide the pixel width and height and the number of bits per pixel. The last argument can be set to 0 for a color table of default size or to a nonzero value to indicate a smaller color table than would otherwise be implied by the bit count.

The DibCopy function creates a new DIB section from an existing DIB section. It uses the DibCreateInfo function that allocates memory for a BITMAPINFO structure and puts all the data into it. A BOOL argument to the DibCopy function indicates whether the DIB width and height are to be switched around when creating the new DIB section. We'll see a use for this later.

The DibCopyToPackedDib and DibCopyFromPackedDib functions are generally used in conjunction with passing DIBs through the clipboard. The DibFileLoad function creates a DIB section from a DIB file; DibFileSave functions saves to a DIB file.

Finally, the DibCopyToDdb function creates a GDI bitmap object from a DIB. Notice that the function requires handles to the current palette and the program's window. The program's window handle is used for getting a device context into which the palette is selected and realized. Only then can the function make a call to CreateDIBitmap. This was demonstrated in the SHOWDIB7 program earlier in this chapter.

The DIBHELP Header File and Macros

The DIBHELP.H header file is shown in Figure 16-23.

Figure 16-23. The DIBHELP.H file.

DIBHELP.H

/*-------------------------------------
   DIBHELP.H header file for DIBHELP.C
  -------------------------------------*/

typedef void * HDIB ;

     // Functions in DIBHELP.C

BOOL DibIsValid (HDIB hdib) ;
HBITMAP DibBitmapHandle (HDIB hdib) ;
int DibWidth (HDIB hdib) ;
int DibHeight (HDIB hdib) ;
int DibBitCount (HDIB hdib) ;
int DibRowLength (HDIB hdib) ;
int DibNumColors (HDIB hdib) ;
DWORD DibMask (HDIB hdib, int i) ;
int DibRShift (HDIB hdib, int i) ;
int DibLShift (HDIB hdib, int i) ;
int DibCompression (HDIB hdib) ;
BOOL DibIsAddressable (HDIB hdib) ;
DWORD DibInfoHeaderSize (HDIB hdib) ;
DWORD DibMaskSize (HDIB hdib) ;
DWORD DibColorSize (HDIB hdib) ;
DWORD DibInfoSize (HDIB hdib) ;
DWORD DibBitsSize (HDIB hdib) ;
DWORD DibTotalSize (HDIB hdib) ;
BITMAPINFOHEADER * DibInfoHeaderPtr (HDIB hdib) ;
DWORD * DibMaskPtr (HDIB hdib) ;
void * DibBitsPtr (HDIB hdib) ;
BOOL DibGetColor (HDIB hdib, int index, RGBQUAD * prgb) ;
BOOL DibSetColor (HDIB hdib, int index, RGBQUAD * prgb) ;
BYTE * DibPixelPtr (HDIB hdib, int x, int y) ;
DWORD DibGetPixel (HDIB hdib, int x, int y) ;
BOOL DibSetPixel (HDIB hdib, int x, int y, DWORD dwPixel) ;
BOOL DibGetPixelColor (HDIB hdib, int x, int y, RGBQUAD * prgb) ;
BOOL DibSetPixelColor (HDIB hdib, int x, int y, RGBQUAD * prgb) ;
HDIB DibCreateFromInfo (BITMAPINFO * pbmi) ;
BOOL DibDelete (HDIB hdib) ;
HDIB DibCreate (int cx, int cy, int cBits, int cColors) ;
HDIB DibCopy (HDIB hdibSrc, BOOL fRotate) ;
BITMAPINFO * DibCopyToPackedDib (HDIB hdib, BOOL fUseGlobal) ;
HDIB DibCopyFromPackedDib (BITMAPINFO * pPackedDib) ;
HDIB DibFileLoad (const TCHAR * szFileName) ;
BOOL DibFileSave (HDIB hdib, const TCHAR * szFileName) ;
HBITMAP DibCopyToDdb (HDIB hdib, HWND hwnd, HPALETTE hPalette) ;
HDIB DibCreateFromDdb (HBITMAP hBitmap) ;

/*-----------------------------------------------
   Quickie no-bounds-checked pixel gets and sets
  -----------------------------------------------*/

#define DibPixelPtr1(hdib, x, y)  (((* (PBYTE **) hdib) [y]) + ((x) >> 3))
#define DibPixelPtr4(hdib, x, y)  (((* (PBYTE **) hdib) [y]) + ((x) >> 1))
#define DibPixelPtr8(hdib, x, y)  (((* (PBYTE **) hdib) [y]) +  (x)      )
#define DibPixelPtr16(hdib, x, y)  \
                        ((WORD *) (((* (PBYTE **) hdib) [y]) +  (x) *  2))

#define DibPixelPtr24(hdib, x, y)  \
                   ((RGBTRIPLE *) (((* (PBYTE **) hdib) [y]) +  (x) *  3))

#define DibPixelPtr32(hdib, x, y)  \
                       ((DWORD *) (((* (PBYTE **) hdib) [y]) +  (x) *  4))

#define DibGetPixel1(hdib, x, y)   \
               (0x01 & (* DibPixelPtr1 (hdib, x, y) >> (7 - ((x) & 7))))

#define DibGetPixel4(hdib, x, y)   \
               (0x0F & (* DibPixelPtr4 (hdib, x, y) >> ((x) & 1 ? 0 : 4)))

#define DibGetPixel8(hdib, x, y)     (* DibPixelPtr8  (hdib, x, y))
#define DibGetPixel16(hdib, x, y)    (* DibPixelPtr16 (hdib, x, y))
#define DibGetPixel24(hdib, x, y)    (* DibPixelPtr24 (hdib, x, y))
#define DibGetPixel32(hdib, x, y)    (* DibPixelPtr32(hdib, x, y))

#define DibSetPixel1(hdib, x, y, p)                                        \
          ((* DibPixelPtr1 (hdib, x, y) &= ~( 1  << (7 - ((x) & 7)))),     \
           (* DibPixelPtr1 (hdib, x, y) |=  ((p) << (7 - ((x) & 7)))))

#define DibSetPixel4(hdib, x, y, p)                                        \
          ((* DibPixelPtr4 (hdib, x, y) &= (0x0F << ((x) & 1 ? 4 : 0))),   \
           (* DibPixelPtr4 (hdib, x, y) |= ((p)  << ((x) & 1 ? 0 : 4))))

#define DibSetPixel8(hdib, x, y, p)  (* DibPixelPtr8 (hdib, x, y) = p)
#define DibSetPixel16(hdib, x, y, p) (* DibPixelPtr16 (hdib, x, y) = p)
#define DibSetPixel24(hdib, x, y, p) (* DibPixelPtr24 (hdib, x, y) = p)
#define DibSetPixel32(hdib, x, y, p) (* DibPixelPtr32 (hdib, x, y) = p)

This header file defines the HDIB handle as a void pointer. An application really shouldn't know about the internal structure of the structure to which HDIB points. The header file also includes declarations of all the functions in DIBHELP.C. And then there are the macros—the very special macros.

If you look back at the DibPixelPtr, DibGetPixel, and DibSetPixel functions in DIBHELP.C and try to improve their performance, you'll see a couple of possible solutions. First, you can remove all the bulletproofing and trust that an application will not call the function with invalid arguments. You can also remove some of the function calls, such as DibBitCount, and obtain that information directly by using the pointer to the DIBSTRUCT structure instead.

A less obvious way to improve performance is to do away with all the logic involving the number of bits per pixel and have separate functions for each type of DIB—for example, DibGetPixel1, DibGetPixel4, DibGetPixel8, and so forth. The next optimization is to remove the function call entirely and incorporate the logic in inline functions or macros.

DIBHELP.H takes the macro approach. It provides three sets of macros based on the DibPixelPtr, DibGetPixel, and DibSetPixel functions. These macros are all specific to a particular bit count.

The DIBBLE Program

The DIBBLE program shown in Figure 16-24 puts the DIBHELP functions and macros to work. Although DIBBLE is the longest program in this book, it is really only a crude sampler of some jobs that might be found in simple digital image-processing programs. One obvious improvement to DIBBLE would be to convert it to a multiple document interface (MDI), but we won't learn how to do that until Chapter 19.

Figure 16-24. The DIBBLE program.

DIBBLE.C

/*----------------------------------------
   DIBBLE.C -- Bitmap and Palette Program
               (c) Charles Petzold, 1998
  ----------------------------------------*/

#include <windows.h>
#include "dibhelp.h"
#include "dibpal.h"
#include "dibconv.h"
#include "resource.h"

#define WM_USER_SETSCROLLS    (WM_USER + 1)
#define WM_USER_DELETEDIB     (WM_USER + 2)
#define WM_USER_DELETEPAL     (WM_USER + 3)
#define WM_USER_CREATEPAL     (WM_USER + 4)

LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;

TCHAR szAppName[] = TEXT ("Dibble") ;

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
                    PSTR szCmdLine, int iCmdShow)
{
     HACCEL   hAccel ;
     HWND     hwnd ;
     MSG      msg ;
     WNDCLASS wndclass ;

     wndclass.style         = CS_HREDRAW | CS_VREDRAW ;
     wndclass.lpfnWndProc   = WndProc ;
     wndclass.cbClsExtra    = 0 ;
     wndclass.cbWndExtra    = 0 ;
     wndclass.hInstance     = hInstance ;
     wndclass.hIcon         = LoadIcon (NULL, IDI_APPLICATION) ;
     wndclass.hCursor       = LoadCursor (NULL, IDC_ARROW) ;
     wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ;
     wndclass.lpszMenuName  = szAppName ;
     wndclass.lpszClassName = szAppName ;

     if (!RegisterClass (&wndclass))
     {
          MessageBox (NULL, TEXT ("This program requires Windows NT!"), 
                      szAppName, MB_ICONERROR) ;
          return 0 ;
     }

     hwnd = CreateWindow (szAppName, szAppName,
                          WS_OVERLAPPEDWINDOW | WM_VSCROLL | WM_HSCROLL,
                          CW_USEDEFAULT, CW_USEDEFAULT,
                          CW_USEDEFAULT, CW_USEDEFAULT, 
                          NULL, NULL, hInstance, NULL) ;

     ShowWindow (hwnd, iCmdShow) ;
     UpdateWindow (hwnd) ;

     hAccel = LoadAccelerators (hInstance, szAppName) ;

     while (GetMessage (&msg, NULL, 0, 0))
     {
          if (!TranslateAccelerator (hwnd, hAccel, &msg))
          {
               TranslateMessage (&msg) ;
               DispatchMessage (&msg) ;
          }
     }
     return msg.wParam ;
}

/*-------------------------------------------------------------
   DisplayDib: Displays or prints DIB actual size or stretched 
               depending on menu selection
  -------------------------------------------------------------*/

int DisplayDib (HDC hdc, HBITMAP hBitmap, int x, int y, 
                int cxClient, int cyClient, 
                WORD wShow, BOOL fHalftonePalette)
{
     BITMAP bitmap ;
     HDC    hdcMem ; 
     int    cxBitmap, cyBitmap, iReturn ;
     GetObject (hBitmap, sizeof (BITMAP), &bitmap) ;
     cxBitmap = bitmap.bmWidth ;
     cyBitmap = bitmap.bmHeight ;

     SaveDC (hdc) ;

     if (fHalftonePalette)
          SetStretchBltMode (hdc, HALFTONE) ;
     else
          SetStretchBltMode (hdc, COLORONCOLOR) ;

     hdcMem = CreateCompatibleDC (hdc) ;
     SelectObject (hdcMem, hBitmap) ;

     switch (wShow)
     {
     case IDM_SHOW_NORMAL:
          if (fHalftonePalette)
               iReturn = StretchBlt (hdc,    0, 0, 
                                             min (cxClient, cxBitmap - x), 
                                             min (cyClient, cyBitmap - y), 
                                     hdcMem, x, y, 
                                             min (cxClient, cxBitmap - x), 
                                             min (cyClient, cyBitmap - y), 
                                     SRCCOPY);
          else
               iReturn = BitBlt (hdc,    0, 0, 
                                         min (cxClient, cxBitmap - x), 
                                         min (cyClient, cyBitmap - y),
                                 hdcMem, x, y, SRCCOPY) ;
          break ;
               
     case IDM_SHOW_CENTER:
          if (fHalftonePalette)
               iReturn = StretchBlt (hdc, (cxClient - cxBitmap) / 2,
                                          (cyClient - cyBitmap) / 2, 
                                          cxBitmap, cyBitmap,
                                     hdcMem, 0, 0, cxBitmap, cyBitmap, SRCCOPY);
          else
               iReturn = BitBlt (hdc, (cxClient - cxBitmap) / 2,
                                      (cyClient - cyBitmap) / 2, 
                                      cxBitmap, cyBitmap,
                                 hdcMem, 0, 0, SRCCOPY) ;
          break ;

     case IDM_SHOW_STRETCH:
          iReturn = StretchBlt (hdc,    0, 0, cxClient, cyClient, 
                                hdcMem, 0, 0, cxBitmap, cyBitmap, SRCCOPY) ;
          break ;

     case IDM_SHOW_ISOSTRETCH:
          SetMapMode (hdc, MM_ISOTROPIC) ;
          SetWindowExtEx (hdc, cxBitmap, cyBitmap, NULL) ;
          SetViewportExtEx (hdc, cxClient, cyClient, NULL) ;
          SetWindowOrgEx (hdc, cxBitmap / 2, cyBitmap / 2, NULL) ;
          SetViewportOrgEx (hdc, cxClient / 2, cyClient / 2, NULL) ;

          iReturn = StretchBlt (hdc,    0, 0, cxBitmap, cyBitmap, 
                                hdcMem, 0, 0, cxBitmap, cyBitmap, SRCCOPY) ;
          break ;
     }
     DeleteDC (hdcMem) ;
     RestoreDC (hdc, -1) ;
     return iReturn ;
}

/*--------------------------------------------------------------------
   DibFlipHorizontal: Calls non-optimized DibSetPixel and DibGetPixel
  --------------------------------------------------------------------*/

HDIB DibFlipHorizontal (HDIB hdibSrc)
{
     HDIB hdibDst ;
     int  cx, cy, x, y ;

     if (!DibIsAddressable (hdibSrc))
          return NULL ;

     if (NULL == (hdibDst = DibCopy (hdibSrc, FALSE)))
          return NULL ;

     cx = DibWidth  (hdibSrc) ;
     cy = DibHeight (hdibSrc) ;
     
     for (x = 0 ; x < cx ; x++)
     for (y = 0 ; y < cy ; y++)
     {
          DibSetPixel (hdibDst, x, cy - 1 - y, DibGetPixel (hdibSrc, x, y)) ;
     }

     return hdibDst ;
}

/*---------------------------------------------------------------
   DibRotateRight: Calls optimized DibSetPixelx and DibGetPixelx
  ---------------------------------------------------------------*/

HDIB DibRotateRight (HDIB hdibSrc)
{
     HDIB hdibDst ;
     int  cx, cy, x, y ;

     if (!DibIsAddressable (hdibSrc))
          return NULL ;

     if (NULL == (hdibDst = DibCopy (hdibSrc, TRUE)))
          return NULL ;

     cx = DibWidth (hdibSrc) ;
     cy = DibHeight (hdibSrc) ;

     switch (DibBitCount (hdibSrc))
     {
     case  1:  
          for (x = 0 ; x < cx ; x++)
          for (y = 0 ; y < cy ; y++)
               DibSetPixel1 (hdibDst, cy - y - 1, x, 
                    DibGetPixel1 (hdibSrc, x, y)) ;
          break ;

     case  4:  
          for (x = 0 ; x < cx ; x++)
          for (y = 0 ; y < cy ; y++)
               DibSetPixel4 (hdibDst, cy - y - 1, x, 
                    DibGetPixel4 (hdibSrc, x, y)) ;
          break ;

     case  8:
          for (x = 0 ; x < cx ; x++)
          for (y = 0 ; y < cy ; y++)
               DibSetPixel8 (hdibDst, cy - y - 1, x, 
                    DibGetPixel8 (hdibSrc, x, y)) ;
          break ;

     case 16:  
          for (x = 0 ; x < cx ; x++)
          for (y = 0 ; y < cy ; y++)
               DibSetPixel16 (hdibDst, cy - y - 1, x, 
                    DibGetPixel16 (hdibSrc, x, y)) ;
          break ;

     case 24:
          for (x = 0 ; x < cx ; x++)
          for (y = 0 ; y < cy ; y++)
               DibSetPixel24 (hdibDst, cy - y - 1, x, 
                    DibGetPixel24 (hdibSrc, x, y)) ;
          break ;

     case 32:  
          for (x = 0 ; x < cx ; x++)
          for (y = 0 ; y < cy ; y++)
               DibSetPixel32 (hdibDst, cy - y - 1, x, 
                    DibGetPixel32 (hdibSrc, x, y)) ;
          break ;
     }
     return hdibDst ;
}

/*----------------------------------------------------------
   PaletteMenu: Uncheck and check menu item on palette menu
  ----------------------------------------------------------*/

void PaletteMenu (HMENU hMenu, WORD wItemNew)
{
     static WORD wItem = IDM_PAL_NONE ;

     CheckMenuItem (hMenu, wItem, MF_UNCHECKED) ;
     wItem = wItemNew ;
     CheckMenuItem (hMenu, wItem, MF_CHECKED) ;
}

LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
     static BOOL         fHalftonePalette ;
     static DOCINFO      di = { sizeof (DOCINFO), TEXT ("Dibble: Printing") } ;
     static HBITMAP      hBitmap ;
     static HDIB         hdib ;
     static HMENU        hMenu ;
     static HPALETTE     hPalette ;
     static int          cxClient, cyClient, iVscroll, iHscroll ;
     static OPENFILENAME ofn ;
     static PRINTDLG     printdlg = { sizeof (PRINTDLG) } ;
     static TCHAR        szFileName [MAX_PATH], szTitleName [MAX_PATH] ;
     static TCHAR        szFilter[] = TEXT ("Bitmap Files (*.BMP)\0*.bmp\0")
                                      TEXT ("All Files (*.*)\0*.*\0\0") ;
     static TCHAR      * szCompression[] = { 
                           TEXT ("BI_RGB"), TEXT ("BI_RLE8"), TEXT ("BI_RLE4"), 
                           TEXT ("BI_BITFIELDS"), TEXT ("Unknown") } ;
     static WORD         wShow = IDM_SHOW_NORMAL ;
     BOOL                fSuccess ;
     BYTE              * pGlobal ;
     HDC                 hdc, hdcPrn ;
     HGLOBAL             hGlobal ;
     HDIB                hdibNew ;
     int                 iEnable, cxPage, cyPage, iConvert ;
     PAINTSTRUCT         ps ;
     SCROLLINFO          si ;
     TCHAR               szBuffer [256] ;

     switch (message)
     {
     case WM_CREATE:
         
               // Save the menu handle in a static variable

          hMenu = GetMenu (hwnd) ;

               // Initialize the OPENFILENAME structure for the File Open
               //   and File Save dialog boxes.

          ofn.lStructSize       = sizeof (OPENFILENAME) ;
          ofn.hwndOwner         = hwnd ;
          ofn.hInstance         = NULL ;
          ofn.lpstrFilter       = szFilter ;
          ofn.lpstrCustomFilter = NULL ;
          ofn.nMaxCustFilter    = 0 ;
          ofn.nFilterIndex      = 0 ;
          ofn.lpstrFile         = szFileName ;
          ofn.nMaxFile          = MAX_PATH ;
          ofn.lpstrFileTitle    = szTitleName ;
          ofn.nMaxFileTitle     = MAX_PATH ;
          ofn.lpstrInitialDir   = NULL ;
          ofn.lpstrTitle        = NULL ;
          ofn.Flags             = OFN_OVERWRITEPROMPT ;
          ofn.nFileOffset       = 0 ;
          ofn.nFileExtension    = 0 ;
          ofn.lpstrDefExt       = TEXT ("bmp") ;
          ofn.lCustData         = 0 ;
          ofn.lpfnHook          = NULL ;
          ofn.lpTemplateName    = NULL ;
          return 0 ;

     case WM_DISPLAYCHANGE:
          SendMessage (hwnd, WM_USER_DELETEPAL, 0, 0) ;
          SendMessage (hwnd, WM_USER_CREATEPAL, TRUE, 0) ;
          return 0 ;

     case WM_SIZE:
               // Save the client area width and height in static variables.

          cxClient = LOWORD (lParam) ;
          cyClient = HIWORD (lParam) ;

          wParam = FALSE ;
                                             // fall through

               // WM_USER_SETSCROLLS:  Programmer-defined Message!
               // Set the scroll bars. If the display mode is not normal,
               //   make them invisible. If wParam is TRUE, reset the 
               //   scroll bar position.

     case WM_USER_SETSCROLLS:
          if (hdib == NULL || wShow != IDM_SHOW_NORMAL)
          {
               si.cbSize = sizeof (SCROLLINFO) ;
               si.fMask  = SIF_RANGE ;
               si.nMin   = 0 ;
               si.nMax   = 0 ;
               SetScrollInfo (hwnd, SB_VERT, &si, TRUE) ;
               SetScrollInfo (hwnd, SB_HORZ, &si, TRUE) ;
          }
          else
          {
                    // First the vertical scroll

               si.cbSize = sizeof (SCROLLINFO) ;
               si.fMask  = SIF_ALL ;

               GetScrollInfo (hwnd, SB_VERT, &si) ;
               si.nMin  = 0 ;
               si.nMax  = DibHeight (hdib) ;
               si.nPage = cyClient ;
               if ((BOOL) wParam)
                    si.nPos = 0 ;

               SetScrollInfo (hwnd, SB_VERT, &si, TRUE) ;
               GetScrollInfo (hwnd, SB_VERT, &si) ;

               iVscroll = si.nPos ;

                    // Then the horizontal scroll

               GetScrollInfo (hwnd, SB_HORZ, &si) ;
               si.nMin  = 0 ;
               si.nMax  = DibWidth (hdib) ;
               si.nPage = cxClient ;
          
               if ((BOOL) wParam)
                    si.nPos = 0 ;

               SetScrollInfo (hwnd, SB_HORZ, &si, TRUE) ;
               GetScrollInfo (hwnd, SB_HORZ, &si) ;

               iHscroll = si.nPos ;
          }
          return 0 ;

          // WM_VSCROLL: Vertically scroll the DIB

     case WM_VSCROLL:
          si.cbSize = sizeof (SCROLLINFO) ;
          si.fMask  = SIF_ALL ;
          GetScrollInfo (hwnd, SB_VERT, &si) ;
          
          iVscroll = si.nPos ;

          switch (LOWORD (wParam))
          {
          case SB_LINEUP:      si.nPos -= 1 ;             break ;
          case SB_LINEDOWN:    si.nPos += 1 ;             break ;
          case SB_PAGEUP:      si.nPos -= si.nPage ;      break ;
          case SB_PAGEDOWN:    si.nPos += si.nPage ;      break ;
          case SB_THUMBTRACK:  si.nPos  = si.nTrackPos ;  break ;
          default:                                        break ;
          }

          si.fMask = SIF_POS ;
          SetScrollInfo (hwnd, SB_VERT, &si, TRUE) ;
          GetScrollInfo (hwnd, SB_VERT, &si) ;

          if (si.nPos != iVscroll)
          {
               ScrollWindow (hwnd, 0, iVscroll - si.nPos, NULL, NULL) ;
               iVscroll = si.nPos ;
               UpdateWindow (hwnd) ;
          }
          return 0 ;

          // WM_HSCROLL: Horizontally scroll the DIB

     case WM_HSCROLL:
          si.cbSize = sizeof (SCROLLINFO) ;
          si.fMask  = SIF_ALL ;
          GetScrollInfo (hwnd, SB_HORZ, &si) ;
          
          iHscroll = si.nPos ;

          switch (LOWORD (wParam))
          {
          case SB_LINELEFT:    si.nPos -= 1 ;             break ;
          case SB_LINERIGHT:   si.nPos += 1 ;             break ;
          case SB_PAGELEFT:    si.nPos -= si.nPage ;      break ;
          case SB_PAGERIGHT:   si.nPos += si.nPage ;      break ;
          case SB_THUMBTRACK:  si.nPos  = si.nTrackPos ;  break ;
          default:                                        break ;
          }

          si.fMask = SIF_POS ;
          SetScrollInfo (hwnd, SB_HORZ, &si, TRUE) ;
          GetScrollInfo (hwnd, SB_HORZ, &si) ;

          if (si.nPos != iHscroll)
          {
               ScrollWindow (hwnd, iHscroll - si.nPos, 0, NULL, NULL) ;
               iHscroll = si.nPos ;
               UpdateWindow (hwnd) ;
          }
          return 0 ;

          // WM_INITMENUPOPUP:  Enable or Gray menu items
     case WM_INITMENUPOPUP:
          if (hdib)
               iEnable = MF_ENABLED ;
          else
               iEnable = MF_GRAYED ;

          EnableMenuItem (hMenu, IDM_FILE_SAVE,       iEnable) ;
          EnableMenuItem (hMenu, IDM_FILE_PRINT,      iEnable) ;
          EnableMenuItem (hMenu, IDM_FILE_PROPERTIES, iEnable) ;
          EnableMenuItem (hMenu, IDM_EDIT_CUT,        iEnable) ;
          EnableMenuItem (hMenu, IDM_EDIT_COPY,       iEnable) ;
          EnableMenuItem (hMenu, IDM_EDIT_DELETE,     iEnable) ;

          if (DibIsAddressable (hdib))
               iEnable = MF_ENABLED ;
          else
               iEnable = MF_GRAYED ;

          EnableMenuItem (hMenu, IDM_EDIT_ROTATE,    iEnable) ;
          EnableMenuItem (hMenu, IDM_EDIT_FLIP,      iEnable) ;
          EnableMenuItem (hMenu, IDM_CONVERT_01,     iEnable) ;
          EnableMenuItem (hMenu, IDM_CONVERT_04,     iEnable) ;
          EnableMenuItem (hMenu, IDM_CONVERT_08,     iEnable) ;
          EnableMenuItem (hMenu, IDM_CONVERT_16,     iEnable) ;
          EnableMenuItem (hMenu, IDM_CONVERT_24,     iEnable) ;
          EnableMenuItem (hMenu, IDM_CONVERT_32,     iEnable) ;

          switch (DibBitCount (hdib))
          {
          case  1: EnableMenuItem (hMenu, IDM_CONVERT_01, MF_GRAYED) ; break ;
          case  4: EnableMenuItem (hMenu, IDM_CONVERT_04, MF_GRAYED) ; break ;
          case  8: EnableMenuItem (hMenu, IDM_CONVERT_08, MF_GRAYED) ; break ;
          case 16: EnableMenuItem (hMenu, IDM_CONVERT_16, MF_GRAYED) ; break ;
          case 24: EnableMenuItem (hMenu, IDM_CONVERT_24, MF_GRAYED) ; break ;
          case 32: EnableMenuItem (hMenu, IDM_CONVERT_32, MF_GRAYED) ; break ;
          }

          if (hdib && DibColorSize (hdib) > 0)
               iEnable = MF_ENABLED ;
          else
               iEnable = MF_GRAYED ;

          EnableMenuItem (hMenu, IDM_PAL_DIBTABLE,    iEnable) ;

          if (DibIsAddressable (hdib) && DibBitCount (hdib) > 8)
               iEnable = MF_ENABLED ;
          else
               iEnable = MF_GRAYED ;

          EnableMenuItem (hMenu, IDM_PAL_OPT_POP4,   iEnable) ;
          EnableMenuItem (hMenu, IDM_PAL_OPT_POP5,   iEnable) ;
          EnableMenuItem (hMenu, IDM_PAL_OPT_POP6,   iEnable) ;
          EnableMenuItem (hMenu, IDM_PAL_OPT_MEDCUT, iEnable) ;

          EnableMenuItem (hMenu, IDM_EDIT_PASTE, 
               IsClipboardFormatAvailable (CF_DIB) ? MF_ENABLED : MF_GRAYED) ;

          return 0 ;

          // WM_COMMAND:  Process all menu commands.

     case WM_COMMAND:
          iConvert = 0 ;

          switch (LOWORD (wParam))
          {
          case IDM_FILE_OPEN:

                    // Show the File Open dialog box

               if (!GetOpenFileName (&ofn))
                    return 0 ;
               
                    // If there's an existing DIB and palette, delete them

               SendMessage (hwnd, WM_USER_DELETEDIB, 0, 0) ;
               
                    // Load the DIB into memory

               SetCursor (LoadCursor (NULL, IDC_WAIT)) ;
               ShowCursor (TRUE) ;

               hdib = DibFileLoad (szFileName) ;

               ShowCursor (FALSE) ;
               SetCursor (LoadCursor (NULL, IDC_ARROW)) ;

                    // Reset the scroll bars
               SendMessage (hwnd, WM_USER_SETSCROLLS, TRUE, 0) ;

                    // Create the palette and DDB

               SendMessage (hwnd, WM_USER_CREATEPAL, TRUE, 0) ;

               if (!hdib)
               {
                    MessageBox (hwnd, TEXT ("Cannot load DIB file!"), 
                                szAppName, MB_OK | MB_ICONEXCLAMATION) ;
               }
               InvalidateRect (hwnd, NULL, TRUE) ;
               return 0 ;

          case IDM_FILE_SAVE:

                    // Show the File Save dialog box

               if (!GetSaveFileName (&ofn))
                    return 0 ;

                    // Save the DIB to memory

               SetCursor (LoadCursor (NULL, IDC_WAIT)) ;
               ShowCursor (TRUE) ;

               fSuccess = DibFileSave (hdib, szFileName) ;

               ShowCursor (FALSE) ;
               SetCursor (LoadCursor (NULL, IDC_ARROW)) ;

               if (!fSuccess)
                    MessageBox (hwnd, TEXT ("Cannot save DIB file!"),
                                szAppName, MB_OK | MB_ICONEXCLAMATION) ;
               return 0 ;

          case IDM_FILE_PRINT:
               if (!hdib)
                    return 0 ;

                    // Get printer DC

               printdlg.Flags = PD_RETURNDC | PD_NOPAGENUMS | PD_NOSELECTION ;

               if (!PrintDlg (&printdlg))
                    return 0 ;

               if (NULL == (hdcPrn = printdlg.hDC))
               {
                    MessageBox (hwnd, TEXT ("Cannot obtain Printer DC"),
                                szAppName, MB_ICONEXCLAMATION | MB_OK) ;
                    return 0 ;
               }
                    
                    // Check if the printer can print bitmaps

               if (!(RC_BITBLT & GetDeviceCaps (hdcPrn, RASTERCAPS)))
               {
                    DeleteDC (hdcPrn) ;
                    MessageBox (hwnd, TEXT ("Printer cannot print bitmaps").
                                szAppName, MB_ICONEXCLAMATION | MB_OK) ;
                    return 0 ;

                    // Get size of printable area of page
                    // Check if the printer can print bitmaps
 
               if (!(RC_BITBLT & GetDeviceCaps (hdcPrn, RASTERCAPS)))
               { 
                   DeleteDC (hdcPRN) ;
                   MessageBox (hwnd, TEXT ("Printer cannot print bitmaps"),
                               szAppName, MB_ICONEXCLAMATION | MB_OK) ;
                   return 0 ;
               } 

               cxPage = GetDeviceCaps (hdcPrn, HORZRES) ;
               cyPage = GetDeviceCaps (hdcPrn, VERTRES) ;

               fSuccess = FALSE ;

                    // Send the DIB to the printer

               SetCursor (LoadCursor (NULL, IDC_WAIT)) ;
               ShowCursor (TRUE) ;

               if ((StartDoc (hdcPrn, &di) > 0) && (StartPage (hdcPrn) > 0))
               {
                    DisplayDib (hdcPrn, DibBitmapHandle (hdib), 0, 0, 
                                cxPage, cyPage, wShow, FALSE) ;
                    
                    if (EndPage (hdcPrn) > 0)
                    {
                         fSuccess = TRUE ;
                         EndDoc (hdcPrn) ;
                    }
               }
               ShowCursor (FALSE) ;
               SetCursor (LoadCursor (NULL, IDC_ARROW)) ;

               DeleteDC (hdcPrn) ;

               if (!fSuccess)
                    MessageBox (hwnd, TEXT ("Could not print bitmap"),
                                szAppName, MB_ICONEXCLAMATION | MB_OK) ;
               return 0 ;

          case IDM_FILE_PROPERTIES:
               if (!hdib)
                    return 0 ;
               wsprintf (szBuffer, TEXT ("Pixel width:\t%i\n")
                                   TEXT ("Pixel height:\t%i\n")
                                   TEXT ("Bits per pixel:\t%i\n")
                                   TEXT ("Number of colors:\t%i\n")
                                   TEXT ("Compression:\t%s\n"),
                         DibWidth (hdib), DibHeight (hdib),
                         DibBitCount (hdib), DibNumColors (hdib),
                         szCompression [min (3, DibCompression (hdib))]) ;

               MessageBox (hwnd, szBuffer, szAppName, 
                           MB_ICONEXCLAMATION | MB_OK) ;
               return 0 ;

          case IDM_APP_EXIT:
               SendMessage (hwnd, WM_CLOSE, 0, 0) ;
               return 0 ;

          case IDM_EDIT_COPY:
          case IDM_EDIT_CUT:
               if (!(hGlobal = DibCopyToPackedDib (hdib, TRUE)))
                    return 0 ;

               OpenClipboard (hwnd) ;
               EmptyClipboard () ;
               SetClipboardData (CF_DIB, hGlobal) ;
               CloseClipboard () ;

               if (LOWORD (wParam) == IDM_EDIT_COPY)
                    return 0 ;
                                   // fall through for IDM_EDIT_CUT
          case IDM_EDIT_DELETE:
               SendMessage (hwnd, WM_USER_DELETEDIB, 0, 0) ;
               InvalidateRect (hwnd, NULL, TRUE) ;
               return 0 ;

          case IDM_EDIT_PASTE:
               OpenClipboard (hwnd) ;

               hGlobal = GetClipboardData (CF_DIB) ;
               pGlobal = GlobalLock (hGlobal) ;

                    // If there's an existing DIB and palette, delete them
                    // Then convert the packed DIB to an HDIB.

               if (pGlobal)
               {
                    SendMessage (hwnd, WM_USER_DELETEDIB, 0, 0) ;
                    hdib = DibCopyFromPackedDib ((BITMAPINFO *) pGlobal) ;
                    SendMessage (hwnd, WM_USER_CREATEPAL, TRUE, 0) ;
               }

               GlobalUnlock (hGlobal) ;
               CloseClipboard () ;

                    // Reset the scroll bars

               SendMessage (hwnd, WM_USER_SETSCROLLS, TRUE, 0) ;
               InvalidateRect (hwnd, NULL, TRUE) ;
               return 0 ;

          case IDM_EDIT_ROTATE:
               if (hdibNew = DibRotateRight (hdib))
               {
                    DibDelete (hdib) ;
                    DeleteObject (hBitmap) ;
                    hdib = hdibNew ;
                    hBitmap = DibCopyToDdb (hdib, hwnd, hPalette) ;
                    SendMessage (hwnd, WM_USER_SETSCROLLS, TRUE, 0) ;
                    InvalidateRect (hwnd, NULL, TRUE) ;
               }
               else
               {
                    MessageBox (hwnd, TEXT ("Not enough memory"),
                                szAppName, MB_OK | MB_ICONEXCLAMATION) ;
               }
               return 0 ;

          case IDM_EDIT_FLIP:
               if (hdibNew = DibFlipHorizontal (hdib))
               {
                    DibDelete (hdib) ;
                    DeleteObject (hBitmap) ;
                    hdib = hdibNew ;
                    hBitmap = DibCopyToDdb (hdib, hwnd, hPalette) ;
                    InvalidateRect (hwnd, NULL, TRUE) ;
               }
               else
               {
                    MessageBox (hwnd, TEXT ("Not enough memory"),
                                szAppName, MB_OK | MB_ICONEXCLAMATION) ;
               }
               return 0 ;

          case IDM_SHOW_NORMAL:
          case IDM_SHOW_CENTER:
          case IDM_SHOW_STRETCH:
          case IDM_SHOW_ISOSTRETCH:
               CheckMenuItem (hMenu, wShow, MF_UNCHECKED) ;
               wShow = LOWORD (wParam) ;
               CheckMenuItem (hMenu, wShow, MF_CHECKED) ;

               SendMessage (hwnd, WM_USER_SETSCROLLS, FALSE, 0) ;

               InvalidateRect (hwnd, NULL, TRUE) ;
               return 0 ;

          case IDM_CONVERT_32:  iConvert += 8 ;
          case IDM_CONVERT_24:  iConvert += 8 ;   
          case IDM_CONVERT_16:  iConvert += 8 ;
          case IDM_CONVERT_08:  iConvert += 4 ;
          case IDM_CONVERT_04:  iConvert += 3 ;
          case IDM_CONVERT_01:  iConvert += 1 ;
               SetCursor (LoadCursor (NULL, IDC_WAIT)) ;
               ShowCursor (TRUE) ;

               hdibNew = DibConvert (hdib, iConvert) ;

               ShowCursor (FALSE) ;
               SetCursor (LoadCursor (NULL, IDC_ARROW)) ;

               if (hdibNew)
               {
                    SendMessage (hwnd, WM_USER_DELETEDIB, 0, 0) ;
                    hdib = hdibNew ;
                    SendMessage (hwnd, WM_USER_CREATEPAL, TRUE, 0) ;
                    InvalidateRect (hwnd, NULL, TRUE) ;
               }

               else
               {
                    MessageBox (hwnd, TEXT ("Not enough memory"),
                                szAppName, MB_OK | MB_ICONEXCLAMATION) ;
               }
               return 0 ;

          case IDM_APP_ABOUT:
               MessageBox (hwnd, TEXT ("Dibble (c) Charles Petzold, 1998"),
                           szAppName, MB_OK | MB_ICONEXCLAMATION) ;
               return 0 ;
          }
     
               // All the other WM_COMMAND messages are from the palette
               //   items. Any existing palette is deleted, and the cursor
               //   is set to the hourglass.

          SendMessage (hwnd, WM_USER_DELETEPAL, 0, 0) ;
          SetCursor (LoadCursor (NULL, IDC_WAIT)) ;
          ShowCursor (TRUE) ;

               // Notice that all messages for palette items are ended
               //   with break rather than return. This is to allow 
               //   additional processing later on.

          switch (LOWORD (wParam))
          {
          case IDM_PAL_DIBTABLE: 
               hPalette = DibPalDibTable (hdib) ; 
               break ;

          case IDM_PAL_HALFTONE: 
               hdc = GetDC (hwnd) ;

               if (hPalette = CreateHalftonePalette (hdc))
                    fHalftonePalette = TRUE ;

               ReleaseDC (hwnd, hdc) ;
               break ;

          case IDM_PAL_ALLPURPOSE: 
               hPalette = DibPalAllPurpose () ; 
               break ;
          case IDM_PAL_GRAY2:   hPalette = DibPalUniformGrays (  2) ; break ;
          case IDM_PAL_GRAY3:   hPalette = DibPalUniformGrays (  3) ; break ;
          case IDM_PAL_GRAY4:   hPalette = DibPalUniformGrays (  4) ; break ;
          case IDM_PAL_GRAY8:   hPalette = DibPalUniformGrays (  8) ; break ;
          case IDM_PAL_GRAY16:  hPalette = DibPalUniformGrays ( 16) ; break ;
          case IDM_PAL_GRAY32:  hPalette = DibPalUniformGrays ( 32) ; break ;
          case IDM_PAL_GRAY64:  hPalette = DibPalUniformGrays ( 64) ; break ;
          case IDM_PAL_GRAY128: hPalette = DibPalUniformGrays (128) ; break ;
          case IDM_PAL_GRAY256: hPalette = DibPalUniformGrays (256) ; break ;

          case IDM_PAL_RGB222: hPalette = DibPalUniformColors (2,2,2); break;
          case IDM_PAL_RGB333: hPalette = DibPalUniformColors (3,3,3); break;
          case IDM_PAL_RGB444: hPalette = DibPalUniformColors (4,4,4); break;
          case IDM_PAL_RGB555: hPalette = DibPalUniformColors (5,5,5); break;
          case IDM_PAL_RGB666: hPalette = DibPalUniformColors (6,6,6); break;
          case IDM_PAL_RGB775: hPalette = DibPalUniformColors (7,7,5); break;
          case IDM_PAL_RGB757: hPalette = DibPalUniformColors (7,5,7); break;
          case IDM_PAL_RGB577: hPalette = DibPalUniformColors (5,7,7); break;
          case IDM_PAL_RGB884: hPalette = DibPalUniformColors (8,8,4); break;
          case IDM_PAL_RGB848: hPalette = DibPalUniformColors (8,4,8); break;
          case IDM_PAL_RGB488: hPalette = DibPalUniformColors (4,8,8); break;

          case IDM_PAL_OPT_POP4:  
               hPalette = DibPalPopularity (hdib, 4) ;
               break ;

          case IDM_PAL_OPT_POP5:  
               hPalette = DibPalPopularity (hdib, 5) ;
               break ;

          case IDM_PAL_OPT_POP6:  
               hPalette = DibPalPopularity (hdib, 6) ;
               break ;                   

          case IDM_PAL_OPT_MEDCUT:
               hPalette = DibPalMedianCut (hdib, 6) ;
               break ;
          }

               // After processing Palette items from the menu, the cursor
               //   is restored to an arrow, the menu item is checked, and
               //   the window is invalidated.

          hBitmap = DibCopyToDdb (hdib, hwnd, hPalette) ;

          ShowCursor (FALSE) ;
          SetCursor (LoadCursor (NULL, IDC_ARROW)) ;

          if (hPalette)
                PaletteMenu (hMenu, (LOWORD (wParam))) ;

          InvalidateRect (hwnd, NULL, TRUE) ;
          return 0 ;

          // This programmer-defined message deletes an existing DIB 
          //   in preparation for getting a new one.  Invoked during 
          //   File Open command, Edit Paste command, and others.

     case WM_USER_DELETEDIB:
          if (hdib)
          {
               DibDelete (hdib) ;
               hdib = NULL ;
          }
          SendMessage (hwnd, WM_USER_DELETEPAL, 0, 0) ;
          return 0 ;

          // This programmer-defined message deletes an existing palette
          //   in preparation for defining a new one.

     case WM_USER_DELETEPAL:
          if (hPalette)
          {
               DeleteObject (hPalette) ;
               hPalette = NULL ;
               fHalftonePalette = FALSE ;
               PaletteMenu (hMenu, IDM_PAL_NONE) ;
          }
          if (hBitmap)
               DeleteObject (hBitmap) ;

          return 0 ;

          // Programmer-defined message to create a new palette based on 
          //   a new DIB.  If wParam == TRUE, create a DDB as well.

     case WM_USER_CREATEPAL:
          if (hdib)
          {
               hdc = GetDC (hwnd) ;
              if (!(RC_PALETTE & GetDeviceCaps (hdc, RASTERCAPS)))
               {
                    PaletteMenu (hMenu, IDM_PAL_NONE) ;
               }
               else if (hPalette = DibPalDibTable (hdib))
               {
                    PaletteMenu (hMenu, IDM_PAL_DIBTABLE) ;
               }
               else if (hPalette = CreateHalftonePalette (hdc))
               {
                    fHalftonePalette = TRUE ;
                    PaletteMenu (hMenu, IDM_PAL_HALFTONE) ;
               }
               ReleaseDC (hwnd, hdc) ;

               if ((BOOL) wParam)
                    hBitmap = DibCopyToDdb (hdib, hwnd, hPalette) ;
          }
          return 0 ;

     case WM_PAINT:
          hdc = BeginPaint (hwnd, &ps) ;

          if (hPalette)
          {
               SelectPalette (hdc, hPalette, FALSE) ;
               RealizePalette (hdc) ;
          }
          if (hBitmap)
          {
               DisplayDib (hdc, 
                           fHalftonePalette ? DibBitmapHandle (hdib) : hBitmap, 
                           iHscroll, iVscroll, 
                           cxClient, cyClient, 
                           wShow, fHalftonePalette) ;
          }
          EndPaint (hwnd, &ps) ;
          return 0 ;

     case WM_QUERYNEWPALETTE:
          if (!hPalette)
               return FALSE ;

          hdc = GetDC (hwnd) ;
          SelectPalette (hdc, hPalette, FALSE) ;
          RealizePalette (hdc) ;
          InvalidateRect (hwnd, NULL, TRUE) ;

          ReleaseDC (hwnd, hdc) ;
          return TRUE ;

     case WM_PALETTECHANGED:
          if (!hPalette || (HWND) wParam == hwnd)
               break ;

          hdc = GetDC (hwnd) ;
          SelectPalette (hdc, hPalette, FALSE) ;
          RealizePalette (hdc) ;
          UpdateColors (hdc) ;

          ReleaseDC (hwnd, hdc) ;
          break ;

     case WM_DESTROY:
          if (hdib)
               DibDelete (hdib) ;

          if (hBitmap)
               DeleteObject (hBitmap) ;

          if (hPalette)
               DeleteObject (hPalette) ;

          PostQuitMessage (0) ;
          return 0 ;
     }
     return DefWindowProc (hwnd, message, wParam, lParam) ;
}

DIBBLE.RC (excerpts)

//Microsoft Developer Studio generated resource script.

#include "resource.h"
#include "afxres.h"
/////////////////////////////////////////////////////////////////////////////
// Menu

DIBBLE MENU DISCARDABLE 
BEGIN
    POPUP "&File"
    BEGIN
        MENUITEM "&Open...\tCtrl+O",            IDM_FILE_OPEN
        MENUITEM "&Save...\tCtrl+S",            IDM_FILE_SAVE
        MENUITEM SEPARATOR
        MENUITEM "&Print...\tCtrl+P",           IDM_FILE_PRINT
        MENUITEM SEPARATOR
        MENUITEM "Propert&ies...",              IDM_FILE_PROPERTIES
        MENUITEM SEPARATOR
        MENUITEM "E&xit",                       IDM_APP_EXIT
    END
    POPUP "&Edit"
    BEGIN
        MENUITEM "Cu&t\tCtrl+X",                IDM_EDIT_CUT
        MENUITEM "&Copy\tCtrl+C",               IDM_EDIT_COPY
        MENUITEM "&Paste\tCtrl+V",              IDM_EDIT_PASTE
        MENUITEM "&Delete\tDelete",             IDM_EDIT_DELETE
        MENUITEM SEPARATOR
        MENUITEM "&Flip",                       IDM_EDIT_FLIP
        MENUITEM "&Rotate",                     IDM_EDIT_ROTATE
    END
    POPUP "&Show"
    BEGIN
        MENUITEM "&Actual Size",                IDM_SHOW_NORMAL, CHECKED
        MENUITEM "&Center",                     IDM_SHOW_CENTER
        MENUITEM "&Stretch to Window",          IDM_SHOW_STRETCH
        MENUITEM "Stretch &Isotropically",      IDM_SHOW_ISOSTRETCH
    END
    POPUP "&Palette"
    BEGIN
        MENUITEM "&None",                       IDM_PAL_NONE, CHECKED
        MENUITEM "&Dib ColorTable",             IDM_PAL_DIBTABLE
        MENUITEM "&Halftone",                   IDM_PAL_HALFTONE
        MENUITEM "&All-Purpose",                IDM_PAL_ALLPURPOSE
        POPUP "&Gray Shades"
        BEGIN
            MENUITEM "&1. 2 Grays",                 IDM_PAL_GRAY2
            MENUITEM "&2. 3 Grays",                 IDM_PAL_GRAY3
            MENUITEM "&3. 4 Grays",                 IDM_PAL_GRAY4
            MENUITEM "&4. 8 Grays",                 IDM_PAL_GRAY8
            MENUITEM "&5. 16 Grays",                IDM_PAL_GRAY16
            MENUITEM "&6. 32 Grays",                IDM_PAL_GRAY32
            MENUITEM "&7. 64 Grays",                IDM_PAL_GRAY64
            MENUITEM "&8. 128 Grays",               IDM_PAL_GRAY128
            MENUITEM "&9. 256 Grays",               IDM_PAL_GRAY256
        END
        POPUP "&Uniform Colors"

        BEGIN
            MENUITEM "&1. 2R x 2G x 2B (8)",        IDM_PAL_RGB222
            MENUITEM "&2. 3R x 3G x 3B (27)",       IDM_PAL_RGB333
            MENUITEM "&3. 4R x 4G x 4B (64)",       IDM_PAL_RGB444
            MENUITEM "&4. 5R x 5G x 5B (125)",      IDM_PAL_RGB555
            MENUITEM "&5. 6R x 6G x 6B (216)",      IDM_PAL_RGB666
            MENUITEM "&6. 7R x 7G x 5B (245)",      IDM_PAL_RGB775
            MENUITEM "&7. 7R x 5B x 7B (245)",      IDM_PAL_RGB757
            MENUITEM "&8. 5R x 7G x 7B (245)",      IDM_PAL_RGB577
            MENUITEM "&9. 8R x 8G x 4B (256)",      IDM_PAL_RGB884
            MENUITEM "&A. 8R x 4G x 8B (256)",      IDM_PAL_RGB848
            MENUITEM "&B. 4R x 8G x 8B (256)",      IDM_PAL_RGB488
        END
        POPUP "&Optimized"
        BEGIN
            MENUITEM "&1. Popularity Algorithm (4 bits)", IDM_PAL_OPT_POP4
            MENUITEM "&2. Popularity Algorithm (5 bits)", IDM_PAL_OPT_POP5
            MENUITEM "&3. Popularity Algorithm (6 bits)", IDM_PAL_OPT_POP6
            MENUITEM "&4. Median Cut Algorithm ",   IDM_PAL_OPT_MEDCUT
        END
    END
    POPUP "Con&vert"
    BEGIN
        MENUITEM "&1. to 1 bit per pixel",      IDM_CONVERT_01
        MENUITEM "&2. to 4 bits per pixel",     IDM_CONVERT_04
        MENUITEM "&3. to 8 bits per pixel",     IDM_CONVERT_08
        MENUITEM "&4. to 16 bits per pixel",    IDM_CONVERT_16
        MENUITEM "&5. to 24 bits per pixel",    IDM_CONVERT_24
        MENUITEM "&6. to 32 bits per pixel",    IDM_CONVERT_32
    END
    POPUP "&Help"
    BEGIN
        MENUITEM "&About",                      IDM_APP_ABOUT
    END
END

/////////////////////////////////////////////////////////////////////////////
// Accelerator

DIBBLE ACCELERATORS DISCARDABLE 
BEGIN
    "C",            IDM_EDIT_COPY,          VIRTKEY, CONTROL, NOINVERT
    "O",            IDM_FILE_OPEN,          VIRTKEY, CONTROL, NOINVERT
    "P",            IDM_FILE_PRINT,         VIRTKEY, CONTROL, NOINVERT
    "S",            IDM_FILE_SAVE,          VIRTKEY, CONTROL, NOINVERT
    "V",            IDM_EDIT_PASTE,         VIRTKEY, CONTROL, NOINVERT
    VK_DELETE,      IDM_EDIT_DELETE,        VIRTKEY, NOINVERT
    "X",            IDM_EDIT_CUT,           VIRTKEY, CONTROL, NOINVERT
END

#define IDM_FILE_OPEN                   40001
#define IDM_FILE_SAVE                   40002
#define IDM_FILE_PRINT                  40003

RESOURCE.H (excerpts)

// Microsoft Developer Studio generated include file.
// Used by Dibble.rc

#define IDM_FILE_PROPERTIES             40004
#define IDM_APP_EXIT                    40005
#define IDM_EDIT_CUT                    40006
#define IDM_EDIT_COPY                   40007
#define IDM_EDIT_PASTE                  40008
#define IDM_EDIT_DELETE                 40009
#define IDM_EDIT_FLIP                   40010
#define IDM_EDIT_ROTATE                 40011
#define IDM_SHOW_NORMAL                 40012
#define IDM_SHOW_CENTER                 40013
#define IDM_SHOW_STRETCH                40014
#define IDM_SHOW_ISOSTRETCH             40015
#define IDM_PAL_NONE                    40016
#define IDM_PAL_DIBTABLE                40017
#define IDM_PAL_HALFTONE                40018
#define IDM_PAL_ALLPURPOSE              40019
#define IDM_PAL_GRAY2                   40020
#define IDM_PAL_GRAY3                   40021
#define IDM_PAL_GRAY4                   40022
#define IDM_PAL_GRAY8                   40023
#define IDM_PAL_GRAY16                  40024
#define IDM_PAL_GRAY32                  40025
#define IDM_PAL_GRAY64                  40026
#define IDM_PAL_GRAY128                 40027
#define IDM_PAL_GRAY256                 40028
#define IDM_PAL_RGB222                  40029
#define IDM_PAL_RGB333                  40030
#define IDM_PAL_RGB444                  40031
#define IDM_PAL_RGB555                  40032
#define IDM_PAL_RGB666                  40033
#define IDM_PAL_RGB775                  40034
#define IDM_PAL_RGB757                  40035
#define IDM_PAL_RGB577                  40036
#define IDM_PAL_RGB884                  40037
#define IDM_PAL_RGB848                  40038
#define IDM_PAL_RGB488                  40039
#define IDM_PAL_OPT_POP4                40040
#define IDM_PAL_OPT_POP5                40041
#define IDM_PAL_OPT_POP6                40042
#define IDM_PAL_OPT_MEDCUT              40043
#define IDM_CONVERT_01                  40044
#define IDM_CONVERT_04                  40045
#define IDM_CONVERT_08                  40046
#define IDM_CONVERT_16                  40047
#define IDM_CONVERT_24                  40048
#define IDM_CONVERT_32                  40049
#define IDM_APP_ABOUT                   40050

DIBBLE uses a couple other files that I'll describe shortly. The DIBCONV files (DIBCONV.C and DIBCONV.H) convert between different formats—for example, from 24 bits per pixel to 8 bits per pixel. The DIBPAL files (DIBPAL.C and DIBPAL.H) create palettes.

DIBBLE maintains three crucial static variables in WndProc. These are an HDIB handle called hdib, an HPALETTE handle called hPalette, and an HBITMAP handle called hBitmap. The HDIB comes from various functions in DIBHELP; the HPALETTE comes from various functions in DIBPAL or the CreateHalftonePalette function; and the HBITMAP handle comes from the DibCopyToDdb function in DIBHELP.C and helps speed up screen displays, particularly in 256-color video modes. However, this handle must be re-created whenever the program creates a new DIB Section (obviously) and also when the program creates a different palette (not so obviously).

Let's approach DIBBLE functionally rather than sequentially.

File Loading and Saving

DIBBLE can load DIB files and save them in response to WM_COMMAND messages of IDM_FILE_LOAD and IDM_FILE_SAVE. In processing these messages, DIBBLE invokes the common file dialog boxes by calling GetOpenFileName and GetSaveFileName, respectively.

For the File Save menu command, DIBBLE need only call DibFileSave. For the File Open menu command, DIBBLE must first delete the previous HDIB, palette, and bitmap objects. It does this by sending itself a WM_USER_DELETEDIB message, which is processed by calls to DibDelete and DeleteObject. DIBBLE then calls the DibFileLoad function in DIBHELP and sends itself WM_USER_SETSCROLLS and WM_USER_CREATEPAL messages to reset the scroll bars and create a palette. The WM_USER_CREATEPAL message is also the place where the program creates a new DDB from the DIB section.

Displaying, Scrolling, and Printing

DIBBLE's menu allows it to display the DIB in actual size oriented at the top left corner of its client area or centered in the client area, or stretched to fill the client area or as much of the client area as possible while still maintaining the proper aspect ratio. You select which option you want through DIBBLE's Show menu. Note that these are the same four display options available in the SHOWDIB2 program from the last chapter.

During the WM_PAINT message—and also while processing the File Print command—DIBBLE calls its DisplayDib function. Notice that DisplayDib uses BitBlt and StretchBlt rather than SetDIBitsToDevice and StretchDIBits. During the WM_PAINT message, the bitmap handle passed to the function is the one created by the DibCopyToDdb function, which is called during the WM_USER_CREATEPAL message. This DDB is compatible with the video device context. When processing the File Print command, DIBBLE calls DisplayDib with the DIB section handle available from DibBitmapHandle function in DIBHELP.C.

Also notice that DIBBLE retains a static BOOL variable named fHalftonePalette, which is set to TRUE if hPalette was obtained from the CreateHalftonePalette function. This forces the DisplayDib function to call StretchBlt rather than BitBlt even if the DIB is being displayed in actual size. The fHalftonePalette variable also causes WM_PAINT processing to pass the DIB section handle to the DisplayDib function rather than the bitmap handle created by the DibCopyToDdb function. The use of the halftone palette was discussed earlier in this chapter and illustrated in the SHOWDIB5 program.

For the first time in any of our sample programs, DIBBLE allows scrolling DIBs in the client area. The scroll bars are shown only when the DIB is displayed in actual size. WndProc simply passes the current position of the scroll bars to the DisplayDib function when processing WM_PAINT.

The Clipboard

For the Cut and Copy menu items, DIBBLE calls the DibCopyToPackedDib function in DIBHELP. This function simply takes all the components of the DIB and puts them in a big memory block.

For the first time in one of the sample programs in this book, DIBBLE pastes a DIB from the clipboard. This involves a call to the DibCopyFromPackedDib function and replaces the HDIB, palette, and bitmap previously stored by the window procedure.

Flipping and Rotating

The Edit menu in DIBBLE contains two additional items beyond the standard Cut, Copy, Paste, and Delete options. These are Flip and Rotate. The Flip option causes the bitmap to be flipped around the horizontal axis—that is, flipped upside down. The Rotate option causes the bitmap to be rotated 90° clockwise. Both of these functions require accessing all the pixels of the DIB by copying them from one DIB to another. (Because these two functions don't require creating a new palette, the palette is not deleted and recreated.)

The Flip menu option uses the DibFlipHorizontal function, also located in the DIBBLE.C file. This function calls DibCopy to obtain an exact copy of the DIB. It then enters a loop that copies pixels from the original DIB to the new DIB, but the pixels are copied so that the image is flipped upside down. Notice that this function calls DibGetPixel and DibSetPixel. These are the general-purpose (but not quite as fast as we may prefer) functions located in DIBHELP.C.

To illustrate the difference between the DibGetPixel and DibSetPixel functions and the much faster DibGetPixel and DibSetPixel macros in DIBHELP.H, the DibRotateRight function uses the macros. Notice first, however, that this function calls DibCopy with a second argument set to TRUE. This causes DibCopy to flip the width and height of the original DIB to create the new DIB. Also, the pixel bits are not copied by the DibCopy function. The DibRotateRight function, then, has six different loops to copy the pixel bits from the original DIB to the new DIB—one for each of the different possible DIB pixel widths (1 bit, 4 bit, 8 bit, 16 bit, 24 bit, and 32 bit). There's more code involved, but the function is much faster.

Although it's possible to use the Flip Horizontal and Rotate Right options to mimic Flip Vertical, Rotate Left, and Rotate 180° functions, normally a program would implement all of these options directly. DIBBLE is, of course, just a demonstration program.

Simple Palettes; Optimized Palettes

In DIBBLE you can choose a variety of palettes for displaying DIBs on 256-color video displays. These are all listed on DIBBLE's Palette menu. With the exception of the halftone palette, which is created directly by a Windows function call, all of the functions to create various palettes are provided by the DIBPAL files shown in Figure 16-25.

Figure 16-25. The DIBPAL files.

DIBPAL.H

/*-----------------------------------
   DIBPAL.H header file for DIBPAL.C
  -----------------------------------*/

HPALETTE DibPalDibTable (HDIB hdib) ;
HPALETTE DibPalAllPurpose (void) ;
HPALETTE DibPalUniformGrays (int iNum) ;
HPALETTE DibPalUniformColors (int iNumR, int iNumG, int iNumB) ;
HPALETTE DibPalVga (void) ;
HPALETTE DibPalPopularity (HDIB hdib, int iRes) ;
HPALETTE DibPalMedianCut (HDIB hdib, int iRes) ;

DIBPAL.C

/*----------------------------------------
   DIBPAL.C -- Palette-Creation Functions
               (c) Charles Petzold, 1998
  ----------------------------------------*/

#include <windows.h>
#include "dibhelp.h"
#include "dibpal.h"

/*------------------------------------------------------------
   DibPalDibTable: Creates a palette from the DIB color table
  ------------------------------------------------------------*/

HPALETTE DibPalDibTable (HDIB hdib)
{
     HPALETTE     hPalette ;
     int          i, iNum ;
     LOGPALETTE * plp ;
     RGBQUAD      rgb ;

     if (0 == (iNum = DibNumColors (hdib)))
          return NULL ;

     plp = malloc (sizeof (LOGPALETTE) + (iNum - 1) * sizeof (PALETTEENTRY)) ;

     plp->palVersion    = 0x0300 ;
     plp->palNumEntries = iNum ;

     for (i = 0 ; i < iNum ; i++)
     {
          DibGetColor (hdib, i, &rgb) ;

          plp->palPalEntry[i].peRed   = rgb.rgbRed ;
          plp->palPalEntry[i].peGreen = rgb.rgbGreen ;
          plp->palPalEntry[i].peBlue  = rgb.rgbBlue ;
          plp->palPalEntry[i].peFlags = 0 ;
     }
     hPalette = CreatePalette (plp) ;
     free (plp) ;
     return hPalette ;
}
/*------------------------------------------------------------------------
   DibPalAllPurpose: Creates a palette suitable for a wide variety
          of images; the palette has 247 entries, but 15 of them are 
          duplicates or match the standard 20 colors.
  ------------------------------------------------------------------------*/

HPALETTE DibPalAllPurpose (void)
{
     HPALETTE     hPalette ;
     int          i, incr, R, G, B ;
     LOGPALETTE * plp ;

     plp = malloc (sizeof (LOGPALETTE) + 246 * sizeof (PALETTEENTRY)) ;

     plp->palVersion    = 0x0300 ;
     plp->palNumEntries = 247 ;

          // The following loop calculates 31 gray shades, but 3 of them
          //        will match the standard 20 colors

     for (i = 0, G = 0, incr = 8 ; G <= 0xFF ; i++, G += incr)
     {
          plp->palPalEntry[i].peRed   = (BYTE) G ;
          plp->palPalEntry[i].peGreen = (BYTE) G ;
          plp->palPalEntry[i].peBlue  = (BYTE) G ;
          plp->palPalEntry[i].peFlags = 0 ;

          incr = (incr == 9 ? 8 : 9) ;
     }

          // The following loop is responsible for 216 entries, but 8 of 
          //        them will match the standard 20 colors, and another
          //        4 of them will match the gray shades above.

     for (R = 0 ; R <= 0xFF ; R += 0x33)
     for (G = 0 ; G <= 0xFF ; G += 0x33)
     for (B = 0 ; B <= 0xFF ; B += 0x33)
     {
          plp->palPalEntry[i].peRed   = (BYTE) R ;
          plp->palPalEntry[i].peGreen = (BYTE) G ;
          plp->palPalEntry[i].peBlue  = (BYTE) B ;
          plp->palPalEntry[i].peFlags = 0 ;

          i++ ;
     }

     hPalette = CreatePalette (plp) ;

     free (plp) ;
     return hPalette ;
}

/*------------------------------------------------------------------------
   DibPalUniformGrays:  Creates a palette of iNum grays, uniformly spaced
  ------------------------------------------------------------------------*/

HPALETTE DibPalUniformGrays (int iNum)
{
     HPALETTE     hPalette ;
     int          i ;
     LOGPALETTE * plp ;

     plp = malloc (sizeof (LOGPALETTE) + (iNum - 1) * sizeof (PALETTEENTRY)) ;

     plp->palVersion    = 0x0300 ;
     plp->palNumEntries = iNum ;

     for (i = 0 ; i < iNum ; i++)
     {
          plp->palPalEntry[i].peRed   =
          plp->palPalEntry[i].peGreen = 
          plp->palPalEntry[i].peBlue  = (BYTE) (i * 255 / (iNum - 1)) ;
          plp->palPalEntry[i].peFlags = 0 ;
     }
     hPalette = CreatePalette (plp) ;
     free (plp) ;
     return hPalette ;
}

/*------------------------------------------------------------------------
   DibPalUniformColors: Creates a palette of iNumR x iNumG x iNumB colors
  ------------------------------------------------------------------------*/

HPALETTE DibPalUniformColors (int iNumR, int iNumG, int iNumB)
{
     HPALETTE     hPalette ;
     int          i, iNum, R, G, B ;
     LOGPALETTE * plp ;

     iNum = iNumR * iNumG * iNumB ;

     plp = malloc (sizeof (LOGPALETTE) + (iNum - 1) * sizeof (PALETTEENTRY)) ;

     plp->palVersion    = 0x0300 ;
     plp->palNumEntries = iNumR * iNumG * iNumB ;

     i = 0 ;
     for (R = 0 ; R < iNumR ; R++)
     for (G = 0 ; G < iNumG ; G++)
     for (B = 0 ; B < iNumB ; B++)
     {
          plp->palPalEntry[i].peRed   = (BYTE) (R * 255 / (iNumR - 1)) ;
          plp->palPalEntry[i].peGreen = (BYTE) (G * 255 / (iNumG - 1)) ;
          plp->palPalEntry[i].peBlue  = (BYTE) (B * 255 / (iNumB - 1)) ;
          plp->palPalEntry[i].peFlags = 0 ;

          i++ ;
     }
     hPalette = CreatePalette (plp) ;
     free (plp) ;
     return hPalette ;
}

/*---------------------------------------------------------------
   DibPalVga:  Creates a palette based on standard 16 VGA colors
  ---------------------------------------------------------------*/

HPALETTE DibPalVga (void)
{
     static RGBQUAD rgb [16] = { 0x00, 0x00, 0x00, 0x00,
                                 0x00, 0x00, 0x80, 0x00,
                                 0x00, 0x80, 0x00, 0x00,
                                 0x00, 0x80, 0x80, 0x00,
                                 0x80, 0x00, 0x00, 0x00,
                                 0x80, 0x00, 0x80, 0x00,
                                 0x80, 0x80, 0x00, 0x00,
                                 0x80, 0x80, 0x80, 0x00,
                                 0xC0, 0xC0, 0xC0, 0x00,
                                 0x00, 0x00, 0xFF, 0x00,
                                 0x00, 0xFF, 0x00, 0x00,
                                 0x00, 0xFF, 0xFF, 0x00,
                                 0xFF, 0x00, 0x00, 0x00,
                                 0xFF, 0x00, 0xFF, 0x00,
                                 0xFF, 0xFF, 0x00, 0x00,
                                 0xFF, 0xFF, 0xFF, 0x00 } ;
     HPALETTE       hPalette ;
     int            i ;
     LOGPALETTE   * plp ;

     plp = malloc (sizeof (LOGPALETTE) + 15 * sizeof (PALETTEENTRY)) ;
     plp->palVersion    = 0x0300 ;
     plp->palNumEntries = 16 ;

     for (i = 0 ; i < 16 ; i++)
     {
          plp->palPalEntry[i].peRed   = rgb[i].rgbRed ;
          plp->palPalEntry[i].peGreen = rgb[i].rgbGreen ;
          plp->palPalEntry[i].peBlue  = rgb[i].rgbBlue ;
          plp->palPalEntry[i].peFlags = 0 ;
     }
     hPalette = CreatePalette (plp) ;
     free (plp) ;
     return hPalette ;
}

/*---------------------------------------------
   Macro used in palette optimization routines
  ---------------------------------------------*/

#define PACK_RGB(R,G,B,iRes) ((int) (R) | ((int) (G) <<  (iRes)) |       \
                                          ((int) (B) << ((iRes) + (iRes))))

/*--------------------------------------------------------------------
   AccumColorCounts: Fills up piCount (indexed by a packed RGB color)
     with counts of pixels of that color.
  --------------------------------------------------------------------*/

static void AccumColorCounts (HDIB hdib, int * piCount, int iRes)
{
     int     x, y, cx, cy ;
     RGBQUAD rgb ;

     cx = DibWidth (hdib) ;
     cy = DibHeight (hdib) ;

     for (y = 0 ; y < cy ; y++)
     for (x = 0 ; x < cx ; x++)
     {
          DibGetPixelColor (hdib, x, y, &rgb) ;

          rgb.rgbRed   >>= (8 - iRes) ;
          rgb.rgbGreen >>= (8 - iRes) ;
          rgb.rgbBlue  >>= (8 - iRes) ;

          ++piCount [PACK_RGB (rgb.rgbRed, rgb.rgbGreen, rgb.rgbBlue, iRes)] ;
     }
}

/*--------------------------------------------------------------
   DibPalPopularity:  Popularity algorithm for optimized colors
  --------------------------------------------------------------*/

HPALETTE DibPalPopularity (HDIB hdib, int iRes)
{
     HPALETTE     hPalette ;
     int          i, iArraySize, iEntry, iCount, iIndex, iMask, R, G, B ;
     int        * piCount ;
     LOGPALETTE * plp ;

          // Validity checks
    
     if (DibBitCount (hdib) < 16)
          return NULL ;

     if (iRes < 3 || iRes > 8)
          return NULL ;

          // Allocate array for counting pixel colors

     iArraySize = 1 << (3 * iRes) ;
     iMask = (1 << iRes) - 1 ;

     if (NULL == (piCount = calloc (iArraySize, sizeof (int))))
          return NULL ;

          // Get the color counts

     AccumColorCounts (hdib, piCount, iRes) ;

          // Set up a palette

     plp = malloc (sizeof (LOGPALETTE) + 235 * sizeof (PALETTEENTRY)) ;

     plp->palVersion = 0x0300 ;

     for (iEntry = 0 ; iEntry < 236 ; iEntry++)
     {
          for (i = 0, iCount = 0 ; i < iArraySize ; i++)
               if (piCount[i] > iCount

               {
                    iCount = piCount[i] ;
                    iIndex = i ;
               }

          if (iCount == 0)
               break ;

          R = (iMask &  iIndex                  ) << (8 - iRes) ;
          G = (iMask & (iIndex >>         iRes )) << (8 - iRes) ;
          B = (iMask & (iIndex >> (iRes + iRes))) << (8 - iRes) ;

          plp->palPalEntry[iEntry].peRed   = (BYTE) R ; 
          plp->palPalEntry[iEntry].peGreen = (BYTE) G ; 
          plp->palPalEntry[iEntry].peBlue  = (BYTE) B ; 
          plp->palPalEntry[iEntry].peFlags = 0 ;

          piCount [iIndex] = 0 ;
     }
          // On exit from the loop iEntry will be the number of stored entries

     plp->palNumEntries = iEntry ;

          // Create the palette, clean up, and return the palette handle

     hPalette = CreatePalette (plp) ;

     free (piCount) ;
     free (plp) ;

     return hPalette ;
}

/*-------------------------------------------------------
   Structures used for implementing median cut algorithm
  -------------------------------------------------------*/

typedef struct           // defines dimension of a box
{
     int Rmin, Rmax, Gmin, Gmax, Bmin, Bmax ;
}
MINMAX ;

typedef struct           // for Compare routine for qsort
{
     int     iBoxCount ;
     RGBQUAD rgbBoxAv ;
}

BOXES ;
/*----------------------------
   FindAverageColor: In a box
  ----------------------------*/

static int FindAverageColor (int * piCount, MINMAX mm, 
                             int iRes, RGBQUAD * prgb)
{
     int R, G, B, iR, iG, iB, iTotal, iCount ;

          // Initialize some variables

     iTotal = iR = iG = iB = 0 ;
          
          // Loop through all colors in the box
          
     for (R = mm.Rmin ; R <= mm.Rmax ; R++)
     for (G = mm.Gmin ; G <= mm.Gmax ; G++)
     for (B = mm.Bmin ; B <= mm.Bmax ; B++)
     {
               // Get the number of pixels of that color

          iCount = piCount [PACK_RGB (R, G, B, iRes)] ;

               // Weight the pixel count by the color value

          iR += iCount * R ;
          iG += iCount * G ;
          iB += iCount * B ;

          iTotal += iCount ;
     }
          // Find the average color

     prgb->rgbRed   = (BYTE) ((iR / iTotal) << (8 - iRes)) ;
     prgb->rgbGreen = (BYTE) ((iG / iTotal) << (8 - iRes)) ;
     prgb->rgbBlue  = (BYTE) ((iB / iTotal) << (8 - iRes)) ;

          // Return the total number of pixels in the box
     
     return iTotal ;
}

/*------------------------------
   CutBox:  Divide a box in two
  ------------------------------*/

static void CutBox (int * piCount, int iBoxCount, MINMAX mm,
                    int iRes, int iLevel, BOXES * pboxes, int * piEntry)
{
     int    iCount, R, G, B ;
     MINMAX mmNew ;
     
          // If the box is empty, return 

     if (iBoxCount == 0)
          return ;

          // If the nesting level is 8, or the box is one pixel, we're ready
          //   to find the average color in the box and save it along with
          //   the number of pixels of that color

     if (iLevel == 8 || (mm.Rmin == mm.Rmax && 
                         mm.Gmin == mm.Gmax && 
                         mm.Bmin == mm.Bmax))
     {
          pboxes[*piEntry].iBoxCount = 
               FindAverageColor (piCount, mm, iRes, &pboxes[*piEntry].rgbBoxAv) ;

          (*piEntry) ++ ;
     }
          // Otherwise, if blue is the largest side, split it

     else if ((mm.Bmax - mm.Bmin > mm.Rmax - mm.Rmin) && 
              (mm.Bmax - mm.Bmin > mm.Gmax - mm.Gmin))
     {
               // Initialize a counter and loop through the blue side

          iCount = 0 ;

          for (B = mm.Bmin ; B < mm.Bmax ; B++)
          {
                    // Accumulate all the pixels for each successive blue value

               for (R = mm.Rmin ; R <= mm.Rmax ; R++)
               for (G = mm.Gmin ; G <= mm.Gmax ; G++)
                    iCount += piCount [PACK_RGB (R, G, B, iRes)] ;

                    // If it's more than half the box count, we're there

               if (iCount >= iBoxCount / 2)
                    break ;

                    // If the next blue value will be the max, we're there
               if (B == mm.Bmax - 1)
                    break ;
          }
               // Cut the two split boxes.
               //   The second argument to CutBox is the new box count.
               //   The third argument is the new min and max values.

          mmNew = mm ;
          mmNew.Bmin = mm.Bmin ;
          mmNew.Bmax = B ;

          CutBox (piCount, iCount, mmNew, iRes, iLevel + 1, 
                  pboxes, piEntry) ;

          mmNew.Bmin = B + 1 ;
          mmNew.Bmax = mm.Bmax ;

          CutBox (piCount, iBoxCount - iCount, mmNew, iRes, iLevel + 1,
                  pboxes, piEntry) ;
     }
          // Otherwise, if red is the largest side, split it (just like blue)

     else if (mm.Rmax - mm.Rmin > mm.Gmax - mm.Gmin)
     {
          iCount = 0 ;

          for (R = mm.Rmin ; R < mm.Rmax ; R++)
          {
               for (B = mm.Bmin ; B <= mm.Bmax ; B++)
               for (G = mm.Gmin ; G <= mm.Gmax ; G++)
                    iCount += piCount [PACK_RGB (R, G, B, iRes)] ;

               if (iCount >= iBoxCount / 2)
                    break ;

               if (R == mm.Rmax - 1)
                    break ;
          }
          mmNew = mm ;
          mmNew.Rmin = mm.Rmin ;
          mmNew.Rmax = R ;

          CutBox (piCount, iCount, mmNew, iRes, iLevel + 1, 
                  pboxes, piEntry) ;

          mmNew.Rmin = R + 1 ;
          mmNew.Rmax = mm.Rmax ;

          CutBox (piCount, iBoxCount - iCount, mmNew, iRes, iLevel + 1,
                  pboxes, piEntry) ;
     }
          // Otherwise, split along the green size
     else 
     {
          iCount = 0 ;

          for (G = mm.Gmin ; G < mm.Gmax ; G++)
          {
               for (B = mm.Bmin ; B <= mm.Bmax ; B++)
               for (R = mm.Rmin ; R <= mm.Rmax ; R++)
                    iCount += piCount [PACK_RGB (R, G, B, iRes)] ;

               if (iCount >= iBoxCount / 2)
                    break ;

               if (G == mm.Gmax - 1)
                    break ;
          }
          mmNew = mm ;
          mmNew.Gmin = mm.Gmin ;
          mmNew.Gmax = G ;

          CutBox (piCount, iCount, mmNew, iRes, iLevel + 1, 
                  pboxes, piEntry) ;

          mmNew.Gmin = G + 1 ;
          mmNew.Gmax = mm.Gmax ;

          CutBox (piCount, iBoxCount - iCount, mmNew, iRes, iLevel + 1,
                  pboxes, piEntry) ;
     }
}

/*---------------------------
   Compare routine for qsort
  ---------------------------*/

static int Compare (const BOXES * pbox1, const BOXES * pbox2)
{
     return pbox1->iBoxCount - pbox2->iBoxCount ;
}

/*-----------------------------------------------------------------
   DibPalMedianCut:  Creates palette based on median cut algorithm
  -----------------------------------------------------------------*/
HPALETTE DibPalMedianCut (HDIB hdib, int iRes)
{
     BOXES        boxes [256] ;
     HPALETTE     hPalette ;
     int          i, iArraySize, iCount, R, G, B, iTotCount, iDim, iEntry = 0 ;
     int        * piCount ;
     LOGPALETTE * plp ;
     MINMAX       mm ;

          // Validity checks
    
     if (DibBitCount (hdib) < 16)
          return NULL ;

     if (iRes < 3 || iRes > 8)
          return NULL ;

          // Accumulate counts of pixel colors

     iArraySize = 1 << (3 * iRes) ;

     if (NULL == (piCount = calloc (iArraySize, sizeof (int))))
          return NULL ;

     AccumColorCounts (hdib, piCount, iRes) ;

          // Find the dimensions of the total box

     iDim = 1 << iRes ;

     mm.Rmin = mm.Gmin = mm.Bmin = iDim - 1 ;
     mm.Rmax = mm.Gmax = mm.Bmax = 0 ;

     iTotCount = 0 ;

     for (R = 0 ; R < iDim ; R++)
     for (G = 0 ; G < iDim ; G++)
     for (B = 0 ; B < iDim ; B++)
          if ((iCount = piCount [PACK_RGB (R, G, B, iRes)]) > 0)
          {
               iTotCount += iCount ;

               if (R < mm.Rmin) mm.Rmin = R ;
               if (G < mm.Gmin) mm.Gmin = G ;
               if (B < mm.Bmin) mm.Bmin = B ;
               if (R > mm.Rmax) mm.Rmax = R ;
               if (G > mm.Gmax) mm.Gmax = G ;
               if (B > mm.Bmax) mm.Bmax = B ;
          }

          // Cut the first box (iterative function).
          //   On return, the boxes structure will have up to 256 RGB values, 
          //        one for each of the boxes, and the number of pixels in
          //        each box.
          //   The iEntry value will indicate the number of non-empty boxes.

     CutBox (piCount, iTotCount, mm, iRes, 0, boxes, &iEntry) ;
     free (piCount) ;

          // Sort the RGB table by the number of pixels for each color

     qsort (boxes, iEntry, sizeof (BOXES), Compare) ;

     plp = malloc (sizeof (LOGPALETTE) + (iEntry - 1) * sizeof (PALETTEENTRY)) ;

     if (plp == NULL)
          return NULL ;

     plp->palVersion    = 0x0300 ;
     plp->palNumEntries = iEntry ;

     for (i = 0 ; i < iEntry ; i++)
     {
          plp->palPalEntry[i].peRed   = boxes[i].rgbBoxAv.rgbRed ;
          plp->palPalEntry[i].peGreen = boxes[i].rgbBoxAv.rgbGreen ;
          plp->palPalEntry[i].peBlue  = boxes[i].rgbBoxAv.rgbBlue ;
          plp->palPalEntry[i].peFlags = 0 ;
     }

     hPalette = CreatePalette (plp) ;

     free (plp) ;
     return hPalette ;
}

The first function—DibPalDibTable—should look familiar. It creates a palette from the DIB's color table. This is quite similar to the PackedDibCreatePalette function from PACKEDIB.C that was put to use in the SHOWDIB3 program earlier in the chapter. As in SHOWDIB3, this function will work only if the DIB has a color table. It is not useful when attempting to display a 16-bit, 24-bit, or 32-bit DIB under an 8-bit video mode.

By default, when running on 256-color displays, DIBBLE will first try to call DibPalDibTable to create the palette from the DIB color table. If the DIB doesn't have the color table, DIBBLE will call CreateHalftonePalette and set the fHalftonePalette variable to TRUE. All of this logic occurs during the WM_USER_CREATEPAL message.

DIBPAL.C also implements a function named DibPalAllPurpose, which should also look familiar because it is quite similar to the CreateAllPurposePalette function in SHOWDIB4. You can also select this palette from DIBBLE's menu.

One of the interesting aspects about displaying bitmaps in 256-color modes is that you can control exactly what colors Windows uses for displaying the image. If you select and realize a palette, Windows will use the colors in the palette and no others.

For example, you can create a palette solely with shades of gray by using the DibPalUniformGrays function. Using two shades of gray gives you a palette with just 00-00-00 (black) and FF-FF-FF (white). Try this out with some images you have: it provides a high-contrast "chalk and charcoal" effect popular with some photographers. Using 3 shades of gray adds a medium gray to black and white, and using 4 shades of gray adds 2 gray shades.

With 8 shades of gray you will probably see obvious contouring—irregular patches of the same gray shade where the nearest-color algorithm is obviously working but certainly not with any aesthetic judgment. Moving to 16 gray shades generally improves the image dramatically. The use of 32 gray shades just about eliminates any contouring; 64 gray shades is commonly considered the limit of most display equipment in use today. After this point, the improvements are marginal if evident at all. Going beyond 64 gray shades provides no improvement on devices with a 6-bit color resolution.

So far, the best we've been able to do for displaying 16-bit, 24-bit, or 32-bit color DIBs under 8-bit video modes is to devise an all-purpose palette (trivial for gray-shade images but usually inadequate for color images) or use the halftone palette, which combines an all-purpose color palette with a dithered display.

You'll also notice that when you select an all-purpose palette for a large 16-bit, 24-bit, or 32-bit DIB in an 8-bit color mode, it takes some time for the program to create the GDI bitmap object from the DIB for display purposes. Less time is required when the program creates a DDB from the DIB when no palette is used. (You can also see this difference when comparing the performance of SHOWDIB1 and SHOWDIB4 in displaying large 24-bit DIBs in an 8-bit color mode.) Why is this?

It's the nearest-color search. Normally, when displaying a 24-bit DIB in an 8-bit video mode (or converting a DIB to a DDB), GDI must match each and every pixel in the DIB to one of the static 20 colors. The only way it can do this is by determining which static color is closest to the pixel color. This involves calculating a distance between the pixel and each static color in a three-dimensional RGB color cube. This takes time, particularly when there may be millions of pixels in the DIB image.

When you create a 232-color palette such as the all-purpose palette in DIBBLE and SHOWDIB4, you are effectively increasing the time required for the nearest-color search by more than 11-times! GDI must now search through 232 colors rather than just 20. That's why the whole job of displaying the DIB is slowing down.

The lesson here is to avoid displaying 24-bit (or 16-bit or 32-bit) DIBs in 8-bit video modes. You should convert them to 8-bit DIBs by finding a palette of 256 colors that most closely approximates the range of colors in the DIB image. This is often referred to as an "optimal palette." A paper by Paul Heckbert entitled "Color Image Quantization for Frame Buffer Displays" that appeared in the July 1982 issue of Computer Graphics was helpful when I was researching this problem.

The Uniform Distribution

The simplest approach to a 256-color palette is choosing a uniform range of RGB color values, similar to the approach in DibPalAllPurpose. The advantage is that you don't need to examine the actual pixels in the DIB. Such a function to create a palette based on uniform ranges of RGB primaries is DibPalCreateUniformColors.

One reasonable distribution involves 8 levels of red and green and 4 levels of blue (to which the eye is less sensitive). The palette is the set of RGB color values with all the possible combinations of red and green values of 0x00, 0x24, 0x49, 0x6D, 0x92, 0xB6, 0xDB, and 0xFF, and blue values of 0x00, 0x55, 0xAA, and 0xFF, for a total of 256 colors. Another possible uniformly distributed palette uses 6 levels of red, green, and blue. This is a palette of all possible combinations of red, green, and blue values of 0x00, 0x33, 0x66, 0x99, 0xCC, and 0xFF. The number of colors in the palette is 6 to the 3rd power, or 216.

These two options and several others are provided by DIBBLE.

The "Popularity" Algorithm

The "popularity" algorithm is a fairly obvious solution to the 256-color palette problem. What you do is plow through all the pixels of the bitmap and find the 256 most common RGB color values. These are the values you use in the palette. This is implemented in DIBPAL's DibPalPopularity function.

However, if you use a whole 24 bits for each color, and if you assume that you'll need integers to count all the colors, your array will occupy 64 megabytes of memory. Moreover, you may find that there are actually no (or few) duplicated 24-bit pixel values in the bitmap and, thus, no most common colors.

To solve this problem, you can use only the most significant n bits of each red, green, and blue value—for example, 6 bits rather than 8. This makes sense because most color scanners and video display adapters have only a 6-bit resolution. This reduces the array to a more reasonable size of 256-KB count values, or 1 megabyte. Using only 5 bits reduces the total number of possible colors to 32,768. The use of 5 bits usually works better than 6 bits, as you can verify for yourself using DIBBLE and some color images you have.

The "Median Cut" Algorithm

The DibPalMedianCut function in DIBPAL.C implements Paul Heckbert's "median cut" algorithm. It's conceptually quite simple and, while the implementation in code is more difficult than the popularity algorithm, it is well-suited to recursive functions.

Picture the RGB color cube. Each pixel of the image is a point within this cube. Some points might represent multiple pixels in the image. Find the three-dimensional box that encloses all the pixels in the image. Find the longest dimension of this box and cut the box in two parts, each containing an equal number of pixels. For these 2 boxes, do the same thing. Now you have 4 boxes. Cut the 4 boxes into 8, and then into 16, and then 32, 64, 128, and 256.

Now you have 256 boxes, each containing about an equal number of pixels. Average the RGB color values of the pixels in each box, and use the results for the palette.

In reality, the boxes don't usually contain an equal number of pixels. Often, for example, a box containing a single point has many pixels. This happens with black and white. Sometimes a box ends up with no pixels at all. If so, you can chop up more boxes, but I decided not to.

Another optimum palette technique is called "octree quantization." This technique was discussed by Jeff Prosise in the August 1996 issue of Microsoft Systems Journal (included on the MSDN CDs).

Converting Formats

DIBBLE also allows converting a DIB from one format to another. This makes use of the DibConvert function in the DIBCONV files shown in Figure 16-26.

Figure 16-26. The DIBCONV files.

DIBCONV.H



/*-------------------------------------
   DIBCONV.H header file for DIBCONV.C
  -------------------------------------*/

HDIB DibConvert (HDIB hdibSrc, int iBitCountDst) ;

DIBCONV.C

/*-------------------------------------------------------
   DIBCONV.C -- Converts DIBs from one format to another
                (c) Charles Petzold, 1998
  -------------------------------------------------------*/

#include <windows.h>
#include "dibhelp.h"
#include "dibpal.h"
#include "dibconv.h"

HDIB DibConvert (HDIB hdibSrc, int iBitCountDst)
{
     HDIB         hdibDst ;
     HPALETTE     hPalette ;
     int          i, x, y, cx, cy, iBitCountSrc, cColors ;
     PALETTEENTRY pe ;
     RGBQUAD      rgb ;
     WORD         wNumEntries ;

     cx = DibWidth (hdibSrc) ;
     cy = DibHeight (hdibSrc) ;
     iBitCountSrc = DibBitCount (hdibSrc) ;

     if (iBitCountSrc == iBitCountDst)
          return NULL ;

          // DIB with color table to DIB with larger color table:

     if ((iBitCountSrc < iBitCountDst) && (iBitCountDst <= 8))
     {
          cColors = DibNumColors (hdibSrc) ;
          hdibDst = DibCreate (cx, cy, iBitCountDst, cColors) ;

          for (i = 0 ; i < cColors ; i++)
          {
               DibGetColor (hdibSrc, i, &rgb) ;
               DibSetColor (hdibDst, i, &rgb) ;
          }

          for (x = 0 ; x < cx ; x++)
          for (y = 0 ; y < cy ; y++)
          {
               DibSetPixel (hdibDst, x, y, DibGetPixel (hdibSrc, x, y)) ;
          }
     }
          // Any DIB to DIB with no color table

     else if (iBitCountDst >= 16)
     {
          hdibDst = DibCreate (cx, cy, iBitCountDst, 0) ;

          for (x = 0 ; x < cx ; x++)
          for (y = 0 ; y < cy ; y++)
          {
               DibGetPixelColor (hdibSrc, x, y, &rgb) ;
               DibSetPixelColor (hdibDst, x, y, &rgb) ;
          }
     }
          // DIB with no color table to 8-bit DIB

     else if (iBitCountSrc >= 16 && iBitCountDst == 8)
     {
          hPalette = DibPalMedianCut (hdibSrc, 6) ;

          GetObject (hPalette, sizeof (WORD), &wNumEntries) ;

          hdibDst = DibCreate (cx, cy, 8, wNumEntries) ;
          
          for (i = 0 ; i < (int) wNumEntries ; i++)
          {
               GetPaletteEntries (hPalette, i, 1, &pe) ;
               rgb.rgbRed   = pe.peRed ;
               rgb.rgbGreen = pe.peGreen ;
               rgb.rgbBlue  = pe.peBlue ;
               rgb.rgbReserved = 0 ;

               DibSetColor (hdibDst, i, &rgb) ;
          }

          for (x = 0 ; x < cx ; x++)
          for (y = 0 ; y < cy ; y++)
          {
               DibGetPixelColor (hdibSrc, x, y, &rgb) ;

               DibSetPixel (hdibDst, x, y,
                    GetNearestPaletteIndex (hPalette, 
                         RGB (rgb.rgbRed, rgb.rgbGreen, rgb.rgbBlue))) ;
          }
          DeleteObject (hPalette) ;
     }
          // Any DIB to monochrome DIB

     else if (iBitCountDst == 1)
     {
          hdibDst = DibCreate (cx, cy, 1, 0) ;
          hPalette = DibPalUniformGrays (2) ;

          for (i = 0 ; i < 2 ; i++)
          {
               GetPaletteEntries (hPalette, i, 1, &pe) ;

               rgb.rgbRed   = pe.peRed ;
               rgb.rgbGreen = pe.peGreen ;
               rgb.rgbBlue  = pe.peBlue ;
               rgb.rgbReserved = 0 ;

               DibSetColor (hdibDst, i, &rgb) ;
          }

          for (x = 0 ; x < cx ; x++)
          for (y = 0 ; y < cy ; y++)
          {
               DibGetPixelColor (hdibSrc, x, y, &rgb) ;

               DibSetPixel (hdibDst, x, y,
                    GetNearestPaletteIndex (hPalette, 
                         RGB (rgb.rgbRed, rgb.rgbGreen, rgb.rgbBlue))) ;
          }
          DeleteObject (hPalette) ;
     }
          // All non-monochrome DIBs to 4-bit DIB

     else if (iBitCountSrc >= 8 && iBitCountDst == 4)
     {
          hdibDst = DibCreate (cx, cy, 4, 0) ;
          hPalette = DibPalVga () ;

          for (i = 0 ; i < 16 ; i++)
          {
               GetPaletteEntries (hPalette, i, 1, &pe) ;

               rgb.rgbRed   = pe.peRed ;
               rgb.rgbGreen = pe.peGreen ;
               rgb.rgbBlue  = pe.peBlue ;
               rgb.rgbReserved = 0 ;

               DibSetColor (hdibDst, i, &rgb) ;
          }

          for (x = 0 ; x < cx ; x++)
          for (y = 0 ; y < cy ; y++)
          {
               DibGetPixelColor (hdibSrc, x, y, &rgb) ;

               DibSetPixel (hdibDst, x, y,
                    GetNearestPaletteIndex (hPalette, 
                         RGB (rgb.rgbRed, rgb.rgbGreen, rgb.rgbBlue))) ;
          }
          DeleteObject (hPalette) ;
     }
          // Should not be necessary

     else
          hdibDst = NULL ;

     return hdibDst ;
}

Several different strategies are required for converting DIBs from one format to another.

To convert a DIB with a color table to another DIB that also has a color table but with a larger pixel width (that is, to convert a 1-bit DIB to a 4-bit or 8-bit DIB, or a 4-bit DIB to an 8-bit DIB), all that needs be done is to create the new DIB by calling DibCreate with the desired bit count but with a number of colors equal to the number of colors in the original DIB. The function then copies the pixel bits and the color table entries.

If the new DIB has no color table (that is, the bit count is 16, 24, or 32), then the DIB needs only to be created in the new format and pixel bits copied from the existing DIB with calls to DibGetPixelColor and DibSetPixelColor.

The next case is probably the most common: The existing DIB does not have a color table (that is, the bit count is 16, 24, or 32) and the new DIB has 8 bits per pixel. In this case, DibConvert calls DibPalMedianCut to create a optimum palette for the image. The color table of the new DIB is set to the RGB values in the palette. The DibGetPixelColor function obtains a pixel color from the existing DIB. This is converted to a pixel value in the 8-bit DIB by a call to GetNearestPaletteIndex and the pixel value is stored in the DIB by calling DibSetPixel.

When a DIB needs to be converted to a monochrome DIB, the new DIB is created with a color table containing two entries—black and white. Again, the GetNearestPaletteIndex helps convert colors in the existing DIB to a pixel value of 0 or 1. Similarly, when DIBs of 8 color bits or more need to be converted to 4-bit DIBs, the DIB color table is obtained from the DibPalVga function and GetNearestPaletteIndex also helps calculate the pixel values.

Although DIBBLE shows the basics of how an image-processing program might be started, such a program is never quite finished. Always another feature becomes obvious. But now, sadly, we must move on.