15.3 DIB 和 DDB 的结合

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

        知道 DIB 的格式后,便可以用两个 DIB 绘制函数 SetDIBitsToDevice 和 StretchDIBits 做很多事情。你可以直接访问 DIB 中的每个位、每个字节和每个像素,而一旦有了一些函数可以让你有条理地查看和修改这些数据,你能做的事情就没有什么限制了。

        但实际上,我们发现还是有一些限制的。在第 14 章中,我们看到了你可以用 GDI 函数在 DDB 上绘图。而到目前为止,好像还没有什么办法能让我们对 DIB 做同样的事情。还有一些问题 SetDIBitsToDevice 和 StretchDIBits 的速度无法和 BItBlt  和 StretchDIBits 相提并论,特别是在 Windows NT 环境下,在有许多最相近颜色的查找需要执行的时候。例如要把一个 24 位的 DIB 显示在 8 位显示适配器上的时候。

        所以,在 DIB 和 DDB 之间进行相互转换有时还是有好处的。例如,我们有一个 DIB 要在屏幕上显示很多次,那么把这个 DIB 转换成 DDB,以便可以使用更快的 BitBlt 和 StretchBlt,这种做法就很有意义了。

15.3.1  从  DIB 创建 DDB

        有没有可能用一个 DIB 来创建一个 GDI 位图对象呢?我们其实已经知道怎么做了:如果你有一个 DIB,便可以用 CreateCompatibleBitmap 来创建一个和显示硬件兼容的同样大小的 GDI 位图对象。然后可以把这个位图对象选入一个内存设备环境中,并调用 SetDIBitsToDevice 函数在这个内存设备环境上绘图。其结果就是一个 DDB,拥有和 DIB 同样的图像,但是其颜色组织和你的显示硬件兼容。

        或者也可以用 CreateDIBitmap 分几步完成同样的事情。这个函数有以下的语法:

hBitmap = CreateDIBitmap (
              hdc,         // 设备环境句柄
              pInfoHdr,    // 指向 DIB 信息头的指针
              pInit,       // 0 或 CBM_INIT
              pBits,       // DIB 像素位的指针
              pInfo,       // DIB 信息指针
              fClurUse,    // 颜色使用标志位
注意 pInfoHdr 和 pInfo 这两个参数,它们分别指向 BITMAPINFOHEADER 和 BITMAPINFO 结构的指针。我们知道,BITMAPINFO 结构就是 BITMAPINFOHEADER 结构后面跟一个颜色表。我们很快就会看到这点区别是怎么起作用的。最后一个参数要么是 DIB_RGB_COLORS(等于 0),要么是 DIB_PAL_COLORS,和 SetDIBitsToDevice 函数中的一样。相关详情将在第 16 章进一步介绍。

        在了解一系列 Windows 的位图函数之后,我们要意识到非常重要的一点是和它的名称不同,CreateDIBitmap 函数并没有创建一个设备无关位图。它是从一个设备无关位图中创建一个设备相关位图。注意,这个函数返回的是一个 GDI 位图对象的句柄,和 CreateBitmap、CreateBitmapIndirect 和 CreateCampatibleBitmap 一样。

        最简单的调用 CreateDIBitmap 函数的方式就像下面这样:

hBitmap = CreateDIBitmap (NULL, pbmih, 0, NULL, NULL, 0);
唯一的一个参数就是一个 BITMAPINFOHEADER 结构的指针(没有颜色表)。 这种形式的调用会创建一个单色 GDI 位图对象。第二个简单调用此函数的方式如下:

hBitmap = CreateDIBitmap (hdc, pbmih, 0, NULL, NULL, 0);
这个方式中,hdc 参数代表了一个设备环境,这个函数会创建一个和它兼容的 DDB。到目前为止,我们还没有做任何 CreateBitmap(创建一个单色位图)和 CreateCompatibleBitmap(创建一个和显示系统兼容的位图)不能做的事情。

        在上面两种最简单的调用 CreateDIBitmap 的方式中,像素位没有被初始化。如果第三个参数是 CBM_INIT,Windows 会用最后三个参数来初始化 DDB 中的像素位。pInfo 参数是一个指向包括颜色表的 BITMAPINFO 结构的指针。pBits 参数是一个指向像素位数组的指针,像素的颜色格式由 BITMAPINFO 结构决定。根据颜色表,这些像素位被转换成现实设备的颜色格式。这和 SetDIBitsToDevice 函数所做的事情是一样的。实际上,整个 CreateDIBitmap 函数大致可以用下面的代码实现:

HBITMAP CreateDIBitmap (HDC hdc, CONST BITMAPINFOHEADER * bpmih,
                        DWORD fInit, CONST VOID * pBits,
                        CONST BITMAPINFO * pbmi, UINT fUsage)
{
    HBITMAP  hBitmap;
    int      cx, cy, iBitCount;

    if (pbmih->biSize == sizeof (BITMAPINFOHEADER))
    {
        cx        = ((PBITMAPCOREHEADER) pbmih)->bcWidth;
        cy        = ((PBITMAPCOREHEADER) pbmih)->bcHeight;
        iBitCount = ((PBITMAPCOREHEADER) pbmih)->bcBitCount;
    }
    else
    {  
        cx        = pbmih->biWidth;
        cy        = pbmih->biHeight;
        iBitCount = pbmih->biBitCount;
    }

    if (hdc)
        hBitmap = CreateCompatibleBitmap(hdc, cx, cy);
    else
        hBitmap = CreateBitmap(cx, cy, 1, 1, NULL);

    if (fInit == CBM_INIT)
    {
        hdcMem = CreateCompatibleDC(hdc);
        SelectObject (hdcMem, hBitmap);
        SetDIBitsToDevice (hdcMem, 0, 0, cx, cy, 0, 0, 0, cy,
                           pBits, pbmi, fUsage);
        DeleteDC (hdcMem);
    }
    return hBitmap;
}

        如果值需要显示一个 DIB 一次,而你担心 SetDIBitsToDevice 的速度,那么调用 CreateDIBitmap 然后用 BitBlt 或 StretchBlt 来显示 DDB 其实并没有什么意义。这两种方式会用同等的时间,因为 SetDIBitsToDevice 和 CreateDIBitmap 都需要进行颜色转换。只有在需要多次显示一个 DIB 时——处理 WM_PAINT 消息时这是非常普遍的——做这种转换才有意义

        如下所示的 DIBCONV 程序显示了你可以如何用 SetDIBitsToDevice 来把一个 DIB 文件转换成 DDB。

/*-------------------------------------------------
	DIBCONV.C -- Converts a DIB to a DDB
		    (c) Charles Petzold, 1998
--------------------------------------------------*/

#include <windows.h>
#include <commdlg.h>
#include "resource.h"

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

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

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("DIB to DDB Conversion"),
		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;
}

