16.4 一个 DIB 位图库的实现

摘录于《Windows程序(第5版,珍藏版).CHarles.Petzold 著》P711

        在前面的章节中,我们学习了 GDI 位图对象、设备无关位图、DIB 区块,以及 Windows 调色板管理器。在这些知识的武装下,现在我们可以设计一组库函数,来帮助我们对位图进行操作。

        前面的 PACKEDIB 文件给出了一种可能的方法:通过一个指向头结构的指针来访问位图的信息。但是,这种方式对像素操作(“get pixel” 和 “set pixel”)的执行效率不高,因而不能满足通常的图像处理任务要求。我们需要一种方式,既能方便地访问位图信息,又能快速地进行像素操作

        一种可能的 C++ 解决方案是,创建一个 DIB 类,用其中的一个成员变量存放指向紧凑 DIB 的指针;其他成员变量和成员函数可帮助实现获取及设置 DIB 中像素的快速例程。但是既然我在本书第一章已经指明,你只需要了解 C 就可以贯通本书,因此 C++ 的解决方案就只能留给其他书了。

        当然,C++ 能做的 C 也能够做到。大量使用句柄的 Windows 函数就是很好的例子。应用程序除了知道句柄是一个数值以外,还知道什么呢?它知道这个句柄用于引用一个特定的对象,而且存在使用该对象的函数。很明显,操作系统利用该句柄以某种方式来引用这个对象的内部信息。句柄可以和指向结构的指针一样简单。

        比如说,假设有一组函数使用一个叫 HDIB 的句柄。HDIB 是什么呢?它有可能在某个头文件中被定义为:

typedef void * HDIB;
这个定义回答了“HDIB是什么”的问题——“与你无关”。

        而实际上,一个 HDIB 可能是一个指向结构的指针,该结构中除了包含紧凑 DIB 指针外,还有其他一些信息:

typedef struct
{
    BITMAPINFO * pBackedDib;
    int          cx, cy, cBitsPerPixel, cBytesPerRow;
    BYTE       * pBits;
}
DIBSTRUCTURE, * PDIBSTRUCTURE
该结构的其他五个字段包含了从紧凑 DIB 得到的信息,而把它们放在结构中则可以更快速地访问这些信息。DIB 库的各种函数都可以使用这个结构,而不是使用 pPackedDib 指针。一个 DIbGetPixelPointer 函数可以这样实现:

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

    return pdib->pBits + y * pdib->cBytesPerRow +
                         x * pdb->cBitsPerPixel / 8;
}
显然,上面的代码比起 PACKEDIB.C 中可能实现的“获取像素”的例程,其速度要快得多。

        尽管这种方法很合理,但我决定放弃紧凑 DIB 而基于 DIB 区块来实现我的 DIB 库。这样做,我们不仅保持了紧凑 DIB 的所有灵活性(即按照某种设备无关的方式进行 DIB 像素位的操作),而且在 Windows NT 下执行速度更快。

16.4.1  DIBSTRUCT 结构

        DIBHELP.C 文件是一个超过 1000 行的程序,顾名思义,它用于提供 DIB 操作方面的帮助,很快我们将分几部分介绍它。这里,先让我们看看 DIBHELP 函数使用的结构,该结构定义如下:

