当前位置: 首页 > 文档资料 > Windows 程序设计 >

调色盘管理器

优质
小牛编辑
152浏览
2023-12-01

如果硬件允许,本章就没有存在的必要。尽管许多现代的显示卡提供24位颜色(也称「true color」或「数百万色」)或16位颜色(「增强色」或「数万种颜色」),一些显示卡-尤其是在便携式计算机上或高分辨率模式中-每个图素只允许8位。这意味着仅有256种颜色。

我们用256种颜色能做什么呢?很明显,要显示真实世界的图像,仅16种颜色是不够的,至少要使用数千或数百万种颜色,256种颜色位于中间状态。是的,用256种颜色来显示真实世界的图像足够了,但需要根据特定的图像来指定这些颜色。这意味着操作系统不能简单地选择「标准」系列的256种颜色,就希望它们对每个应用程序都是理想的颜色。

这就是Windows调色盘管理器所要涉及的全部内容。它用于指定程序在8位显示模式下执行时所需要的颜色。如果知道程序肯定不会在8位显示模式下执行,那么您也不需要使用调色盘管理器。不过,由于补充了位图的一些细节,所以本章还是包含重要信息的。

使用调色盘

传统上讲,调色盘是画家用来混合颜色的板子。这个词也可以指画家在绘画过程中使用的所有颜色。在计算机图形中,调色盘是在图形输出设备(例如视讯显示器)上可用的颜色范围。这个名词也可以指支持256色模式的显示卡上的对照表。

视频硬件

显示卡上的调色盘对照表运作过程如下图所示:

在8位显示模式中,每个图素占8位。图素值查询包含256RGB值的对照表的地址。这些RGB值可以正好24位宽,或者小一点,通常是18位宽(即主要的红、绿和蓝各6位)。每种颜色的值都输入到数字模拟转换器,以得到发送给监视器的红、绿和蓝三个模拟信号。

通常,软件可以用任意值来加载调色盘对照表,但这对设备无关的窗口接口,例如MicrosoftWindows,会有一些干扰。首先,Windows必须提供软件接口,以便在不直接干扰硬件的情况下,应用程序就可以存取调色盘管理器。第二个问题更严重:因为所有的应用程序都共享同一个视讯显示器,而且同时执行,所以一个应用程序使用了调色盘对照表可能会影响其它程序的使用。

这时就需要使用Windows调色盘管理器(在Windows3.0中提出)了。Windows保留了256种颜色中的20种,而允许应用程序修改其余的236种。(在某些情况下,应用程序最多可以改变256种颜色中的254种-只有黑色和白色除外-但这有一点麻烦)。Windows为系统保留的20种颜色(有时称为20种「静态」颜色)如表16-1所示。

表16-1 256种颜色显示模式中的20种保留的颜色

图素位

RGB值

颜色名称

图素位

RGB值

颜色名称

00000000

00 00 00

11111111

FF FF FF

00000001

80 00 00

暗红

11111110

00 FF FF

00000010

00 80 00

暗绿

11111101

FF 00 FF

洋红

00000011

80 80 00

暗黄

11111100

00 00 FF

00000100

00 00 80

暗蓝

11111011

FF FF 00

00000101

80 00 80

暗洋红

11111010

00 FF 00

绿

00000110

00 80 80

暗青

11111001

FF 00 00

00000111

C0 C0 C0

亮灰

11111000

80 80 80

暗灰

00001000

C0 DC C0

美元绿

11110111

A0 A0 A4

中性灰

00001001

A6 CA F0

天蓝

11110110

FF FB F0

乳白色

在256种颜色显示模式下执行时,由Windows维护系统调色盘,此调色盘与显示卡上的硬件调色盘对照表相同。内定的系统调色盘如表16-1所示。应用程序可以通过指定「逻辑调色盘(logicalpalettes)」来修改其余236种颜色。如果有多个应用程序使用逻辑调色盘,那么Windows就给活动窗口最高优先权(我们知道,活动窗口有高亮显示标题列,并且显示在其它所有窗口的前面)。我们将用一些简单的范例程序来检查它是如何工作的。

要执行本章其它部分的程序,您可能需要将显示卡切换成256色模式。在桌面上单擎鼠标右键,从菜单中选择「属性」,然后选择「设定」页面标签。

显示灰阶

程序16-1所示的GRAYS1程序没有使用Windows调色盘管理器,而尝试用正常显示的65级种阶作为从黑到白的多种彩色的「来源」。

程序16-1 GRAYS1
GRAYS1.C  
/*---------------------------------------------------------------------------  
  GRAYS1.C --   Gray Shades (c) Charles Petzold, 1998  
-----------------------------------------------------------------------------*/  
#include <windows.h>  
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;  
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,  PSTR szCmdLine, int iCmdShow)  
{   static TCHAR szAppName[] = TEXT ("Grays1") ;   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=  NULL ;   wndclass.lpszClassName   =  szAppName ;  
 if (!RegisterClass (&wndclass))   {  MessageBox (  NULL, TEXT ("This program requires Windows NT!"), szAppName, MB_ICONERROR) ;return 0 ;   }  
  hwnd = CreateWindow (szAppName, TEXT ("Shades of Gray #1"),  WS_OVERLAPPEDWINDOW,   CW_USEDEFAULT, CW_USEDEFAULT,   CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL) ;  
  ShowWindow (hwnd, iCmdShow) ;   UpdateWindow (hwnd) ;  
  while (GetMessage (&msg, NULL, 0, 0))   {  TranslateMessage (&msg) ;  DispatchMessage (&msg) ; }   return msg.wParam ;  
}  

LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam,LPARAM lParam)  
{   static intcxClient, cyClient ;   HBRUSHhBrush ;   HDC   hdc ;   int   i ;   PAINTSTRUCT   ps ;  RECT  rect ;  
  switch (message)   {   case   WM_SIZE:  cxClient = LOWORD (lParam) ;  cyClient = HIWORD (lParam) ;  return 0 ;   case   WM_PAINT:  hdc = BeginPaint (hwnd, &ps) ;  
  // Draw the fountain of grays  
for (i = 0 ; i < 65 ; i++)  { rect.left = i * cxClient / 65 ; rect.top  = 0 ; rect.right= (i + 1) * cxClient / 65 ; rect.bottom   = cyClient ;  
hBrush = CreateSolidBrush (RGB(min (255, 4 * i),  min (255, 4 * i),  min (255, 4 * i))) ; FillRect (hdc, &rect, hBrush) ; DeleteObject (hBrush) ; }  EndPaint (hwnd, &ps) ;  return 0 ;  
 case   WM_DESTROY:  PostQuitMessage (0) ;  return 0 ;   }   return DefWindowProc (hwnd, message, wParam, lParam) ;  
}  

在WM_PAINT消息处理期间,程序呼叫了65次FillRect函数,每次都使用不同灰阶建立的画刷。灰阶值是RGB值(0,0,0)、(4,4,4)、(8,8,8)等等,直到最后一个值(255,255,255)。最后一个值来自CreateSolidBrush函数中的min宏。

如果在256色显示模式下执行该程序,您将看到从黑到白的65种灰阶,而且它们几乎都用混色着色。纯颜色只有黑色、暗灰色(128,128,128)、亮灰色(192,192,192)和白色。其它颜色是混合了这些纯颜色的多位模式。如果我们在显示行或文字,而不是用这65种灰阶填充区域,Windows将不使用混色而只使用这四种纯色。如果我们正在显示位图,则图像将用20种标准Windows颜色近似。这时正如同您在执行最后一章中的程序的同时又加载了彩色或灰阶DIB所见到的一样。通常,Windows在位图中不使用混色。

程序16-2所示的GRAYS2程序用较少的外部程序代码验证了调色盘管理器中最重要的函数和消息。

程序16-2 GRAYS2
GRAYS2.C  
/*---------------------------------------------------------------------------  
  GRAYS2.C --   Gray Shades Using Palette Manager(c) Charles Petzold, 1998  
-----------------------------------------------------------------------------*/  
#include <windows.h>  
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;  
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,PSTR szCmdLine, int iCmdShow)  
{   static TCHAR szAppName[] = TEXT ("Grays2") ;   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= NULL ;   wndclass.lpszClassName   = szAppName ;  
 if (! RegisterClass (&wndclass))   {  MessageBox (  NULL, TEXT ("This program requires Windows NT!"), szAppName, MB_ICONERROR) ;   return 0 ;   }  
  hwnd = CreateWindow ( szAppName, TEXT ("Shades of Gray #2"), WS_OVERLAPPEDWINDOW,  CW_USEDEFAULT, CW_USEDEFAULT,  CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL) ;  
  ShowWindow (hwnd, iCmdShow) ;   UpdateWindow (hwnd) ;   while (GetMessage (&msg, NULL, 0, 0))   {  TranslateMessage (&msg) ;  DispatchMessage (&msg) ;   }   return msg.wParam ;  
}  

LRESULT CALLBACK WndProc ( HWND hwnd, UINT message, WPARAM wParam,LPARAM lParam)  
{   static HPALETTE   hPalette ;   static intcxClient, cyClient ;   HBRUSHhBrush ;   HDC   hdc ;   int   i ;   LOGPALETTE*  plp ;   PAINTSTRUCT   ps ;   RECT  rect ;  
  switch (message)   {   case   WM_CREATE: // Set up a LOGPALETTE structure and create a palette  
plp = malloc (sizeof (LOGPALETTE) + 64 * sizeof (PALETTEENTRY)) ;  
plp->palVersion   = 0x0300 ;  plp->palNumEntries= 65 ;  
for (i = 0 ; i < 65 ; i++) {  plp->palPalEntry[i].peRed = (BYTE) min (255, 4 * i) ;  plp->palPalEntry[i].peGreen   = (BYTE) min (255, 4 * i) ;  plp->palPalEntry[i].peBlue= (BYTE) min (255, 4 * i) ;  plp->palPalEntry[i].peFlags   = 0 ;  }  hPalette = CreatePalette (plp) ;  free (plp) ;  return 0 ;  
 case   WM_SIZE:  cxClient = LOWORD (lParam) ;  cyClient = HIWORD (lParam) ;  return 0 ;   case   WM_PAINT:  hdc = BeginPaint (hwnd, &ps) ;  
   // Select and realize the palette in the device context  
SelectPalette (hdc, hPalette, FALSE) ;  RealizePalette (hdc) ;  
   // Draw the fountain of grays  
for (i = 0 ; i < 65 ; i++)  {rect.left = i * cxClient / 64 ; rect.top  = 0 ; rect.right= (i + 1) * cxClient / 64 ; rect.bottom   = cyClient ;  
hBrush = CreateSolidBrush (PALETTERGB(  min(255, 4 * i),  min   (255, 4 * i), min   (255, 4 * i))) ; FillRect (hdc, &rect, hBrush) ; DeleteObject (hBrush) ;  }  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:  DeleteObject (hPalette) ;  PostQuitMessage (0) ;  return 0 ;  
}   return DefWindowProc (hwnd, message, wParam, lParam) ;  
}  

通常,使用调色盘管理器的第一步就是呼叫CreatePalette函数来建立逻辑调色盘。逻辑调色盘包含程序所需要的全部颜色-即236种颜色。GRAYS1程序在WM_CREATE消息处理期间处理此作业。它初始化LOGPALETTE(「logicalpalette:逻辑调色盘」)结构的字段,并将这个结构的指针传递给CreatePalette函数。CreatePalette传回逻辑调色盘的句柄,并将此句柄储存在静态变量hPalette中。

LOGPALETTE结构定义如下:

typedef struct  
{   WORD palVersion ;   WORD palNumEntries ;   PALETTEENTRY palPalEntry[1] ;  
}  
LOGPALETTE, * PLOGPALETTE ;  

第一个字段通常设为0x0300,表示兼容Windows3.0。第二个字段设定为调色盘表中的项目数。LOGPALETTE结构中的第三个字段是一个PALETTEENTRY结构的数组,此结构也是一个调色盘项目。PALETTEENTRY结构定义如下:

typedef struct  
{   BYTE peRed ;   BYTE peGreen ;   BYTE peBlue ; BYTE peFlags ;  
}  
PALETTEENTRY, * PPALETTEENTRY ;  

每个PALETTEENTRY结构都定义了一个我们要在调色盘中使用的RGB颜色值。

注意,LOGPALETTE中只能定义一个PALETTEENTRY结构的数组。您需要为LOGPALETTE结构和附加的PALETTEENTRY结构配置足够大的内存空间。GRAYS2需要65种灰阶,因此它为LOGPALETTE结构和64个附加的PALETTEENTRY结构配置了足够大的内存空间。GRAYS2将palNumEntries字段设定为65,然后从0到64循环,计算灰阶等级(一般是循环索引的4倍,但不超过255),将结构中的peRed、peGreen和peBlue字段设定为此灰阶等级。peFlags字段设为0。程序将指向这个内存块的指针传递给CreatePalette,在一个静态变量中储存该调色盘句柄,然后释放内存。

逻辑调色盘是GDI对象。程序应该删除它们建立的所有逻辑调色盘。WndProc透过在WM_DESTROY消息处理期间呼叫DeleteObject,仔细地删除了逻辑调色盘。

注意逻辑调色盘是独立的设备内容。在真正使用之前,必须确保将其选进设备内容。在WM_PAINT消息处理期间,SelectPalette将逻辑调色盘选进设备内容。除了含有第三个参数以外,此函数与SelectObject函数相似。通常,第三个参数设为FALSE。如果SelectPalette的第三个参数设为TRUE,那么调色盘将始终是「背景调色盘」,这意味着当其它所有程序都显现了各自的调色盘之后,该调色盘才可以获得仍位于系统调色盘中的一个未使用项目。

在任何时候都只有一个逻辑调色盘能选进设备内容。函数将传回前一个选进设备内容的逻辑调色盘句柄。如果您希望将此逻辑调色盘重新选进设备内容,则可以储存此句柄。

通过将颜色映像到系统调色盘,RealizePalette函数使Windows在设备内容中「显现」逻辑调色盘,而系统调色盘是与显示卡实际的实际调色盘相对应。实际工作在此函数呼叫期间进行。Windows必须决定呼叫函数的窗口是活动的还是非活动的,并尽可能将系统调色盘已改变通知给其它窗口(我们将简要说明一下通知的程序)。

回忆一下GRAYS1,它用RGB宏来指定纯色画刷的颜色。RGB宏建构一个32位长整数(记作COLORREF值),其中高字节是0,3个低字节是红、绿和蓝的亮度。

使用Windows调色盘管理器的程序可以继续使用RGB颜色值来指定颜色。不过,这些RGB颜色值将不能存取逻辑调色盘中的附加颜色。它们的作用与没有使用调色盘管理器相同。要在逻辑调色盘中使用附加的颜色,就要用到PALETTERGB宏。除了COLORREF值的高字节设为2而不是0以外,「调色盘RGB」颜色与RGB颜色很相似。

下面是重要的规则:

  • 为了使用逻辑调色盘中的颜色,请用调色盘RGB值或调色盘索引来指定(我将简要讨论调色盘索引)。不要使用常规的RGB值。如果使用了常规的RGB值,您将得到一种标准颜色,而不是逻辑调色盘中的颜色。
  • 没有将调色盘选进设备内容时,不要使用调色盘RGB值或调色盘索引。
  • 尽管可以使用调色盘RGB值来指定逻辑调色盘中没有的颜色,但您还是要从逻辑调色盘获得颜色。

例如,在GRAYS2中处理WM_PAINT期间,当您选择并显现了逻辑调色盘之后,如果试图显示红色,则将显示灰阶。您必须用RGB颜色值来选择不在逻辑调色盘中的颜色。

注意,GRAYS2从不检查视讯显示驱动程序是否支持调色盘管理程序。在不支持调色盘管理程序的显示模式(即所有非256种颜色的显示模式)下执行GRAYS2时,GRAYS2的功能与GRASY1相同。

调色盘信息

如果程序在逻辑调色盘中指定一种颜色,该颜色又是20种保留颜色之一,那么Windows将把逻辑调色盘项目映像给该颜色。另外,如果两个或多个应用程序都在它们的逻辑调色盘中指定了同一种颜色,那么这些应用程序将共享系统调色盘项目。程序可以通过将PALETTEENTRY结构的peFlags字段指定为常数PC_NOCOLLAPSE来忽略该内定状态(其余两个可能的标记是PC_EXPLICIT(用于显示系统调色盘)和PC_RESERVED(用于调色盘动画),我将在本章的后面展示这两个标记)。

要帮助组织系统调色盘,Windows调色盘管理器含有两个发送给主窗口的消息。

第一个是QM_QUERYNEWPALETTE。当主窗口活动时,该消息发送给主窗口。如果程序在您的窗口上绘画时使用了调色盘管理器,则它必须处理该消息。GRAYS2展示具体的作法。程序获得设备内容句柄,并选进调色盘,呼叫RealizePalette,然后使窗口失效以产生WM_PAINT消息。如果显现了逻辑调色盘,则窗口消息处理程序从该消息传回TRUE,否则传回FALSE。

当系统调色盘改成与WM_QUERYNEWPALETTE消息的结果相同时,Windows将WM_PALETTECHANGED消息发送给由目前活动的窗口来启动并终止处理窗口链的所有主窗口。这允许前台窗口有优先权。传递给窗口消息处理程序的wParam值是活动窗口的句柄。只有当wParam不等于程序的窗口句柄时,使用调色盘管理器的程序才会处理该消息。

通常,在处理WM_PALETTECHANGED时,使用自订调色盘的任何程序都呼叫SelectPalette和RealizePalette。后续的窗口在消息处理期间呼叫RealizePalette时,Windows首先检查逻辑调色盘中的RGB颜色是否与已加载到系统调色盘中的RGB颜色相匹配。如果两个程序需要相同的颜色,那么这两个程序就共同使用一个系统调色盘项目。接下来,Windows检查未使用的系统调色盘项目。如果都已使用,则逻辑调色盘中的颜色从20种保留项目映像到最近的颜色。

如果不关心程序非活动时显示区域的外观,那么您不必处理WM_PALETTECHANGED消息。否则,您有两个选择。GRAYS2显示其中之一:在处理WM_QUERYNEWPALETTE消息时,它获得设备内容,选进调色盘,然后呼叫RealizePalette。这时就可以在处理WM_QUERYNEWPALETTE时呼叫InvalidateRect了。相反地,GRAYS2呼叫UpdateColors。这个函数通常比重新绘制窗口更有效,同时它改变窗口中图素的值来帮助保护以前的颜色。

使用调色盘管理器的许多程序都将让WM_QUERYNEWPALETTE和WM_PALETTECHANGED消息用GRAYS2所显示的方法来处理。

调色盘索引方法

程序16-3所示的GRAYS3程序与GRAYS2非常相似,只是在处理WM_PAINT期间使用了呼叫PALETTEINDEX的宏,而不是PALETTERGB。

程序16-3 GRAYS3
GRAYS3.C  
/*---------------------------------------------------------------------------  
  GRAYS3.C --   Gray Shades Using Palette Manager (c) Charles Petzold, 1998  
-----------------------------------------------------------------------------*/  
#include <windows.h>  
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;  
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,PSTR szCmdLine, int iCmdShow)  
{   static TCHAR szAppName[] = TEXT ("Grays3") ;  
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= NULL ;   wndclass.lpszClassName   = szAppName ;  
 if (!RegisterClass (&wndclass))  
{MessageBox (  NULL, TEXT ("This program requires Windows NT!"), szAppName, MB_ICONERROR) ;  return 0 ;   }  
  hwnd = CreateWindow ( szAppName, TEXT ("Shades of Gray #3"),  WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT,  CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL) ;  
  ShowWindow (hwnd, iCmdShow) ;   UpdateWindow (hwnd) ;  
  while (GetMessage (&msg, NULL, 0, 0))   {  TranslateMessage (&msg) ;  DispatchMessage (&msg) ;   }   return msg.wParam ;  
}  

LRESULT CALLBACK WndProc ( HWND hwnd, UINT message, WPARAM wParam,LPARAM lParam)  
{   static HPALETTE   hPalette ;   static intcxClient, cyClient ;   HBRUSHhBrush ;  
   HDC   hdc ;   int   i ;  LOGPALETTE*  plp ;   PAINTSTRUCT   ps ;   RECT  rect ;   switch (message)  
{   case   WM_CREATE:  // Set up a LOGPALETTE structure and create a palette  
plp = malloc (sizeof (LOGPALETTE) + 64 * sizeof (PALETTEENTRY)) ;  
plp->palVersion   = 0x0300 ;  plp->palNumEntries= 65 ;  
for (i = 0 ; i < 65 ; i++)  {  plp->palPalEntry[i].peRed = (BYTE) min (255, 4 * i) ; plp->palPalEntry[i].peGreen   = (BYTE) min (255, 4 * i) ;  plp->palPalEntry[i].peBlue= (BYTE) min (255, 4 * i) ; plp->palPalEntry[i].peFlags   = 0 ;  }  hPalette = CreatePalette (plp) ; free (plp) ;  return 0 ;  
 case   WM_SIZE:  cxClient = LOWORD (lParam) ;  cyClient = HIWORD (lParam) ; return 0 ;   case   WM_PAINT:  hdc = BeginPaint (hwnd, &ps) ;  
   // Select and realize the palette in the device context  
SelectPalette (hdc, hPalette, FALSE) ;  RealizePalette (hdc) ;  
   // Draw the fountain of grays  
 for (i = 0 ; i < 65 ; i++)   { rect.left = i * cxClient / 64 ; rect.top  = 0 ; rect.right= (i + 1) * cxClient / 64 ; rect.bottom   = cyClient ;  
   hBrush = CreateSolidBrush (PALETTEINDEX (i)) ;  
   FillRect (hdc, &rect, hBrush) ; DeleteObject (hBrush) ;  }  
EndPaint (hwnd, &ps) ;  return 0 ;  
 case   WM_QUERYNEWPALETTE:  if (!hPalette) return FALSE ;  
hdc = GetDC (hwnd) ;  SelectPalette (hdc, hPalette, FALSE) ;  RealizePalette (hdc) ;  InvalidateRect (hwnd, NULL, FALSE) ;  
 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:  PostQuitMessage (0) ;  return 0 ;   }   return DefWindowProc (hwnd, message, wParam, lParam) ;  
}  

「调色盘」索引的颜色不同于调色盘RGB颜色,其高字节是1,而低字节的值是目前在设备内容中选择的、逻辑调色盘中的索引。在GRAYS3中,逻辑调色盘有65个项目,用于这些项目的索引从0到64。值

PALETTEINDEX (0)  

指黑色,

PALETTEINDEX (32)  

指灰色,而

PALETTEINDEX (64)  

指白色。

因为Windows不需要执行最近颜色的搜索,所以使用调色盘索引比使用RGB值更有效。

查询调色盘支持

您可以容易地验证:当Windows在16位或24位显示模式下执行时,GRAYS2和GRAYS3程序执行良好。但是在某些情况下,要使用调色盘管理器的Windows应用程序可能要先确定设备驱动程序是否支持它。这时,您可以呼叫GetDeviceCaps,并以视讯显示的设备内容句柄和PASTERCAPS作为参数。函数将传回由一系列旗标组成的整数。通过在传回值和常数RC_PALETTE之间执行位操作来检验支持的调色盘:

RC_PALETTE & GetDeviceCaps (hdc, RASTERCAPS)  

如果此值非零,则视讯显示器设备驱动程序将支持调色盘操作。在这种情况之下,来自GetDeviceCaps的其它三个重要项目也是可用的。函数呼叫

GetDeviceCaps (hdc, SIZEPALETTE)  

将传回在显示卡上调色盘表的总尺寸。这与同时显示的颜色总数相同。因为调色盘管理器只用于每图素8位的视讯显示模式,所以此值将是256。

函数呼叫

GetDeviceCaps (hdc, NUMRESERVED)  

传回在调色盘表中的颜色数,该表是设备驱动程序为系统保留的,此值是20。不呼叫调色盘管理器,这些只是Windows应用程序在256色显示模式下使用的纯色。要使用其余的236种颜色,程序必须使用调色盘管理器函数。

一个附加项目也可用:

GetDeviceCaps (hdc, COLORRES)  

此值告诉您加载到硬件调色盘表的RGB颜色值分辨率(以位计)。这些是进入数字模拟转换器的位。某些视讯显示卡只使用6位ADC,所以该值是18。其余使用8位的ADC,所以值是24。

Windows程序注意颜色分辨率并因此采取一些动作是很有用的。例如,如果该颜色分辨率是18,那么程序将不可能要求到128种灰阶,因为只有64个离散的灰阶可用。要求到128种灰阶就不必用多余的项目来填充硬件调色盘表。

系统调色盘

我在前面提过,Windows系统调色盘直接与显示卡上的硬件调色盘查询表相符(然而,硬件调色盘查询表可能比系统调色盘的颜色分辨率低)。程序可以通过呼叫下面的函数来获得系统调色盘中的某些或全部的RGB项目:

GetSystemPaletteEntries (hdc, uStart, uNum, &pe) ;  

只有显示卡模式支持调色盘操作时,该函数才能执行。第二个和第三个参数是无正负号整数,显示第一个调色盘项目的索引和调色盘项目数。最后一个参数是指向PALETTEENTRY型态的指针。

您可以在几种情况下使用该函数。程序可以定义PALETTEENTRY结构如下:

PALETTEENTRY pe ;  

然后可按下面的方法多次呼叫GetSystemPaletteEntries:

GetSystemPaletteEntries (hdc, i, 1, &pe) ;  

其中的i从0到某个值,该值小于从GetDeviceCaps(带有SIZEPALETTE索引255)传回的值。或者,程序要获得所有的系统调色盘项目,可以通过定义指向PALETTEENTRY结构的指针,然后重新配置足够的内存块,以储存与调色盘大小指定同样多的PALETTEENTRY结构。

GetSystemPaletteEntries函数确实允许您检验硬件调色盘表。系统调色盘中的项目按图素值增加的顺序排列,这些值用于表示视讯显示缓冲区中的颜色。我将简单地讨论一下具体作法。

其它调色盘函数

我们在前面看过,Windows程序能够改变系统调色盘,但只是间接改变:第一步建立逻辑调色盘,它基本上是程序要使用的RGB颜色值数组。CreatePalette函数不会导致系统调色盘或者显示卡调色盘表的任何变化。逻辑调色盘必须在任何事情发生之前就选进设备内容并显现。