HBITMAP CreateBitmapObjectFromDibFile(HDC hdc, PTSTR szFileName)
{
	BITMAPFILEHEADER  * pbmfh;
	BOOL				bSuccess;
	DWORD				dwFileSize, dwHighSize, dwBytesRead;
	HANDLE				hFile;
	HBITMAP				hBitmap;

		// 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 whole file
	
	dwFileSize = GetFileSize(hFile, &dwHighSize);

	if (dwHighSize)
	{
		CloseHandle(hFile);
		return NULL;
	}

	pbmfh = (BITMAPFILEHEADER *)malloc(dwFileSize);

	if (!pbmfh)
	{
		CloseHandle(hFile);
		return NULL;
	}

	bSuccess = ReadFile(hFile, pbmfh, dwFileSize, &dwBytesRead, NULL);
	CloseHandle(hFile);

		// Verify the file

	if (!bSuccess || (dwBytesRead != dwFileSize)
				  || (pbmfh->bfType != *(WORD *) "BM")
				  || (pbmfh->bfSize != dwFileSize))
	{
		free(pbmfh);
		return NULL;
	}
		
		// Create the DDB

	hBitmap = CreateDIBitmap(hdc,
							(BITMAPINFOHEADER *) (pbmfh + 1),
							CBM_INIT,
							(BYTE *) pbmfh + pbmfh->bfOffBits,
							(BITMAPINFO *) (pbmfh + 1),
							DIB_RGB_COLORS);
	free(pbmfh);

	return hBitmap;
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	static HBITMAP		hBitmap;
	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");
	BITMAP				bitmap;
	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 DIB, delete it

			if (hBitmap)
			{
				DeleteObject(hBitmap);
				hBitmap = NULL;
			}

				// Create the DDB from the DIB

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

			hdc = GetDC(hwnd);
			hBitmap = CreateBitmapObjectFromDibFile(hdc, szFileName);
			ReleaseDC(hwnd, hdc);

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

				// Invalidate the client area for later update

			InvalidateRect(hwnd, NULL, TRUE);

			if (hBitmap == NULL)
			{
				MessageBox(hwnd, TEXT("Cannot load DIB file"),
					szAppName, MB_OK | MB_ICONEXCLAMATION);
			}
			return 0;
		}
		break;

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

		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_DESTROY:
		if (hBitmap)
			DeleteObject(hBitmap);

		PostQuitMessage(0);
		return 0;
	}
	return DefWindowProc(hwnd, message, wParam, lParam);
}
DIBCONV.RC  (excerpts)