typedef struct
{
    BYTE         * ppRow;      // array of row pointers
    int          iSignature;   // = "Dib "
    HBITMAP      hBitmap;      // handle returned from CreateDIBSection
    BYTE         * pBits;      // pinter 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;

        现在我们先略过第一个字段。它之所以是第一个字段是因为它能使某些宏操作更容易,而在先介绍其他字段之后再回过头来介绍它会更容易理解。

        该结构中的第二个字段在初始化时(由 DIBHELP.C 中的某个 DIB 创建函数执行)被设置为文本串“Dib”的二进制形式,一些 DIBHELP 函数用它来验证这个结构指针的有效性

        该结构中的第三个字段——hBitmap——存储了 CreateDIBSection 函数返回的位图句柄,和第 14 章的 GDI 位图句柄一样,它可用于各种不同的操作。不同的一点是,这个由 CreateDIBSection 返回的句柄指向的位图是以设备无关的格式存储的,直到它被 BitBlt 或 StretchBlt 调用传给输出设备。

        DIBSTRUCT 结构中的第四个字段存储了一个指向位图位的指针,它的值也在 CreateDIBSection 函数中设定。如上文所述,存储位图的内存块由操作系统管理,但应用程序可以访问该内存块该内存块在位图句柄被删除后会自动释放

        DIBSTRUCT 结构中的第五个字段是一个 DIBSECTION 结构。如前文所述,从 CreateDIBSection 得到的位图句柄可以传给 GetObject 函数,以便获取 DIBSECTION 结构中有关位图的信息:

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

        在此提醒你DIBSECTION 结构在 WINGDI.H 中是如下定义的

typedef struct tagDIBSECTION {
    BITMAP                dsBm;
    BITMAPINFOHEADER      dsBmih;
    DWORD                 dsBitfields[3];  // Color masks
    HANDLE                dshSection;
    DWORD                 dsOffset;
}
DIBSECTION, * PDIBSECTION;
第一个字段是一个 BITMAP 结构,CreateBitmapIndirect 用这个类型来创建一个位图对象;GetObject 函数用这个类型来获取设备相关位图 (DDB) 的信息。第二个字段是一个 BITMAPINFOHEADER 结构。无论传给 CreateDIBSection 函数的位图信息结构如何,DIBSECTION 结构将总是包含一个 BITMAPINFOHEADER 结构(而非 BITMAPCOREHEADER)。这就意味着,DIBHELP.C 中的很多函数在访问这一结构时不必考虑 OS/2 的兼容问题。

        你应该记得,对于 16 位或 32 位 DIB,如果 BITMAPINFOHEADER 结构中的字段 biCompression 被设置为 BI_BITFIELDS,就意味着我们需要使用三个掩码转换图像的格式为 RGB 色彩模式。这三个掩码通常被存储在 BITMAPINFOHEADER 结构后,DIBSECTION 结构中第三个字段就用来存储这三个掩码。

        DIBSECTION 结构中的最后两个字段用于从文件创建设备无关位图。我们的库 DIBHELP 没有用到这个功能,所以也用不到这两个字段。

        DIBSTRUCT 结构中最后两个字段存储左移位和右移位的值,用于位图编码格式的转换,我们在第 15 章讨论过它们的用法。

        让我们再回到 DIBSTRUCT 结构的第一个字段(PBYTE * ppRow),当 DIB 首次建立时,这个字段就被设置成一个指向指针数组的指针。在这个指针数组中,每个指针都指向位图中的一行像素。利用这些指针,我们可以更快访问像素信息。数组的第一个指针指向位图中的最上面的一行像素,最后一个指针指向 DIB 图像的最下面的一行(和 DIBSTRUCT 结构中的 pBits 字段一样)。

16.4.2  信息获取函数

        DIBHELP.C 的开始定义了 DIBSTRUCT 结构,并提供了一组函数用于帮助应用程序获取 DIB 区块的信息。图 16-20 给出了 DBHELP.C 的第一部分。

DIBHELP.C (第 1 部分)

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

#include <Windows.h>
#include "DibHelp.h"

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

typedef struct<pre name="code" class="cpp">SIGNATURE 
{
    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 = (PDIBSTRUCT)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 = (PDIBSTRUCT)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 = (PDIBSTRUCT)hdib;

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

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

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

    if (!DibIsValid(hdib) || i < 0 || i > 2)
        return 0;

    return pdib->iRShift[i];
}


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

int DibLShift(HDIB hdib, int i)
{
    PDIBSTRUCT pdib = (PDIBSTRUCT)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 = (PDIBSTRUCT)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 = (PDIBSTRUCT)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 = (PDIBSTRUCT)hdib;

    if (!DibIsValid(hdib))
        return 0;

    return pdib->ds.dsBitfields;
}

void * DibBitsPtr(HDIB hdib)
{
    if (!DibIsValid(hdib))
        return NULL;

    return ((PDIBSTRUCT)hdib)->pBits;
}

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

BOOL DibGetColor(HDIB hdib, int index, RGBQUAD * prgb)
{
    PDIBSTRUCT    pdib = (PDIBSTRUCT)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;
}

/*-------------------------------------------------------
    DibSetColor: Sets an entry in the DIB color table
--------------------------------------------------------*/

BOOL DibSetColor(HDIB hdib, int index, RGBQUAD * prgb)
{
    PDIBSTRUCT    pdib = (PDIBSTRUCT)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;
}
 

        DIBHELP.C 中,大部分函数都一目了然,不需讲述。DibIsValid 函数是整个系统的“防弹衣”,其他函数调用它来确保 DIBSTRUCT 结构的有效性。所有这些函数的第一个并且通常只有一个 HDIB 参数,我们很快将看到,它是一个在 DIBHELP.H 定义为 void 类型的指针。函数通常把它转换为 PDIBSTRUCT 类型的指针,并通过它来访问 DIBSTRUCT 结构中的字段。

        注意,DibIsAddressable 函数返回值的类型是 BOOL。这个函数也可以称为 DibIsNotCompressed 函数。其返回值指出一个位图是否可以被寻址(即不能压缩)。

        以调用 DibInfoHeaderSize 开始的一组函数用于获取 DIB 区块中各部分的大小。这些函数可以用来将一个 DIB 区块转换为紧凑的 DIB 存于内存中,或存于文件中。下面的一组函数用于获取 DIB 中各部分的指针。

        在 DIBHELP.C 中,虽然有一个叫 DibInfoHeaderPtr 的函数用于获取 BITMAPINFOHEADER 结构的指针,但是并没有一个函数可以获取 BITMAPINFO 结构(紧跟着位图颜色表定义的那个结构)的指针。这是因为应用程序没有直接访问这类结构的权限。虽然我们可以访问定义在 DIBSECTION 中的 BITMAPINFOHEADER 的结构和颜色掩码,并能通过 CreateDIBSection 返回的指针访问像素信息,但DIB 颜色表只能间接通过调用 GetDIBColorTable 和 SetDIBColorTable 来访问。这两函数被封装在 DIBHELP 的 DibGetColor 和 DibSetColor 函数里。

        在 DIBHELP.C 稍后部分,DibCopyToInfo 返回一个指向 BITMAPINFO 结构的指针并为这个结构填信息,但这个指针和取得内存中现有结构的指针并不完全一样。

16.4.3  读/写像素信息

        使用紧凑 DIB 或 DIB 区块的很大一个好处是应用程序可以直接操作设备独立位图的像素。图 16-21 中,DIBHELP.C 的第二部分代码便是为此目的提供的函数。

DIBHELP.C (第 2 部分)

/*-----------------------------------------------------------------
    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 = (PDIBSTRUCT)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 = (PDIBSTRUCT)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;
}

        DIBHELP.C 这部分首先是一个 DibPixelPtr 函数,用来获取存储特定像素的内存指针。如前文所述,DIBSTRUCT 结构的 ppRow 字段是一个指向从上向下排列的 DIB 像素行的指针。因此,以下指针是指向位图中最上面一行最左边像素的指针:

((PDIBSTRUCT) hdib)->ppRow)[0];
而以下指针是指向位于(x, y)的像素的指针:
((PDIBSTRUCT) hdib)->ppRow)[y] + (x * DibBitCount (hdib) >> 3);
需要注意的是,如果 DIB 不可寻址(即压缩的),或者 x 和 y 参数是负值或指向 DIB 外面的区域,DibPixelPtr 函数就返回一个 NULL 值。这些检查降低了 DibPixelPtr 函数(以及任何调用 DibPixelPtr 的函数)的运行速度,后面我会介绍如何改进。

        文件中随后出现的函数 DibGetPixel 和 DibSetPixel 就利用了 DibPixelPtr。对于 8 位、16 位和 32 位的位图,这些函数只需要把指针转换到相应的数据位,就可以访问像素值。对于 1 位和 4 位的位图,则需要使用掩码和移位操作。

        DibGetColor 函数用于读取像素点的颜色,其结果被放在 RGBQUAD 结构中。对于 1 位、4 位和 8 位的位图,其 RGB 色值需要通过位图的颜色表来查找。对于 16 位、24 位和 32 位的位图,RGB 色值可通过掩码和移位操作从像素数据得出。DibSetColor 的功能正好相反,我们可以通过这个函数来设定像素点的颜色。这个函数只用于 16 位、24 位和 32 位的位图。

16.4.4  创建和转换

        图 16-22 所示的 DIBHELP 库的第三部分(也是最后一部分)说明了如何创建 DIB 区块,如何与紧凑 DIB 进行相互转换。

DIBHELP.C  (第 3 部分)

/*---------------------------------------------------------------
    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, (VOID**)&pBits, NULL, 0);

    if (hBitmap == NULL)
        return NULL;

    if (NULL == (pdib = (PDIBSTRUCT)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 = (PBYTE *)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 = (DIBSTRUCT *)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(BITMAPINFO) + (cEntries - 1) * sizeof(RGBQUAD);

    if (NULL == (pbmi = (PBITMAPINFO)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 = (BITMAPINFO*)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 heigth;
--------------------------------------------------------------------*/

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

    if (!DibIsValid(hdibSrc))
        return NULL;

    if (NULL == (pbmi = (BITMAPINFO *)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 = (BYTE*)DibBitsPtr(hdibSrc);
        pBitsDst = (BYTE*)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 = (PDIBSTRUCT)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 = (BITMAPINFO*)GlobalLock(hGlobal);
    }
    else
    {
        pPackedDib = (BITMAPINFO*)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 = (PDIBSTRUCT)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 = (BITMAPINFO*)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 entrie 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;
}

        这部分首先定义两个小函数,用于在 16 位和 32 位位图中根据掩码产生左位移和右位移值,这些函数已经在第 15 章里介绍过。

        DibCreateFromInfo 函数是 DIBHELP 库中唯一一个调用 CreateDIBSection 并为 DIBSTRUCT 结构分配内存的函数。其他所有创建或复制函数都通过调用该函数来完成它们的工作。传递给 DibCreateFromInfo 的唯一参数是一个指向 BITMAPINFO 结构的指针。这个结构中的颜色表必须存在,但不需要包含有效数值。在 DibCreateFromInfo 函数调用 CreateDIBSection 后,它会初始化 DIBSTRUCT 结构的所有字段。需要注意的是,在对 DIBSTRUCT 结构的 ppRow 字段(指向位图像素行地址的指针)赋值时,对于从上到下或者从下到上的 DIB,其算法是不同的。ppRow 的第一个元素总是 DIB 的最上面一行。

        DibDelete 函数用于删除通过 DibCreateFromInfo 创建的位图,并释放相应的内存。

        相对于 DibCreateFromInfo 函数,应用程序也许更像是可以从应用程序调用的 DibCreate 函数。这个函数的前三个参数提供了位图的宽和高(像素)和每个像素所占的位数。最后一个参数指定颜色表的大小,如果设置为 0,意味着默认的颜色表(其大小由位数决定);如果不为 0,那它就直接表颜色表的大小(但不应该超过默认值)。

        DibCopy 函数从一个现有的位图创建出一个新的位图。这个函数利用 DibCreateInfo 来创建和初始化 BITMAPINFO 结构,并为其赋值。DibCopy 函数有一个 BOOL 类型的参数,用来决定是否用源位图的宽和高来初始化辛未土,我们将在后面看到它的应用。

        函数 DibCopyToPackedDib 和 DibCopyFromPackedDib 通常用于在应用程序和剪贴板之间传递 DIB。DibFileLoad 函数从一个 DIB 文件创建一个 DIB 区块。DibFileSave 则将一个位图存于一个 DIB 文件。

        最后,函数 DibCopyToDdb 从一个 DIB 创建一个 GDI 位图对象。注意,这个函数需要使用当前调色板和程序窗口的句柄。窗口句柄用来获取当前实现的调色板的设备环境。然后,我们才能调用函数 CreateDIbitmap。我们在前面的例子 SHOWDIB7 见过这种用法。

16.4.5  DIBHELP 头文件的宏

        图 16-23 显示了 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)

        该头文件把 HDIB 句柄定义成一个空指针。应用程序不必知道 HDIB 句柄指向的内部数据结构。头文件中还包括对了 DIBHELP.C 中所有函数的声明。接着就是宏定义,非常特殊的宏。

        回顾一下 DIBHELP.C 文件里的 DibPixlePtr,DibGetPixel 和 DibSetPixel 函数,如果想提高它们的性能,你会想到几个可能的解决方法第一种方法就是去掉所有的检查部分,相信应用程序不会以无效的参数来调用这些函数。另外,就是去掉一些函数调用,比如 DibBitCount,转而直接使用指向 DIBSTRUCT 结构的指针来获取所需信息。

        还有一种提高性能但不是很直接的方法,就是去掉与每个像素所占位数有关的逻辑计算,转而分别给 DIB 的每种类型(比如 DibGetPixel1, DibGetPixel4,DibGetPixel8,以此类推)定义各自的函数。更进一步的优化是去掉整个函数调用,把逻辑部分整合到内联函数或宏里

        DIBHELP.H 采用了定义宏的方法。文件基于 DibPixelPtr,DibGetPixel 和 DibSetPixel 函数定义了三种宏。这些宏对应于特定的位计数。

16.4.6  DIBBLE 程序

        图 16-24 所示的 DIBBLE 程序显示了 DIBHELP 的函数和宏的功能。虽然 DIBBLE 是本书最长的程序,其实它不过是在一些简单的数字图像处理程序里就能看到的原始例子。显然,可以把 DIBBLE 程序改进成多文档界面(MDI),我们会在第 19 章学习具体做法。

/*----------------------------------------
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

			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 = (BYTE*)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;

		// Programmers-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 Visual C++ generated resource script.
//
#include "resource.h"

/
//
// Menu
//

DIBBLE MENU
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
RESOURCE.H (excerpts)

// Microsoft Visual C++ 生成的包含文件。
// 供 Dibble.rc 使用
//
#define IDM_FILE_OPEN                   40001
#define IDM_FILE_SAVE                   40002
#define IDM_FILE_PRINT                  40003
#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 使用的一些文件。DIBCONV 文件(DIBCONV.C 和 DIBCONV.H) 能把 DIB 在不同格式之间进行转换。比如,从每像素 24 位转成每像素 8 位。DIBPAL 文件(DIBPAL.C 和 DIBPAL.H)能创建调色板。

        DIBBLE 包含 WndProc 里的三个重要的静态变量。它们分别是一个叫 hdib 的 HDIB 句柄、一个叫 hPalette 的 HPALETTE 句柄和一个叫 hBitmap 的 HBITMAP 句柄。HDIB 被 DIBHELP 里的多个函数用到;HPALETTE 被 DIBPAL 里的多个函数和 CreateHalftonePalette 函数用到;HBITMAP 句柄被 DIBHELP.C 的 DibCopyToDdb 函数用到,以加速屏幕显示,尤其是在 256 色显示模式下。但是,每当程序创建了新的 DIB 区块,或是创建了一个不同的调色板,HBITMAP 句柄必须被重新创建

        接下来让我们按功能而非按代码的顺序,来研究一下 DIBBLE。

      文件加载和保存

        DIBBLE 在处理 IDM_FILE_LOAD 和 IDM_FILE_SAVE 的 WM_COMMAND 消息时加载并且保存 DIB 文件。处理这些消息时,DIBBLE 通过分别调用 GetOpenFileName 和 GetSaveFileName 打开文件对话框。

        对于 File Save(文件保存)菜单命令,DIBBLE 仅需要调用 DibFileSave。对于 File Open(文件打开)菜单命令,DIBBLE 应该首先删除之前的 HDIB,调色板和位图对象。程序通过给自己发送一个 WM_USER_DELETEDIB 消息完成该操作,然后程序在处理该消息时,调用 DibDelete 和 DeleteObject。之后,DIBBLE 调用 DIBHELP 中定义的 DibFileLoad 函数,并且给自己发送 WM_USER_SETSCROLLS 和 WM_USER_CREATEPAL 消息来重置滚动条和创建调色板。在 WM_USER_CREATEPAL 消息中,程序还根据 DIB 区块创建了一个新的 DDB。

      显示、滚动和打印

        DIBBLE 的菜单能让程序按实际尺寸在工作区的左上角或者中间显示 DIB。菜单还能让程序显示拉伸到填充了整个工作区的 DIB,或者是在保持适当的纵横比的同时向整个工作区拉伸 DIB。通过 DIBBLE 的 Show 菜单,可以选择想要的显示方式。这四种显示选项也在第 17 章的 SHOWDIB2 程序里用到。

        在处理 WM_PAINT 消息期间及处理 File | Print 菜单命令时, DIBBLE调用 DisplayDib 函数。值得注意的是,DisplayDib 使用了 BitBlt 和 StretchBlt,而不是 SetDIBitsToDevice 和 StretchDIBits。在处理 WM_PAINT 消息时,传给 DisplayDib 函数的位图句柄是程序函数在处理 WM_USER_CREATEPAL 消息时调用 DibCopyToDdb 函数创建的那个句柄。这个 DDB 和显示设备环境兼容。处理 File | Print 命令时,DIBBLE 用从 DIBHELP.C 里的 DibBitmapHandle 函数得到的 DIB 区块句柄来调用 DisplayDib。

        还要注意的是,DIBBLE 包含了一个静态 BOOL 变量 fHalftonePalette,如果 hPalette 是从 CreateHalftonePalette 函数得到的,该变量就被设为 TRUE。这使得 DisplayDib 函数只能调用 StretchBlt 而不是 BitBlt,即使 DIB 按实际大小显示也这样。fHalftonePalette 变量也使得程序在处理 WM_PAINT 消息时把 DIB 区块句柄,而不是 DibCopyToDdb 函数创建的位图句柄传递给 DisplayDib 函数。本章早前讨论了半色调调色板的使用,并且在 SHOWDIB5 程序中展示了怎样使用。

        在本书的范例程序中,DIBBLE 是第一个能够在工作区用滚动条显示 DIB 的程序。只有当 DIB 按实际大小显示时,滚动条才会出现。处理 WM_PAINT 消息时,WndProc 只是把当前滚条的位置 DisplayDib 函数。

      剪贴板

        对于 Cut(剪贴)和 Copy(复制)菜单项,DIBBLE 调用 DIBHELP 里的 DibCopyToPackedDib 函数。该函数把 DIB 里的所有部分放入一块大容量的内存。

        在本书的范例程序中,DIBBLE 是第一个粘贴剪贴板里的 DIB 的程序。这个操作包括调用 DibCopyFromPackedDib 函数,并替换窗口过程先前保存的 HDIB,调色板和位图。

      翻转和旋转

        除了标准的 Cut、Copy、Paste 和 Delete 选项,DIBBLE 程序里的 Edit 菜单还包括两个额外的选项:Flip 和 Rotate。Flip 选项能让位图沿着水平轴翻转,即上下翻转。Rotate 选项能使位图顺时针旋转 90 度。这两个函数都需要复制一份 DIB 并访问 DIB 的所有像素。(这两个函数并不需要创建新的调色板,所以调色板没有被删除,也没有被重新创建。)

        Flip 菜单项使用了 DibFlipHorizontal 函数,此函数在 DIBBLE.C 文件中定义。函数调用 DibCopy 来获取 DIB 的一份副本。然后用一个循环把原有的 DIB 像素复制到新的 DIB 里,但是复制像素时要使得图像上下翻转。值得注意的是,该函数调用 DibGetPixel 和 DibSetPixel 函数。这些通用函数(也许速度达不到我们的期望)来自于 DIBHELP.C。

        DibRotateRight 函数演示了 DibGetPixel 和 DibSetPixel 的区别,以及如何利用它们对应的宏(在 DIBHELP.H 中定义)来提高速度。但首先要注意,在调用 DibCopy 函数的时候,我们把其第二个参数设置为 TRUE,这样可使 DibCopy 函数翻转源位图的宽和长来创建新的位图。还要注意的是,像素值并不是由 DibCopy 函数复制的,而是由 DibRotateRight 函数复制的,它有 6 个不同的循环用于复制,对应于 6 种不同的 DIB 像素的位数(1 位,4 位,8 位,16 位,24 位和 32 位。)代码看起来很长,但是速度提高了很多

        虽然应用程序可以用 Flip Horizontal(水平翻转)和 Rotate Right(右旋转)实现垂直翻转、左旋转和 180 度旋转,但是一个程序通常会直接实现所有的这些函数。当然,DIBBLE 只不过是一个演示程序。

16.4.7  简单的调色板的优化的调色板

        在 DIBBLE 程序中,在 256 色的图像显示系统中可以选择不同的调色板。DIBBLE 的菜单中包含所有的调色板选项。在图 16-25 中,除了半色调调色板(由 Windows 函数直接创建)以外,DIBPAL 文件提供了创建不同调色板的所有函数。

/*-----------------------------------
   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 -- 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 = (LOGPALETTE*)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 = (LOGPALETTE *)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 = (LOGPALETTE *)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 color
-------------------------------------------------------------------------*/

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

	iNum = iNumR * iNumG * iNumB;

	plp = (LOGPALETTE *)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 = (LOGPALETTE*)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 apacked 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 = (int*)calloc(iArraySize, sizeof(int))))
		return NULL;

		// Get the color counts

	AccumColorCounts(hdib, piCount, iRes);

		// Set up a palette

	plp = (LOGPALETTE*)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 pixles 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 void * pbox1, const void * pbox2)
{
	return ((BOXES *)pbox1)->iBoxCount - ((BOXES*)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 = (int*)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 = (LOGPALETTE*)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;
}

        第一个函数 DibPalDibTable 应该看起来比较熟悉。它从 DIB 颜色表中生成一个调色板。这个功能类似于 PACKEDIB.C 文件中的 PackedDIbCreatePalette 函数(来自于本章前些部分使用的 SHOWDIB3 程序)。在 SHOWDIB3 中,该函数仅在 DIB 有一个颜色表的情况下工作。如果试图在一个 8 位的视频模式中显示一个 16 位,24 位,甚至是 32 位的 DIB,该函数则无能为力。

        当运行在默认的 256 色显示模式下,DIBBLE 会先尝试调用 PalDibTable 来从 DIB 颜色表中生成调色板。如果 DIB 没有对应的颜色表,DIBBLE 会调用 CreateHalftonePalette 函数,并设置 fHalftonePalette 值为 TRUE。这一切都发生在处理 WM_USER_CREATEPAL 消息的时候。

        DIBPAL.C 还实现了一个我们熟悉的 DibPalAllPurpose 函数,这个函数类似于 SHOWDIB4 程序中的 CreateAllPurposePalette。也可以从 DIBBLE 的菜单中选择这个调色板。

        在 256 色系统中显示位图的有趣的一点是,可以完全控制 Windows 用来显示图像的颜色。选择并生成一个调色板后,Windows 将只使用这个调色板中的颜色。

        举个例子来说,你可以用 DibPalUniformGrays 函数生成一个只有灰色谱的调色板。用两种灰度可以生成一个只有 00-00-00 (黑色)和 FF-FF-FF(白色)的调色板。你可以用一些图片来试一下,该试验能获得在一些摄影师中流行的“粉笔和木炭”的高对比效果。使用 3 级灰色色调就会在黑色和白色之间增加一个中等灰色色调,使用 4 级灰色色调就会增加 2 个灰色色调。

        使用 8 级灰色色调时你可能会看到明显的轮廓,它是同样的灰色色调构成的不规则补丁。在这样的图像里,最接近颜色算法显然是工作的,但肯定不能带来任何美感,转而使用 16 级灰色色调能极大地提高图像质量。使用 32 级灰色色调只是消除任何轮廓,64 级灰色色调则被普遍认为是大多数显示设备的极限。此后,更高的级数对图像质量带来的提高效果微乎其微。在 6 位色彩分辨率的显示设备上,高于 64 级的灰色色调不会带来任何更好的效果

        迄今为止,对于在 8 位视频模式下显示 16 位、24 位或 32 位的 DIB,我们力所能及的是制定一个通用调色板(对灰色色调图像绰绰有余,但对彩色图像通常还有不足),或使用能把万能调色板和抖动显示结合起来的半色调调色板

        你还会注意到,在 8 位色彩模式下, 如果用通用调色板来处理大的 16 位、24 位或 32 位的 DIB,为了显示图像它需要花相当多的时间从 DIB 创建 GDI 位图对象。如果不使用调色板,而从 DIB 创建 DDB,所花时间会少一些(你可以在比较程序 SHOWDIB1 和程序 SHOWDIB4 在 8 为色彩模式下显示 24 位 DIBs 时的性能看到这种区别)。这是为什么呢?

        问题的根源在于这个最近颜色搜索算法。本来,在 8 位色彩模式下显示和一个 24 位 DIB(或将一个 DIB 转换为 DDB)的时候,GDI 需要把 DIB 中的每一个像素色值映射到 20 种保留色中的一个。能够达到这个目的的唯一的方法是计算哪个保留色最接近像素颜色。这涉及计算像素色值到每一个保留色在三维 RGB 坐标上的距离。这种算法很耗时间,尤其当我们要处理的对象是一个拥有几百万像素的图像的时候。

        在创建一个像 DIBBLE 和 SHOWDIB4 程序的通用调色板那样的 232 色调色板的时候,实际上为最接近色算法的计算量增加了 11 倍。这就是为什么整个图像显示操作明显变慢的原因。

        我们在这里得到的经验是,不要在 8 为色彩模式下显示 24 位(或 16,32 位)的 DIB。应当通过找出一个最能匹配原图颜色范围的 256 色调色板,把它们转化为 8 位的 DIB。这种调色板通常被称为“最优调色板”。1982 年,Paul Heckbert 在 Computer Graphics 杂志上发表了名为 “Color Image Quantization for Frame Buffer Displays” 文章,其中的论述对我研究“最优调色板”问题颇有助益。

      均匀分布

        对 256 色调色板来说最简单的方式是选取均匀分布的 RGB 颜色值范围,类似于我们在 DibPalAllPurpose 函数中采用的方法。这种方法的好处在于,并不需要检验 DIB 中像素的实际色值。DibPalCreateUniformColors 函数就是用这种方式创建的调色板。

        一种合理的分布可以为,8 级红色色调,8 级绿色色调和 4 级的蓝色色调(蓝色对眼睛来说是相对没那么敏感)。调色板包含了这些色调级值的所有组合,其中红色和绿色的色调级值为 0x00, 0x24, 0x49, 0x6D, 0x92, 0xB6, 0xDB 和 0xFF,蓝色的色调级值为 0x00, 0x55, 0xAA 以及 0xFF,这样我们总共有 256 种颜色组合。另一种可行的均匀分布的调色板使用六级红绿蓝色调,红绿蓝各色的色调级值都是 0x00, 0x33, 0x66, 0x99, 0xCC 以及 0xFF,它包含了三种颜色的所有组合,这样我们的调色板就有 6 的三次方(216)种颜色。

      流行度算法

        流行度算法名副其实,选取位图中被使用最多的 256 种 RGB 色值,用它们构成调色板。DIBPAL 中的 DibPalPopularity 函数实现了这种算法。

        然而,问题并非那么简单。如果我们直接用 RGB 色值,就是说用所有的 24 位来统计谁被重复的最多,我们就需要 64MB 的内存空间来统计所有的颜色。并且,你会发现真正重复的颜色很少。

        为了解决这个问题,我们可以只用 RGB 色值权重最高的几位。比如,用 6 位而非 8 位。这样做也合理,因为很多扫描和视频显示设备的解析度也只有 6 位。这样,我们只需 256KB 个计数值,或者 1MB 就够了。使用 5 位色值能进一步将内存消耗减少到 32768 个字节而且大多数时候,5 位比 6 位更好,我们用 DIBBLE 库和一些彩色图片试试就知道了。

      中分算法

        DIBPAL.C 中的 DibPalMedianCut 函数实现了 Paul Heckbert 的中分算法。这个算法虽然概念上很简单,但是实现上比流行度算法更复杂,需要用到递归的方式。

        在中分算法中,RGB 的值被映射到一个三维坐标系统上,有些坐标的点会代表图像中的多个像素。在坐标中找出一个能够包容所有图像像素的立方体。取最长的一条边,把这个立方体分成两部分,使划分后的每个部分含有相同数量的像素。然后对这两个盒子做同样的划分。2 个分成 4 个,4 个分成 8 个,8 个分成 16 个,然后是 32 个,64 个,128 个,直至 256 个。

        现在,你得到了 256 个盒子,每个都包含了相等的像素数,我们对每个盒子里像素的 RGB 颜色取其平均值(RGB),并用这些结果来设置调色板。

        在实际中,这些盒子不一定包含等量的像素。例如,经常一个盒子里只有一个点,对应于图像中的许多像素。这种情况在黑色和白色里经常出现。有时,盒子根本没有包含任何像素。如果是这样,你可以把很多这些盒子去掉,但我决定不这么做。

        另外一种优化调色板方法是叫八叉树量化(octree quantization)。Jeff Prosise 在 1996 年 8 月 Microsoft Systems Journal 杂志上讨论了这个技术。

16.4.8  格式转换

        DIBBLE 库中的 DibConvert 函数实现了 DIB 位图格式转换功能,图 16-26 所示的 DIBCONV 文件给出了使用 DIbConvert 函数的代码。

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

HDIB DibConvert (HDIB hdibSrc, int iBitCountDst) ;
/*-------------------------------------------------------------
	DIBCONV.C -- Converts DIBs from one format to another
				(c) Charlees 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;
}

        对应不同的 DIB 格式变化,我们需要不同的转换策略。

        如果源 DIB 和目标 DIB 都有颜色表,并且目标 DIB 的像素数位数比源 DIB 高(比如把 1 位 DIB 转换为 4 位或 8 位 DIB,或者把 4 位 DIB 转换为 8 位 DIB),我们需要把目标像素位数和源 DIB 像素颜色数传递给 DibCreate 函数来创建目标 DIB,然后,DibCreate 会复制像素点阵和颜色表。

        如果目标 DIB 没有颜色表(即 16 位、24 位或 32 位 DIB),我们只需根据目标 DIB 的格式创建目标 DIB,并通过调用 DibGetPixelColor 和 DibSetPixelColor 函数在新的 DIB 中设置像素点阵。

        下面的情况可能更普遍,源 DIB 没有颜色表(16 位、24 位或 32 位 DIB),而目标 DIB 有颜色表(如 8 位 DIB)。这种情况下,DibConvert 调用 DibPalMedianCut 函数来为这个图像创建一个优化的调色板。目标 DIB 的颜色表被设置成调色板里的 RGB 值。然后我们调用 DibGetPixelColor 函数从源 DIB 获取像素点的 RGB 值,再用 GetNearestPaletteIndex 函数得到 8 位 DIB 对应的像素值,最后调用 DibSetPixel 设置目标 DIB 的像素值。

        当目标 DIB 是单色 DIB 的时候,它的调色板只有两个数据:黑色和白色。这时候,我们仍需调用 GetNearestPaletteIndex 函数来把源色值转化为 0 或 1。同理,从 8 位 DIB 或更高位数的 DIB 转换为 4 位的 DIB 的时候,我们仍可调用 DibPalVga 和 GetNearestPaletteIndex 函数取得 DIB 颜色表和计算像素值。

        DIBBLE 库函数讲到这里,可以说仅仅给出了一些图像处理的基本函数,还有很多功能可以扩展。但是,我们需要进入下一章了。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值