程序可以通过呼叫

GetPaletteEntries (hPalette, uStart, uNum, &pe) ;  

来查询逻辑调色盘中的RGB颜色值。您可以按使用GetSystemPaletteEntries的方法来使用此函数。但是要注意,第一个参数是逻辑调色盘的句柄,而不是设备内容的句柄。

建立逻辑调色盘以后,让您改变其中的值的相应函数是:

SetPaletteEntries (hPalette, uStart, uNum, &pe) ;  

另外,记住呼叫此函数不引起系统调色盘的任何变化-即使目前调色盘选进了设备内容。此函数也不改变逻辑调色盘的尺寸。要改变逻辑调色盘的尺寸,请使用ResizePalette。

下面的函数接受RGB颜色引用值作为最后的参数,并将索引传回给逻辑调色盘,该逻辑调色盘与和它最接近的RGB颜色值相对应:

iIndex = GetNearestPaletteIndex (hPalette, cr) ;  

第二个参数是COLORREF值。如果希望的话,呼叫GetPaletteEntries就可以获得逻辑调色盘中实际的RGB颜色值。

如果程序在8位显示模式下需要多于236种自订颜色,则可以呼叫GetSystemPaletteUse。这允许程序设定254种自订颜色;系统仅保留黑色和白色。不过,程序仅在最大化充满全屏幕时才允许这样,而且它还将一些系统颜色设为黑色和白色,以便标题列和菜单等仍然可见。

位映像操作问题

从第五章可以了解到,GDI允许使用不同的「绘画模式」或「位映像操作」来画线并填充区域。用SetROP2设定绘画模式,其中的「2」表示两个对象之间的二元(binary)位映像操作。三元位映像操作用于处理BitBlt和类似功能。这些位映像操作决定了正在画的对象图素与表面图素的结合方式。例如,您可以画一条直线,以便在线的图素与显示的图素按位异或的方式相结合。

位映像操作就是在图素位上照着各个位的顺序进行操作。改变调色盘会影响到这些位映像操作。位映像操作的操作对象是图素位,而这些图素位可能与实际颜色没有关联。

透过执行GRAYS2或GRAYS3程序,您自己就可以得出这个结论。调整尺寸时,拖动顶部或底部的边界穿过窗口,Windows利用反转背景图素位的位映像操作来显示拖动尺寸的边界,其目的是使拖动尺寸边界总是可见的。但在GRAYS2和GRAYS3程序中,您将看到各种随机变换的颜色,这些颜色恰好与对应于调色盘表中未使用的项目,那是反转显示图素位的结果。可视颜色没有反转-只有图素位反转了。

正如您在表16-1中所看到的一样,20种标准保留颜色位于系统调色盘的顶部和底部,以便位映像操作的结果仍然正常。然而,一旦您开始修改调色盘-尤其是替换了保留颜色-那么颜色对象的位映像操作就变得没有意义了。

唯一保证的是位映像操作将用黑色和白色运作。黑色是系统调色盘中的第一个项目(所有的图素位都设为0),而白色是最后的项目(所有的图素位都设为1)。这两个项目不能改变。如果需要预知在颜色对象上进行位映像操作的结果,则可以先获得系统调色盘表,然后查看不同图素位值的RGB颜色值。

查看系统调色盘

在Windows下执行的程序将处理逻辑调色盘,为使逻辑调色盘更好地服务于所有使用逻辑调色盘的程序,Windows将在系统调色盘中设定颜色。该系统调色盘复制了显示卡的硬件对照表内容。这样,查看系统调色盘有助于调适调色盘应用程序。

因为对于这个问题有三种截然不同的处理方式,所以我将向您展示三个程序,以显示系统调色盘的内容。

SYSPAL1程序,如程序16-4所示,使用了前面所讲的GetSystemPaletteEntries函数。

程序16-4 SYSPAL1
SYSPAL1.C  
/*---------------------------------------------------------------------------  
  SYSPAL1.C --Displays system palette (c) Charles Petzold, 1998  
-----------------------------------------------------------------------------*/  
#include <windows.h>  

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

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

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,PSTR szCmdLine, int iCmdShow)  
{   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= NULL ;   wndclass.lpszClassName   = szAppName ;  
if (!RegisterClass (&wndclass))   {   MessageBox (  NULL, TEXT ("This program requires Windows NT!"), szAppName, MB_ICONERROR) ;  return 0 ;   }  
  hwnd = CreateWindow ( szAppName, TEXT ("System Palette #1"),WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL) ;  
 if (!hwnd)  return 0 ; ShowWindow (hwnd, iCmdShow) ;   UpdateWindow (hwnd) ;  
while (GetMessage (&msg, NULL, 0, 0))   {  TranslateMessage (&msg) ;  DispatchMessage (&msg) ;   }   return msg.wParam ;  
}  

BOOL CheckDisplay (HWND hwnd)  
{   HDChdc ;   intiPalSize ;  
 hdc = GetDC (hwnd) ;   iPalSize = GetDeviceCaps (hdc, SIZEPALETTE) ;   ReleaseDC (hwnd, hdc) ;  
 if (iPalSize != 256)  
  {MessageBox (hwnd,TEXT ("This program requires that the video ")TEXT ("display mode have a 256-color palette."),szAppName, MB_ICONERROR) ;  return FALSE ;   }   return TRUE ;  
}  

LRESULT CALLBACK WndProc ( HWND hwnd, UINT message, WPARAM wParam,LPARAM lParam)  
{   static intcxClient, cyClient ;   static SIZE   sizeChar ;   HDC   hdc ;   HPALETTE  hPalette ;   int   i, x, y ;   PAINTSTRUCT   ps ;   PALETTEENTRY  pe [256] ;   TCHARszBuffer [16] ;  
 switch (message) {   case WM_CREATE:  if (!CheckDisplay (hwnd)) return -1 ;  hdc = GetDC (hwnd) ;  SelectObject (hdc, GetStockObject (SYSTEM_FIXED_FONT)) ;  GetTextExtentPoint32 (hdc, TEXT ("FF-FF-FF"), 10, &sizeChar) ;  ReleaseDC (hwnd, hdc) ;  return 0 ;  
  case   WM_DISPLAYCHANGE:  if (!CheckDisplay (hwnd)) DestroyWindow (hwnd) ;  
return 0 ;  
 case   WM_SIZE: cxClient = LOWORD (lParam) ;  cyClient = HIWORD (lParam) ;  return 0 ;  
 case   WM_PAINT:  hdc = BeginPaint (hwnd, &ps) ;  SelectObject (hdc, GetStockObject (SYSTEM_FIXED_FONT)) ;  
GetSystemPaletteEntries (hdc, 0, 256, pe) ;  
for (i = 0, x = 0, y = 0 ; i < 256 ; i++)  {  wsprintf (szBuffer, TEXT ("%02X-%02X-%02X"),   pe[i].peRed, pe[i].peGreen, pe[i].peBlue) ;  
   TextOut (hdc, x, y, szBuffer, lstrlen (szBuffer)) ;  
if ((  x += sizeChar.cx) + sizeChar.cx > cxClient) { x = 0 ; if ((  y += sizeChar.cy) > cyClient)   break ; } }  EndPaint (hwnd, &ps) ;  return 0 ;  
 case   WM_PALETTECHANGED:  InvalidateRect (hwnd, NULL, FALSE) ;  return 0 ;  
 case   WM_DESTROY: PostQuitMessage (0) ;  return 0 ;   }   return DefWindowProc (hwnd, message, wParam, lParam) ;  
}  

与SYSPAL系列中的其它程序一样,除非带有SIZEPALETTE参数的GetDeviceCaps传回值为256,否则SYSPAL1不会执行。

注意无论SYSPAL1的显示区域什么时候收到WM_PALETTECHANGED消息,它都是无效的。在合并WM_PAINT消息处理期间,SYSPAL1呼叫GetSystemPaletteEntries,并用一个含256个PALETTEENTRY结构的数组作为参数。RGB值作为文字字符串显示在显示区域。程序执行时,注意20种保留颜色是RGB值列表中的前10个和后10个,这与表16-1所示相同。

当SYSPAL1显示有用的信息时,它与实际看到的256种颜色不同。那就是SYSPAL2的作业,如程序16-5所示。

程序16-5 SYSPAL2
SYSPAL2.C  
/*---------------------------------------------------------------------------  
  SYSPAL2.C --Displays system palette (c) Charles Petzold, 1998  
----------------------------------------------------------------------------*/  
#include <windows.h>  

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

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

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,  PSTR szCmdLine, int iCmdShow)  
{   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= NULL ;   wndclass.lpszClassName   = szAppName ;  
  if (!RegisterClass (&wndclass))   {  MessageBox (  NULL, TEXT ("This program requires Windows NT!"),   szAppName, MB_ICONERROR) ;  return 0 ;  
}  
  hwnd = CreateWindow ( szAppName, TEXT ("System Palette #2"),  WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT,  CW_USEDEFAULT, CW_USEDEFAULT,  NULL, NULL, hInstance, NULL) ;   if (!hwnd)  return 0 ;   ShowWindow (hwnd, iCmdShow) ;   UpdateWindow (hwnd) ;  
 while (GetMessage (&msg, NULL, 0, 0))   {  TranslateMessage (&msg) ;  DispatchMessage (&msg) ;   }   return msg.wParam ;  
}  

BOOL CheckDisplay (HWND hwnd)  
{   HDC hdc ;   int iPalSize ;  
 hdc = GetDC (hwnd) ;   iPalSize = GetDeviceCaps (hdc, SIZEPALETTE) ;   ReleaseDC (hwnd, hdc) ;  

if (iPalSize != 256)   {  
   MessageBox (hwnd, TEXT("This program requires that the video ") TEXT ("display mode have a 256-color palette."), szAppName, MB_ICONERROR) ;  return FALSE ;   }   return TRUE ;  
}  