// Microsoft Visual C++ generated resource script.
//
#include "resource.h"

/
//
// Menu
//

DIBCONV MENU
BEGIN
    POPUP "&File"
    BEGIN
        MENUITEM "&Open",                       IDM_FILE_OPEN
    END
END
RESOURCE.H (excerpts)

// Microsoft Visual C++ 生成的包含文件。
// 供 DibConv.rc 使用
//
#define IDM_FILE_OPEN                    40001

        DIBCONV.C 是一个完整的独立文件,不需要之前的其他一些文件。它只有一个菜单命令(File Open),收到这个命令后,WndProc 调用这个程序中的 CreateBitmapObjectFromDibFile 函数。这个函数把整个文件读入内存,并且把内存块的指针传给 CreateDIBitmap 函数。该函数返回一个位图的句柄。这时,包含 DIB 的内存块就可以释放了。在 WM_PAINT 消息的处理过程中,WndProc 把这个位图选入到一个兼容的内存设备环境中,然后用 BitBlt 而不是 SetDIBitsToDevice 来把位图显示在客户区。它用位图的句柄和 BITMAP 结构来调用 GetObject,以此来得到位图的宽度和高度。

        用 CreateDIBitmap 创建位图时,不需要初始化 DDB 像素位。你可以在以后调用 SetDIBits 来完成初始化。该函数的语法如下:

iLines = SetDIBits (
               hdc,              // 设备环境句柄
               hBitmap,          // 位图句柄
               yScan,            // 要转换的第一行扫描线
               cyScans,          // 要转换的扫描线的行数
               pBits,            // 指向像素位的指针
               pInfo,            // 指向 DIB 信息的指针
               fClurUse);        // 颜色使用标志位 
这个函数用 BITMAPINFO 结构中的颜色表来把像素位转换成设备相关的格式。设备环境句柄只有在最后一个参数是 DIB_PAL_COLORS 时才需要。

15.3.2  从 DDB 到 DIB

        和 SetDIBits 类似的一个函数是 GetDIBits。可以用这个函数来把一个 DDB 转换成 DIB:

int WINAPI GetDIBits (
               hdc,           // 设备环境句柄
               hBitmap,       // 位图句柄
               yScan,         // 要转换的第一行扫描线
               cyScans,       // 要转换的扫描线的行数
               pBits,         // 指向像素位的指针
               pInfo,         // 指向 DIB 信息的指针
               fClrUse);      // 颜色使用标志位

        然而,这个函数并不是 SetDIBits 的简单逆反而已。在一般情况下,如果用 CreateDIBitmap 和 SetDIBits 把一个 DIB 转换成 DDB,然后又用 GetDIBits 把它转换回 DIB,那么得到的并不会是原先的数据。因为在把 DIB 转换成设备相关的格式时,有些信息丢失了具体有多少信息丢失,则取决于转换时你的 Windows 的视频模式

        你可能很少需要用到 GetDIBits。想一下:在什么情况下你的程序有一个位图对象的句柄,但是却没有最初创建这个位图时的数据呢?剪贴板?但是剪贴板提供了自动转换成 DIB 的功能。不过在有一个情况下 GetDIBits 还是有些用处的,那就是当你截屏的时候,就像第 14 章的 BLOWUP 程序那样。我不准备演示这个功能,有关信息可以从知识库文章 Q80080 中得到

15.3.3  DIB 区块

        现在,我希望你已充分了解设备无关位图和设备相关位图的区别。一个 DIB 可以采用采用几种颜色组织方式中的一种;而一个 DDB 必须或者是黑白的,或者是实际输出设备一样的格式。一个 DIB 是一个文件或是一块内存区域;一个 DDB 则是一个 GDI 位图对象,以一个位图的句柄来表示。一个 DIB 可以被转换成 DDB 显示出来,并且可以转换回来,但这要涉及设备无关的像素位和设备相关的像素位之间的转换

        现在,你将要遇到一个看起来打破这些规则的函数。这个函数是在 32 位的 Windows 中引入的,名为 CreateDIBSection。它的语法如下:

