显示位图似乎相当简单。在应用程序的资源中添加一张漂亮的位图,使用函数 LoadBitmap 将位图装 入内存,然后将位图选入设备描述表,再使用函数 BitBlt 便可显示出位图。但是这样显示的位图和目标环境或许有所不同。比如在256色的环境里,即使你载入的是256色位图,也会按屏幕颜色(一般为24位色)画出,那应该怎么按位图原来的颜色画出呢?
要显示位图,首先必须了解 Windows 中位图的概念。位图有两种基本格式:设备相关位图(DDB) 和设备无关位图(DIB)。在设备相关位图中,只有大小信息和位图数据,没有位图的设备分辨率信息,也 没有位图的颜色信息。
在 Windows 3.x 中引入了一种新的位图格式:设备无关位图。这种格式的位图包含位图的分辨率和 颜色信息,资源编辑器创建的 .BMP 文件便是此种位图格式。
在编译应用程序时,开发环境将资源数据编联在应用程序末,位图以设备无关位图的格式编联,包含所 有颜色的调色板信息。但 Windows API 中的位图句柄(HBITMAP)所指的位图为设备相关位图,设备相关 位图只有在选入设备描述表后才能操作,设备描
述表为位图提供颜色、大小和分辨率信息。
当调用 API 函数 LoadBitmap 时,Windows 将设备无关位图装入内存,并自动转换成 4 位(16 色)的设备相关位图,所以会有颜色信息的丢失。在本节的例子中将调用更底层的函数,自己将位图资源 装入内存,并建立指针以便应用程序可以存取,接着调
用 GDI 函数将设备无关的位图转换成设备相关的 位图,以便于显示。使用此方法,便
可保留所有需要的颜色信息。
本文作者特别提示:
在高版本的 Visual C++ 中,如果你的屏幕颜色设置大于 256 色,则生成的应用程序使用函数 LoadBitmap 可以显示大于 16 色以上的位图。
在本节的例子完成以后,如果例子程序中两个菜单项的显示没有区别,你可以试着将你的屏幕的颜色 设为 256 色试一试。
步骤
按照下列步骤实现一个例子程序。运行此例子程序,将显示出一个有菜单的简单窗口,从菜单 File 中 选择菜单项 LoadBitmap,例子程序便调用 API 函数 LoadBitmap 装入位图资源,接着将其选入设备描 述表并显示在窗口中。
从菜单 File 中选择菜单项 LoadResource,例子程序将调用更底层的 API 函数来装入位图资源并 将其转换成设备相关位图。你可以比较一下这两种位图显示的效果。注意,如果例子程序是运行在单色显示 器的系统上,或显示器的驱动程序只支持 16 色,那么这两个菜单项的显示将没有任何区别。
实现例子程序的具体步骤如下:
1.在 Visual C++ 中,创建一个新的工程,选择工程类型为 Win32 Application。
2.进入 App Studio,为刚才新建的工程插入资源文件。
3.在资源中添加一个主菜单。在主菜单中添加一个下拉菜单,命名菜单为 &File。在 File 菜单中添 加三个菜单项,分别命名三个菜单项为 Use &LoadBitmap、Use &DIB Functions 和 E&xit,菜单项 ID 对应分别为 ID_USELOADBITMAP、ID_USEDIBFUNCS 以及 ID_EXIT。在菜单项 Exit 与 Use DIB Functions 之间添加一条分隔线。
4.在资源中插入一幅 256 色的位图文件,要确保位图不是用 RLE(run length encoding) 压缩算 法保存的,因为有的显示驱动程序不支持此种压缩算法。
5.在工程中插入 C++ 源文件,命名文件为 256Color.cpp。
6.在文件 256Color.cpp 中键入下列代码。代码包含了本例子程序所需要的 Windows 头文件以及 包含资源标识符定义的文件 resource.h,另外定义了预处理符号 STRICT,用来表示对 Windows 应用 程序中使用的窗口句柄、菜单句柄和位图句柄进行严格的类型检查。
#define STRICT #include <windows.h> #include <windowsx.h> #include "resource.h"
7.在同一源文件中添加下列定义。MainWindowClassName 定义了此例子程序使用的窗口类的名字, 其它的句柄和指针是全局变量,用在整个例子程序中,初始化为零或 NULL 是为了避免以后出现错误。
char * MainWindowClassName="256ColorTest"; HBITMAP hDDBitmap=NULL; HPALETTE hPalette=NULL; BITMAPINFOHEADER * pInfoHeader=NULL; HINSTANCE hInstance=NULL;
8.在源文件中添加下列函数。函数 CreateDIBPalette 用来从设备无关位图中取出颜色信息,创建 逻辑调色板,创建和显示设备相关位图时使用此调色板。传送给函数的是 BITMAPINFOHEADER 结构的指 针,指向内存中的设备无关位图。此函数返回值是创建
的逻辑调色板的句柄,如果此函数失败,则句柄为 NULL。
HPALETTE CreateDIBPalette(BITMAPINFOHEADER * info) { LOGPALETTE * palPtr; RGBQUAD * colorTable; WORD i; DWORD numEntries; HPALETTE hPal; if(info->biClrUsed!=0) numEntries=info->biClrUsed; else numEntries=1 << info->biBitCount; palPtr=(NPLOGPALETTE)LocalAlloc(LMEM_FIXED, sizeof(LOGPALETTE)+numEntries*sizeof(PALETTEENTRY)); if(!palPtr) return NULL; palPtr->palVersion=0x300; palPtr->palNumEntries=(WORD)numEntries; colorTable=(RGBQUAD *)((LPSTR)info+(WORD)info->biSize); for(i=0;i<numEntries;i++) { palPtr->palPalEntry[i].peRed=colorTable[i].rgbRed; palPtr->palPalEntry[i].peGreen=colorTable[i].rgbGreen; palPtr->palPalEntry[i].peBlue=colorTable[i].rgbBlue; palPtr->palPalEntry[i].peFlags=0; } hPal=CreatePalette(palPtr); LocalFree((HLOCAL)palPtr); return hPal; }
9.在源文件中添加下列代码。函数 ConvertDIBToDDB 用来把设备无关位图转换成设备相关位图,而 设备相关位图可以被快速地显示在屏幕上。此函数的实现方法为:首先建立一个内存设备描述表,同显示位 图的屏幕相兼容,接着在设备描述表中创建设备相关位图,大小要同设备无关位图的大小一样,在设备描述 表中调入前面刚创建的逻辑调色板,并实现调色板,最后,使用函数 etDIBits 拷贝位图,按要求转换颜 色。DIB_RGB_COLORS 表示设备无关位图中的颜色表的颜色是 RGB 值,而不是系统调色板的索引。
HBITMAP ConvertDIBToDDB(HWND hWnd,BITMAPINFOHEADER * info, HPALETTE * hPalette) { HDC hDC,hMemDC; HPALETTE hOldPalette; HBITMAP hOldBitmap,hDDBitmap; DWORD numEntries; hDC=GetDC(hWnd); if((*hPalette=CreateDIBPalette(info))!=NULL) { hMemDC=CreateCompatibleDC(hDC); hOldPalette=SelectPalette(hMemDC,* hPalette,FALSE); RealizePalette(hMemDC); hDDBitmap=CreateCompatibleBitmap(hDC, info->biWidth,info->biHeight); hOldBitmap=(HBITMAP)SelectObject(hMemDC,hDDBitmap); if(info->biClrUsed!=0) numEntries=info->biClrUsed; else numEntries=1<<info->biBitCount; SetDIBits(hMemDC,hDDBitmap,0,info->biHeight, (LPSTR)info+(info->biSize+ (numEntries *sizeof(RGBQUAD))), (BITMAPINFO *)info,DIB_RGB_COLORS); SelectObject(hMemDC,hOldBitmap); SelectPalette(hMemDC,hOldPalette,FALSE); DeleteDC(hMemDC); } ReleaseDC(hWnd,hDC); return hDDBitmap; }
10.在同一源文件中添加函数 LoadBitmapResource。此函数联合使用函数 FindResource、LoadResource 和 LockResource 来将需要的位图资源装入内存,返回一个 BITMAPINFOHEADER 结构的指针,指向内存中的设备无关位图的开始处。
使用这些函数应该注意两件事情:第一件事情是资源是只读的。尽管函数 LoadResource 返回一个 指向资源的指针,但任何修改资源的企图都会引起保护错误,要修改位图需要创建一个拷贝,并且只能修 改拷贝。
应该注意的第二件事情是 Windows 9x 和 Windows NT 跟踪应用程序使用的资源,当应用程序结 束后,Windows 负责进行释放,所以应用程序可以不考虑资源的释放。
BITMAPINFOHEADER * LoadBitmapResource(HINSTANCE hInstance,WORD resId) { HRSRC hResource; HGLOBAL hDib; if(((hResource=FindResource(hInstance, MAKEINTRESOURCE(resId),RT_BITMAP))!=NULL)&& ((hDib=LoadResource(hInstance,hResource))!=NULL)) return (BITMAPINFOHEADER *)LockResource(hDib); return NULL; }
11.在源文件中添加函数 Paint。每当 Windows 操作系统发送消息 WM_PAINT 给例子程序窗口 时,此函数将被调用,这时,位图已经转换成为内存中的设备相关位图和调色板,所以可以相当迅速地将 位图显示在幕上,将调色板选入当前屏幕的设备描述表并实现调色板,以便显示正确的图象颜色,接着 将位图选入内存设备描述表,调用 API 函数 BitBlt 将位图显示在屏幕上。
void Paint(HWND hWnd,HPALETTE hPalette,HBITMAP hBitmap, int BitmapWidth,int BitmapHeight) { PAINTSTRUCT ps; HDC hDC,hMemDC; HPALETTE hOldPalette; HBITMAP hOldBitmap; hDC=BeginPaint(hWnd,&ps); if(hPalette) { hOldPalette=SelectPalette(hDC,hPalette,FALSE); RealizePalette(hDC); } if((hMemDC=CreateCompatibleDC(hDC))!=NULL) { hOldBitmap=(HBITMAP)SelectObject(hMemDC,hBitmap); BitBlt(hDC,0,0,BitmapWidth,BitmapHeight, hMemDC,0,0,SRCCOPY); SelectObject(hMemDC,hOldBitmap); DeleteDC(hMemDC); } if(hPalette) SelectPalette(hDC,hOldPalette,FALSE); EndPaint(hWnd,&ps); }
12.现在已经键入了此例子程序的大部分代码,在下面几步中所添加的函数构成了本例子程序的框架。 下面的函数 MainWndProc 是一个回调函数,用来处理所有发送给例子程序窗口的消息。在本例子程序 中,需要响应三个菜单项:ID_USELOADBITMAP 对应菜单项 LoadBitmap,ID_USEDIBFUNCS 对应菜 单项 LoadResource,ID_EXIT 对应菜单项 Exit。当用户选择了菜单项 LoadResource 后,此函数 将调用前面刚创建的函数,装入位图,创建调色板,并将设备无关位图转换成设备相关位图,接着调用 InvalidateRect 刷新窗口,从而显示出新装入的位图。
当用户从菜单中选择了菜单项 LoadBitmap 后,例子程序将丢弃前面已装入的位图,调用 API 函 数 LoadBitmap 从资源中装入位图,Windows 将对位图中的颜色进行转换,生成 16 色设备相关位 图。
LPARAM CALLBACK MainWndProc(HWND hWnd,UINT message, WPARAM wParam,LPARAM lParam) { switch(message) { case WM_COMMAND: switch(wParam) { case ID_USELOADBITMAP: if((hDDBitmap)&&(!pInfoHeader)) break; if(pInfoHeader) pInfoHeader=NULL; if(hDDBitmap) { DeleteObject(hDDBitmap); hDDBitmap=NULL; } if(hPalette) { DeleteObject(hPalette); hPalette=NULL; } if((hDDBitmap=LoadBitmap(hInstance, MAKEINTRESOURCE(IDB_BITMAP1)))!=NULL) InvalidateRect(hWnd,NULL,FALSE); break; case ID_USEDIBFUNCS: if(pInfoHeader) break; if(hDDBitmap) { DeleteObject(hDDBitmap); hDDBitmap=NULL; } if((pInfoHeader=LoadBitmapResource(hInstance, IDB_BITMAP1))!=NULL) { if((hDDBitmap=ConvertDIBToDDB(hWnd, pInfoHeader,&hPalette))!=NULL) InvalidateRect(hWnd,NULL,FALSE); else MessageBox(hWnd,"Unable to create DDB", NULL,MB_OK|MB_ICONSTOP); } else MessageBox(hWnd,"Unable to create DIB", NULL,MB_OK|MB_ICONSTOP); break; case ID_EXIT: DestroyWindow(hWnd); break; default: return (DefWindowProc(hWnd,message,wParam,lParam)); } case WM_PALETTECHANGED: if(((HWND)wParam!=hWnd)&&(hDDBitmap!=NULL)) InvalidateRect(hWnd,NULL,TRUE); break; case WM_PAINT: if(hDDBitmap!=NULL) { if(pInfoHeader) Paint(hWnd,hPalette,hDDBitmap, pInfoHeader->biWidth,pInfoHeader->biHeight); else Paint(hWnd,hPalette,hDDBitmap,640,480); // 此处的 640 与 480 可换为你插入的图片的实际大小 } else return DefWindowProc(hWnd,message,wParam,lParam); break; case WM_DESTROY: if(hDDBitmap) DeleteObject(hDDBitmap); if(hPalette) DeleteObject(hPalette); PostQuitMessage(0); break; default: return DefWindowProc(hWnd,message,wParam,lParam); } return 0; }
13.在源文件中添加函数 InitApplication。例子程序第一个实例运行时调用此函数,接着只有在窗口 类不再存在时才调用此函数,此函数为例子程序的主窗口初始化并登记窗口类。
BOOL InitApplication(HINSTANCE hInst) { WNDCLASS WndClass; WndClass.style = 0; WndClass.lpfnWndProc = MainWndProc; WndClass.cbClsExtra = 0; WndClass.cbWndExtra = 0; WndClass.hInstance = hInst; WndClass.hIcon = LoadIcon(NULL,IDI_WINLOGO); WndClass.hCursor = LoadCursor(NULL,IDC_ARROW); WndClass.hbrBackground = (HBRUSH)(COLOR_WINDOW+1); WndClass.lpszMenuName = MAKEINTRESOURCE(IDR_MENU1); WndClass.lpszClassName = MainWindowClassName; return RegisterClass(&WndClass); }
14.添加函数 initInstance。例子程序的每个实例运行都将调用此函数,用来创建并显示此例子程序 的主窗口。
BOOL InitInstance(HINSTANCE hInst,int nCmdShow) { hInstance = hInst; HWND hwnd = CreateWindow(MainWindowClassName, "256ColorTest", WS_OVERLAPPEDWINDOW,CW_USEDEFAULT, CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT, NULL,NULL,hInst,NULL); if(hwnd == NULL) return FALSE; ShowWindow(hwnd,nCmdShow); UpdateWindow(hwnd); return TRUE; }
15.此例子程序的最后一段代码是函数 WinMain。此函数是 Windows 操作系统上的应用程序的入口 点,完成窗口类和窗口的初始化,接着循环处理消息直到例子程序结束。
int WINAPI WinMain(HINSTANCE hInst,HINSTANCE hPrevInstance, LPSTR lpszCmdParam,int nCmdShow) { MSG Msg; if(!FindWindow(MainWindowClassName,NULL)) if(!InitApplication(hInst)) return FALSE; if(! InitInstance(hInst,nCmdShow)) return FALSE; while(GetMessage(&Msg,NULL,0,0)) { TranslateMessage(&Msg); DispatchMessage(&Msg); } return Msg.wParam; }
16.编译并运行此例子程序。
在 Windows 中显示 256 色的位图决不是一件简单的任务。本节的例子程序只适合显示静态的位 图,显示动画则效果不是很好,因为大量时间花费在逻辑调色板向物理调色板的转换上,以及将设备无 关位图中 RGB 值转换到设备相关位图中的调色板中,所以使处理速度非常缓慢。其他处理调色板的方 法将在以后的章节中讨论。