LRESULT CALLBACK WndProc ( HWND hwnd, UINT message, WPARAM wParam,LPARAM lParam)  
{   static HPALETTE   hPalette ;   static intcxClient, cyClient ;   HBRUSHhBrush ;   HDC   hdc ;   int   i, x, y ;   LOGPALETTE*  plp ;   PAINTSTRUCT   ps ;   RECT  rect ;  
 switch (message)  
{   case   WM_CREATE:  if (!CheckDisplay (hwnd)) return -1 ;  
 plp = malloc (sizeof (LOGPALETTE) + 255 * sizeof (PALETTEENTRY)) ;  
 plp->palVersion  = 0x0300 ;   plp->palNumEntries= 256 ;  
for (i = 0 ; i < 256 ; i++)   {   plp->palPalEntry[i].peRed= i ;  plp->palPalEntry[i].peGreen  = 0 ; plp->palPalEntry[i].peBlue= 0 ; plp->palPalEntry[i].peFlags   = PC_EXPLICIT ;}   hPalette = CreatePalette (plp) ;   free (plp) ;   return 0 ;  
 
case   WM_DISPLAYCHANGE:  if (!CheckDisplay (hwnd)) DestroyWindow (hwnd) ;  
return 0 ;  
 case   WM_SIZE:  cxClient = LOWORD (lParam) ;  cyClient = HIWORD (lParam) ;  return 0 ;  
 case   WM_PAINT:  hdc = BeginPaint (hwnd, &ps) ;  
SelectPalette (hdc, hPalette, FALSE) ;  RealizePalette (hdc) ;  
for (y = 0 ; y < 16 ; y++)  for (x = 0 ; x < 16 ; x++)  { hBrush = CreateSolidBrush (PALETTEINDEX (16 * y + x)) ; SetRect (&rect, x * cxClient /16, y * cyClient / 16,(x + 1)` *  cxClient  /  16,(y+1)  * cyClient / 16); FillRect (hdc, &rect, hBrush) ; DeleteObject (hBrush) ;  }  EndPaint (hwnd, &ps) ;  return 0 ;  
 case   WM_PALETTECHANGED:  if ((HWND) wParam != hwnd) InvalidateRect (hwnd, NULL, FALSE) ;  
   return 0 ;  
 case   WM_DESTROY:  DeleteObject (hPalette) ;  PostQuitMessage (0) ;  return 0 ;  
}   return DefWindowProc (hwnd, message, wParam, lParam) ;  
}  

SYSPAL2在WM_CREATE消息处理期间建立了逻辑调色盘。但是请注意:逻辑调色盘中所有的256个值都是从0到255的调色盘索引,并且peFlags字段是PC_EXPLICIT。该旗标是这样定义的:「逻辑调色盘项目的较低字组指定了一个硬件调色盘索引。此旗标允许应用程序显示硬件调色盘的内容。」该旗标就是专为我们要做的这件事情而设计的。

在WM_PAINT消息处理期间,SYSPAL2将该调色盘选进设备内容并显现它。这不会引起系统调色盘的任何重组,而是允许程序使用PALETTEINDEX宏来指定系统调色盘中的颜色。按此方法,SYSPAL2显示了256个矩形。另外,当您执行该程序时,注意顶行和底行的前10种和后10种颜色是20种保留颜色,如表16-1所示。当您执行使用自己逻辑调色盘的程序时,显示就改变了。

如果您既喜欢看SYSPAL2中的颜色,又喜欢RGB的值,那么请与第八章的WHATCLR程序同时执行。

SYSPAL系列中的第三版使用的技术对我来说是最近才出现的-从我开始研究Windows调色盘管理器七年多后,才出现了那些技术。

事实上,所有的GDI函数都直接或间接地指定颜色作为RGB值。在GDI内部,这将转换成与那个颜色相关的图素位。在某些显示模式中(例如,16位或24位颜色模式),这些转换是相当直接的。在其它显示模式中(4位或8位颜色),这可能涉及最接近颜色的搜索。

然而,有两个GDI函数让您直接指定图素位中的颜色。当然在这种方式中使用的这两个函数都与设备高度相关。它们太依赖设备了,以至于它们可以直接显示视讯显示卡上实际的调色盘对照表。这两个函数是BitBlt和StretchBlt。

程序16-6所示的SYSPAL3程序显示了使用StretchBlt显示系统调色盘中颜色的方法。

程序16-6 SYSPAL3
SYSPAL3.C  
/*---------------------------------------------------------------------------  
  SYSPAL3.C -- Displays system palette (c) Charles Petzold, 1998  
----------------------------------------------------------------------------*/  
#include <windows.h>  
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;  
TCHAR szAppName [] = TEXT ("SysPal3") ;  
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,PSTR szCmdLine, int iCmdShow)  
{   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= NULL ;   wndclass.lpszClassName   = szAppName ;   if (!RegisterClass (&wndclass))   {  MessageBox (  NULL, TEXT ("This program requires Windows NT!"),   szAppName, MB_ICONERROR) ;  return 0 ;   }  

hwnd = CreateWindow ( szAppName, TEXT ("System Palette #3"), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,NULL, NULL, hInstance, NULL) ;  

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

BOOL CheckDisplay (HWND hwnd)  
{   HDC hdc ;   int iPalSize ;  
 hdc = GetDC (hwnd) ;   iPalSize = GetDeviceCaps (hdc, SIZEPALETTE) ;   ReleaseDC (hwnd, hdc) ;  
 if (iPalSize != 256)  {  MessageBox (hwnd,TEXT("This program requires that the video ")   TEXT("display mode have a 256-color palette."),  szAppName, MB_ICONERROR) ;  return FALSE ;   }  
return TRUE ;  
}  

LRESULT CALLBACK WndProc ( HWND hwnd, UINT message, WPARAM wParam,LPARAM lParam)  
{   static HBITMAP hBitmap ;   static intcxClient, cyClient ;   BYTE  bits [256] ;   HDC   hdc, hdcMem ;   int   i ;   PAINTSTRUCT   ps ;  
 switch (message)   {   case   WM_CREATE:  if (!  CheckDisplay (hwnd)) return -1 ;  
for (  i = 0 ; i < 256 ; i++)  bits [i] = i ; hBitmap = CreateBitmap (16, 16, 1, 8, &bits) ;  return 0 ; case   WM_DISPLAYCHANGE:  if (!CheckDisplay (hwnd)) DestroyWindow (hwnd) ;  
   return 0 ;  
 case   WM_SIZE:  cxClient = LOWORD (lParam) ;  cyClient = HIWORD (lParam) ;  return 0 ;  
 case   WM_PAINT:  hdc = BeginPaint (hwnd, &ps) ;  
hdcMem = CreateCompatibleDC (hdc) ;  SelectObject (hdcMem, hBitmap) ;  
StretchBlt (hdc,0, 0, cxClient, cyClient,hdcMem, 0, 0, 16, 16, SRCCOPY) ;  
DeleteDC (hdcMem) ;  EndPaint (hwnd, &ps) ;  return 0 ;   case   WM_DESTROY:  DeleteObject (hBitmap) ;  PostQuitMessage (0) ;  return 0 ;   }   return DefWindowProc (hwnd, message, wParam, lParam) ;  
}  

在WM_CREATE消息处理期间,SYSPAL3使用CreateBitmap来建立16×16的每图素8位的位图。该函数的最后一个参数是包括数值0到255的256字节数组。这些是256种可能的图素位值。在处理WM_PAINT消息的程序中,程序将这个位图选进内存设备内容,用StretchBlt来显示并填充该显示区域。Windows仅将位图中的图素位传输到视讯显示器硬件,从而允许这些图素位存取调色盘对照表中的256个项目。程序的显示区域甚至不必使接收WM_PALETTECHANGED消息无效-对于对照表的任何修改都会立即影响到SYSPAL3的显示。

调色盘动画

在本节的标题中看到「动画」一词,并开始考虑屏幕周围执行的「计算机宠物」时,您的眼前可能会为之一亮。是的,您可以使用Windows调色盘管理器作一些动画,而且是有一定专业水平的动画。

通常,Windows下的动画就是快速连续地显示一系列位图。调色盘动画与这种方法有很大的区别。您透过在屏幕上绘制您所需要的每件东西开始,然后您处理调色盘来改变这些对象的颜色,可能是画一些相对于屏幕背景来说是不可见的图像。您用这种方法就可以获得动画效果,而不必重画任何东西。调色盘动画的速度是相当快的。

对于调色盘动画,最初的建立工作与我们前面看见的有些不同:对于动画期间要修改的每种RGB颜色值,PALETTEENTRY结构的peFlags字段必须设定为PC_RESERVED。

通常,就像我们所看到的一样,在建立逻辑调色盘时,您将peFlags标记设为0。这允许GDI将多个逻辑调色盘中同样的颜色映像到相同的系统调色盘项目。例如,假设两个Windows程序都建立了包含RGB项目10-10-10的逻辑调色盘,那么在系统调色盘表中,Windows只需要一个10-10-10项目。但如果这两个程序中的一个使用调色盘动画,那您就不要再让GDI使用调色盘了。调色盘动画意味着速度非常快-而且如果不重画,它也只可能提高速度。当使用调色盘动画的程序修改调色盘时,它不会影响其它程序,或者迫使GDI重组系统调色盘表。PC_RESERVED的peFlags值为单个逻辑调色盘储存系统调色盘项目。

使用调色盘动画时,通常您可以在WM_PAINT消息处理期间呼叫SelectPalette和RealizePalette,使用PALETTEINDEX宏来指定颜色。该宏将一个索引带进逻辑调色盘表。

对于动画,您可能要通过改变调色盘来响应WM_TIMER消息。要改变逻辑调色盘中的RGB颜色值,请使用一个PALETTEENTRY结构的数组来呼叫函数AnimatePalette。此函数速度很快,因为它只需要改变系统调色盘以及显示卡硬件调色盘表中的项目。

跳动的球

程序16-7显示了BOUNCE程序的组件,但还有一个程序可显示跳动的球。为了简单起见,根据显示区域的大小将球画成了椭圆形。因为本章有几个调色盘动画程序,所以PALANIM.C(「调色盘动画」)文件包含一些通用内容。

程序16-7 BOUNCE
PALANIM.C  
/*--------------------------------------------------------------------------  
  PALANIM.C -- Palette Animation Shell Program s(c) Charles Petzold, 1998  
---------------------------------------------------------------------------*/  
#include <windows.h>  
extern HPALETTECreateRoutine (HWND) ;  
extern voidPaintRoutine   (HDC, int, int) ;  
extern voidTimerRoutine   (HDC, HPALETTE) ;  
extern voidDestroyRoutine (HWND, HPALETTE) ;  

LRESULT CALLBA CK WndProc  (HWND, UINT, WPARAM, LPARAM) ;  

extern TCHAR szAppName [] ;  
extern TCHAR szTitle [] ;  

int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,PSTR szCmdLine, int iCmdShow)  
{   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= NULL ;   wndclass.lpszClassName   = szAppName ;  
  if (!RegisterClass (&wndclass))   {  MessageBox (  NULL, TEXT ("This program requires Windows NT!"),   szAppName, MB_ICONERROR) ;  return 0 ;  
}  
  hwnd = CreateWindow (szAppName, szTitle, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL) ;  
 if (!hwnd)  return 0 ;  ShowWindow (hwnd, iCmdShow) ;   UpdateWindow (hwnd) ;  
 while (GetMessage (&msg, NULL, 0, 0))  
{  TranslateMessage (&msg) ;  DispatchMessage (&msg) ;   }   return msg.wParam ;  
}  

BOOL CheckDisplay (HWND hwnd)  
{   HDC hdc ;   int iPalSize ;  
 hdc = GetDC (hwnd) ;   iPalSize = GetDeviceCaps (hdc, SIZEPALETTE) ;   ReleaseDC (hwnd, hdc) ;  

if (iPalSize != 256)   {  
   MessageBox (hwnd,TEXT ("This program requires that the video ")   TEXT ("display mode have a 256-color palette."),  szAppName, MB_ICONERROR) ;  return FALSE ;   }   return TRUE ;  
}  

LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam,LPARAM lParam)  
{   static HPALETTE   hPalette ;   static int   cxClient, cyClient ;   HDC  hdc ;   PAINTSTRUCT  ps ;  
 switch (message)   {  case   WM_CREATE:  if (!CheckDisplay (hwnd)) return -1 ;  
hPalette = CreateRoutine (hwnd) ;  return 0 ;  
 
  case   WM_DISPLAYCHANGE:  if (!CheckDisplay (hwnd)) DestroyWindow (hwnd) ;  
return 0 ;  

  case   WM_SIZE:  cxClient = LOWORD (lParam) ;  cyClient = HIWORD (lParam) ;  return 0 ;  

   case   WM_PAINT:  hdc = BeginPaint (hwnd, &ps) ;  
SelectPalette (hdc, hPalette, FALSE) ;  RealizePalette (hdc) ;  
PaintRoutine (hdc, cxClient, cyClient) ;  
EndPaint (hwnd, &ps) ;  return 0 ;  
   case   WM_TIMER:  hdc = GetDC (hwnd) ;  
SelectPalette (hdc, hPalette, FALSE) ;  
TimerRoutine (hdc, hPalette) ;  
ReleaseDC (hwnd, hdc) ;  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:  DestroyRoutine (hwnd, hPalette) ;  PostQuitMessage (0) ;  return 0 ;   }   return DefWindowProc (hwnd, message, wParam, lParam) ;  
}  
BOUNCE.C  
/*--------------------------------------------------------------------------  
  BOUNCE.C --   Palette Animation Demo(c) Charles Petzold, 1998  
----------------------------------------------------------------------------*/  
#include <windows.h>  
#define ID_TIMER 1  
TCHAR szAppName[] = TEXT ("Bounce") ;  
TCHAR szTitle  [] = TEXT ("Bounce: Palette Animation Demo") ;  

static LOGPALETTE * plp ;  
HPALETTE CreateRoutine (HWND hwnd)  
{   HPALETTE  hPalette ;   int   i ;  
 plp = malloc (sizeof (LOGPALETTE) + 33 * sizeof (PALETTEENTRY)) ;   plp->palVersion   = 0x0300 ;   plp->palNumEntries= 34 ;  
 for (i = 0 ; i < 34 ; i++)   {  plp->palPalEntry[i].peRed = 255 ;  plp->palPalEntry[i].peGreen   = (i == 0 ? 0 : 255) ;  plp->palPalEntry[i].peBlue= (i == 0 ? 0 : 255) ;  plp->palPalEntry[i].peFlags   = (i == 33 ? 0 : PC_RESERVED) ;  
}  
hPalette = CreatePalette (plp) ;  
SetTimer (hwnd, ID_TIMER, 50, NULL) ;  
return hPalette ;  
}  

void PaintRoutine (HDC hdc, int cxClient, int cyClient)  
{   HBRUSH hBrush ;   int   i, x1, x2, y1, y2 ;   RECT  rect ;  // Draw window background using palette index 33  
 SetRect (&rect, 0, 0, cxClient, cyClient) ;   hBrush = CreateSolidBrush (PALETTEINDEX (33)) ;  
FillRect (hdc, &rect, hBrush) ;  
DeleteObject (hBrush) ;  
// Draw the 33 balls   SelectObject (hdc, GetStockObject (NULL_PEN)) ;   for (i = 0 ; i < 33 ; i++)   {  x1 =  i * cxClient / 33 ;  x2 = (i + 1)* cxClient / 33 ;  
if (i < 9)  { y1  = i * cyClient / 9 ;y2 = (i + 1) * cyClient / 9 ;  }  else if (i < 17)  { y1 = (16 - i) * cyClient / 9 ; y2 = (17 - i) * cyClient / 9 ;  }  else if (i < 25) { y1 = (i - 16) * cyClient / 9 ; y2 = (i - 15) * cyClient / 9 ;  }  else  { y1 = (32 - i) * cyClient / 9 ; y2 = (33 - i) * cyClient / 9 ; }  
hBrush = CreateSolidBrush (PALETTEINDEX (i)) ;  SelectObject (hdc, hBrush) ;  Ellipse (hdc, x1, y1, x2, y2) ;  DeleteObject (SelectObject (hdc, GetStockObject (WHITE_BRUSH))) ;   }   return ;  
}  

void TimerRoutine (HDC hdc, HPALETTE hPalette)  
{  
  static BOOL bLeftToRight = TRUE ;   static int  iBall ;  
// Set old ball to white   plp->palPalEntry[iBall].peGreen = 255 ;   plp->palPalEntry[iBall].peBlue  = 255 ;  
 iBall += (bLeftToRight ? 1 : -1) ;   if (   iBall == (bLeftToRight ? 33 : -1))   {  iBall = (bLeftToRight ? 31 : 1) ;  bLeftToRight ^= TRUE ;   }  
// Set new ball to red  
 plp->palPalEntry[iBall].peGreen = 0 ;   plp->palPalEntry[iBall].peBlue  = 0 ;  
// Animate the palette  
 AnimatePalette (hPalette, 0, 33, plp->palPalEntry) ;   return ;  
}  

void DestroyRoutine (HWND hwnd, HPALETTE hPalette)  
{   KillTimer (hwnd, ID_TIMER) ;   DeleteObject (hPalette) ;   free (plp) ;   return ;  
}  

除非Windows处于支持调色盘的显示模式下,否则调色盘动画将不能工作。因此,PALANIM.C通过呼叫CheckDisplay函数(与SYSPAL程序中的函数相同)来开始处理WM_CREATE。

PALANIM.C呼叫BOUNCE.C中的四个函数:在WM_CREATE消息处理期间呼叫CreateRoutine(在BOUNCE中用于建立逻辑调色盘);在WM_PAINT消息处理期间呼叫PaintRoutine;在WM_TIMER消息处理期间呼叫TimerRoutine;在WM_DESTROY消息处理期间呼叫DestroyRoutine(在BOUNCE中用于清除)。在呼叫PaintRoutine和TimerRoutine之前,PALANIM.C获得设备内容,并将其选进逻辑调色盘。在呼叫PaintRoutine之前,它也显现调色盘。PALANIM.C期望TimerRoutine呼叫AnimatePalette。尽管AnimatePalette需要从设备内容中选择调色盘,但它不需要呼叫RealizePalette。

BOUNCE中的球按「W」路线在显示区域中来回跳动。显示区域背景是白色,球是红色。任何时候,都可以在33个不重迭的位置之一看见球。这需要34个调色盘项目:一个用于背景,其它33个用于不同位置的球。在CreateRoutine中,BOUNCE初始化PALETTEENTRY结构的一个数组,将第一个调色盘项目(与球在左上角的位置对应)设定为红色,其它的设定为白色。注意,对于除背景以外的所有项目,peFlags字段都设定为PC_RESERVED(背景是最后的一个调色盘项目)。BOUNCE通过将Windows定时器的间隔设定为50毫秒来终止CreateRoutine。

BOUNCE在PaintRoutine完成所有的绘画工作。窗口背景用一个实心画刷和调色盘索引33所指定的颜色来绘制。33个球的颜色是依据从0到32的调色盘索引的颜色。当BOUNCE第一次在显示区域内绘画时,0的调色盘索引映像成红色,其它调色盘索引映像到白色。这导致球出现在左上角。

当WndProc处理WM_TIMER消息并呼叫TimerRoutine时,动画就发生了。TimerRoutine通过呼叫AnimatePalette来结束,语法如下:

AnimatePalette (hPalette, uStart, uNum, &pe) ;  

其中,第一个参数是调色盘句柄,最后一个参数是指向数组的指针,该数组由一个或多个PALETTEENTRY结构组成。该函数改变逻辑调色盘中从uStart项目到uNum项目之间的若干项目。逻辑调色盘中新的uStart项目是PALETTEENTRY结构中的第一个成员。当心!uStart参数是进入原始逻辑调色盘表的索引,而不是进入PALETTEENTRY数组的索引。

为了方便起见,BOUNCE使用PALETTEENTRY结构的数组,该结构是建立逻辑调色盘时使用的LOGPALETTE结构的一部分。球的目前位置(从0到32)储存在静态变量iBall中。在TimerRoutine期间,BOUNCE将PALETTEENTRY成员设为白色。然后计算球的下一个位置,并将该元素设为红色。用下面的呼叫来改变调色盘:

AnimatePalette (hPalette, 0, 33, plp->palPalEntry) ;  

GDI改变33逻辑调色盘项目中的第一个(尽管实际上只改变了两个),使它与系统调色盘表中的变化相对应,然后修改显示卡上的硬件调色盘表。这样,不用重画球就开始移动了。

BOUNCE执行时,您会发现同时执行SYSPAL2或SYSPAL3效果会更好。

尽管AnimatePalette执行得非常快,但是当只有一两个项目改变时,您还应该尽量避免改变所有的逻辑调色盘项目。这在BOUNCE中有点复杂,因为球要来回地跳-iBall要先增加,然后再减少。一种方法是使用两个变量:分别称为iBallOld(设定球的目前位置)和iBallMin(iBall和iBallOld中较小的)。然后您就可以像下面这样呼叫AnimatePalette来改变两个项目了:

iBallMin = min (iBall, iBallOld) ;  
AnimatePalette (hPal, iBallMin, 2, plp->palPalEntry + iBallMin) ;  

还有另一种方法:我们先假定您定义了一个PALETTEENTRY结构:

PALETTEENTRY pe ;  

在TimerRoutine期间,您将PALETTEENTRY字段设为白色,并呼叫AnimatePalette来改变逻辑调色盘中iBall位置的一个项目:

pe.peRed   = 255 ;   pe.peGreen= 255 ;   pe.peBlue = 255 ;   pe.peFlags= PC_RESERVED ;   AnimatePalette (hPalette, iBall, 1, &pe) ;  

然后计算显示在BOUNCE中的iBall的新值,将PALETTEENTRY结构的字段定义为红色,然后再次呼叫AnimatePalette:

pe.peRed   = 255 ;   pe.peGreen= 0 ;   pe.peBlue = 0 ;   pe.peFlags= PC_RESERVED ;   AnimatePalette (hPalette, iBall, 1, &pe) ;  

尽管跳动的球是对动画的一个传统的简单说明,但它实际上并不适合调色盘动画,因为必须先画出球的所有可能位置。调色盘动画更适合于显示运动的重复图案。

一个项目的调色盘动画

调色盘动画中一个更有趣的方面就是,可以只使用一个调色盘项目来完成一些有趣的技术。例如程序16-8所示的FADER程序。这个程序也需要前面的PALANIM.C文件。

程序16-8 FADER
FADER.C  
/*--------------------------------------------------------------------------  
  FADER.C --Palette Animation Demo (c) Charles Petzold, 1998  
--------------------------------------------------------------------------*/  
#include <windows.h>  
#define ID_TIMER 1  
TCHAR szAppName[] = TEXT ("Fader") ;  
TCHAR szTitle  [] = TEXT ("Fader: Palette Animation Demo") ;  

static LOGPALETTE lp ;  
HPALETTE CreateRoutine (HWND hwnd)  
{   HPALETTE hPalette ;   lp.palVersion= 0x0300 ;  lp.palNumEntries = 1 ;   lp.palPalEntry[0].peRed  = 255 ;   lp.palPalEntry[0].peGreen= 255 ;   lp.palPalEntry[0].peBlue = 255 ;   lp.palPalEntry[0].peFlags= PC_RESERVED ;  
hPalette = CreatePalette (&lp) ;   SetTimer (hwnd, ID_TIMER, 50, NULL) ;   return hPalette ;  
}  

voidPaintRoutine (HDC hdc, int cxClient, int cyClient)  
{  
   static TCHAR  szText [] = TEXT (" Fade In and Out ") ;  
   int  x, y ;   SIZE sizeText ;  
 SetTextColor (hdc, PALETTEINDEX (0)) ;   GetTextExtentPoint32 (hdc, szText, lstrlen (szText), &sizeText) ;  
 for (x = 0 ; x < cxClient ; x += sizeText.cx)   for (y = 0 ; y < cyClient ; y += sizeText.cy)   {  TextOut (hdc, x, y, szText, lstrlen (szText)) ;   }  
 return ;  
}  

void TimerRoutine (HDC hdc, HPALETTE hPalette)  
{   static BOOL bFadeIn = TRUE ;   if (bFadeIn)   {  lp.palPalEntry[0].peRed   -= 4 ;  lp.palPalEntry[0].peGreen -= 4 ;  
if (   lp.palPalEntry[0].peRed == 3) bFadeIn = FALSE ;   }   else   {  lp.palPalEntry[0].peRed   += 4 ;  lp.palPalEntry[0].peGreen += 4 ;  
if (lp.palPalEntry[0].peRed == 255) bFadeIn = TRUE ;   }  
 AnimatePalette (hPalette, 0, 1, lp.palPalEntry) ;   return ;  
}  

void DestroyRoutine (HWND hwnd, HPALETTE hPalette)  
{   KillTimer (hwnd, ID_TIMER) ;   DeleteObject (hPalette) ;   return ;  
}  

FADER在显示区域上显示满了文字字符串「Fade In AndOut」。文字首先显示为白色,这对于白色背景的窗口来说是看不出来的。通过使用调色盘动画,FADER慢慢地将文字的颜色改为蓝色,然后再改回白色,这样一遍一遍地重复。文字就有渐现渐隐的显示效果了。

FADER用CreateRoutine函数建立了逻辑调色盘,它只需要一个调色盘项目,并将颜色初始化为白色-红色、绿色和蓝色值都设为255。在PaintRoutine中(您可能想起,当逻辑调色盘选进设备内容并显现以后,PALANIM呼叫过此函数),FADER呼叫SetTextColor将文字颜色设定为PALETTEINDEX(0)。这意味着文字颜色设定为调色盘表格中的第一个项目,此项目初始为白色。然后FADER用「FadeIn AndOut」文字字符串填充显示区域。这时,窗口背景是白色,文字也是白色,所以文字不可见。

在TimerRoutine函数中,FADER通过改变PALETTEENTRY结构并将其传递给AnimatePalette来完成调色盘动画。最初,对每一个WM_TIMER消息,程序都将红色和绿色值减4,直到等于3;然后将这些值加4,直到等于255。这将使文字颜色逐渐从白色变到蓝色,然后又回到白色。

程序16-9所示的ALLCOLOR程序只用了逻辑调色盘的一个项目来显示显示卡可以着色的所有颜色。当然,程序不是同时显示这些颜色,而是连续显示。如果显示卡有18位的分辨率(这时能有262144种不同的颜色),那么在两种颜色间隔55毫秒的速度下,只需要4小时就可以在屏幕上看到所有的颜色。

程序16-9 ALLCOLOR
ALLCOLOR.C  
/*---------------------------------------------------------------------------  
  ALLCOLOR.C -- Palette Animation Demo   (c) Charles Petzold, 1998  
-----------------------------------------------------------------------------*/  
#include <windows.h>  
#define ID_TIMER1  

TCHAR szAppName[] = TEXT ("AllColor") ;  
TCHAR szTitle  [] = TEXT ("AllColor: Palette Animation Demo") ;  

static  int  iIncr ;  
static  PALETTEENTRY pe ;  

HPALETTE CreateRoutine (HWND hwnd)  
{   HDC   hdc ;  
HPALETTE  hPalette ;   LOGPALETTElp ;  
// Determine the color resolution and set iIncr   hdc = GetDC (hwnd) ;   iIncr = 1 << (8 - GetDeviceCaps (hdc, COLORRES) / 3) ;   ReleaseDC (hwnd, hdc) ;  
// Create the logical palette   lp.palVersion= 0x0300 ;   lp.palNumEntries= 1 ;   lp.palPalEntry[0].peRed  = 0 ;   lp.palPalEntry[0].peGreen= 0 ;   lp.palPalEntry[0].peBlue = 0 ;   lp.palPalEntry[0].peFlags= PC_RESERVED ;  
hPalette = CreatePalette (&lp) ;  // Save global for less typing   pe = lp.palPalEntry[0] ;   SetTimer (hwnd, ID_TIMER, 10, NULL) ;   return hPalette ;  
}  

void DisplayRGB (HDC hdc, PALETTEENTRY * ppe)  
{   TCHAR szBuffer [16] ;   wsprintf (szBuffer, TEXT (" %02X-%02X-%02X "), ppe->peRed, ppe->peGreen, ppe->peBlue) ;   TextOut (hdc, 0, 0, szBuffer, lstrlen (szBuffer)) ;  
}  

void PaintRoutine (HDC hdc, int cxClient, int cyClient)  
{   HBRUSHhBrush ;   RECT  rect ;  
// Draw Palette Index 0 on entire window  

hBrush = CreateSolidBrush (PALETTEINDEX (0)) ;   SetRect (&rect, 0, 0, cxClient, cyClient) ;   FillRect (hdc, &rect, hBrush) ;   DeleteObject (SelectObject (hdc, GetStockObject (WHITE_BRUSH))) ;  // Display the RGB value   DisplayRGB (hdc, &pe) ;   return ;  
}  

void TimerRoutine (HDC hdc, HPALETTE hPalette)  
{   static BOOL  bRedUp = TRUE, bGreenUp = TRUE, bBlueUp = TRUE ; // Define new color value   pe.peBlue += (bBlueUp ? iIncr : -iIncr) ;   if (   pe.peBlue == (BYTE) (bBlueUp ? 0 : 256 - iIncr))   {  pe.peBlue = (bBlueUp ? 256 - iIncr : 0) ;  bBlueUp ^= TRUE ; pe.peGreen += (bGreenUp ? iIncr : -iIncr) ;  
if (   pe.peGreen == (BYTE) (bGreenUp ? 0 : 256 - iIncr))   {   pe.peGreen = (bGreenUp ? 256 - iIncr : 0) ;bGreenUp ^= TRUE ;   pe.peRed += (bRedUp ? iIncr : -iIncr) ;  
   if (   pe.peRed == (BYTE) (bRedUp ? 0 : 256 - iIncr)) { pe.peRed = (bRedUp ? 256 - iIncr : 0) ;bRedUp ^= TRUE ; }  }  
}  
   // Animate the palette   AnimatePalette (hPalette, 0, 1, &pe) ;   DisplayRGB (hdc, &pe) ;   return ;  
}  

void DestroyRoutine (HWND hwnd, HPALETTE hPalette)  
{   KillTimer (hwnd, ID_TIMER) ;   DeleteObject (hPalette) ;   return ;  
}  

在结构上,ALLCOLOR与FADER非常相似。在CreateRoutine中,ALLCOLOR只用一个设为黑色的调色盘项目(PALETTEENTRY结构的red、green和blue字段设为0)来建立调色盘。在PaintRoutine中,ALLCOLOR用PALETTEINDEX(0)建立实心画刷,并呼叫FillRect来用此画刷为整个显示区域着色。

在TimerRoutine中,ALLCOLOR通过改变PALETTEENTRY颜色并呼叫AnimatePalette来启动调色盘。我编写ALLCOLOR程序,以便颜色变化顺畅。首先,蓝色值渐渐增加。达到最大时,绿色值增加,而蓝色值渐渐减少。红色、绿色和蓝色值的增加和减少取决于iIncr变量。在CreateRoutine期间,这将根据用COLORRES参数从GetDeviceCaps传回的值来计算。例如,如果GetDeviceCaps传回18,那么iIncr设为4-获得所有颜色所需要的最小值。

ALLCOLOR还在显示区域的左上角显示目前的RGB颜色值。我最初添加这个程序代码是出于测试目的,但是现在证明它是有用的,所以我保留了它。

工程应用程序

在工程应用程序中,动画对于显示机械或电的作用过程很有用。在计算机屏幕上显示内燃引擎虽然简单,但是动画可以使它变得更加生动,且更清楚地显示其工作程序。

使用调色盘动画的一个好范例就是显示流体通过管子的过程。这是一个例子,图像不必十分精确-实际上,如果图像很精确(就像看透明的管子),则很难说明管子里的流体是如何运动的。这时用符号会更好一些。程序16-10所示的PIPES程序是此技术的简单示范:在显示区域有两个水平的管子,流体在上面的管子里从左向右流动,而在下面的管子里从右向左移动。

程序16-10 PIPES

PIPES.C  
/*-------------------------------------------------------------------------  
  PIPES.C --Palette Animation Demo (c) Charles Petzold, 1998  
---------------------------------------------------------------------------*/  
#include <windows.h>  
#define ID_TIMER 1  
TCHAR szAppName [] = TEXT ("Pipes") ;  
TCHAR szTitle   [] = TEXT ("Pipes: Palette Animation Demo") ;  

static LOGPALETTE * plp ;  

HPALETTE CreateRoutine (HWND hwnd)  
{   HPALETTE  hPalette ;  int   i ;  
plp = malloc (sizeof (LOGPALETTE) + 32 * sizeof (PALETTEENTRY)) ;  // Initialize the fields of the LOGPALETTE structure   plp->palVersion   = 0x300 ;   plp->palNumEntries= 16 ;  
 for (i = 0 ; i <= 8 ; i++)  
{  plp->palPalEntry[i].peRed = (BYTE) min (255, 0x20 * i) ;  plp->palPalEntry[i].peGreen   = 0 ;  plp->palPalEntry[i].peBlue= (BYTE) min (255, 0x20 * i) ;  plp->palPalEntry[i].peFlags   = PC_RESERVED ;  
plp->palPalEntry[16 - i]  = plp->palPalEntry[i] ;  plp->palPalEntry[16 + i]  = plp->palPalEntry[i] ;  plp->palPalEntry[32 - i]  = plp->palPalEntry[i] ;   }  
 hPalette = CreatePalette (plp) ;   SetTimer (hwnd, ID_TIMER, 100, NULL) ;   return hPalette ;  
}  

void PaintRoutine (HDC hdc, int cxClient, int cyClient)  
{   HBRUSH hBrush ;   int   i ;   RECT  rect ;  
   // Draw window background  
 SetRect (&rect, 0, 0, cxClient, cyClient) ;   hBrush = SelectObject (hdc, GetStockObject (WHITE_BRUSH)) ;   FillRect (hdc, &rect, hBrush) ;  
   // Draw the interiors of the pipes   for (i = 0 ; i < 128 ; i++)   {  hBrush = CreateSolidBrush (PALETTEINDEX (i % 16)) ;  SelectObject (hdc, hBrush) ;  
rect.left = (127 - i) * cxClient / 128 ;  zrect.right   = (128 - i) * cxClient / 128 ;  rect.top  = 4 * cyClient / 14 ;  rect.bottom   = 5 * cyClient / 14 ;  
FillRect (hdc, &rect, hBrush) ;  
rect.left =  i * cxClient / 128 ;  rect.right= (i + 1) * cxClient / 128 ;  rect.top  =  9  * cyClient / 14 ; rect.bottom   =  10 * cyClient / 14 ;  
FillRect (hdc, &rect, hBrush) ;  
DeleteObject (SelectObject (hdc, GetStockObject (WHITE_BRUSH))) ;  
}  
   // Draw the edges of the pipes   MoveToEx  (hdc, 0,  4 * cyClient / 14, NULL) ;   LineTo(hdc, cxClient,  4* cyClient / 14) ;  
 MoveToEx  (hdc, 0,  5 * cyClient / 14, NULL) ;   LineTo(hdc, cxClient,5  * cyClient / 14) ;  
 MoveToEx  (hdc, 0,  9   * cyClient / 14, NULL) ;   LineTo(hdc, cxClient, 9 * cyClient / 14) ;  
 MoveToEx  (hdc, 0,  10 * cyClient / 14, NULL) ;   LineTo   (hdc, cxClient, 10 * cyClient / 14) ;   return ;  
}  

void TimerRoutine (HDC hdc, HPALETTE hPalette)  
{   static int iIndex ;   AnimatePalette (hPalette, 0, 16, plp->palPalEntry + iIndex) ;   iIndex = (iIndex + 1) % 16 ;  
return ;  
}  

void DestroyRoutine (HWND hwnd, HPALETTE hPalette)  
{   KillTimer (hwnd, ID_TIMER) ;   DeleteObject (hPalette) ;   free (plp) ;  return ;  
}  

PIPES为动画使用了16个调色盘项目,而您可能会使用更少的项目。最小化时,真正需要的是有足够的项目来显示流动的方向。用三个调色盘项目要比用一个静态箭头好。

程序16-11所示的TUNNEL程序是这组程序中最贪心的程序,它为动画使用了128个调色盘项目,但是从效果来看,值得这样做。

程序16-11 TUNNEL
TUNNEL.C  
/*---------------------------------------------------------------------------  
  TUNNEL.C --   Palette Animation Demo (c) Charles Petzold, 1998  
----------------------------------------------------------------------------*/  
#include <windows.h>  
#define ID_TIMER 1  
TCHAR szAppName[] = TEXT ("Tunnel") ;  
TCHAR szTitle  [] = TEXT ("Tunnel: Palette Animation Demo") ;  

static LOGPALETTE * plp ;  
HPALETTE CreateRoutine (HWND hwnd)  
{   BYTE  byGrayLevel ;   HPALETTE  hPalette ;   int   i ;  
 plp = malloc (sizeof (LOGPALETTE) + 255 * sizeof (PALETTEENTRY)) ;  // Initialize the fields of the LOGPALETTE structure   plp->palVersion   = 0x0300 ;   plp->palNumEntries= 128 ;  
  for (i = 0 ; i < 128 ; i++)   {  if (i < 64) byGrayLevel = (BYTE) (4 * i) ;  else byGrayLevel = (BYTE) min (255, 4 * (128 - i)) ; plp->palPalEntry[i].peRed = byGrayLevel ;  plp->palPalEntry[i].peGreen   = byGrayLevel ;  plp->palPalEntry[i].peBlue= byGrayLevel ;  plp->palPalEntry[i].peFlags   = PC_RESERVED ;  plp->palPalEntry[i + 128].peRed  = byGrayLevel ;  plp->palPalEntry[i + 128].peGreen = byGrayLevel ;  plp->palPalEntry[i + 128].peBlue  = byGrayLevel ;  plp->palPalEntry[i + 128].peFlags = PC_RESERVED ;   }  
hPalette = CreatePalette (plp) ;   SetTimer (hwnd, ID_TIMER, 50, NULL) ;   return hPalette ;  
}  

void PaintRoutine (HDC hdc, int cxClient, int cyClient)  
{   HBRUSH hBrush ;   int   i ;   RECT  rect ;  
  for (i = 0 ; i < 127 ; i++)  
{ // Use a RECT structure for each of 128 rectangles  rect.left   =   i * cxClient / 255 ;  rect.top=   i * cyClient / 255 ;  rect.right  = cxClient - i * cxClient / 255 ;  rect.bottom = cyClient - i * cyClient / 255 ;  hBrush = CreateSolidBrush (PALETTEINDEX (i)) ;// Fill the rectangle and delete the brush  FillRect (hdc, &rect, hBrush) ;  DeleteObject (hBrush) ;   }   return ;  
}  

void TimerRoutine (HDC hdc, HPALETTE hPalette)  
{   static int iLevel ;   iLevel = (iLevel + 1) % 128 ;   AnimatePalette (hPalette, 0, 128, plp->palPalEntry + iLevel) ;   return ;  
}  

void DestroyRoutine (HWND hwnd, HPALETTE hPalette)  
{   KillTimer (hwnd, ID_TIMER) ;   DeleteObject (hPalette) ;  free (plp) ;   return ;  
}  

TUNNEL在128个调色盘项目中使用64种移动的灰阶-从黑到白,再从白到黑-表现在隧道旅行的效果。

调色盘和真实世界图像

当然,尽管我们已经完成了许多有趣的事:连续显示色彩的底纹、做了调色盘动画,但调色盘管理器的真正目的是允许在8位显示模式下显示真实世界中的图像。对于本章的其余部分,我们正好研究一下。正如您所期望的,在使用packedDIB、GDI位图对象和DIB区块时,必须按照不同的方法来使用调色盘。下面的六个程序阐明了用调色盘来处理位图的各种技术。

调色盘和Packed DIB

下面三个程序,有助于我们建立处理packedDIB内存块的一系列函数。这些函数都在程序16-12所示的PACKEDIB文件中。

程序16-12 PACKEDIB文件
PACKEDIB.H  
/*-------------------------------------------------------------------------  
  PACKEDIB.H --   Header file for PACKEDIB.C (c) Charles Petzold, 1998  
--------------------------------------------------------------------------*/  
#include <windows.h>  

BITMAPINFO * PackedDibLoad (PTSTR szFileName) ;  
int PackedDibGetWidth (BITMAPINFO * pPackedDib) ;  
int PackedDibGetHeight (BITMAPINFO * pPackedDib) ;  
int PackedDibGetBitCount (BITMAPINFO * pPackedDib) ;  
int PackedDibGetRowLength (BITMAPINFO * pPackedDib) ;  
int PackedDibGetInfoHeaderSize (BITMAPINFO * pPackedDib) ;  
int PackedDibGetColorsUsed (BITMAPINFO * pPackedDib) ;  
int PackedDibGetNumColors (BITMAPINFO * pPackedDib) ;  
int PackedDibGetColorTableSize (BITMAPINFO * pPackedDib) ;  
RGBQUAD * PackedDibGetColorTablePtr (BITMAPINFO * pPackedDib) ;  
RGBQUAD * PackedDibGetColorTableEntry (BITMAPINFO * pPackedDib, int i) ;  
BYTE * PackedDibGetBitsPtr (BITMAPINFO * pPackedDib) ;  
int PackedDibGetBitsSize (BITMAPINFO * pPackedDib) ;  
HPALETTE PackedDibCreatePalette (BITMAPINFO * pPackedDib) ;  
PACKEDIB.C  
/*-------------------------------------------------------------------------  
  PACKEDIB.C -- Routines for using packed DIBs  (c) Charles Petzold, 1998  
---------------------------------------------------------------------------*/  
#include <windows.h>  

/*---------------------------------------------------------------------------  
  PackedDibLoad: Load DIB File as Packed-Dib Memory Block  
----------------------------------------------------------------------------*/  

BITMAPINFO * PackedDibLoad (PTSTR szFileName)  
{   BITMAPFILEHEADER  bmfh ;   BITMAPINFO*  pbmi ; BOOL  bSuccess ;   DWORD dwPackedDibSize, dwBytesRead ;   HANDLEhFile ;  // 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 packed DIB & read it in   dwPackedDibSize = bmfh.bfSize - sizeof (BITMAPFILEHEADER) ;   pbmi = malloc (dwPackedDibSize) ;  
  bSuccess = ReadFile (hFile, pbmi, dwPackedDibSize, &dwBytesRead, NULL) ;   CloseHandle (hFile) ;  
 if (!bSuccess || (dwBytesRead != dwPackedDibSize))   { free (pbmi) ;  return NULL ;   }  
 return pbmi ;  
}  

/*--------------------------------------------------------------------------  
  Functions to get information from packed DIB  
----------------------------------------------------------------------------*/  

int PackedDibGetWidth (BITMAPINFO * pPackedDib)  
{   if (pPackedDib->bmiHeader.biSize == sizeof (BITMAPCOREHEADER))  return ((PBITMAPCOREINFO)pPackedDib)->bmciHeader.bcWidth ;   else  return pPackedDib->bmiHeader.biWidth ;  
}  

int PackedDibGetHeight (BITMAPINFO * pPackedDib)  
{  
   if (pPackedDib->bmiHeader.biSize == sizeof (BITMAPCOREHEADER))  return ((PBITMAPCOREINFO)pPackedDib)->bmciHeader.bcHeight ;   else  return abs (pPackedDib->bmiHeader.biHeight) ;  
}  

int PackedDibGetBitCount (BITMAPINFO * pPackedDib)  
{   if (pPackedDib->bmiHeader.biSize == sizeof (BITMAPCOREHEADER)) return ((PBITMAPCOREINFO)pPackedDib)->bmciHeader.bcBitCount ;   else  return pPackedDib->bmiHeader.biBitCount ;  
}  

int PackedDibGetRowLength (BITMAPINFO * pPackedDib)  
{   return (( PackedDibGetWidth (pPackedDib) *PackedDibGetBitCount (pPackedDib) + 31) & ~31) >> 3 ;  
}  

/*---------------------------------------------------------------------------  
  PackedDibGetInfoHeaderSize includes possible color masks!  
----------------------------------------------------------------------------*/  

int PackedDibGetInfoHeaderSize (BITMAPINFO * pPackedDib)  
{   if (   pPackedDib->bmiHeader.biSize == sizeof (BITMAPCOREHEADER))return ((PBITMAPCOREINFO)pPackedDib)->bmciHeader.bcSize ;  
 else   if (pPackedDib->bmiHeader.biSize == sizeof (BITMAPINFOHEADER))  return pPackedDib->bmiHeader.biSize +   (pPackedDib->bmiHeader.biCompression ==BI_BITFIELDS ? 12 : 0) ;   else return pPackedDib->bmiHeader.biSize ;  
}  

/*--------------------------------------------------------------------------  
  PackedDibGetColorsUsed returns value in information header; could be 0 to indicate non-truncated color table!  
----------------------------------------------------------------------------*/  

int PackedDibGetColorsUsed (BITMAPINFO * pPackedDib)  
{   if (pPackedDib->bmiHeader.biSize == sizeof (BITMAPCOREHEADER)) return 0 ;   else  return pPackedDib->bmiHeader.biClrUsed ;  
}  

/*----------------------------------------------------------------------------  
  PackedDibGetNumColors is actual number of entries in color table  
-----------------------------------------------------------------------------*/  

int PackedDibGetNumColors (BITMAPINFO * pPackedDib)  
{   int iNumColors ;   iNumColors = PackedDibGetColorsUsed (pPackedDib) ;   if (   iNumColors == 0 && PackedDibGetBitCount (pPackedDib) < 16) iNumColors =1 << PackedDibGetBitCount (pPackedDib) ;  
return iNumColors ;  
}  

int PackedDibGetColorTableSize (BITMAPINFO * pPackedDib)  
{   if (pPackedDib->bmiHeader.biSize == sizeof (BITMAPCOREHEADER)) return PackedDibGetNumColors (pPackedDib) * sizeof (RGBTRIPLE) ;   else  return PackedDibGetNumColors (pPackedDib) * sizeof (RGBQUAD) ;  
}  

RGBQUAD * PackedDibGetColorTablePtr (BITMAPINFO * pPackedDib)  
{  
if (PackedDibGetNumColors (pPackedDib) == 0)  return 0 ;   return (RGBQUAD *) (((BYTE *)pPackedDib) +  PackedDibGetInfoHeaderSize (pPackedDib)) ;  
}  

RGBQUAD * PackedDibGetColorTableEntry (BITMAPINFO * pPackedDib, int i)  
{   if (  PackedDibGetNumColors (pPackedDib) == 0)  return 0 ;  
 if (  pPackedDib->bmiHeader.biSize == sizeof (BITMAPCOREHEADER))  return (RGBQUAD *) (((RGBTRIPLE *) PackedDibGetColorTablePtr (pPackedDib)) + i) ;   else  return PackedDibGetColorTablePtr (pPackedDib) + i ;  
}  

/*--------------------------------------------------------------------------  
  PackedDibGetBitsPtr finally!  
----------------------------------------------------------------------------*/  

BYTE * PackedDibGetBitsPtr (BITMAPINFO * pPackedDib)  
{   return ((BYTE *) pPackedDib)+PackedDibGetInfoHeaderSize (pPackedDib) +   PackedDibGetColorTableSize (pPackedDib) ;  
}  

/*-----------------------------------------------------------------------------  
  PackedDibGetBitsSize can be calculated from the height and row length  if it's not explicitly in the biSizeImage field  
-----------------------------------------------------------------------------*/  

int PackedDibGetBitsSize (BITMAPINFO * pPackedDib)  
{  if ((pPackedDib->bmiHeader.biSize != sizeof (BITMAPCOREHEADER)) &&  (pPackedDib->bmiHeader.biSizeImage != 0))  return pPackedDib->bmiHeader.biSizeImage ;  
return PackedDibGetHeight (pPackedDib) *PackedDibGetRowLength (pPackedDib) ;  
}  

/*---------------------------------------------------------------------------  
  PackedDibCreatePalette creates logical palette from PackedDib  
-----------------------------------------------------------------------------*/  
HPALETTE PackedDibCreatePalette (BITMAPINFO * pPackedDib)  
{   HPALETTE   hPalette ;   inti, iNumColors ;   LOGPALETTE*  plp ;   RGBQUAD* prgb ;  
 if (0 == ( iNumColors = PackedDibGetNumColors (pPackedDib))) return NULL ;   plp = malloc (sizeof (LOGPALETTE) *   (iNumColors - 1) * sizeof (PALETTEENTRY)) ;  
 plp->palVersion= 0x0300 ;   plp->palNumEntries = iNumColors ;   for (i = 0 ; i < iNumColors ; i++)   {  prgb = PackedDibGetColorTableEntry (pPackedDib, i) ;  plp->palPalEntry[i].peRed = prgb->rgbRed ;  plp->palPalEntry[i].peGreen   = prgb->rgbGreen ;  plp->palPalEntry[i].peBlue= prgb->rgbBlue ;  plp->palPalEntry[i].peFlags   = 0 ;   }  
 hPalette = CreatePalette (plp) ;   free (plp) ;  
 return hPalette ;  
}  

第一个函数是PackedDibLoad,它将唯一的参数作为文件名,并传回指向内存中packedDIB的指针。其它所有函数都将这个packedDIB指标作为它们的第一个参数并传回有关DIB的信息。这些函数按「由下而上」顺序排列到文件中。每个函数都使用从前面函数获得的信息。

我不倾向于说这是在处理packedDIB时有用的「完整」函数集。而且,我也不想汇编一个真正的扩展集,因为我不认为这是处理packedDIB的一个好方法。在写类似下面的函数时,您会很明显地发现这一点:

dwPixel = PackedDibGetPixel (pPackedDib, x, y) ;  

这种函数包括太多的巢状函数呼叫,以致于效率非常低而且很慢。本章的后面将讨论一种我认为更好的方法。

另外,您将注意到,其中许多函数都需要对OS/2兼容的DIB采取不同的处理程序;这样,函数将频繁地检查BITMAPINFO结构的第一个字段是否与BITMAPCOREHEADER结构的大小相同。

特别注意最后一个函数PackedDibCreatePalette。这个函数用DIB中的颜色表来建立调色盘。如果DIB中没有颜色表(这意味着DIB的每图素有16、24或32位),那么就不建立调色盘。我们有时会将从DIB颜色表建立的调色盘称为DIB自己的调色盘。

PACKEDIB文件都放在SHOWDIB3,如程序16-13所示。

程序16-13 SHOWDIB3
SHOWDIB3.C  
/*--------------------------------------------------------------------------  
SHOWDIB3.C -- Displays DIB with native palette(c) Charles Petzold, 1998  
----------------------------------------------------------------------------*/  
#include <windows.h>  
#include "PackeDib.h"  
#include "resource.h"  

LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;  
TCHAR szAppName[] = TEXT ("ShowDib3") ;  
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,   PSTR szCmdLine, int iCmdShow)  
{   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, TEXT ("Show DIB #3: Native Palette"),WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL) ;  
  ShowWindow (hwnd, iCmdShow) ;   UpdateWindow (hwnd) ;  

   while (GetMessage (&msg, NULL, 0, 0))   {   TranslateMessage (&msg) ;  DispatchMessage (&msg) ;   }  
  return msg.wParam ;  
}  

LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam,LPARAM lParam)  
{   static BITMAPINFO *   pPackedDib ;   static HPALETTE  hPalette ;   static int   cxClient, cyClient ;   static OPENFILENAME   ofn ;   static TCHAR  szFileName [MAX_PATH], szTitleName [MAX_PATH] ;   static TCHAR szFilter[] = TEXT ("Bitmap Files (*.BMP)\0*.bmp\0")   TEXT ("All Files (*.*)\0*.*\0\0") ;   HDC  hdc ;   PAINTSTRUCT  ps ;  
 switch (message)   {   case   WM_CREATE:  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 = 0 ;  ofn.nFileOffset   = 0 ;  ofn.nFileExtension= 0 ;  ofn.lpstrDefExt   = TEXT ("bmp") ;  ofn.lCustData = 0 ;  ofn.lpfnHook  = NULL ;  ofn.lpTemplateName= NULL ;  
return 0 ;  
 case   WM_SIZE:  cxClient = LOWORD (lParam) ;  cyClient = HIWORD (lParam) ;  return 0 ;  

case   WM_COMMAND:  switch (LOWORD (wParam))  {  case IDM_FILE_OPEN:  
// Show the File Open dialog box  
   if (!GetOpenFileName (&ofn))   return 0 ;   // If there's an existing packed DIB, free the memory  
   if (pPackedDib) { free (pPackedDib) ; pPackedDib = NULL ; }   // If there's an existing logical palette, delete it  
   if (hPalette) {   DeleteObject (hPalette) ;hPalette = NULL ; }   // Load the packed DIB into memory  
   SetCursor (LoadCursor (NULL, IDC_WAIT)) ; ShowCursor (TRUE) ;  
 pPackedDib = PackedDibLoad (szFileName) ;  
   ShowCursor (FALSE) ; SetCursor (LoadCursor (NULL, IDC_ARROW)) ;  
   if (pPackedDib) {  // Create the palette from the DIB color table  
hPalette = PackedDibCreatePalette (pPackedDib) ; } else {   MessageBox (   hwnd, TEXT ("Cannot load DIB file"),   szAppName, 0) ; } InvalidateRect (hwnd, NULL, TRUE) ; return 0 ;  }  break ;  
 case   WM_PAINT:  hdc = BeginPaint (hwnd, &ps) ;  
if (hPalette)  { SelectPalette (hdc, hPalette, FALSE) ;RealizePalette (hdc) ;  }  if (pPackedDib)  SetDIBitsToDevice (hdc, 0,0,PackedDibGetWidth (pPackedDib), PackedDibGetHeight (pPackedDib), 0,0,0,PackedDibGetHeight (pPackedDib),  PackedDibGetBitsPtr (pPackedDib), pPackedDib,   DIB_RGB_COLORS) ;  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 (pPackedDib) free (pPackedDib) ;  
if (hPalette) DeleteObject (hPalette) ;  
   PostQuitMessage (0) ;  return 0 ;   }   return DefWindowProc (hwnd, message, wParam, lParam) ;  
}  
SHOWDIB3.RC (摘录)  
//Microsoft Developer Studio generated resource script.  
#include "resource.h"  
#include "afxres.h"  
/////////////////////////////////////////////////////////////////////////////  
// Menu  
SHOWDIB3 MENU DISCARDABLE  
BEGIN  
   POPUP "&File"  
   BEGIN   MENUITEM "&Open", IDM_FILE_OPEN  
   END  
END  
RESOURCE.H (摘录)  
// Microsoft Developer Studio generated include file.  
// Used by ShowDib3.rc  
#define IDM_FILE_OPEN 40001  

SHOWDIB3中的窗口消息处理程序将packedDIB指针作为静态变量来维护,窗口消息处理程序在「FileOpen」命令期间呼叫PACKEDIB.C中的PackedDibLoad函数时获得了此指标。在处理此命令的过程中,SHOWDIB3也呼叫PackedDibCreatePalette来获得可能用于DIB的调色盘。注意,无论SHOWDIB3什么时候准备加载新的DIB,都应先释放前一个DIB的内存,并删除前一个DIB的调色盘。在处理WM_DESTROY消息的程序中,最后的DIB最后释放,最后的调色盘最后删除。

处理WM_PAINT消息很简单:如果存在调色盘,则SHOWDIB3将它选进设备内容并显现它。然后它呼叫SetDIBitsToDevice,并传递有关DIB的函数信息(例如宽、高和指向DIB图素位的指针),这些信息从PACKEDIB中的函数获得。

另外,请记住SHOWDIB3依据DIB中的颜色表建立了调色盘。如果在DIB中没有颜色表-通常是16位、24位和32位DIB的情况-就不建立调色盘。在8位显示模式下显示DIB时,它只能用标准保留的20种颜色显示。

对这个问题有两种解决方法:第一种是简单地使用「通用」调色盘,这种调色盘适用于许多图形。您也可以自己建立调色盘。第二种解决方法是分析DIB的图素位,并决定要显示图像的最佳颜色。很明显,第二种方法将涉及更多的工作(对于程序写作者和处理器都是如此),但是我将在本章结束之前告诉您如何使用第二种方法。

「通用」调色盘

程序16-14所示的SHOWDIB4程序建立了一个通用的调色盘,它用于显示加载到程序中的所有DIB。另外,SHOWDIB4与SHOWDIB3非常相似。

程序16-14 SHOWDIB4
SHOWDIB4.C  
/*---------------------------------------------------------------------------  
  SHOWDIB4.C -- Displays DIB with "all-purpose" palette (c) Charles Petzold, 1998  
-----------------------------------------------------------------------------*/  
#include <windows.h>  
#include "..\\ShowDib3\\PackeDib.h"  
#include "resource.h"  

LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;  
TCHAR szAppName[] = TEXT ("ShowDib4") ;  
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,  PSTR szCmdLine, int iCmdShow)  
{   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, TEXT ("Show DIB #4: All-Purpose Palette"),WS_OVERLAPPEDWINDOW,CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL) ;  
 ShowWindow (hwnd, iCmdShow) ; UpdateWindow (hwnd) ;  
 while (GetMessage (&msg, NULL, 0, 0))   {  TranslateMessage (&msg) ;  DispatchMessage (&msg) ;   }   return msg.wParam ;  
}  

/*-----------------------------------------------------------------------------  
  CreateAllPurposePalette: 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 CreateAllPurposePalette (void)  
{   HPALETTE hPalette ;   int  i, incr, R, G, B ;   LOGPALETTE * plp ;  
 plp = malloc (sizeof (LOGPALETTE) + 246 * sizeof (PALETTEENTRY)) ;   plp->palVersion   = 0x0300 ;   plp->palNumEntries= 247 ;  
// The following loop calculates 31 gray shades, but 3 of them  // will match the standard 20 colors  
 for (i = 0, G = 0, incr = 8 ; G <= 0xFF ; i++, G += incr)   {  plp->palPalEntry[i].peRed = (BYTE) G ;  plp->palPalEntry[i].peGreen   = (BYTE) G ;  plp->palPalEntry[i].peBlue= (BYTE) G ;  plp->palPalEntry[i].peFlags   = 0 ;  
incr = (incr == 9 ? 8 : 9) ;  }  
// The following loop is responsible for 216 entries, but 8 of  // them will match the standard 20 colors, and another  // 4 of them will match the gray shades above.  
 for (R = 0 ; R <= 0xFF ; R += 0x33)  for (G = 0 ; G <= 0xFF ; G += 0x33)   for (B = 0 ; B <= 0xFF ; B += 0x33)  
{  plp->palPalEntry  [i].peRed = (BYTE) R ;  plp->palPalEntry  [i].peGreen   = (BYTE) G ;  plp->palPalEntry  [i].peBlue= (BYTE) B ;  plp->palPalEntry  [i].peFlags   = 0 ;i++ ;   }   hPalette = CreatePalette (plp) ;   free (plp) ;   return hPalette ;  
}  

LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam,LPARAM lParam)  
{   static BITMAPINFO * pPackedDib ;   static HPALETTE  hPalette ;   static int  cxClient, cyClient ;   static OPENFILENAME ofn ;   static TCHARszFileName [MAX_PATH], szTitleName [MAX_PATH] ;   static TCHAR   szFilter[] = TEXT ("Bitmap Files (*.BMP)\0*.bmp\0")   TEXT ("All Files (*.*)\0*.*\0\0") ;   HDChdc ;   PAINTSTRUCT  ps ;   switch (message)  
{   case   WM_CREATE:  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 = 0 ; ofn.nFileOffset   = 0 ;  ofn.nFileExtension= 0 ;  ofn.lpstrDefExt   = TEXT ("bmp") ;  ofn.lCustData = 0 ;  ofn.lpfnHook  = NULL ;  ofn.lpTemplateName= NULL ;  
   // Create the All-Purpose Palette  
hPalette = CreateAllPurposePalette () ;  return 0 ;  
 case   WM_SIZE:  cxClient = LOWORD (lParam) ;  cyClient = HIWORD (lParam) ;  return 0 ;  
 case   WM_COMMAND:switch (LOWORD (wParam))  { case   IDM_FILE_OPEN:  
   // Show the File Open dialog box  
   if (!GetOpenFileName (&ofn))return 0 ;// If there's an existing packed DIB, free the memory if (pPackedDib) { free (pPackedDib) ; pPackedDib = NULL ; }  // Load the packed DIB into memory  
   SetCursor (LoadCursor (NULL, IDC_WAIT)) ; ShowCursor (TRUE) ;  
  pPackedDib = PackedDibLoad (szFileName) ;  
   ShowCursor (FALSE) ; SetCursor (LoadCursor (NULL, IDC_ARROW)) ;  
   if (!pPackedDib) {   MessageBox (   hwnd, TEXT ("Cannot load DIB file"), szAppName, 0) ; } InvalidateRect (hwnd, NULL, TRUE) ; return 0 ;  }  break ;  
 case   WM_PAINT:  hdc = BeginPaint (hwnd, &ps) ;  
if (pPackedDib)  { SelectPalette (hdc, hPalette, FALSE) ;RealizePalette (hdc) ;  SetDIBitsToDevice (hdc,0,0,PackedDibGetWidth (pPackedDib),   PackedDibGetHeight (pPackedDib), 0,0,0,PackedDibGetHeight (pPackedDib),PackedDibGetBitsPtr (pPackedDib), pPackedDib,   DIB_RGB_COLORS) ;  }  EndPaint (hwnd, &ps) ;  return 0 ;  
 case   WM_QUERYNEWPALETTE:  hdc = GetDC (hwnd) ;  SelectPalette (hdc, hPalette, FALSE) ;  RealizePalette (hdc) ;  InvalidateRect (hwnd, NULL, TRUE) ;  
ReleaseDC (hwnd, hdc) ;  return TRUE ;  
 case   WM_PALETTECHANGED:  if ((HWND) wParam != hwnd)  
hdc = GetDC (hwnd) ;  SelectPalette (hdc, hPalette, FALSE) ;  RealizePalette (hdc) ;  UpdateColors (hdc) ;  
ReleaseDC (hwnd, hdc) ;  break ;   case   WM_DESTROY: if (pPackedDib) free (pPackedDib) ;  
DeleteObject (hPalette) ;  
PostQuitMessage (0) ;  return 0 ;   }   return DefWindowProc (hwnd, message, wParam, lParam) ;  
}  
SHOWDIB4.RC (摘录)  
//Microsoft Developer Studio generated resource script.  
#include "resource.h"  
#include "afxres.h"  
/////////////////////////////////////////////////////////////////////////////  
// Menu  
SHOWDIB4 MENU DISCARDABLE  
BEGIN  
   POPUP "&Open"  
   BEGIN  MENUITEM "&File",IDM_FILE_OPEN  
   END  
END  
RESOURCE.H (摘录)  
// Microsoft Developer Studio generated include file.  
// Used by ShowDib4.rc  
#define IDM_FILE_OPEN40001  

在处理WM_CREATE消息时,SHOWDIB4将呼叫CreateAllPurposePalette,并在程序中保留该调色盘,而在WM_DESTROY消息处理期间删除它。因为程序知道调色盘一定存在,所以在处理WM_PAINT、WM_QUERYNEWPALETTE或WM_PALETTECHANGED消息时,不必检查调色盘的存在。

CreateAllPurposePalette函数似乎是用247个项目来建立逻辑调色盘,它超出了系统调色盘中允许程序正常存取的236个项目。的确如此,不过这样做很方便。这些项目中有15个被复制或者映射到20种标准的保留颜色中。

CreateAllPurposePalette从建立31种灰阶开始,即0x00、0x09、0x11、0x1A、0x22、0x2B、0x33、0x3C、0x44、0x4D、0x55、0x5E、0x66、0x6F、0x77、0x80、0x88、0x91、0x99、0xA2、0xAA、0xB3、0xBB、0xC4、0xCC、0xD5、0xDD、0xE6、0xEE、0xF9和0xFF的红色、绿色和蓝色值。注意,第一个、最后一个和中间的项目都在标准的20种保留颜色中。下一个函数用红色、绿色和蓝色值的所有组合建立了颜色0x00、0x33、0x66、0x99、0xCC和0xFF。这样就共有216种颜色,但是其中8种颜色复制了标准的20种保留颜色,而另外4个复制了前面计算的灰阶。如果将PALETTEENTRY结构的peFlags字段设为0,则Windows将不把复制的项目放进系统调色盘。

显然地,实际的程序不希望计算16位、24位或者32位DIB的最佳调色盘,程序将继续使用DIB颜色表来显示8位DIB。SHOWDIB4不完成这项工作,它只对每件事都使用通用调色盘。因为SHOWDIB4是一个展示程序,而且您可以与SHOWDIB3显示的8位DIB进行比较。如果看一些人像的彩色DIB,那么您可能会得出这样的结论:SHOWDIB4没有足够的颜色来精确地表示鲜艳的色调。

如果用SHOWDIB4中的CreateAllPurposePalette函数来试验(可能是通过将逻辑调色盘的大小减少到只有几个项目的方法),您将发现当调色盘选进设备内容时,Windows将只使用调色盘中的颜色,而不使用标准的20种颜色调色盘的颜色。

中间色调色盘

WindowsAPI包括一个通用调色盘,程序可以通过呼叫CreateHalftonePalette来获得该调色盘。使用此调色盘的方法与使用从SHOWDIB4中的CreateAllPurposePalette获得调色盘的方法相同,或者您也可以与位图缩放模式中的HALFTONE设定-用SetStretchBltMode设定-一起使用。程序16-15所示的SHOWDIB5程序展示了使用中间色调色盘的方法。

程序16-15  SHOWDIB5  
SHOWDIB5.C  
/*--------------------------------------------------------------------------  
  SHOWDIB5.C -- Displays DIB with halftone palette (c) Charles Petzold, 1998  
--------------------------------------------------------------------------*/  
#include <windows.h>  
#include "..\\ShowDib3\\PackeDib.h"  
#include "resource.h"  

LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;  
TCHAR szAppName[] = TEXT ("ShowDib5") ;  
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,PSTR szCmdLine, int iCmdShow)  
{   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, TEXT ("Show DIB #5: Halftone Palette"),  WS_OVERLAPPEDWINDOW,  CW_USEDEFAULT, CW_USEDEFAULT,  CW_USEDEFAULT, CW_USEDEFAULT,  NULL, NULL, hInstance, NULL) ;  
 ShowWindow (hwnd, iCmdShow) ;   UpdateWindow (hwnd) ;  
while (GetMessage (&msg, NULL, 0, 0))  
{TranslateMessage (&msg) ;  DispatchMessage (&msg) ;  
}   return msg.wParam ;  
}  

LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam,LPARAM lParam)  
{   static BITMAPINFO * pPackedDib ;   static HPALETTE hPalette ;   static int  cxClient, cyClient ;   static OPENFILENAME ofn ;   static TCHARszFileName [MAX_PATH], szTitleName [MAX_PATH] ;   static TCHARszFilter[] =  TEXT ("Bitmap Files (*.BMP)\0*.bmp\0")   TEXT ("All Files (*.*)\0*.*\0\0") ;   HDC  hdc ;   PAINTSTRUCT  ps ;  
 switch (message)  
{   case   WM_CREATE:  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 = 0 ;  ofn.nFileOffset   = 0 ;  ofn.nFileExtension= 0 ;  ofn.lpstrDefExt   = TEXT ("bmp") ;  ofn.lCustData= 0 ;  ofn.lpfnHook  = NULL ;  ofn.lpTemplateName= NULL ;  
   // Create the All-Purpose Palette  
hdc = GetDC (hwnd) ;  hPalette = CreateHalftonePalette (hdc) ; ReleaseDC (hwnd, hdc) ;  return 0 ;  
 case   WM_SIZE:  cxClient = LOWORD (lParam) ;  cyClient = HIWORD (lParam) ;  return 0 ;  
 case   WM_COMMAND:  switch (LOWORD (wParam))  { case   IDM_FILE_OPEN:  
   // Show the File Open dialog box  
   if (!GetOpenFileName (&ofn))return 0 ;   // If there's an existing packed DIB, free the memory  
  if (pPackedDib) { free (pPackedDib) ; pPackedDib = NULL ; } // Load the packed DIB into memory  
  SetCursor (LoadCursor (NULL, IDC_WAIT)) ; ShowCursor (TRUE) ;  
  pPackedDib = PackedDibLoad (szFileName) ;  
   ShowCursor (FALSE) ; SetCursor (LoadCursor (NULL, IDC_ARROW)) ;  
   if (!pPackedDib){  MessageBox (hwnd, TEXT ("Cannot load DIB file"),   szAppName, 0) ; } InvalidateRect (hwnd, NULL, TRUE) ; return 0 ;  } break ;  
 case   WM_PAINT:  hdc = BeginPaint (hwnd, &ps) ;  
if (pPackedDib)  {// Set halftone stretch mode  
   SetStretchBltMode (hdc, HALFTONE) ; SetBrushOrgEx (hdc, 0, 0, NULL) ;  
   // Select and realize halftone palette  
   SelectPalette (hdc, hPalette, FALSE) ; RealizePalette (hdc) ;  
 // StretchDIBits rather than SetDIBitsToDevice StretchDIBits (hdc,0,0,PackedDibGetWidth (pPackedDib), PackedDibGetHeight (pPackedDib), 0,0,PackedDibGetWidth (pPackedDib), PackedDibGetHeight (pPackedDib),PackedDibGetBitsPtr (pPackedDib), pPackedDib,   DIB_RGB_COLORS,   SRCCOPY) ;  }  EndPaint (hwnd, &ps) ;  return 0 ;  
 case   WM_QUERYNEWPALETTE:  hdc = GetDC (hwnd) ;  SelectPalette (hdc, hPalette, FALSE) ;  RealizePalette (hdc) ;  InvalidateRect (hwnd, NULL, TRUE) ;  
ReleaseDC (hwnd, hdc) ;  return TRUE ;  
 case   WM_PALETTECHANGED:  if ((HWND) wParam != hwnd)  
hdc = GetDC (hwnd) ;  SelectPalette (hdc, hPalette, FALSE) ;  RealizePalette (hdc) ;  UpdateColors (hdc) ;  
ReleaseDC (hwnd, hdc) ;  break ;   case   WM_DESTROY:  if (pPackedDib) free (pPackedDib) ;  
DeleteObject (hPalette) ;  
PostQuitMessage (0) ;  return 0 ;   }   return DefWindowProc (hwnd, message, wParam, lParam) ;  
}  
SHOWDIB5.RC (摘录)  
//Microsoft Developer Studio generated resource script.  
#include "resource.h"  
#include "afxres.h"  
/////////////////////////////////////////////////////////////////////////////  
// Menu  
SHOWDIB5 MENU DISCARDABLE  
BEGIN  
   POPUP "&Open"  
 BEGIN  MENUITEM "&File", IDM_FILE_OPEN  
   END  
END  
RESOURCE.H (摘录)  
// Microsoft Developer Studio generated include file.  
// Used by ShowDib5.rc  
#define IDM_FILE_OPEN40001  

SHOWDIB5程序类似于SHOWDIB4,SHOWDIB4中不使用DIB中的颜色表,而使用适用于图像范围更大的调色盘。为此,SHOWDIB5使用了由Windows支持的逻辑调色盘,其句柄可以从CreateHalftonePalette函数获得。

中间色调色盘并不比SHOWDIB4中的CreateAllPurposePalette函数所建立的调色盘更复杂。的确,如果只是拿来自用,结果是相似的。然而,如果您呼叫下面两个函数:

SetStretchBltMode (hdc, HALFTONE) ;  SetBrushOrgEx (hdc, x, y, NULL) ;  

其中,x和y是DIB左上角的设备坐标,并且如果您用StretchDIBits而不是SetDIBitsToDevice来显示DIB,那么结果会让您吃惊:颜色色调要比不设定位图缩放模式来使用CreateAllPurposePalette或者CreateHalftonePalette更精确。Windows使用一种混色图案来处理中间色调色盘上的颜色,以使其更接近8位显示卡上原始图像的颜色。与您所想象的一样,这样做的缺点是需要更多的处理时间。

索引调色盘颜色

现在开始处理SetDIBitsToDevice、StretchDIBits、CreateDIBitmap、SetDIBits、GetDIBits和CreateDIBSection的fClrUse参数。通常,您将这个参数设定为DIB_RGB_COLORS(等于0)。不过,您也能将它设定为DIB_PAL_COLORS。在这种情况下,假定BITMAPINFO结构中的颜色表不包括RGB颜色值,而是包括逻辑调色盘中颜色项目的16位索引。逻辑调色盘是作为第一个参数传递给函数的设备内容中目前选择的那个。实际上,在CreateDIBSection中,之所以需要指定一个非NULL的设备内容句柄作为第一个参数,只是因为使用了DIB_PAL_COLORS。

DIB_PAL_COLORS能为您做些什么呢?它可能提高一些性能。考虑一下在8位显示模式下呼叫SetDIBitsToDevice显示的8位DIB。Windows首先必须在DIB颜色表的所有颜色中搜索与设备可用颜色最接近的颜色。然后设定一个小表,以便将DIB图素值映像到设备图素。也就是说,最多需要搜索256次最接近的颜色。但是如果DIB颜色表中含有从设备内容中选择颜色的逻辑调色盘项目索引,那么就可能跳过搜索。

除了使用调色盘索引以外,程序16-16所示的SHOWDIB6程序与SHOWDIB3相似。

程序16-16 SHOWDIB6
SHOWDIB6.C  
/*---------------------------------------------------------------------------  
  SHOWDIB6.C -- Display DIB with palette indices (c) Charles Petzold, 1998  
----------------------------------------------------------------------------*/  
#include <windows.h>  
#include "..\\ShowDib3\\PackeDib.h"  
#include "resource.h"  

LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;  
TCHAR szAppName[] = TEXT ("ShowDib6") ;  
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,PSTR szCmdLine, int iCmdShow)  
{   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, TEXT ("Show DIB #6: Palette Indices"),WS_OVERLAPPEDWINDOW,CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL) ;  
 ShowWindow (hwnd, iCmdShow) ;   UpdateWindow (hwnd) ;  
 while (GetMessage (&msg, NULL, 0, 0))  
{   TranslateMessage (&msg) ;  DispatchMessage (&msg) ;   }   return msg.wParam ;  
}  

LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam,LPARAM lParam)  
{   static BITMAPINFO *  pPackedDib ;   static HPALETTE hPalette ;   static int  cxClient, cyClient ;   static OPENFILENAME ofn ;   static TCHARszFileName [MAX_PATH], szTitleName [MAX_PATH] ;   static TCHAR   szFilter[] = TEXT ("Bitmap Files (*.BMP)\0*.bmp\0")   TEXT ("All Files (*.*)\0*.*\0\0") ;   HDC   hdc ;   int   i, iNumColors ;   PAINTSTRUCT   ps ;   WORD  * pwIndex ;  
 switch (message)   {   case   WM_CREATE:  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 = 0 ;  ofn.nFileOffset   = 0 ; ofn.nFileExtension= 0 ;  ofn.lpstrDefExt   = TEXT ("bmp") ; ofn.lCustData = 0 ;  ofn.lpfnHook  = NULL ;  ofn.lpTemplateName= NULL ;  
return 0 ;  
 case   WM_SIZE:  cxClient = LOWORD (lParam) ;  cyClient = HIWORD (lParam) ; return 0 ;  
 case   WM_COMMAND:  switch (LOWORD (wParam))  {  case   IDM_FILE_OPEN:  
  // Show the File Open dialog box if (!GetOpenFileName (&ofn))return 0 ;   // If there's an existing packed DIB, free the memory  
if (pPackedDib)  { free (pPackedDib) ; pPackedDib = NULL ;  }   // If there's an existing logical palette, delete it  
if (hPalette) { DeleteObject (hPalette) ;   hPalette = NULL ;  }  // Load the packed DIB into memory  
SetCursor (LoadCursor (NULL, IDC_WAIT)) ;  ShowCursor (TRUE) ;  
pPackedDib = PackedDibLoad (szFileName) ;  
ShowCursor (FALSE) ;  SetCursor (LoadCursor (NULL, IDC_ARROW)) ;  
if (pPackedDib) {  // Create the palette from the DIB color table  
 hPalette = PackedDibCreatePalette (pPackedDib) ;  
// Replace DIB color table with indices  
   if (hPalette) {  iNumColors = PackedDibGetNumColors (pPackedDib) ;pwIndex = (WORD *) PackedDibGetColorTablePtr (pPackedDib) ;  
   for (i = 0 ; i < iNumColors ; i++)   pwIndex[i] = (WORD) i ;   } } else { MessageBox (hwnd, TEXT ("Cannot load DIB file"),  szAppName, 0) ; } InvalidateRect (hwnd, NULL, TRUE) ; return 0 ;  }  break ;  
 case   WM_PAINT:  hdc = BeginPaint (hwnd, &ps) ;  
   if (hPalette)  { SelectPalette (hdc, hPalette, FALSE) ; RealizePalette (hdc) ;  }  if (pPackedDib)  SetDIBitsToDevice (hdc,0,0,PackedDibGetWidth  (pPackedDib),   PackedDibGetHeight (pPackedDib), 0,0,0,PackedDibGetHeight (pPackedDib),   PackedDibGetBitsPtr (pPackedDib), pPackedDib,   DIB_PAL_COLORS) ;  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 (pPackedDib) free (pPackedDib) ;  
if (hPalette) DeleteObject (hPalette) ;  
PostQuitMessage (0) ;  return 0 ;  
}   return DefWindowProc (hwnd, message, wParam, lParam) ;  
}  
SHOWDIB6.RC (摘录)  
//Microsoft Developer Studio generated resource script.  
#include "resource.h"  
#include "afxres.h"  
/////////////////////////////////////////////////////////////////////////////  
// Menu  
SHOWDIB6 MENU DISCARDABLE  
BEGIN  
   POPUP "&File"  
   BEGIN  MENUITEM "&Open",IDM_FILE_OPEN  
   END  
END  
RESOURCE.H (摘录)  
// Microsoft Developer Studio generated include file.  
// Used by ShowDib6.rc  
//  
#define IDM_FILE_OPEN   40001  

SHOWDIB6将DIB加载到内存并由此建立了调色盘以后,SHOWDIB6简单地用以0开始的WORD索引替换了DIB颜色表中的颜色。PackedDibGetNumColors函数将表示有多少种颜色,而PackedDibGetColorTablePtr函数传回指向DIB颜色表起始位置的指针。

注意,只有直接从DIB颜色表来建立调色盘时,此技术才可行。如果使用通用调色盘,则必须搜索最接近的颜色,以获得放入DIB的索引。

如果要使用调色盘索引,那么请在将DIB储存到磁盘之前,确实替换掉DIB中的颜色表。另外,不要将包含调色盘索引的DIB放入剪贴簿。实际上,在显示之前,将调色盘索引放入DIB,然后将RGB颜色值放回,会更安全一些。

调色盘和位图对象

程序16-17中的SHOWDIB7程序显示了如何使用与DIB相关联的调色盘,这些DIB是使用CreateDIBitmap函数转换成GDI位图对象的。

程序16-17 SHOWDIB7
SHOWDIB7.C  
/*--------------------------------------------------------------------------  
  SHOWDIB7.C --  Shows DIB converted to DDB   (c) Charles Petzold, 1998  
----------------------------------------------------------------------------*/  
#include <windows.h>  
#include "..\\ShowDib3\\PackeDib.h"  
#include "resource.h"  

LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;  
TCHAR szAppName[] = TEXT ("ShowDib7") ;  
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,   PSTR szCmdLine, int iCmdShow)  
{  
   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, TEXT ("Show DIB #7: Converted to DDB"), WS_OVERLAPPEDWINDOW,CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL) ;  
 ShowWindow (hwnd, iCmdShow) ;   UpdateWindow (hwnd) ;  
 while (GetMessage (&msg, NULL, 0, 0))   {  TranslateMessage (&msg) ;  DispatchMessage (&msg) ;  
}   return msg.wParam ;  
}  

LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)  
{   static HBITMAP   hBitmap ;   static HPALETTE   hPalette ;   static intcxClient, cyClient ;   static OPENFILENAME  ofn ;   static TCHAR  szFileName [MAX_PATH], szTitleName [MAX_PATH] ;   static TCHAR  szFilter[] = TEXT ("Bitmap Files (*.BMP)\0*.bmp\0")   TEXT ("All Files (*.*)\0*.*\0\0") ;   BITMAPbitmap ;   BITMAPINFO  *   pPackedDib ;   HDC   hdc, hdcMem ;   PAINTSTRUCT   ps ;  

switch (message)  
{   case   WM_CREATE:  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 = 0 ;  ofn.nFileOffset   = 0 ;  ofn.nFileExtension= 0 ; ofn.lpstrDefExt   = TEXT ("bmp") ;  ofn.lCustData = 0 ;  ofn.lpfnHook  = NULL ;  ofn.lpTemplateName= NULL ;  
return 0 ;  
 case   WM_SIZE:  cxClient = LOWORD (lParam) ; cyClient = HIWORD (lParam) ;  return 0 ;  
 case   WM_COMMAND:  switch (LOWORD (wParam))  {  case IDM_FILE_OPEN:  
   // Show the File Open dialog box  
  if (!GetOpenFileName (&ofn))   return 0 ;// If there's an existing packed DIB, free the memory  
 if (hBitmap)  { DeleteObject (hBitmap) ; hBitmap = NULL ;  }   // If there's an existing logical palette, delete it  
 if (hPalette)  { DeleteObject (hPalette) ; hPalette = NULL ; }  // Load the packed DIB into memory  
 SetCursor (LoadCursor (NULL, IDC_WAIT)) ;   ShowCursor (TRUE) ;  
pPackedDib = PackedDibLoad (szFileName) ;  
ShowCursor (FALSE) ;  SetCursor (LoadCursor (NULL, IDC_ARROW)) ;  
   if (pPackedDib)   {   // Create palette from the DIB and select it into DC  
 hPalette = PackedDibCreatePalette (pPackedDib) ;  
   hdc = GetDC (hwnd) ;   if (hPalette) {  SelectPalette (hdc, hPalette, FALSE) ;   RealizePalette (hdc) ; }// Create the DDB from the DIB   hBitmap = CreateDIBitmap(hdc,(PBITMAPINFOHEADER) pPackedDib, CBM_INIT,PackedDibGetBitsPtr (pPackedDib),  
pPackedDib, DIB_RGB_COLORS) ; ReleaseDC (hwnd, hdc) ;  
// Free the packed-DIB memory  
   free (pPackedDib) ; } else { MessageBox ( hwnd, TEXT ("Cannot load DIB file"), szAppName, 0) ;  } InvalidateRect (hwnd, NULL, TRUE) ; return 0 ;  } break ;   case   WM_PAINT:  hdc = BeginPaint (hwnd, &ps) ;  
if (hPalette)  { SelectPalette (hdc, hPalette, FALSE) ; RealizePalette (hdc) ;  }  if (hBitmap)  { GetObject (hBitmap, sizeof (BITMAP), &bitmap) ;  
   hdcMem = CreateCompatibleDC (hdc) ; SelectObject (hdcMem, hBitmap) ;  
   BitBlt (hdc,0,0,bitmap.bmWidth, bitmap.bmHeight, hdcMem,0, 0,SRCCOPY) ;  
   DeleteDC (hdcMem) ;  }  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 (hBitmap) DeleteObject (hBitmap) ;  
if (hPalette)DeleteObject (hPalette) ;  
   PostQuitMessage (0) ; return 0 ;   }   return DefWindowProc (hwnd, message, wParam, lParam) ;  
}  
SHOWDIB7.RC (摘录)  
//Microsoft Developer Studio generated resource script.  
#include "resource.h"  
#include "afxres.h"  
/////////////////////////////////////////////////////////////////////////////  
// Menu  
SHOWDIB7 MENU DISCARDABLE  
BEGIN  
  POPUP "&File"  
   BEGIN  MENUITEM "&Open",IDM_FILE_OPEN  
   END  
END  
RESOURCE.H (摘录)  
// Microsoft Developer Studio generated include file.  
// Used by ShowDib7.rc  
#define IDM_FILE_OPEN  40001  

与前面的程序一样,SHOWDIB7获得了一个指向packedDIB的指标,该DIB回应菜单的「File」、「Open」命令。程序从packedDIB建立了调色盘,然后-还是在WM_COMMAND消息的处理过程中-获得了用于视讯显示的设备内容,并选进调色盘,显现调色盘。然后SHOWDIB7呼叫CreateDIBitmap以便从DIB建立DDB。如果调色盘没有选进设备内容并显现,那么CreateDIBitmap建立的DDB将不使用逻辑调色盘中的附加颜色。

呼叫CreateDIBitmap以后,该程序将释放packedDIB占用的内存空间。pPackedDib变量不是静态变量。相反的,SHOWDIB7按静态变量保留了位图句柄(hBitmap)和逻辑调色盘句柄(hPalette)。

在WM_PAINT消息处理期间,调色盘再次选进设备内容并显现。GetObject函数可获得位图的宽度和高度。然后,程序通过建立兼容的内存设备内容在显示区域显示位图,选进位图,并执行BitBlt。显示DDB时所用的调色盘,必须与从CreateDIBitmap呼叫建立时所用的一样。

如果将位图复制到剪贴簿,则最好使用packedDIB格式。然后Windows可以将位图对象提供给希望使用这些位图的程序。然而,如果需要将位图对象复制到剪贴簿,则首先要获得视讯设备内容并显现调色盘。这允许Windows依据目前的系统调色盘将DDB转换为DIB。

调色盘和DIB区块

最后,程序16-18所示的SHOWDIB8说明了如何使用带有DIB区块的调色盘。

程序16-18 SHOWDIB8
SHOWDIB8.C  
/*--------------------------------------------------------------------------  
  SHOWDIB8.C -- Shows DIB converted to DIB section   (c) Charles Petzold, 1998  
---------------------------------------------------------------------------*/  
#include <windows.h>  
#include "..\\ShowDib3\\PackeDib.h"  
#include "resource.h"  

LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;  
TCHAR szAppName[] = TEXT ("ShowDib8") ;  
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,   PSTR szCmdLine, int iCmdShow)  
{   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, TEXT ("Show DIB #8: DIB Section"), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT,CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL) ;  
 ShowWindow (hwnd, iCmdShow) ;   UpdateWindow (hwnd) ;  
 while (GetMessage (&msg, NULL, 0, 0))   {  TranslateMessage (&msg) ; DispatchMessage (&msg) ;   }   return msg.wParam ;  
}  

LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)  
{   static HBITMAP   hBitmap ;   static HPALETTE  hPalette ;   static int   cxClient, cyClient ;   static OPENFILENAME  ofn ;  static PBYTE pBits ;   static TCHAR szFileName [MAX_PATH], szTitleName [MAX_PATH] ;   static TCHAR szFilter[] = TEXT ("Bitmap Files (*.BMP)\0*.bmp\0")  TEXT ("All Files (*.*)\0*.*\0\0") ;   BITMAP   bitmap ;   BITMAPINFO   *  pPackedDib ;  
 HDC  hdc, hdcMem ;   PAINTSTRUCT   ps ;  
 switch (message)   {   case   WM_CREATE:  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 = 0 ;  ofn.nFileOffset   = 0 ;  ofn.nFileExtension= 0 ;  ofn.lpstrDefExt   = TEXT ("bmp") ;  ofn.lCustData= 0 ;  ofn.lpfnHook  = NULL ;  ofn.lpTemplateName= NULL ;  
return 0 ;  
 case   WM_SIZE:  cxClient = LOWORD (lParam) ;  cyClient = HIWORD (lParam) ;return 0 ;  
 case   WM_COMMAND:  switch (LOWORD (wParam))  {  case   IDM_FILE_OPEN:  
   // Show the File Open dialog box  
   if (!GetOpenFileName (&ofn))   return 0 ;// If there's an existing packed DIB, free the memory  
   if (hBitmap) {DeleteObject (hBitmap) ;hBitmap = NULL ;}  
 // If there's an existing logical palette, delete it  
 if (hPalette) {DeleteObject (hPalette) ;hPalette = NULL ;}   // Load the packed DIB into memory  
  SetCursor (LoadCursor (NULL, IDC_WAIT)) ; ShowCursor (TRUE) ;  
  pPackedDib = PackedDibLoad (szFileName) ;  
   ShowCursor (FALSE) ; SetCursor (LoadCursor (NULL, IDC_ARROW)) ;  
   if (pPackedDib) {  // Create the DIB section from the DIB  

hBitmap = CreateDIBSection (NULL,pPackedDib,DIB_RGB_COLORS,&pBits,NULL, 0) ;  
// Copy the bits  
CopyMemory (pBits, PackedDibGetBitsPtr  (pPackedDib),   PackedDibGetBitsSize (pPackedDib)) ;  
  // Create palette from the DIB  
 hPalette = PackedDibCreatePalette (pPackedDib) ;  
// Free the packed-DIB memory  
   free (pPackedDib) ; }else {   MessageBox (   hwnd, TEXT ("Cannot load DIB file"), szAppName, 0) ;   } InvalidateRect (hwnd, NULL, TRUE) ;return 0 ;  }  break ;   case   WM_PAINT:  hdc = BeginPaint (hwnd, &ps) ;  
if (hPalette)  { SelectPalette (hdc, hPalette, FALSE) ; RealizePalette (hdc) ;  }  if (hBitmap)  { GetObject (hBitmap, sizeof (BITMAP), &bitmap) ;  
  hdcMem = CreateCompatibleDC (hdc) ; SelectObject (hdcMem, hBitmap) ;  
BitBlt (  hdc,0, 0, bitmap.bmWidth, bitmap.bmHeight,  hdcMem, 0, 0, SRCCOPY) ;  
   DeleteDC (hdcMem) ; }  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 (hBitmap)DeleteObject (hBitmap) ;  
if (hPalette) DeleteObject (hPalette) ;  
PostQuitMessage (0) ;  return 0 ;  
}   return DefWindowProc (hwnd, message, wParam, lParam) ;  
}  
SHOWDIB8.RC (摘录)  
//Microsoft Developer Studio generated resource script.  
#include "resource.h"  
#include "afxres.h"  
/////////////////////////////////////////////////////////////////////////////  
// Menu  
SHOWDIB8 MENU DISCARDABLE  
BEGIN  
   POPUP "&File"  
   BEGIN   MENUITEM "&Open",IDM_FILE_OPEN  
   END  
END  
RESOURCE.H (摘录)  
// Microsoft Developer Studio generated include file.  
// Used by ShowDib8.rc  
#define IDM_FILE_OPEN 40001  

在SHOWDIB7和SHOWDIB8中的WM_PAINT处理是一样的:两个程序都将位图句柄(hBitmap)和逻辑调色盘句柄(hPalette)作为静态变量。调色盘被选进设备内容并显现,位图的宽度和高度从GetObject函数获得,程序建立内存设备内容并选进位图,然后通过呼叫BitBlt将位图显示到显示区域。

两个程序之间最大的差别在于处理「File」、「Open」菜单命令的程序。在获得指向packedDIB的指标并建立了调色盘以后,SHOWDIB7必须将调色盘选进视讯设备内容,并在呼叫CreateDIBitmap之前显现。SHOWDIB8在获得packedDIB指标以后呼叫CreateDIBSection。不必将调色盘选进设备内容,这是因为CreateDIBSection不将DIB转换成设备相关的格式。的确,CreateDIBSection的第一个参数(即设备内容句柄)的唯一用途在于您是否使用DIB_PAL_COLORS旗标。

呼叫CreateDIBSection以后,SHOWDIB8将图素位从packedDIB复制到从CreateDIBSection函数传回的内存位置,然后呼叫PackedDibCreatePalette。尽管此函数便于程序使用,但是SHOWDIB8将依据从GetDIBColorTable函数传回的信息建立调色盘。

DIB 处理链接库

就是现在-经过我们长时间地学习GDI位图对象、设备无关位图、DIB区块和Windows调色盘管理器之后-我们才做好了开发一套有助于处理位图的函数的准备。

前面的PACKEDIB文件展示了一种可能的方法:内存中的packedDIB只用指向它的指标表示。程序所的有关DIB的全部信息都可以从存取表头信息结构的函数获得。然而,实际上到「getpixel」和「setpixel」例程时,这种方法就会产生严重的执行问题。图像处理任务当然需要存取位图位,并且这些函数也应该尽可能地快。

可能的C++的解决方式中包括建立DIB类别,这时指向packedDIB的指标正好是一个成员变量。其它成员变量和成员函数有助于更快地执行获得和设定DIB中的图素的例程。不过,因为我在第一章已经指出,对于本书您只需要了解C,使用C++将是其它书的范围。

当然,用C++能做的事情用C也能做。一个好的例子就是许多Windows函数都使用句柄。除了将句柄当作数值以外,应用程序对它还了解什么呢?程序知道句柄引用特殊的函数对象,还知道函数用于处理现存的对象。显然,操作系统按某种方式用句柄来引用对象的内部信息。句柄可以与结构指针一样简单。

例如,假设有一个函数集,这些函数都使用一个称为HDIB的句柄。HDIB是什么呢?它可能在某个表头文件中定义如下:

typedef void * HDIB ;  

此定义用「不关您的事」回答了「HDIB是什么」这个问题。

然而,实际上HDIB可能是结构指针,该结构不仅包括指向packedDIB的指针,还包括其它信息:

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

此结构的其它五个字段包括从packedDIB中引出的信息。当然,结构中这些值允许更快速地存取它们。不同的DIB链接库函数都可以处理这个结构,而不是pPackedDib指标。可以按下面的方法来执行DibGetPixelPointer函数:

BYTE * DibGetPixelPointer (HDIB hdib, int x, int y)  
{  PDIBSTRUCTURE pdib = hdib ;   return pdib->pBits + y * pdib->cBytesPerRow + x * pdib->cBitsPerPixel / 8 ;  
}  

当然,这种方法可能要比PACKEDIB.C中执行「getpixel」例程快。

由于这种方法非常合理,所以我决定放弃packedDIB,并改用处理DIB区块的DIB链接库。这实际上使我们对packedDIB的处理有更大的弹性(也就是说,能够在设备无关的方式下操纵DIB图素位),而且在WindowsNT下执行时将更有效。

DIBSTRUCT结构

DIBHELP.C文件-如此命名是因为对处理DIB提供帮助-有上千行,并在几个小部分中显示。但是首先让我们看一下DIBHELP函数所处理的结构,该结构在DIBHELP.C中定义如下:

typedef struct  
{   PBYTE * ppRow ;   // array of row pointers   int   iSignature ;  // = "Dib "   HBITMAP  hBitmap ; // handle returned from CreateDIBSection   BYTE  *  pBits ; // pointer to bitmap bits   DIBSECTIONds ;  // 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函数传回的位图句柄。您将想起该句柄可有多种使用方式,它与我们在第十四章遇到的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兼容的DIB。

对于16位和32位的DIB,如果BITMAPINFOHEADER结构的biCompression字段是BI_BITFIELDS,那么在信息表头结构后面通常有三个屏蔽值。这些屏蔽值决定如何将16位和32位图素值转换成RGB颜色。屏蔽储存在DIBSECTION结构的第三个字段中。

DIBSECTION结构的最后两个字段指的是DIB区块,此区块由文件映射建立。DIBHELP不使用CreateDIBSection的这个特性,因此可以忽略这些字段。

DIBSTRUCT的最后两个字段储存左右移位值,这些值用于处理16位和32位DIB的颜色屏蔽。我们将在第十五章讨论这些移位值。

让我们再回来看一下DIBSTRUCT的第一个字段。正如我们所看到的一样,在开始建立DIB时,此字段设定为指向一个指针数组的指针,该数组中的每个指针都指向DIB中的一行图素。这些指标允许以更快的方式来获得DIB图素位,同时也被定义,以便顶行可以首先引用DIB图素位。此数组的最后一个元素-引用DIB图像的最底行-通常等于DIBSTRUCT的pBits字段。

信息函数

DIBHELP.C以定义DIBSTRUCT结构开始,然后提供一个函数集,此函数集允许应用程序获得有关DIB区块的信息。程序16-19显示了DIBHELP.C的第一部分。

程序16-19 DIBHELP.C文件的第一部分
DIBHELP.C (第一部分)  
/*--------------------------------------------------------------------------  
  DIBHELP.C -- DIB Section Helper Routines (c) Charles Petzold, 1998  
----------------------------------------------------------------------------*/  
#include <windows.h>  
#include "dibhelp.h"  

#define HDIB_SIGNATURE (* (int *) "Dib ")  
typedef struct  
{   PBYTE *  ppRow ;// must be first field for macros!   int   iSignature ;   HBITMAP   hBitmap ;   BYTE  *  pBits ;   DIBSECTIONds ;   int iRShift[3] ;   int  iLShift[3] ;  
}  
DIBSTRUCT, * PDIBSTRUCT ;  

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

BOOL DibIsValid (HDIB hdib)  
{  PDIBSTRUCT pdib = hdib ;   if (pdib == NULL)  return FALSE ;   if (IsBadReadPtr (pdib, sizeof (DIBSTRUCT)))  return FALSE ;   if (pdib->iSignature != HDIB_SIGNATURE)  return FALSE ;   return TRUE ;  
}  

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

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

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

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

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

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

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

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

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

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

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

int DibNumColors (HDIB hdib)  
{   PDIBSTRUCT pdib = hdib ;  
 if (!DibIsValid (hdib))  return 0 ;  
 if (pdib->ds.dsBmih.biClrUsed != 0)  
{  return pdib->ds.dsBmih.biClrUsed ;  
}   else if (DibBitCount (hdib) <= 8)  
{  return 1 << DibBitCount (hdib) ;  
}   return 0 ;  
}  
/*---------------------------------------------------------------------------  
  DibMask:  Returns one of the color masks  
---------------------------------------------------------------------------*/  

DWORD   DibMask (HDIB hdib, int i)  
{   PDIBSTRUCT pdib = hdib ;  
 if (!DibIsValid (hdib) || i < 0 || i > 2)  return 0 ;  
  return pdib->ds.dsBitfields[i] ;  
}  

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

int DibRShift (HDIB hdib, int i)  
{ PDIBSTRUCT pdib = hdib ;  
 if (!DibIsValid (hdib) || i < 0 || i > 2)  return 0 ;  
  return pdib->iRShift[i] ;  
}  

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

int DibLShift (HDIB hdib, int i)  
{   PDIBSTRUCT pdib = hdib ;  
 if (!DibIsValid (hdib) || i < 0 || i > 2)  return 0 ;  
  return pdib->iLShift[i] ;  
}  

/*---------------------------------------------------------------------------  
  DibCompression:  Returns the value of the biCompression field  
----------------------------------------------------------------------------*/  
int DibCompression (HDIB hdib)  
{   if (!DibIsValid (hdib))  return 0 ;  
 return ((PDIBSTRUCT) hdib)->ds.dsBmih.biCompression ;  
}  

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

BOOLDibIsAddressable (HDIB hdib)  
{   int iCompression ;  
 if (!DibIsValid (hdib))  return FALSE ;  
 iCompression = DibCompression (hdib) ;  
 if (   iCompression == BI_RGB || iCompression == BI_BITFIELDS)  return TRUE ;  
 return FALSE ;  
}  

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

DWORD DibInfoHeaderSize (HDIB hdib)  
{   if (!DibIsValid (hdib))  return 0 ;   return ((PDIBSTRUCT) hdib)->ds.dsBmih.biSize ;  
}  

DWORD   DibMaskSize (HDIB hdib)  
{ PDIBSTRUCT pdib = hdib ;   if (!DibIsValid (hdib))  return 0 ;  

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

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

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

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

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

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

DWORD * DibMaskPtr (HDIB hdib)  
{   PDIBSTRUCT pdib = hdib ;   if (!DibIsValid (hdib))return 0 ;   return pdib->ds.dsBitfields ;  
}  

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

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

BOOL DibGetColor (HDIB hdib, int index, RGBQUAD * prgb)  
{   PDIBSTRUCT pdib = hdib ;   HDC hdcMem ;   int  iReturn ;  
 if (!DibIsValid (hdib))return 0 ;   hdcMem = CreateCompatibleDC (NULL) ;   SelectObject (hdcMem, pdib->hBitmap) ;   iReturn = GetDIBColorTable (hdcMem, index, 1, prgb) ;   DeleteDC (hdcMem) ;   return iReturn ? TRUE : FALSE ;  
}  

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

BOOL DibSetColor (HDIB hdib, int index, RGBQUAD * prgb)  
{   PDIBSTRUCTpdib = 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中的信息之前,其它函数都呼叫DibIsValid。所有这些函数都有(而且通常是只有)HDIB型态的第一个参数,(我们将立即看到)该参数在DIBHELP.H中定义为空指标。这些函数可以将此参数储存到PDIBSTRUCT,然后再存取结构中的字段。

注意传回BOOL值的DibIsAddressable函数。DibIsNotCompressed函数也可以呼叫此函数。传回值表示独立的DIB图素能否寻址。

以DibInfoHeaderSize开始的函数集将取得DIB区块中不同组件出现在packedDIB中的大小。与我们所看到的一样,这些函数有助于将DIB区块转换成packedDIB,并储存DIB文件。这些函数的后面是获得指向不同DIB组件的指针的函数集。

尽管DIBHELP.C包括名称为DibInfoHeaderPtr的函数,而且该函数将获得指向BITMAPINFOHEADER结构的指针,但还是没有函数可以获得BITMAPINFO结构指针-即接在DIB颜色表后面的信息结构。这是因为在处理DIB区块时,应用程序并不直接存取这种型态的结构。BITMAPINFOHEADER结构和颜色屏蔽都在DIBSECTION结构中有效,而且从CreateDIBSection函数传回指向图素位的指针,这时通过呼叫GetDIBColorTable和SetDIBColorTable,就只能间接存取DIB颜色表。这些功能都封装到DIBHELP的DibGetColor和DibSetColor函数里头了。

在DIBHELP.C的后面,文件DibCopyToInfo配置一个指向BITMAPINFO结构的指针,并填充信息,但是那与获得指向内存中现存结构的指针不完全相同。

读、写图素

应用程序维护packedDIB或DIB区块的一个引人注目的优点是能够直接操作DIB图素位。程序16-20所示的DIBHELP.C第二部分列出了提供此功能的函数。

程序16-20 DIBHELP.C文件的第二部分
DIBHELP.C (第二部分)  
/*----------------------------------------------------------------------------  
  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)  
{  DWORDdwPixel ;   int   iBitCount ;   PDIBSTRUCTpdib = 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 ;   PDIBSTRUCTpdib = 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]  

就是指向DIB顶行最左端图素的指标,而

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

是指向位于(x,y)的图素的指标。注意,如果DIB中的图素不可被寻址(即如果已压缩),或者如果函数的x和y参数是负数或相对位于DIB外面的区域,则函数将传回NULL。此检查降低了函数(和所有依赖于DibPixelPtr的函数)的执行速度,下面我将讲述一些更快的例程。

文件后面的DibGetPixel和DibSetPixel函数利用了DibPixelPtr。对于8位、16位和32位DIB,这些函数只记录指向合适数据尺寸的指针,并存取图素值。对于1位和4位的DIB,则需要屏蔽和移位角度。

DibGetColor函数按RGBQUAD结构获得图素颜色。对于1位、4位和8位DIB,这包括使用图素值来从DIB颜色表获得颜色。对于16位、24位和32位DIB,通常必须将图素值屏蔽和移位以得到RGB颜色。DibSetPixel函数则相反,它允许从RGBQUAD结构设定图素值。该函数只为16位、24位和32位DIB定义。

建立和转换

程序16-21所示的DIBHELP第三部分和最后部分展示了如何建立DIB区块,以及如何将DIB区块与packedDIB相互转换。

程序16-21 DIBHELP.C文件的第三部分和最后部分
DIBHELP.C (第三部分)  
/*--------------------------------------------------------------------------  
Calculating shift values from color masks is required by the  
DibCreateFromInfo function.  
----------------------------------------------------------------------------*/  
static int MaskToRShift (DWORD dwMask)  
{   int iShift ;   if (dwMask == 0)  return 0 ;   for (iShift = 0 ; !(dwMask & 1) ; iShift++)  dwMask >>= 1 ;   return iShift ;  
}  

static int MaskToLShift (DWORD dwMask)  
{   int iShift ;   if (dwMask == 0)  return 0 ;   while (!(dwMask & 1))  dwMask >>= 1 ;   for (iShift = 0 ; dwMask & 1 ; iShift++)  dwMask >>= 1 ;   return 8 - iShift ;  
}  
/*----------------------------------------------------------------------------  
DibCreateFromInfo: All DIB creation functions ultimately call this one.  
This function is responsible for calling CreateDIBSection, allocating  
memory for DIBSTRUCT, and setting up the row pointer.  
-----------------------------------------------------------------------------*/  

HDIB DibCreateFromInfo (BITMAPINFO * pbmi)  
{   BYTE  *  pBits ;   DIBSTRUCT *  pdib ;   HBITMAP   hBitmap ;   int i, iRowLength, cy, y ;  
  hBitmap = CreateDIBSection (NULL, pbmi, DIB_RGB_COLORS, &pBits, NULL, 0) ;   if (hBitmap == NULL)  return NULL ;   if (NULL == (pdib = malloc (sizeof (DIBSTRUCT))))   {  DeleteObject (hBitmap) ;  return NULL ;  
   }  
 pdib->iSignature  = HDIB_SIGNATURE ;   pdib->hBitmap = hBitmap ;   pdib->pBits   = pBits ;  
 GetObject (hBitmap, sizeof (DIBSECTION), &pdib->ds) ;  // Notice that we can now use the DIB information functions  //   defined above.  
// If the compression is BI_BITFIELDS, calculate shifts from masks  
 if (DibCompression (pdib) == BI_BITFIELDS)   {  for (i = 0 ; i < 3 ; i++) {  pdib->iLShift[i] = MaskToLShift (pdib->ds.dsBitfields[i]) ;  pdib->iRShift[i] = MaskToRShift (pdib->ds.dsBitfields[i]) ;  }   }  
// If the compression is BI_RGB, but bit-count is 16 or 32,  //   set the bitfields and the masks   else if (DibCompression (pdib) == BI_RGB)   {  if (DibBitCount (pdib) == 16)  {  pdib->ds.dsBitfields[0] = 0x00007C00 ; pdib->ds.dsBitfields[1] = 0x000003E0 ;  pdib->ds.dsBitfields[2] = 0x0000001F ;  
pdib->iRShift [0]= 10 ;  pdib->iRShift [1]= 5  ;  pdib->iRShift [2]= 0  ;  
pdib->iLShift [0]= 3  ;  pdib->iLShift [1]= 3  ;  pdib->iLShift [2]= 3  ;  }  else if (DibBitCount (pdib) == 24 || DibBitCount (pdib) == 32) {  pdib->ds.dsBitfields[0] = 0x00FF0000 ;  pdib->ds.dsBitfields[1] = 0x0000FF00 ;  pdib->ds.dsBitfields[2] = 0x000000FF ;  
pdib->iRShift [0]= 16 ;  pdib->iRShift [1]= 8  ;  pdib->iRShift [2]= 0  ;  
pdib->iLShift [0]= 0  ;  pdib->iLShift [1]= 0  ;  pdib->iLShift [2]= 0  ;  }  
}  // Allocate an array of pointers to each row in the DIB   cy = DibHeight (pdib) ;   if (NULL == (pdib->ppRow = malloc (cy * sizeof (BYTE *))))   {  free (pdib) ;  DeleteObject (hBitmap) ;  return NULL ;   }  
// Initialize them.   iRowLength = DibRowLength (pdib) ;   if (pbmi->bmiHeader.biHeight > 0)   // ie, bottom up   {  for (y = 0 ; y < cy ; y++) pdib->ppRow[y] = pBits + (cy - y - 1) * iRowLength ;   }   else 
// top down  
{  for (y = 0 ; y < cy ; y++)   pdib->ppRow[y] = pBits + y * iRowLength ;   }   return pdib ;  
}  

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

BOOL DibDelete (HDIB hdib)  
{  
DIBSTRUCT * pdib = hdib ;   if (!DibIsValid (hdib))  return FALSE ;   free (pdib->ppRow) ;   DeleteObject (pdib->hBitmap) ;   free (pdib) ;   return TRUE ;  
}  

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

HDIB DibCreate (int cx, int cy, int cBits, int cColors)  
{ BITMAPINFO*  pbmi ;   DWORDdwInfoSize ;   HDIB hDib ;   int  cEntries ;  
 if (cx <= 0 || cy <= 0 ||  ((cBits !=  1) && (cBits !=  4) && (cBits != 8) &&  (cBits != 16) && (cBits != 24) && (cBits != 32)))  
   {  return NULL ;  
  }  

   if (   cColors != 0)  cEntries = cColors ;  
  else   if (cBits <= 8)  cEntries = 1 << cBits ;  
 dwInfoSize = sizeof (BITMAPINFOHEADER) + (cEntries - 1) * sizeof (RGBQUAD);  
 if (NULL == (pbmi = malloc (dwInfoSize)))   {  return NULL ;   }  
 ZeroMemory (pbmi, dwInfoSize) ;  
 pbmi->bmiHeader.biSize   = sizeof (BITMAPINFOHEADER) ;   pbmi->bmiHeader.biWidth  = cx ;   pbmi->bmiHeader.biHeight = cy ;   pbmi->bmiHeader.biPlanes = 1 ;   pbmi->bmiHeader.biBitCount   = cBits ;   pbmi->bmiHeader.biCompression= BI_RGB ;   pbmi->bmiHeader.biSizeImage  = 0 ;   pbmi->bmiHeader.biXPelsPerMeter  = 0 ;   pbmi->bmiHeader.biYPelsPerMeter  = 0 ;   pbmi->bmiHeader.biClrUsed= cColors ;   pbmi->bmiHeader.biClrImportant   = 0 ;  
 hDib = DibCreateFromInfo (pbmi) ;   free (pbmi) ;  
 return hDib ;  
}  

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

static BITMAPINFO * DibCopyToInfo (HDIB hdib)  
{   BITMAPINFO*  pbmi ;   int  i, iNumColors ;  RGBQUAD   *  prgb ;   if (!DibIsValid (hdib))  return NULL ;  // Allocate the memory   if (NULL == (pbmi = malloc (DibInfoSize (hdib)))) return NULL ;  // Copy the information header   CopyMemory (pbmi, DibInfoHeaderPtr   (hdib), sizeof (BITMAPINFOHEADER));  // Copy the possible color masks  
 prgb = (RGBQUAD *) ((BYTE *) pbmi + sizeof (BITMAPINFOHEADER)) ;   if (DibMaskSize (hdib))   {  CopyMemory (prgb, DibMaskPtr (hdib), 3 * sizeof (DWORD)) ;  prgb = (RGBQUAD *) ((BYTE *) prgb + 3 * sizeof (DWORD)) ;  
   }  // Copy the color table   iNumColors = DibNumColors (hdib) ;   for (i = 0 ; i < iNumColors ; i++) DibGetColor (hdib, i, prgb + i) ;   return pbmi ;  
}  

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

HDIB DibCopy (HDIB hdibSrc, BOOL fRotate)  
{   BITMAPINFO*  pbmi ;   BYTE  *  pBitsSrc, * pBitsDst ;   HDIBhdibDst ;  
 if (!DibIsValid (hdibSrc))  return NULL ;   if (NULL == (pbmi = DibCopyToInfo (hdibSrc)))  return NULL ;   if (fRotate)   {  pbmi->bmiHeader.biWidth = DibHeight (hdibSrc) ;  pbmi->bmiHeader.biHeight = DibWidth (hdibSrc) ;   }  
 hdibDst = DibCreateFromInfo (pbmi) ;   free (pbmi) ;  
 if (   hdibDst == NULL)  return NULL ;  
// Copy the bits  
 if (!fRotate)   {  pBitsSrc = DibBitsPtr (hdibSrc) ;  pBitsDst = DibBitsPtr (hdibDst) ;  
   CopyMemory (pBitsDst, pBitsSrc, DibBitsSize (hdibSrc)) ;   }  
return hdibDst ;  
}  

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

BITMAPINFO * DibCopyToPackedDib (HDIB hdib, BOOL fUseGlobal)  
{   BITMAPINFO*  pPackedDib ;   BYTE  *  pBits ;   DWORD dwDibSize ;   HDC   hdcMem ;   HGLOBAL   hGlobal ;   int   iNumColors ;   PDIBSTRUCTpdib = hdib ;   RGBQUAD   *  prgb ;  
 if (!DibIsValid (hdib))  return NULL ;  // Allocate memory for packed DIB   dwDibSize = DibTotalSize (hdib) ;   if (fUseGlobal)   {  hGlobal = GlobalAlloc (GHND | GMEM_SHARE, dwDibSize) ;  pPackedDib = GlobalLock (hGlobal) ;   }   else  
  {  pPackedDib = malloc (dwDibSize) ;   }  
 if (pPackedDib == NULL)  return NULL ;  // Copy the information header   CopyMemory (pPackedDib, &pdib->ds.dsBmih, sizeof (BITMAPINFOHEADER)) ;   prgb = (RGBQUAD *) ((BYTE *) pPackedDib + sizeof (BITMAPINFOHEADER)) ; // Copy the possible color masks   if (pdib->ds.dsBmih.biCompression == BI_BITFIELDS)   {  CopyMemory (prgb, pdib->ds.dsBitfields, 3 * sizeof (DWORD)) ;  prgb = (RGBQUAD *) ((BYTE *) prgb + 3 * sizeof (DWORD)) ;   }  // Copy the color table   if (iNumColors = DibNumColors (hdib))   {hdcMem = CreateCompatibleDC (NULL) ;  SelectObject (hdcMem, pdib->hBitmap) ;  GetDIBColorTable (hdcMem, 0, iNumColors, prgb) ;DeleteDC (hdcMem) ;   }  
 pBits = (BYTE *) (prgb + iNumColors) ;  // Copy the bits   CopyMemory (pBits, pdib->pBits, DibBitsSize (pdib)) ;  // If last argument is TRUE, unlock global memory block and //   cast it to pointer in preparation for return  
 if (fUseGlobal)   {  GlobalUnlock (hGlobal) ;  pPackedDib = (BITMAPINFO *) hGlobal ;  }  
return pPackedDib ;  
}  

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

HDIB DibCopyFromPackedDib (BITMAPINFO * pPackedDib)  
{   BYTE  *  pBits ;  DWORDdwInfoSize, dwMaskSize, dwColorSize ;   int  iBitCount ;   PDIBSTRUCTpdib ;  
// Get the size of the information header and do validity check  
  dwInfoSize = pPackedDib->bmiHeader.biSize ;   if (   dwInfoSize != sizeof (BITMAPCOREHEADER) &&  dwInfoSize != sizeof (BITMAPINFOHEADER) &&  dwInfoSize != sizeof (BITMAPV4HEADER) &&  dwInfoSize != sizeof (BITMAPV5HEADER))   {  return NULL ;   }  // Get the possible size of the color masks  
 if (dwInfoSize == sizeof (BITMAPINFOHEADER) &&  pPackedDib->bmiHeader.biCompression == BI_BITFIELDS)   {  dwMaskSize = 3 * sizeof (DWORD) ;   }   else   {  dwMaskSize = 0 ;   }  // Get the size of the color table   if (dwInfoSize == sizeof (BITMAPCOREHEADER))   {  iBitCount = ((BITMAPCOREHEADER *) pPackedDib)->bcBitCount ;  
   if (iBitCount <= 8)  {  dwColorSize = (1 << iBitCount) * sizeof (RGBTRIPLE) ;  }  else dwColorSize = 0 ;  
  }   else // all non-OS/2 compatible DIBs   { if (pPackedDib->bmiHeader.biClrUsed > 0)  { dwColorSize = pPackedDib->bmiHeader.biClrUsed * sizeof (RGBQUAD);  }  else if (pPackedDib->bmiHeader.biBitCount <= 8)  { dwColorSize = (1 << pPackedDib->bmiHeader.biBitCount) * sizeof (RGBQUAD) ;  }  else  { dwColorSize = 0 ;  }   } // Finally, get the pointer to the bits in the packed DIB  pBits = (BYTE *) pPackedDib + dwInfoSize + dwMaskSize + dwColorSize ;  // Create the HDIB from the packed-DIB pointer   pdib = DibCreateFromInfo (pPackedDib) ; // Copy the pixel bits   CopyMemory (pdib->pBits, pBits, DibBitsSize (pdib)) ;   return pdib ;  
}  

/*----------------------------------------------------------------------------  
  DibFileLoad:  Creates a DIB section from a DIB file  
-----------------------------------------------------------------------------*/  
HDIB DibFileLoad (const TCHAR * szFileName)  
{   BITMAPFILEHEADER  bmfh ;   BITMAPINFO*  pbmi ;   BOOLbSuccess ;   DWORD   dwInfoSize, dwBitsSize, dwBytesRead ;   HANDLE   hFile ;   HDIBhDib ;  
  // Open the file: read access, prohibit write access  
 hFile = CreateFile (szFileName, GENERIC_READ, FILE_SHARE_READ, NULL,   OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL) ;  if (hFile == INVALID_HANDLE_VALUE)  return NULL ;  // Read in the BITMAPFILEHEADER   bSuccess = ReadFile ( hFile, &bmfh, sizeof (BITMAPFILEHEADER), &dwBytesRead, NULL) ;  
 if (!bSuccess || (dwBytesRead != sizeof (BITMAPFILEHEADER))|| (bmfh.bfType != * (WORD *) "BM"))   {  CloseHandle (hFile) ;  return NULL ;  
}  // Allocate memory for the information structure & read it in   dwInfoSize = bmfh.bfOffBits - sizeof (BITMAPFILEHEADER) ;   if (NULL == (pbmi = malloc (dwInfoSize)))   {  CloseHandle (hFile) ;  return NULL ;   }  
 bSuccess = ReadFile (hFile, pbmi, dwInfoSize, &dwBytesRead, NULL) ;   if (!bSuccess || (dwBytesRead != dwInfoSize))  
{  CloseHandle (hFile) ;  free (pbmi) ;  return NULL ;   }  // Create the DIB   hDib = DibCreateFromInfo (pbmi) ;   free (pbmi) ;  
 if (hDib == NULL)   {  CloseHandle (hFile) ;  return NULL ;   }  // Read in the bits   dwBitsSize = bmfh.bfSize - bmfh.bfOffBits ;   bSuccess = ReadFile ( hFile, ((PDIBSTRUCT) hDib)->pBits,dwBitsSize, &dwBytesRead, NULL) ;   CloseHandle (hFile) ;   if (!bSuccess || (dwBytesRead != dwBitsSize))   {  DibDelete (hDib) ;  return NULL ;   }   return hDib ;  
}  

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

BOOL DibFileSave (HDIB hdib, const TCHAR * szFileName)  
{   BITMAPFILEHEADER  bmfh ;   BITMAPINFO*  pbmi ;   BOOL  bSuccess ;   DWORD dwTotalSize, dwBytesWritten ;   HANDLEhFile ;  
 hFile = CreateFile (szFileName, GENERIC_WRITE, 0, NULL,  CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL) ;   if (hFile == INVALID_HANDLE_VALUE)  return FALSE ;   dwTotalSize  = DibTotalSize (hdib) ;   bmfh.bfType  = * (WORD *) "BM" ;   bmfh.bfSize = sizeof (BITMAPFILEHEADER) + dwTotalSize ;   bmfh.bfReserved1  = 0 ;   bmfh.bfReserved2  = 0 ;   bmfh.bfOffBits= bmfh.bfSize - DibBitsSize (hdib) ;  
// Write the BITMAPFILEHEADER  
 bSuccess = WriteFile (hFile, &bmfh, sizeof (BITMAPFILEHEADER),  &dwBytesWritten, NULL) ;  
 if (!bSuccess || (dwBytesWritten != sizeof (BITMAPFILEHEADER)))   {  CloseHandle (hFile) ;  DeleteFile (szFileName) ;  return FALSE ;   }  // Get entire DIB in packed-DIB format   if (NULL == (pbmi = DibCopyToPackedDib (hdib, FALSE)))  
{  CloseHandle (hFile) ;  DeleteFile (szFileName) ;  return FALSE ;   } // Write out the packed DIB   bSuccess = WriteFile (hFile, pbmi, dwTotalSize, &dwBytesWritten, NULL) ;   CloseHandle (hFile) ;   free (pbmi) ;  
 if (!bSuccess || (dwBytesWritten != dwTotalSize))   {  DeleteFile (szFileName) ;  return FALSE ;   }   return TRUE ;  
}  

/*---------------------------------------------------------------------------  
  DibCopyToDdb:  For more efficient screen displays  
---------------------------------------------------------------------------*/  
HBITMAP DibCopyToDdb (HDIB hdib, HWND hwnd, HPALETTE hPalette)  
{   BITMAPINFO*  pbmi ;   HBITMAP  hBitmap ;   HDC  hdc ;  
 if (!DibIsValid (hdib))  return NULL ;   if (NULL == (pbmi = DibCopyToInfo (hdib)))  return NULL ;   hdc = GetDC (hwnd) ;   if (hPalette)   {  SelectPalette (hdc, hPalette, FALSE) ;  RealizePalette (hdc) ;   }  
  hBitmap = CreateDIBitmap (hdc, DibInfoHeaderPtr (hdib), CBM_INIT,   DibBitsPtr (hdib), pbmi, DIB_RGB_COLORS) ;  
 ReleaseDC (hwnd, hdc) ;   free (pbmi) ;  
 return hBitmap ;  
}  

这部分的DIBHELP.C文件从两个小函数开始,这两个函数根据16位和32位DIB的颜色屏蔽得到左、右移位值。这些函数在第十五章「颜色屏蔽」一节说明。

DibCreateFromInfo函数是DIBHELP中唯一呼叫CreateDIBSection并为DIBSTRUCT结构配置内存的函数。其它所有建立和复制函数都重复此函数。DibCreateFromInfo唯一的参数是指向BITMAPINFO结构的指针。此结构的颜色表必须存在,但是它不必用有效的值填充。呼叫CreateDIBSection之后,该函数将初始化DIBSTRUCT结构的所有字段。注意,在设定DIBSTRUCT结构的ppRow字段的值时(指向DIB行地址的指针),DIB有由下而上和由上而下的不同储存方式。ppRow开头的元素就是DIB的顶行。

DibDelete删除DibCreateFromInfo中建立的位图,同时释放在该函数中配置的内存。

DibCreate可能比DibCreateFromInfo更像一个从应用程序呼叫的函数。前三个参数提供图素的宽度、高度和每图素的位数。最后一个参数可以设定为0(用于颜色表的内定尺寸),或者设定为非0(表示比每图素位数所需要的颜色表更小的颜色表)。

DibCopy函数根据现存的DIB区块建立新的DIB区块,并用DibCreateInfo函数为BITMAPINFO结构配置了内存,还填了所有的数据。DibCopy函数的一个BOOL参数指出是否在建立新的DIB时交换了DIB的宽度和高度。我们将在后面看到此函数的用法。

DibCopyToPackedDib和DibCopyFromPackedDib函数的使用通常与透过剪贴簿传递DIB相关。DibFileLoad函数从DIB文件建立DIB区块;DibFileSave函数将数据储存到DIB文件。

最后,DibCopyToDdb函数根据DIB建立GDI位图对象。注意,该函数需要目前调色盘的句柄和程序窗口的句柄。程序窗口句柄用于获得选进并显现调色盘的设备内容。只有这样,函数才可以呼叫CreateDIBitmap。这曾在本章前面的SHOWDIB7中展示。

DIBHELP表头文件和宏

DIBHELP.H表头文件如程序16-22所示。

程序16-22 DIBHELP.H文件
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定义为空指标(void*)。应用程序的确不需要了解HDIB所指结构的内部结构。此表头文件还包括DIBHELP.C中所有函数的说明,还有一些宏-非常特殊的宏。

如果再看一看DIBHELP.C中的DibPixelPtr、DibGetPixel和DibSetPixel函数,并试图提高它们的执行速度表现,那么您将看到两种可能的解决方法。第一种,可以删除所有的检查保护,并相信应用程序不会使用无效参数呼叫函数。还可以删除一些函数呼叫,例如DibBitCount,并使用指向DIBSTRUCT结构内部的指针来直接获得信息。

提高执行速度表现另一项较不明显的方法是删除所有对每图素位数的处理方式,同时分离出处理不同DIB函数-例如DibGetPixel1、DibGetPixel4、DibGetPixel8等等。下一个最佳化步骤是删除整个函数呼叫,将其处理动作透过inlinefunction或宏中进行合并。

DIBHELP.H采用宏的方法。它依据DibPixelPtr、DibGetPixel和DibSetPixel函数提出了三套宏。这些宏都明确对应于特殊的图素位数。

DIBBLE程序

DIBBLE程序,如程序16-23所示,使用DIBHELP函数和宏工作。尽管DIBBLE是本书中最长的程序,它确实只是一些作业的粗略范例,这些作业可以在简单的数字影像处理程序中找到。对DIBBLE的明显改进是转换成了多重文件接口(MDI:multipledocument interface),我们将在第十九章学习有关多重文件接口的知识。

程序16-23 DIBBLE
DIBBLE.C  
/*---------------------------------------------------------------------------  
  DIBBLE.C --   Bitmap and Palette Program(c) Charles Petzold, 1998  
-----------------------------------------------------------------------------*/  
#include <windows.h>  
#include "dibhelp.h"  
#include "dibpal.h"  
#include "dibconv.h"  
#include "resource.h"  

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

LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;  
TCHAR szAppName[] = TEXT ("Dibble") ;  
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,PSTR szCmdLine, int iCmdShow)  
{ HACCELhAccel ;   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)  
{   BITMAPbitmap ;   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 ;   intcx, 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 ;   staticDOCINFO   di = {sizeof(DOCINFO),TEXT("Dibble:Printing")} ;   static HBITMAP   hBitmap ;   static HDIB hdib ;   static HMENUhMenu ;   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 TCHARszFilter[]=   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 ;   SCROLLINFOsi ;   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 = GlobalLock (hGlobal) ;  
// If there's an existing DIB and palette,delete them.  // Then convert the packed DIB to an HDIB.  
   if (pGlobal) { SendMessage (hwnd, WM_USER_DELETEDIB, 0, 0) ; hdib = DibCopyFromPackedDib ((BITMAPINFO *) pGlobal) ;  SendMessage (hwnd, WM_USER_CREATEPAL, TRUE, 0) ; }  
   GlobalUnlock (hGlobal) ; CloseClipboard () ;// Reset the scroll bars  
   SendMessage (hwnd, WM_USER_SETSCROLLS, TRUE, 0) ; InvalidateRect (hwnd, NULL, TRUE) ; return 0 ;  
   case   IDM_EDIT_ROTATE: if (hdibNew = DibRotateRight (hdib)) {  DibDelete (hdib) ;  DeleteObject (hBitmap) ;  hdib = hdibNew ;  hBitmap = DibCopyToDdb (hdib, hwnd, hPalette) ; SendMessage (hwnd, WM_USER_SETSCROLLS, TRUE, 0) ;  InvalidateRect (hwnd, NULL, TRUE) ; } else {   MessageBox (   hwnd, TEXT ("Not enough memory"), szAppName, MB_OK | MB_ICONEXCLAMATION) ; } return 0 ;  
case   IDM_EDIT_FLIP: if (hdibNew = DibFlipHorizontal (hdib)) { DibDelete (hdib) ;  DeleteObject (hBitmap) ;  hdib = hdibNew ;  hBitmap = DibCopyToDdb (hdib, hwnd, hPalette) ;  InvalidateRect (hwnd, NULL, TRUE) ;} else {   MessageBox (   hwnd, TEXT ("Not enough memory"),  szAppName, MB_OK | MB_ICONEXCLAMATION) ; } return 0 ;  
case   IDM_SHOW_NORMAL:  case   IDM_SHOW_CENTER:  case   IDM_SHOW_STRETCH:  case   IDM_SHOW_ISOSTRETCH: CheckMenuItem (hMenu, wShow, MF_UNCHECKED) ; wShow = LOWORD (wParam) ;CheckMenuItem (hMenu, wShow, MF_CHECKED) ; SendMessage (hwnd, WM_USER_SETSCROLLS, FALSE, 0) ;  
   InvalidateRect (hwnd, NULL, TRUE) ; return 0 ;  
case   IDM_CONVERT_32:  iConvert += 8 ;  case   IDM_CONVERT_24:  iConvert += 8 ;   case   IDM_CONVERT_16:  iConvert += 8 ;  case   IDM_CONVERT_08:  iConvert += 4 ;  case   IDM_CONVERT_04:  iConvert += 3 ;  case   IDM_CONVERT_01:  iConvert += 1 ; SetCursor (LoadCursor (NULL, IDC_WAIT)) ; ShowCursor (TRUE) ;  
   hdibNew = DibConvert (hdib, iConvert) ;  
   ShowCursor (FALSE) ; SetCursor (LoadCursor (NULL, IDC_ARROW)) ;  
   if (hdibNew) {   SendMessage (hwnd, WM_USER_DELETEDIB, 0, 0) ; hdib = hdibNew ;   SendMessage (hwnd, WM_USER_CREATEPAL, TRUE, 0) ;  InvalidateRect (hwnd, NULL, TRUE) ; } else {MessageBox (  hwnd, TEXT ("Not enough memory"),  szAppName, MB_OK | MB_ICONEXCLAMATION) ;} return 0 ;  
case   IDM_APP_ABOUT:  MessageBox (hwnd, TEXT ("Dibble (c) Charles Petzold, 1998"),   szAppName, MB_OK | MB_ICONEXCLAMATION) ;  return 0 ; }  
 // All the other WM_COMMAND messages are from the palette  // items. Any existing palette is deleted, and the cursor  // is set to the hourglass.  
SendMessage (hwnd, WM_USER_DELETEPAL, 0, 0) ;  SetCursor (LoadCursor (NULL, IDC_WAIT)) ;  ShowCursor (TRUE) ;  // Notice that all messages for palette items are ended  // with break rather than return. This is to allow // additional processing later on.  
switch (LOWORD (wParam))  {  case   IDM_PAL_DIBTABLE: hPalette = DibPalDibTable (hdib) ; break ;  
case   IDM_PAL_HALFTONE: hdc = GetDC (hwnd) ;  
  if (hPalette = CreateHalftonePalette (hdc))   fHalftonePalette = TRUE ;  
   ReleaseDC (hwnd, hdc) ; break ;  
   case   IDM_PAL_ALLPURPOSE: hPalette = DibPalAllPurpose () ; break ;  
case IDM_PAL_GRAY2:hPalette= DibPalUniformGrays   (2);  break;  case IDM_PAL_GRAY3:hPalette= DibPalUniformGrays  (3);  break;  case IDM_PAL_GRAY4:hPalette= DibPalUniformGrays  (4);  break;  case IDM_PAL_GRAY8:hPalette= DibPalUniformGrays   (8);  break;  case IDM_PAL_GRAY16:hPalette   = DibPalUniformGrays  (16);  break;  case IDM_PAL_GRAY32:hPalette   = DibPalUniformGrays  (32);  break;  case IDM_PAL_GRAY64:hPalette   = DibPalUniformGrays  (64);  break;  case IDM_PAL_GRAY128:hPalette  = DibPalUniformGrays (128);  break;  case IDM_PAL_GRAY256:hPalette  = DibPalUniformGrays (256);  break;  case IDM_PAL_RGB222:hPalette   = DibPalUniformColors (2,2,2);   break;  case IDM_PAL_RGB333:hPalette   = DibPalUniformColors (3,3,3);   break;  case IDM_PAL_RGB444:hPalette   = DibPalUniformColors (4,4,4);   break;  case IDM_PAL_RGB555:hPalette   = DibPalUniformColors (5,5,5);   break;  case IDM_PAL_RGB666:hPalette   = DibPalUniformColors (6,6,6);   break;  case IDM_PAL_RGB775:hPalette   = DibPalUniformColors (7,7,5);   break;  case IDM_PAL_RGB757:hPalette   = DibPalUniformColors (7,5,7);   break;  case IDM_PAL_RGB577:hPalette   = DibPalUniformColors (5,7,7);   break;  case IDM_PAL_RGB884:hPalette   = DibPalUniformColors (8,8,4);   break;  case IDM_PAL_RGB848:hPalette   = DibPalUniformColors (8,4,8);   break;  case IDM_PAL_RGB488:hPalette   = DibPalUniformColors (4,8,8);   break;  case IDM_PAL_OPT_POP4:hPalette   = DibPalPopularity (hdib, 4) ;  break ;  case IDM_PAL_OPT_POP5:hPalette   = DibPalPopularity (hdib, 5) ;  break ;  case IDM_PAL_OPT_POP6:hPalette   = DibPalPopularity (hdib, 6) ;  break ;   case IDM_PAL_OPT_MEDCUT:hPalette = DibPalMedianCut  (hdib, 6) ;  break ;  }  
// After processing Palette items from the menu, the cursor  // is restored to an arrow, the menu item is checked, and  // the window is invalidated.  
hBitmap = DibCopyToDdb (hdib, hwnd, hPalette) ;  
ShowCursor (FALSE) ;  SetCursor (LoadCursor (NULL, IDC_ARROW)) ;  
if (hPalette) PaletteMenu (hMenu, (LOWORD (wParam))) ;  
   InvalidateRect (hwnd, NULL, TRUE) ;  return 0 ;  
// This programmer-defined message deletes an existing DIB  //   in preparation for getting a new one.  Invoked during  //   File Open command, Edit Paste command, and others.  

   case   WM_USER_DELETEDIB:  if (hdib) { DibDelete (hdib) ; hdib = NULL ;  }  SendMessage (hwnd, WM_USER_DELETEPAL, 0, 0) ;  return 0 ;  
   // This programmer-defined message deletes an existing palette  //in preparation for defining a new one.  
 case   WM_USER_DELETEPAL:  if (hPalette)  { DeleteObject (hPalette) ; hPalette = NULL ; fHalftonePalette = FALSE ; PaletteMenu (hMenu, IDM_PAL_NONE) ;  }  if (hBitmap) DeleteObject (hBitmap) ;  
return 0 ;  
// Programmer-defined message to create a new palette based on//a new DIB.  If wParam == TRUE, create a DDB as well.  
 case   WM_USER_CREATEPAL:  if (hdib)  { hdc = GetDC (hwnd) ;  
if (!(RC_PALETTE & GetDeviceCaps (hdc, RASTERCAPS))){   PaletteMenu (hMenu, IDM_PAL_NONE) ; } else if (hPalette = DibPalDibTable (hdib)) {   PaletteMenu (hMenu, IDM_PAL_DIBTABLE) ; }   else if (hPalette = CreateHalftonePalette (hdc)) {   fHalftonePalette = TRUE ;   PaletteMenu (hMenu, IDM_PAL_HALFTONE) ; } ReleaseDC (hwnd, hdc) ;  
   if ((BOOL) wParam)   hBitmap = DibCopyToDdb (hdib, hwnd, hPalette) ;  }  return 0 ;  
 case   WM_PAINT:  hdc = BeginPaint (hwnd, &ps) ;  
if (hPalette){ SelectPalette (hdc, hPalette, FALSE) ; RealizePalette (hdc) ;  }  if (hBitmap) { DisplayDib (  hdc,  fHalftonePalette ? DibBitmapHandle (hdib) : hBitmap,  iHscroll, iVscroll,  cxClient, cyClient,  wShow, fHalftonePalette) ;  }  EndPaint (hwnd, &ps) ;  return 0 ;  
case   WM_QUERYNEWPALETTE:  if (!hPalette) return FALSE ;  
hdc = GetDC (hwnd) ;  SelectPalette (hdc, hPalette, FALSE) ;  RealizePalette (hdc) ;  InvalidateRect (hwnd, NULL, TRUE) ;  
  ReleaseDC (hwnd, hdc) ;  return TRUE ;  
 case   WM_PALETTECHANGED:  if (!hPalette || (HWND) wParam == hwnd) break ;  
hdc = GetDC (hwnd) ; SelectPalette (hdc, hPalette, FALSE) ; RealizePalette (hdc) ;  UpdateColors (hdc) ;  
ReleaseDC (hwnd, hdc) ;  break ;  
 case   WM_DESTROY:  if (hdib) DibDelete (hdib) ;  
if (hBitmap) DeleteObject (hBitmap) ;  
if (hPalette) DeleteObject (hPalette) ;  
PostQuitMessage (0) ;  return 0 ;   }   return DefWindowProc (hwnd, message, wParam, lParam) ;  
}  
DIBBLE.RC (摘录)  
//Microsoft Developer Studio generated resource script.  
#include "resource.h"  
#include "afxres.h"  
/////////////////////////////////////////////////////////////////////////////  
// Menu  
DIBBLE MENU DISCARDABLE BEGIN POPUP "&File"  
   BEGINMENUITEM "&Open...\tCtrl+O",IDM_FILE_OPENMENUITEM "&Save...\tCtrl+S",IDM_FILE_SAVE   MENUITEM SEPARATORMENUITEM "&Print...\tCtrl+P",   IDM_FILE_PRINTMENUITEM SEPARATORMENUITEM "Propert&ies...",  IDM_FILE_PROPERTIESMENUITEM SEPARATORMENUITEM "E&xit",IDM_APP_EXIT  
END  
   POPUP "&Edit"  
   BEGIN MENUITEM "Cu&t\tCtrl+X",  IDM_EDIT_CUTMENUITEM "&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 (摘录)  
// Microsoft Developer Studio generated include file.  
// Used by Dibble.rc  
#define IDM_FILE_OPEN   40001  
#define IDM_FILE_SAVE40002  
#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_FLIP40010  
#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_GRAY240020  
#define IDM_PAL_GRAY340021  
#define IDM_PAL_GRAY440022  
#define IDM_PAL_GRAY840023  
#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_ABOUT40050  

DIBBLE使用了两个其它文件,我将简要地说明它们。DIBCONV文件(DIBCONV.C和DIBCONV.H)在两种不同格式之间转换-例如,从每图素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色显示模式下。不过,无论在程序建立新的「DIBSection」(显而易见地)或在程序建立不同的调色盘(不很明显)时,这个句柄都必须重新建立。

让我们从功能上而非循序渐进地来介绍一下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,或伸展到填充显示区域,或者在保持纵横比的情况下尽量填充显示区域。您可以在DIBBLE的「Show」菜单上来选择需要的选项。注意,这些与上一章的SHOWDIB2程序中四个选项相同。

在WM_PAINT消息处理期间-也是处理「File」、「Print」命令的过程中-DIBBLE呼叫DisplayDib函数。注意,DisplayDib使用BitBlt和StretchBlt,而不是使用SetDIBitsToDevice和StretchDIBits。在WM_PAINT消息处理期间,传递给函数的位图句柄由DibCopyToDdb函数建立,并在WM_USER_CREATEPAL消息处理期间呼叫。其中DDB与视讯设备内容兼容。当处理「File」、「Print」命令时,DIBBLE呼叫DisplayDib,其中可用的DIB区块句柄来自DIBHELP.C中的DibBitmapHandle函数。

另外要注意,DIBBLE保留一个称作fHalftonePalette的静态BOOL变量,如果从CreateHalftonePalette函数中获得hPalette,则此变量设定为TRUE。这将迫使DisplayDib函数呼叫StretchBlt而不是呼叫BitBlt,即使DIB被指定按实际尺寸显示。fHalftonePalette变量也导致WM_PAINT处理程序将DIB区块句柄传递给DisplayDib函数,而不是由DibCopyToDdb函数建立的位图句柄。本章前面讨论过中间色调色盘的使用,并在SHOWDIB5程序中进行了展示。

第一次使用范例程序时,DIBBLE允许在显示区域中卷动DIB。只有按实际尺寸显示DIB时,才显示滚动条。在处理WM_PAINT时,WndProc简单地将滚动条的目前位置传递给DisplayDib函数。

剪贴簿

对于「Cut」和「Copy」菜单项,DIBBLE呼叫DIBHELP中的DibCopyToPackedDib函数。该函数将获得所有的DIB组件并将它们放入大的内存块中。

对于第一次使用本书中的某些范例程序来说,DIBBLE从剪贴簿中粘贴DIB。这包括呼叫DibCopyFromPackedDib函数,并替换窗口消息处理程序前面储存的HDIB、调色盘和位图。

翻转和旋转

DIBBLE中的「Edit」菜单中除了常见的「Cut」、「Copy」、「Paste」和「Delete」选项之外,还包括两个附加项-「Flip」和「Rotate」。「Flip」选项使位图绕水平轴翻转-即上下颠倒翻转。「Rotate」选项使位图顺时针旋转90度。这两个函数都需要透过将它们从一个DIB复制到另一个来存取所有的DIB图素(因为这两个函数不需要建立新的调色盘,所以不删除和重新建立调色盘)。

「Flip」菜单选项使用DibFlipHorizontal函数,此函数也位于DIBBLE.C文件。此函数呼叫DibCopy来获得DIB精确的副本。然后,进入将原DIB中的图素复制到新DIB的循环,但是复制这些图素是为了上下翻转图像。注意,此函数呼叫DibGetPixel和DibSetPixel。这些是DIBHELP.C中的通用(但不像我们所希望的那么快)函数。

为了说明DibGetPixel和DibSetPixel函数与DIBHELP.H中执行更快的DibGetPixel和DibSetPixel宏之间的区别,DibRotateRight函数使用了宏。然而,首先要注意的是,该函数呼叫DibCopy时,第二个参数设定为TRUE。这导致DibCopy翻转原DIB的宽度和高度来建立新的DIB。另外,图素位不能由DibCopy函数复制。但是,DibRotateRight函数有六个不同的循环将图素位从原DIB复制到新的DIB-每一个都对应不同的DIB图素宽度(1位、4位、8位、16位、24位和32位)。虽然包括了更多的程序代码,但是函数更快了。

尽管可以使用「Flip Horizontal」和「RotateRight」选项来产生「Flip Vertical」、「Rotate Left」和「Rotate180°」功能,但通常程序将直接执行所有选项。毕竟,DIBBLE只是个展示程序而已。

简单调色盘;最佳化调色盘

在DIBBLE中,您可以在256色视讯显示器上选择不同的调色盘来显示DIB。这些都在DIBBLE的「Palette」菜单中列出。除了中间色调色盘以外,其余的都直接由Windows函数呼叫建立,建立不同调色盘的所有函数都由程序16-24所示的DIBPAL文件提供。

程序16-24 DIBPAL文件
DIBPAL.H  
/*--------------------------------------------------------------------------  
  DIBPAL.H header file for DIBPAL.C  
----------------------------------------------------------------------------*/  
HPALETTE DibPalDibTable (HDIB hdib) ;  
HPALETTE DibPalAllPurpose (void) ;  
HPALETTE DibPalUniformGrays (int iNum) ;  
HPALETTE DibPalUniformColors (int iNumR, int iNumG, int iNumB) ;  
HPALETTE DibPalVga (void) ;  
HPALETTE DibPalPopularity (HDIB hdib, int iRes) ;  
HPALETTE DibPalMedianCut (HDIB hdib, int iRes) ;  
DIBPAL.C  
/*----------------------------------------------------------------------------  
  DIBPAL.C --   Palette-Creation Functions   (c) Charles Petzold, 1998  
-------------------------------------------------------------------------*/  
#include <windows.h>  
#include "dibhelp.h"  
#include "dibpal.h"  

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

HPALETTE DibPalDibTable (HDIB hdib)  
{  
   HPALETTE  hPalette ;   int  i, iNum ;   LOGPALETTE*  plp ;   RGBQUAD   rgb ;  
 if (0 == (iNum = DibNumColors (hdib)))  return NULL ;   plp = malloc (sizeof (LOGPALETTE) + (iNum - 1) * sizeof (PALETTEENTRY)) ;   plp->palVersion   = 0x0300 ;   plp->palNumEntries= iNum ;  
 for (i = 0 ; i < iNum ; i++)   {  DibGetColor (hdib, i, &rgb) ;  plp->palPalEntry[i].peRed   = rgb.rgbRed ;  plp->palPalEntry[i].peGreen = rgb.rgbGreen ;  plp->palPalEntry[i].peBlue  = rgb.rgbBlue ;  plp->palPalEntry[i].peFlags = 0 ;   }   hPalette = CreatePalette (plp) ;   free (plp) ;   return hPalette ;  
}  
/*---------------------------------------------------------------------------  
  DibPalAllPurpose: Creates a palette suitable for a wide variety of images; the palette has 247 entries, but 15 of them are  duplicates or match the standard 20 colors.  
----------------------------------------------------------------------------*/  

HPALETTE DibPalAllPurpose (void)  
{   HPALETTE hPalette ;   int  i, incr, R, G, B ;   LOGPALETTE * plp ;  
 plp = malloc (sizeof (LOGPALETTE) + 246 * sizeof (PALETTEENTRY)) ;   plp->palVersion   = 0x0300 ;   plp->palNumEntries= 247 ;  
// The following loop calculates 31 gray shades, but 3 of them  //will match the standard 20 colors  
 for (i = 0, G = 0, incr = 8 ; G <= 0xFF ; i++, G += incr)  
{  plp->palPalEntry[i].peRed= (BYTE) G ;  plp->palPalEntry[i].peGreen   = (BYTE) G ;  plp->palPalEntry[i].peBlue= (BYTE) G ;  plp->palPalEntry[i].peFlags   = 0 ;  
incr = (incr == 9 ? 8 : 9) ;  
}  
  // The following loop is responsible for 216 entries, but 8 of  //them will match the standard 20 colors, and another  //4 of them will match the gray shades above.  

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

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

HPALETTE DibPalUniformGrays (int iNum)  
{   HPALETTE  hPalette ;   int  i ;   LOGPALETTE*  plp ;  
 plp = malloc (sizeof (LOGPALETTE) + (iNum - 1) * sizeof (PALETTEENTRY)) ;   plp->palVersion  = 0x0300 ;   plp->palNumEntries= iNum ;  
   for (i = 0 ; i < iNum ; i++)   {  plp->palPalEntry[i].peRed=  plp->palPalEntry[i].peGreen   =  plp->palPalEntry[i].peBlue  = (BYTE) (i * 255 / (iNum - 1)) ;  plp->palPalEntry[i].peFlags = 0 ;   } hPalette = CreatePalette (plp) ;   free (plp) ;   return hPalette ;  
}  

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

HPALETTE DibPalUniformColors (int iNumR, int iNumG, int iNumB)  
{   HPALETTE  hPalette ;   int  i, iNum, R, G, B ;   LOGPALETTE * plp ;  
 iNum = iNumR * iNumG * iNumB ;   plp = malloc (sizeof (LOGPALETTE) + (iNum - 1) * sizeof (PALETTEENTRY)) ;   plp->palVersion= 0x0300 ;   plp->palNumEntries = iNumR * iNumG * iNumB ;  
 i = 0 ;   for (R = 0 ; R < iNumR ; R++)   for (G = 0 ; G < iNumG ; G++)   for (B = 0 ; B < iNumB ; B++)   {  plp->palPalEntry[i].peRed = (BYTE) (R * 255 / (iNumR - 1)) ;  plp->palPalEntry[i].peGreen   = (BYTE) (G * 255 / (iNumG - 1)) ;  plp->palPalEntry[i].peBlue= (BYTE) (B * 255 / (iNumB - 1)) ;  plp->palPalEntry[i].peFlags   = 0 ;  
i++ ;   }   hPalette = CreatePalette (plp) ;   free (plp) ;   return hPalette ;  
}  

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

HPALETTE DibPalVga (void)  
{  
static RGBQUAD rgb [16] = { 0x00, 0x00, 0x00,0x00,   0x00, 0x00, 0x80, 0x00,  0x00, 0x80, 0x00, 0x00,   0x00, 0x80, 0x80, 0x00,   0x80, 0x00, 0x00, 0x00,   0x80, 0x00, 0x80, 0x00,   0x80, 0x80, 0x00, 0x00,  0x80, 0x80, 0x80, 0x00,   0xC0, 0xC0, 0xC0, 0x00,   0x00, 0x00, 0xFF, 0x00,   0x00, 0xFF, 0x00, 0x00,   0x00, 0xFF, 0xFF, 0x00,  0xFF, 0x00, 0x00, 0x00,   0xFF, 0x00, 0xFF, 0x00,   0xFF, 0xFF, 0x00, 0x00,   0xFF, 0xFF, 0xFF, 0x00 } ;   HPALETTE hPalette ;  int   i ;   LOGPALETTE   * plp ;  
 plp = malloc (sizeof (LOGPALETTE) + 15 * sizeof (PALETTEENTRY)) ;   plp->palVersion   = 0x0300 ;   plp->palNumEntries= 16 ;  
 for (i = 0 ; i < 16 ; i++)   { plp->palPalEntry[i].peRed = rgb[i].rgbRed ;  plp->palPalEntry[i].peGreen   = rgb[i].rgbGreen ;  plp->palPalEntry[i].peBlue= rgb[i].rgbBlue ;  plp->palPalEntry[i].peFlags  = 0 ;   }   hPalette = CreatePalette (plp) ;   free (plp) ;   return hPalette ;  
}  

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

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

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

static void AccumColorCounts (HDIB hdib, int * piCount, int iRes)  
{   int   x, y, cx, cy ;   RGBQUADrgb ;  
 cx = DibWidth (hdib) ;   cy = DibHeight (hdib) ;  
 for (y = 0 ; y < cy ; y++)   for (x = 0 ; x < cx ; x++)   {  DibGetPixelColor (hdib, x, y, &rgb) ;  
rgb.rgbRed   >>= (8 - iRes) ;  rgb.rgbGreen >>= (8 - iRes) ;  rgb.rgbBlue  >>= (8 - iRes) ;  
++piCount [PACK_RGB (rgb.rgbRed, rgb.rgbGreen, rgb.rgbBlue, iRes)] ;   }  
}  

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

HPALETTE DibPalPopularity (HDIB hdib, int iRes)  
{   HPALETTE  hPalette ;   int  i, iArraySize, iEntry, iCount, iIndex, iMask, R, G, B ;   int   *  piCount ;   LOGPALETTE*  plp ;  
// Validity checks  
 if (DibBitCount (hdib) < 16)  return NULL ;   if (iRes < 3 || iRes > 8) return NULL ;   // Allocate array for counting pixel colors   iArraySize = 1 << (3 * iRes) ;   iMask = (1 << iRes) - 1 ;  
 if (NULL == (piCount = calloc (iArraySize, sizeof (int))))  return NULL ; // Get the color counts   AccumColorCounts (hdib, piCount, iRes) ;  // Set up a palette   plp = malloc (sizeof (LOGPALETTE) + 235 * sizeof (PALETTEENTRY)) ;   plp->palVersion = 0x0300 ;   for (iEntry = 0 ; iEntry < 236 ; iEntry++)   {  for (i = 0, iCount = 0 ; i < iArraySize ; i++) if (piCount[i] > iCount)  
   {iCount = piCount[i] ;iIndex = i ;}  if (iCount == 0) break ;  R = (iMask &  iIndex) << (8 - iRes) ;  G = (iMask & (iIndex >> iRes )) << (8 - iRes) ;   B = (iMask & (iIndex >> (iRes + iRes)))<< (8 - iRes) ;  
plp->palPalEntry[iEntry].peRed   = (BYTE) R ;  plp->palPalEntry[iEntry].peGreen = (BYTE) G ;  plp->palPalEntry[iEntry].peBlue  = (BYTE) B ;  plp->palPalEntry[iEntry].peFlags = 0 ;  
piCount [iIndex] = 0 ;  
}  // On exit from the loop iEntry will be the number of stored entries   plp->palNumEntries = iEntry ; // Create the palette, clean up, and return the palette handle   hPalette = CreatePalette (plp) ;   free (piCount) ;   free (plp) ;  
 return hPalette ;  
}  

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

typedef struct// defines dimension of a box  
{   int Rmin, Rmax, Gmin, Gmax, Bmin, Bmax ;  
}  
MINMAX ;  
typedef struct// for Compare routine for qsort  
{   int   iBoxCount ;   RGBQUAD   rgbBoxAv ;  
}  

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

static int FindAverageColor (  int * piCount, MINMAX mm,  int iRes, RGBQUAD * prgb)  
{   int R, G, B, iR, iG, iB, iTotal, iCount ;  // Initialize some variables   iTotal = iR = iG = iB = 0 ;  // Loop through all colors in the box   for (R = mm.Rmin ; R <= mm.Rmax ; R++)   for (G = mm.Gmin ; G <= mm.Gmax ; G++)   for (B = mm.Bmin ; B <= mm.Bmax ; B++)   { // Get the number of pixels of that color iCount = piCount [PACK_RGB (R, G, B, iRes)] ; // Weight the pixel count by the color value  iR += iCount * R ;  iG += iCount * G ;  iB += iCount * B ;  
iTotal += iCount ;   } // Find the average color   prgb->rgbRed  = (BYTE) ((iR / iTotal) << (8 - iRes)) ;   prgb->rgbGreen= (BYTE) ((iG / iTotal) << (8 - iRes)) ;   prgb->rgbBlue = (BYTE) ((iB / iTotal) << (8 - iRes)) ;  
// Return the total number of pixels in the box  
  return iTotal ;  
}  

/*---------------------------------------------------------------------------  
  CutBox:  Divide a box in two  
----------------------------------------------------------------------------*/  
static void CutBox (int * piCount, int iBoxCount, MINMAX mm,   int iRes, int iLevel, BOXES * pboxes, int * piEntry)  
{   int   iCount, R, G, B ;   MINMAX mmNew ;  
 // If the box is empty, return  
 if (iBoxCount == 0)  return ;  
// If the nesting level is 8, or the box is one pixel, we're ready  // to find the average color in the box and save it along with  // the number of pixels of that color  
 if (iLevel == 8 || (  mm.Rmin == mm.Rmax &&  mm.Gmin == mm.Gmax &&  mm.Bmin == mm.Bmax))  
{  pboxes[*piEntry].iBoxCount =  FindAverageColor (piCount, mm, iRes, &pboxes[*piEntry].rgbBoxAv) ;  (*piEntry) ++ ;   } // Otherwise, if blue is the largest side, split it   else if ((mm.Bmax - mm.Bmin > mm.Rmax - mm.Rmin) && (mm.Bmax - mm.Bmin > mm.Gmax - mm.Gmin))   { // Initialize a counter and loop through the blue side  iCount = 0 ;  for (B = mm.Bmin ; B < mm.Bmax ; B++)  { // Accumulate all the pixels for each successive blue value  for (  R = mm.Rmin ; R <= mm.Rmax ; R++)  for (  G = mm.Gmin ; G <= mm.Gmax ; G++)  iCount += piCount [PACK_RGB (R, G, B, iRes)] ;  
// If it's more than half the box count, we're there  
  if (i  Count >= iBoxCount / 2) break ;  
   // If the next blue value will be the max, we're thereif (   B == mm.Bmax - 1)break ;  } // Cut the two split boxes. //   The second argument to CutBox is the new box count. //   The third argument is the new min and max values.  
mmNew = mm ;  mmNew.Bmin = mm.Bmin ;  mmNew.Bmax = B ;  
CutBox (  piCount, iCount, mmNew, iRes, iLevel + 1,pboxes, piEntry) ;  
mmNew.Bmin = B + 1 ;  mmNew.Bmax = mm.Bmax ;  
CutBox (  piCount, iBoxCount - iCount, mmNew, iRes, iLevel + 1, pboxes, piEntry) ;  
}  // Otherwise, if red is the largest side, split it (just like blue)   else if (mm.Rmax - mm.Rmin > mm.Gmax - mm.Gmin)  
{  iCount = 0 ;  for (R = mm.Rmin ; R < mm.Rmax ; R++)  { for (B = mm.Bmin ; B <= mm.Bmax ; B++)for (G = mm.Gmin ; G <= mm.Gmax ; G++) iCount += piCount [PACK_RGB (R, G, B, iRes)] ; if (iCount >= iBoxCount / 2)break ; if (R == mm.Rmax - 1)   break ;  }  mmNew = mm ;  mmNew.Rmin = mm.Rmin ;  mmNew.Rmax = R ;  
CutBox (  piCount, iCount, mmNew, iRes, iLevel + 1, pboxes, piEntry) ;  
mmNew.Rmin= R + 1 ; mmNew.Rmax= mm.Rmax ;  CutBox (  piCount, iBoxCount - iCount, mmNew, iRes, iLevel + 1, pboxes, piEntry) ;  
}  // Otherwise, split along the green size   else   {  iCount = 0 ;  for (G = mm.Gmin ; G < mm.Gmax ; G++){  for (  B = mm.Bmin ; B <= mm.Bmax ; B++)  for (  R = mm.Rmin ; R <= mm.Rmax ; R++)  iCount += piCount [PACK_RGB (R, G, B, iRes)] ;  
if (   iCount >= iBoxCount / 2)break ;  
if (   G == mm.Gmax - 1) break ;  }  mmNew = mm ;  mmNew.Gmin = mm.Gmin ;  mmNew.Gmax = G ;  
CutBox (  piCount, iCount, mmNew, iRes, iLevel + 1, pboxes, piEntry) ;  
mmNew.Gmin= G + 1 ;  mmNew.Gmax= mm.Gmax ;  
CutBox (  piCount, iBoxCount - iCount, mmNew, iRes, iLevel + 1, pboxes, piEntry) ;   }  
}  

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

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

/*---------------------------------------------------------------------------  
  DibPalMedianCut:  Creates palette based on median cut algorithm  
-------------------------------------------------------------------------*/  
HPALETTE DibPalMedianCut (HDIB hdib, int iRes)  
{   BOXES boxes [256] ;   HPALETTE  hPalette ;   int  i, iArraySize, iCount, R, G, B, iTotCount, iDim, iEntry = 0 ;   int   *  piCount ; LOGPALETTE*  plp ;   MINMAXmm ;  
// Validity checks  
 if (DibBitCount (hdib) < 16)  return NULL ;   if (iRes < 3 || iRes > 8)  return NULL ;  // Accumulate counts of pixel colors  
iArraySize = 1 << (3 * iRes) ;  
if (NULL == (piCount = calloc (iArraySize, sizeof (int))))  return NULL ;  
AccumColorCounts (hdib, piCount, iRes) ;   // Find the dimensions of the total box  
iDim = 1 << iRes ;  
mm.Rmin = mm.Gmin = mm.Bmin = iDim - 1 ;  
mm.Rmax = mm.Gmax = mm.Bmax = 0 ;  

iTotCount = 0 ;   for (R = 0 ; R < iDim ; R++)   for (G = 0 ; G < iDim ; G++)   for (B = 0 ; B < iDim ; B++)  if ((iCount = piCount [PACK_RGB (R, G, B, iRes)]) > 0)  { iTotCount += iCount ; if (R < mm.Rmin) mm.Rmin = R ; if (G < mm.Gmin) mm.Gmin = G ; if (B < mm.Bmin) mm.Bmin = B ; if (R > mm.Rmax) mm.Rmax = R ; if (G > mm.Gmax) mm.Gmax = G ;   if (B > mm.Bmax) mm.Bmax = B ;  }  
// Cut the first box (iterative function). // On return, the boxes structure will have up to 256 RGB values,  // one for each of the boxes, and the number of pixels in  //  each box.  //  The iEntry value will indicate the number of non-empty boxes.  
 CutBox (piCount, iTotCount, mm, iRes, 0, boxes, &iEntry) ;   free (piCount) ;  
// Sort the RGB table by the number of pixels for each color   qsort (boxes, iEntry, sizeof (BOXES), Compare) ;   plp = malloc (sizeof (LOGPALETTE) + (iEntry - 1) * sizeof (PALETTEENTRY)) ;   if (plp == NULL)  return NULL ;   plp->palVersion   = 0x0300 ;   plp->palNumEntries= iEntry ;  
 for (i = 0 ; i < iEntry ; i++)   {  plp->palPalEntry[i].peRed  = boxes[i].rgbBoxAv.rgbRed ;  plp->palPalEntry[i].peGreen= boxes[i].rgbBoxAv.rgbGreen ;  plp->palPalEntry[i].peBlue = boxes[i].rgbBoxAv.rgbBlue ;  plp->palPalEntry[i].peFlags= 0 ;  
}  

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

第一个函数-DibPalDibTable-看起来应该很熟悉。它根据DIB的颜色表建立了调色盘。这与本章前面的SHOWDIB3中所用到的PACKEDIB.C里的PackedDibCreatePalette函数相似。在SHOWDIB3中,只有当DIB有颜色表时才执行此函数。在8位显示模式下试图显示16位、24位或32位DIB时,此函数就没用了。

预设情况下,执行在256色显示模式下时,DIBBLE将首先尝试呼叫DibPalDibTable来根据DIB颜色表建立调色盘。如果DIB没有颜色表,则DIBBLE将呼叫CreateHalftonePalette并将fHalftonePalette变量设定为TRUE。此逻辑发生在WM_USER_CREATEPAL消息处理期间。

DIBPAL.C也执行函数DibPalAllPurpose,因为此函数与SHOWDIB4中的CreateAllPurposePalette函数非常相似,所以它看起来也很熟悉。您也可以从DIBBLE菜单中选择此调色盘。

在256色模式下显示位图最有趣的是,您可以直接控制Windows用于显示图像的颜色。如果您选择并显现调色盘,则Winsows将使用此调色盘中的颜色,而不是其它调色盘中的颜色。

例如,您可以用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的时间会更少(用8位彩色模式显示大24位DIB时,比较SHOWDIB1和SHOWDIB4的性能,您也能看出这点区别)。这是为什么呢?

它按最接近颜色搜寻。通常,用8位显示模式显示24位DIB时(或者将DIB转换为DDB),GDI必须将DIB中的每个图素都与静态20种颜色中的一种相贴近。完成此操作的唯一方法是决定哪种静态颜色与图素颜色最接近。这包括计算图素与三维RGB颜色中每种静态颜色的距离。这将花些时间,特别是在DIB图像中有上百万个图素时。

在建立232色调色盘时,例如DIBBLE和SHOWDIB4中的通用调色盘,您会很快将搜索最接近颜色的时间增加到超过11倍!GDI现在必须彻底检查232种颜色,而不是20种。那就是显示DIB的整个作业放慢的原因。

这里的教训是避免在8位显示模式下显示24位(或16位,或32位)DIB。您应该找出最接近DIB图像颜色范围的256色调色盘,来将它们转换成8位DIB。这经常称为「最佳调色盘」。当我研究这个问题的时候,PaulHeckbert编写的〈Color Image Quantization for Frame BufferDisplays〉(刊登在1982年7月出版的《ComputerGraphics》)对此问题有所帮助。

均匀分布

建立256色调色盘最简单的方法是选择范围统一的RGB颜色值,它与DibPalAllPurpose中的方法相似。此方法的优点是您不必检查DIB中的实际图素。这个函数是DibPalCreateUniformColors,它依据范围统一的RGB三原色索引建立调色盘。

一个合理的分布包括8阶红色和绿色以及4阶蓝色(肉眼对蓝色较不敏感)。调色盘是RGB颜色值的集合,它是红色和绿色值0x00、0x24、0x49、0x6D、0x92、0xB6、0xDB和0xFF以及蓝色值0x00、0x55、0xAA和0xFF的所有可能的组合,共有256种颜色。另一种可能的统一分布调色盘使用6阶红色、绿色和蓝色。此调色盘是红色、绿色和蓝色值为0x00、0x33、0x66、0x99、0xCC和0xFF的所有可能的组合,调色盘中的颜色数是6的3次方,即216。

这两个选项和其它几个选项都由DIBBLE提供。

「Popularity」算法

「Popularity」算法是256色调色盘问题相当明显的解决方法。您要做的就是走遍位图中的所有图素,并找出256种最普通的RGB颜色值。这些就是您在调色盘中使用的值。DIBPAL的DibPalPopularity函数中实作了这种算法。

不过,如果每种颜色都使用整个24位,而且假设需要用整数来计算所有的颜色,那么数组将占据64MB内存。另外,您可以发现位图中实际上没有(或很少)重复的24位图素值,这样就没有所谓常见的颜色了。

要解决这个问题,您可以只使用每个红色、绿色和蓝色值中最重要的n位-例如,6位而不是8位。因为大多数的彩色扫描仪和视讯显示卡都只有6位的分辨率,所以这样规定更有意义。这将数组减少到大小更合理的256KB或1MB。只使用5位能将可用的颜色总数减少到32,768。通常,使用5位要比6位的性能更好。对此,您可以用DIBBLE和一些图像颜色来自己检验。

「MedianCut」算法

DIBPAL.C中的DibPalMedianCut函数执行Paul Heckbert的MedianCut算法。此算法在概念上相当简单,但在程序代码中实作要比Popularity算法更困难,它适合递归函数。

画出RGB颜色立方体。图像中的每个图素都是此立方体中的一个点。一些点可能代表图像中的多个图素。找出包括图像中所有图素的立体方块,找出此方块的最大尺寸,并将方块分成两个,每个方块都包括相同数量的图素。对于这两个方块,执行相同的操作。现在您就有4个方块,将这4个方块分成8个,然后再分成16个、32个、64个、128个和256个。

现在您有256个方块,每个方块都包括相同数量的图素。取每个方块中图素RGB颜色值的平均值,并将结果用于调色盘。

实际上,这些方块通常包含图素的数量并不相同。例如,通常包括单个点的方块会有更多的图素。这发生在黑色和白色上。有时,一些方块里头根本没有图素。如果这样,您就可以省下更多的方块,但是我决定不这样做。

另一种最佳化调色盘的技术称为「octreequantization」,此技术由JeffProsise提出,并于1996年8月发表在《Microsoft SystemsJournal》上(包含在MSDN的CD中)。

转换格式

DIBBLE还允许将DIB从一种格式转换到另一种格式。这用到了DIBCONV文件中的DibConvert函数,如程序16-25所示。

程序16-25 DIBCONV文件
DIBCONV.H  
/*----------------------------------------------------------------------------  
  DIBCONV.H header file for DIBCONV.C  
-----------------------------------------------------------------------------*/  

HDIB DibConvert (HDIB hdibSrc, int iBitCountDst) ;  
DIBCONV.C  
/*---------------------------------------------------------------------------  
  DIBCONV.C --  Converts DIBs from one format to another   (c) Charles Petzold, 1998  
----------------------------------------------------------------------------*/  
#include <windows.h>  
#include "dibhelp.h"  
#include "dibpal.h"  
#include "dibconv.h"  

HDIB DibConvert (HDIB hdibSrc, int iBitCountDst)  
{   HDIB hdibDst ;   HPALETTE  hPalette ;   int  i, x, y, cx, cy, iBitCountSrc, cColors ;   PALETTEENTRY pe ;   RGBQUAD   rgb ;   WORD wNumEntries ;  
 cx = DibWidth (hdibSrc) ;   cy = DibHeight (hdibSrc) ;   iBitCountSrc = DibBitCount (hdibSrc) ;  
 if (iBitCountSrc == iBitCountDst)  return NULL ;  // DIB with color table to DIB with larger color table:   if ((iBitCountSrc < iBitCountDst) && (iBitCountDst <= 8))   {  cColors = DibNumColors (hdibSrc) ;  hdibDst = DibCreate (cx, cy, iBitCountDst, cColors) ;  
for (i = 0 ; i < cColors ; i++)  { DibGetColor (hdibSrc, i, &rgb) ;DibSetColor (hdibDst, i, &rgb) ; }  
for (x = 0 ; x < cx ; x++)  for (y = 0 ; y < cy ; y++)  {  DibSetPixel (hdibDst, x, y, DibGetPixel (hdibSrc, x, y)) ;  }   } // Any DIB to DIB with no color table   else if (iBitCountDst >= 16)   {  hdibDst = DibCreate (cx, cy, iBitCountDst, 0) ;  for (x = 0 ; x < cx ; x++)  for (y = 0 ; y < cy ; y++)  { DibGetPixelColor (hdibSrc, x, y, &rgb) ;   DibSetPixelColor (hdibDst, x, y, &rgb) ;}   }  // DIB with no color table to 8-bit DIB   else if (iBitCountSrc >= 16 && iBitCountDst == 8)   {  hPalette = DibPalMedianCut (hdibSrc, 6) ;  GetObject (hPalette, sizeof (WORD), &wNumEntries) ;  
hdibDst = DibCreate (cx, cy, 8, wNumEntries) ;  for (i = 0 ; i < (int) wNumEntries ; i++)  { GetPaletteEntries (hPalette, i, 1, &pe) ; rgb.rgbRed= pe.peRed ;rgb.rgbGreen  = pe.peGreen ; rgb.rgbBlue   = pe.peBlue ; rgb.rgbReserved   = 0 ;  
   DibSetColor (hdibDst, i, &rgb) ;  }  
for (x = 0 ; x < cx ; x++)  for (y = 0 ; y < cy ; y++)  { DibGetPixelColor (hdibSrc, x, y, &rgb) ;  
   DibSetPixel (hdibDst, x, y, GetNearestPaletteIndex (hPalette,  RGB (rgb.rgbRed, rgb.rgbGreen, rgb.rgbBlue))) ;  }  DeleteObject (hPalette) ;  }  // Any DIB to monochrome DIB  
else if (iBitCountDst == 1)  {  hdibDst = DibCreate (cx, cy, 1, 0) ;  hPalette = DibPalUniformGrays (2) ;  
for (i = 0 ; i < 2 ; i++)  {GetPaletteEntries (hPalette, i, 1, &pe) ;  
   rgb.rgbRed   = pe.peRed ; rgb.rgbGreen = pe.peGreen ; rgb.rgbBlue  = pe.peBlue ;rgb.rgbReserved = 0 ;  
   DibSetColor (hdibDst, i, &rgb) ;  }  
for (x = 0 ; x < cx ; x++)  for (y = 0 ; y < cy ; y++)  { DibGetPixelColor (hdibSrc, x, y, &rgb) ;  
  DibSetPixel (hdibDst, x, y,  GetNearestPaletteIndex (hPalette, RGB (rgb.rgbRed, rgb.rgbGreen, rgb.rgbBlue))) ;  }  DeleteObject (hPalette) ;   }  // All non-monochrome DIBs to 4-bit DIB   else if (iBitCountSrc >= 8 && iBitCountDst == 4)   {  hdibDst = DibCreate (cx, cy, 4, 0) ;  hPalette = DibPalVga () ;  
for (i = 0 ; i < 16 ; i++)  {GetPaletteEntries (hPalette, i, 1, &pe) ; rgb.rgbRed   = pe.peRed ; rgb.rgbGreen  = pe.peGreen ; rgb.rgbBlue  = pe.peBlue ; rgb.rgbReserved   = 0 ;  
   DibSetColor (hdibDst, i, &rgb) ;  }  
for (x = 0 ; x < cx ; x++)  for (y = 0 ; y < cy ; y++)  { DibGetPixelColor (hdibSrc, x, y, &rgb) ;  
   DibSetPixel (hdibDst, x, y, GetNearestPaletteIndex (hPalette,  RGB (rgb.rgbRed, rgb.rgbGreen, rgb.rgbBlue))) ;  }  DeleteObject (hPalette) ;   }  // Should not be necessary   else  hdibDst = NULL ;   return hdibDst ;  
}  

将DIB从一种格式转换成另一种格式需要几种不同的方法。

要将带有颜色表的DIB转换成另一种也带有颜色表但有较大的图素宽度的DIB(亦即,将1位DIB转换成4位或8位DIB,或将4位DIB转换成8位DIB),所需要做的就是透过呼叫DibCreate来建立新的DIB,并在呼叫时带有希望的位数以及与原始DIB中的颜色数相等的颜色数。然后函数复制图素位和颜色表项目。

如果新的DIB没有颜色表(即位数是16、24或32),那么DIB只需要按新格式建立,而且通过呼叫DibGetPixelColor和DibSetPixelColor从现有的DIB中复制图素位。

下面的情况可能更普遍:现有的DIB没有颜色表(即位数是16、24或32),而新的DIB每图素占8位。这种情况下,DibConvert呼叫DibPalMedianCut来为图像建立最佳化的调色盘。新DIB的颜色表设定为调色盘中的RGB值。DibGetPixelColor函数从现有的DIB中获得图素颜色。透过呼叫GetNearestPaletteIndex来转换成8位DIB中的图素值,并透过呼叫DibSetPixel将图素值储存到DIB。

当DIB需要转换成单色DIB时,用包括两个项目-黑色和白色-的颜色表建立新的DIB。另外,GetNearestPaletteIndex有助于将现有DIB中的颜色转换成图素值0或1。类似地,当8个图素位或更多位的DIB要转换成4位DIB时,可从DibPalVga函数获得DIB颜色表,同时GetNearestPaletteIndex也有助于计算图素值。

尽管DIBBLE示范了如何开始写一个图像处理程序基础,但是程序最后还是没有全部完成,我们总是会想到还有些功能没有加进去里头。但是很可惜的是,我们现在得停止继续研究这些东西,而往下讨论别的东西了。