hBitmap = CreateDIBSection (
                 hdc,              // 设备环境句柄
                 pInfo,            // 指向 DIB 信息的指针
                 fClrUse,          // 颜色使用标志位
                 ppBits,           // 指向指针的指针
                 hSection,         // 文件映射对象句柄
                 dwOffset);        // 文件映射对象中到像素位的位移
CreateDIBSection 是 Windows API 中最重要的函数之一 (嗯,最起码对那些经常与位图打交道的人来说是这样的)。但是,它又有着很多奇怪的地方,你会发现它不同寻常地深奥和难以理解。
        让我们从这个函数的名称开始说起吧。我们知道什么是 DIB,但“DIB 这块”(DIB Section)到底是什么东西?当你开始研究 CreateDIBSection 函数时,你可能一直在寻找这个函数只和 DIB 的某部分打交道的某种方式。那就差不多对了。 CreateDIBSection 做的就是创建一个 DIB 的部分——一块用来保存像素位的内存区域

        现在,让我们再来看看它的返回值。它是一个 GDI 位图对象的句柄。这个返回值可能是这个函数最迷惑人的地方了。它似乎暗示着 CreateDIBSection 和 CreateDIBitmap 的功能类似。的确,它们是类似,但是又完全不同。事实上,CreateDIBSection 返回的这个位图句柄和这一章与第 14 章中所有创建位图的函数返回的句柄在本质上就是不同的。

        理解 CreateDIBSection 的真正本质之后,你也许会问为什么这个返回值没有定义成其他的类型。之后也许会得出这样的结论:CreateDIBSection 才应该叫 CreateDIBitmap,而 CreateDIBitmap,就像我以前提到的,应该叫 CreateDDBitmap。

        为了和 CreateDIBSection 进行第一次接触,让我们看看可以怎样简化并马上用上它。首先,可以把最后两个参数 hSection 和 dwOffset 分别设置为 NULL 和 0。我会在本章将要结束时讨论这两个参数。其次,hdc 参数只有在 fClrUse 参数设置为 DIB_PAL_COLORS 时才可用。如果 fClrUse 是 DIB_RGB_COLORS(或是 0),hdc 将被忽略。(这和 CreateDIBitmap 的情况不同,hdc 在那里是用来取得与设备兼容的颜色格式的。)

        所以,在最简单的形式下,CreateDIBSection 只需要第二和第四个参数。第二个参数是一个 BITMAPINFO 结构的指针,我们以前已经用过了。我希望第四个参数指针的指针的定义不会让你感到不适。实际上用这个函数是非常简单的。

        假设你要创建一个 384 * 256 像素的 DIB,每一个像素位为 24 位。24 位格式是最简单的,因为它不需要颜色表,所以我们可以把 BITMAPINFOHEADER 结构用到 BITMAPINFO 参数上。

        定义三个变量:一个 BITMAPINFOHEADER 结构,一个 BYTE 指针和一个位图句柄:

BITMAPINFOHEADER bmih;
BYTE *           pBits;
HBITMAP          hBitmap;

        现在,初始化 BITMAPINFOHEADER 的各个字段:

bmih->biSize          = sizeof(BITMAPINFOHEADER);
bmih->biWidth         = 284;
bmih->biHeight        = 256;
bmih->biPlanes        = 1;
bmih->biBitCount      = 24;
bmih->biCompression   = BI_RGB;
bmih->biSizeImage     = 0;
bmih->biXPelsPerMeter = 0;
bmih->biYPelsPerMeter = 0;
bmih->biClrUsed       = 0;
bmih->biClrImportant  = 0

        经过最简单的准备工作,我们现在可以调用这个函数了:

hBitmap = CreateDIBSection (NULL, (BITMAPINFO *) &bmih, 0, &pBits, NULL, 0);
请注意,我们把 BITMAPINFOHEADER 结构的地址作为第二个参数,这是很平常的。但是我们还对一个 BYTE 的指针 pBits 取址,这就不寻常了。这样,第四个参数是一个指针的指针,正如这个函数要求的一样。

        下面是这个函数做的事情:CreateDIBSection 根据 BIMAPINFOHEADER 结构的数据分配一块内存区域,其大小足以存放下 DIB 的像素数据。(在我们这个例子里,这块内存的大小是 284 * 256 * 3 字节。)它把这块内存的指针存放在你提供的 pBits 参数里。这个函数还返回一个位图的句柄,正如我提到的,这个句柄和 CreateDIBitmap 以及其他创建位图的函数返回的句柄不一样。

        但是,我们还没有结束呢。这个位图的像素位是没有初始化的。如果你正在读入一个 DIB 文件。那么可以简单地把 pBits 参数直接传给 ReadFile 函数,以便把像素位读进来。或者,你也可以用一些代码“手工”地设置这些数据。

        如下所示的 DIBSECT 程序和 DIBCONV 程序很相似,只不过它调用的是 CreateDIBSection 函数,而不是 CreateDIBitmap。

/*----------------------------------------------------------
	DIBSECT.C -- Displays a DIB Section in the client area
		    (c) Charles Petzold, 1998
-----------------------------------------------------------*/

#include <windows.h>
#include <commdlg.h>
#include "resource.h"

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

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

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("DIB Section Display"),
		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;
}

HBITMAP CreateDibSectionFromDibFile(HDC hdc, PTSTR szFileName)
{
	BITMAPFILEHEADER    bmfh;
	BITMAPINFO		  * pbmi;
	BYTE			  * pBits;
	BOOL				bSuccess;
	DWORD				dwInfoSize, dwBytesRead;
	HANDLE				hFile;
	HBITMAP				hBitmap;

	// 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 BITMAPINFO structure & read it in

	dwInfoSize = bmfh.bfOffBits - sizeof(BITMAPFILEHEADER);

	pbmi = (BITMAPINFO *)malloc(dwInfoSize);

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

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

		// Create the DIB Section

	hBitmap = CreateDIBSection(NULL, pbmi, DIB_RGB_COLORS, (VOID **)&pBits, NULL, 0);

	if (hBitmap == NULL)
	{
		free(pbmi);
		CloseHandle(hFile);
		return NULL;
	}

		// Read in the bitmap bits

	ReadFile(hFile, pBits, bmfh.bfSize - bmfh.bfOffBits, &dwBytesRead, NULL);

	free(pbmi);
	CloseHandle(hFile);

	return hBitmap;
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	static HBITMAP		hBitmap;
	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");
	BITMAP				bitmap;
	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 DIB, delete it

			if (hBitmap)
			{
				DeleteObject(hBitmap);
				hBitmap = NULL;
			}

			// Create the DIB Section from the DIB file

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

			hBitmap = CreateDibSectionFromDibFile(NULL, szFileName);
			ShowCursor(FALSE);
			SetCursor(LoadCursor(NULL, IDC_ARROW));

			// Invalidate the client area for later update

			InvalidateRect(hwnd, NULL, TRUE);

			if (hBitmap == NULL)
			{
				MessageBox(hwnd, TEXT("Cannot load DIB file"),
					szAppName, MB_OK | MB_ICONEXCLAMATION);
			}
			return 0;
		}
		break;

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

		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_DESTROY:
		if (hBitmap)
			DeleteObject(hBitmap);

		PostQuitMessage(0);
		return 0;
	}
	return DefWindowProc(hwnd, message, wParam, lParam);
}
DIBSECT.RC (excerpts)

// Microsoft Visual C++ generated resource script.
//
#include "resource.h"

/
//
// Menu
//

DIBSECT MENU
BEGIN
    POPUP "&File"
    BEGIN
        MENUITEM "&Open",                       IDM_FILE_OPEN
    END
END
RESOURCE.H (excerpts)

// Microsoft Visual C++ 生成的包含文件。
// 供 DibSect.rc 使用
//
#define IDM_FILE_OPEN                    40001

        请注意 DIBCONV 中的 CreateBitmapObjectFromDibFile 函数和 DIBSECT 中的 CreateDibSectionFromDibFile 函数的区别。DIBCONV 是把整个文件一次性读入内存,然后把指向 DIB 内存块的指针传给 CreateDIBitmap 函数。DIBSECT 则是先把 BITMAPFILEHEADER 读入,然后决定 BITMAPINFO 结构的大小,为它分配内存,再用 ReadFile 把它读进来。接着,它又把 BITMAPINFO 的指针和 pBits 变量的指针传给 CreateDIBSection。这个函数返回一个位图句柄并把 pBits 设置成一块内存的指针,程序会把 DIB 的像素位读到这块内存中。

        pBits 指向的内存区域由系统管理。当你用 DeleteObject 删除这个位图时,这块内存就会被自动释放。但程序仍然可以用这个指针直接修改 DIB 数据。由系统拥有这块内存就可以避免在 Windows NT 中当应用程序通过 API 传送大内存块时会出现的速度下降。

        如前所述,在把一个 DIB 显示到屏幕上时,一定要经过从设备无关的像素到设备相关的像素的转换。有时,这种转换会很费时。让我们来看一下用于显示 DIB 的下面三种方式。

  • 在采用 SetDIBitsToDevice 或 StretchDIBits 直接在屏幕上显示 DIB 时,格式转换发送在 SetDIBitsToDevice 或 StretchDIBits 函数中。
  • 在采用 CreateDIBitmap 和(可能的)SetDIBits 来把 DIB 转换成 DDB,并用 BitBlt 或 StretchBlt 来显示它的时候,如果设置了 CBM_INIT 标志位,格式转换会发生在 CreateDIBitmap 函数中,否则转换发生在 SetDIBits 函数中。
  • 在采用 CreateDIBSection 创建 DIB 区块,然后用 BitBlt 或 StretchBlt 显示时,格式转换发生在 BitBlt 或 StretchBlt 函数中。

        请把上面的最后一句话再读一遍以确保你没有读错。这就是 CreateDIBSection 返回的句柄与其他位图句柄的不同之一。这个句柄实际上指向的是一个 DIB,该 DIB 存储在由系统维护的内存中,但是应用程序也可以访问。这个 DIB 在需要时被转换成某种颜色格式,这通常发生在用 BitBlt 或 StretchBlt 显示的时候。

        也可以把这个位图句柄选入一个内存设备环境中,然后用 GDI 函数在上面绘图,其结果将反映在由 pBits 变量指向的 DIB 像素位中。因为在 Windows NT 环境下,GDI 调用是批量处理的,因此在内存设备环境的绘图结束后,还需要调用 GdiFlush,然后才能“手工”地访问像素位

        在 DIBSECT 程序中,我们忽略掉了 pBits,因为程序不再需要它了。如果需要以后直接修改像素位(这是用到 CreateDIBSection 的一个重要原因),则应该继续保留它。在 CreateDIBSection 调用之后好像没有什么办法可以再次获得这个指针。

15.3.4  DIB 区块的更多不同点

        CreateDIBitmap返回的位图句柄与 hdc 参数所指定的设备具有同样的平面和每像素位数。可以通过用 BITMAP 结构调用 GetObject 来检验这一点。

        CreateDIBSection 返回的位图句柄则是不同的。如果对这个函数返回的句柄用 BITMAP 结构来调用 GetObject,你会发现相应位图的颜色组织方式和 BITMAPINFOHEADER 结构中指定的是一样的。但是你仍然可以把这个句柄选入一个和显示设备兼容的内存设备环境中。显然,这和我在第 14 章中讲到的 DDB 是矛盾的。而这也是为什么我认为这个 DIB 区块位图句柄与众不同的原因。

        另一个奇怪的地方是:正如你记得的,DIB 中每一行像素数据的字节长度总是4 的倍数而 GDI 位图对象的每一行的长度总是2 的倍数,这你可以由 GetObject 所用到的 BITMAP 结构的 bmWidthBytes 字段得到。现在如果你把 BITMAPINFOHEADER 结构设置成每像素 24 位,每一行两像素(举例来说),然后调用 GetObject,你会发现 bmWidthBytes 字段是 8 而不是 6。

        对于 CreateDIBSection 返回的位图句柄,还可以用一个 DIBSECTION 结构来调用 GetObject,如下所示:

GetObject (hBitmap, sizeof(DIBSECTION), &dibsection);
这种方式不能用在其他任何创建位图的函数返回的句柄上。DIBSECTION 结构定义如下:

typedef struct tagDIBSECTION    // ds
{
    BITMAP            dsBm;           // BITMAP 结构
    BITMAPINFOHEADER  dsBmih;         // DIB 信息头
    DWORD             dsBitfields[3]; // 颜色遮罩
    HANDLE            dshSection;     // 文件映射对象句柄
    DWORD             dsOffset;       // 像素位数据的位移
}
DIBSECTION, * PDIBSECTION;
这个结构同时包括了 BITMAP 结构和 BITMAPINFOHEADER 结构。最后两个字段就是传给 CreateDIBSection 的最后两个参数,我会很快讨论到。

        DIBSECTION 结构告诉了你很多有关位图的信息,但是不包括颜色表。在把 DIB 区块位图句柄选入一个内存设备环境之后,可以用 GetDIBColorTable 来获得颜色表:

hdcMem = CreateCompatibleDC(NULL);
SelectObject (hdcMem, hBitmap);
GetDIBColorTable (hdcMem, uFirstIndex, uNumEntries, &rgb);
DeleteDC (hdcMem);
类似地,还可以用 SetDIBColorTable 来设置颜色表中的数据。

15.3.5  文件映射选项

        我一直还没有讨论 CreateDIBSection 的最后两个参数。它们一个是文件映射对象(file-mapping object)的句柄,另一个是在这个文件中位图位的开始位置。文件映射对象让你可以把一个文件当作其已经在内存中一样也就是说你可以用内存指针来访问文件,但是文件并不需要完全放到内存中

        对于很大的 DIB 来说,这项技术可以减少内存的需求。DIB 像素位可以保存在磁盘上,但是却可以像它们在内存中一样访问,除了有一些速度上的减慢。问题是,尽管像素位确实可以保存在磁盘上,它们却不能是实际 DIB 文件的一部分。它们必须在另外的文件中

        为了说明这一点,下面这个函数和 DIBSECT 程序中的函数非常类似,但是它并不把像素位读入内存,而是给 CreateDIBSection 函数传递一个文件映射对象的句柄和一个位移量:

HBITMAP CreateDibSectionMappingFromFile(PTSTR szFileName)
{
    BITMAPFILEHEADER    bmfh;
    BITMAPINFO          * pbmi;
    BYTE              * pBits;
    BOOL                bSuccess;
    DWORD                dwInfoSize, dwBytesRead;
    HANDLE                hFile, hFileMap;
    HBITMAP                hBitmap;

    hFile = CreateFile(szFileName, GENERIC_READ | GENERIC_WRITE,
                        0,                    // No sharing!
                        NULL, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL);

    if (hFile == INVALID_HANDLE_VALUE)
        return NULL;

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

    if (!bSuccess || (dwBytesRead != sizeof(BITMAPFILEHEADER))
                  || (bmfh.bfType != *(WORD *) "BM"))
    {
        CloseHandle(hFile);
        return NULL;
    }
    
    dwInfoSize = bmfh.bfOffBits - sizeof(BITMAPFILEHEADER);

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

    if (!bSuccess || (dwBytesRead != dwInfoSize))
    {
        free(pbmi);
        CloseHandle(hFile);
        return NULL;
    }
    hFileMap = CreateFileMapping(hFile, NULL, PAGE_READWRITE, 0, 0, NULL);

    hBitmap = CreateDIBSection(NULL, pbmi, DIB_RGB_COLORS, (VOID **)&pBits,
                                hFileMap, bmfh.bfOffBits);
    free(pbmi);
    return hBitmap;
}

        唉,可惜这并不管用。CreateDIBSection 的文档指出:“dwOffset(最后一个参数)必须是 DWORD 大小的倍数。”虽然信息头的大小总是 4 的倍数,颜色表也是 4 的倍数,但位图文件的文件头却不是。它是 14 个字节。所以 bmfh.bfOffBits 永远不会是 4 的倍数。

        如果你有一些小的 DIB,并且需要经常对像素位进行操作,就可以用 SetDIBitsToDevice 和 StretchDIBits 来显示他们。但是,对于大的 DIB 来说,这个方法会有一些性能方面的问题,特别是对 Windows NT 环境下的 8 位视频显示而言。

        可以把一个 DIB 用 CreateDIBitmap 和 SetDIBits 转换成 DDB。如此一来,在显示位图时,就可以用速度更快的 BitBlt 和 StretchBlt 函数。但缺点是,不能再访问和设备无关的像素位。

        CreateDIBSection 是一个不错的折中方案。你可以对其句柄使用 BitBlt 和 StretchBlt,在 Windows NT 环境下,它的性能比 SetDIBitsToDevice 和 StretchDIBits 更优,并且摒弃了 DDB 的缺点。你仍然可以访问 DIB 像素位。

        在第 16 章,我将先介绍 Windows 调色板管理器,再完成对位图的探索。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值