第十五章 与设备无关的位图(DIB 和 DDB 的结合2)

来源:互联网 发布:田径女神走红网络 编辑:程序博客网 时间:2024/05/16 04:35

与SetDIBits函数相似的函数是GetDIBits,您可以使用此函数把DDB转化为DIB:

int WINAPI GetDIBits (

hdc, // device context handle

hBitmap, // bitmap handle

yScan, // first scan line to convert

cyScans, // number of scan lines to convert

pBits, // pointer to pixel bits (out)

pInfo, // pointer to DIB information (out)

fClrUse) ; // color use flag

然而,此函数产生的恐怕不是SetDIBits的反运算结果。在一般情况下,如果使用CreateDIBitmap和SetDIBits将DIB转 换为DDB,然后使用GetDIBits把DDB转换回DIB,您就不会得到原来的图像。这是因为在DIB被转换为设备相关的格式时,有一些信息遗失了。 遗失的信息数量取决于进行转换时Windows所执行的显示模式。

您可能会发现没有使用GetDIBits的必要性。考虑一下:在什么环境下您的程序发现自身带有位图句柄,但没有用于在起始的位置建立位图的数据? 剪贴簿?但是剪贴簿为DIB提供了自动的转换。GetDIBits函数的一个例子是在捕捉屏幕显示内容的情况下,例如第十四章中BLOWUP程序所做的。 我不示范这个函数,但在Microsoft网站的Knowledge Base文章Q80080中有一些信息。

DIB区块

我希望您已经对设备相关和设备无关位图的区别有了清晰的概念。DIB能拥有几种色彩组织中的一种,DDB必须是单色的或是与真实输出设备相同的格 式。DIB是一个文件或内存块;DDB是GDI位图对象并由位图句柄表示。DIB能被显示或转换为DDB并转换回DIB,但是这里包含了设备无关位和设备 相关位之间的转换程序。

现在您将遇到一个函数,它打破了这些规则。该函数在32位Windows版本中发表,称为CreateDIBSection,语法为:

hBitmap = CreateDIBSection (

hdc, // device context handle

pInfo, // pointer to DIB information

fClrUse, // color use flag

ppBits, // pointer to pointer variable

hSection, // file-mapping object handle

dwOffset) ; // offset to bits in file-mapping object

CreateDIBSection是Windows API中最重要的函数之一(至少在使用位图时),然而您会发现它很深奥并难以理解。

让我们从它的名称开始,我们知道DIB是什么,但「DIB section」到底是什么呢?当您第一次检查CreateDIBSection时,可能会寻找该函数与DIB区块工作的方式。这是正确的,CreateDIBSection所做的就是建立了DIB的一部分(位图图素位的内存块)。

现在我们看一下传回值,它是GDI位图对象的句柄,这个传回值可能是该函数呼叫最会拐人的部分。传回值似乎暗示着CreateDIBSection 在功能上与CreateDIBitmap相同。事实上,它只是相似但完全不同。实际上,从CreateDIBSection传回的位图句柄与我们在本章和 上一章遇到的所有位图建立函数传回的位图句柄在本质上不同。

一旦理解了CreateDIBSection的真实特性,您可能觉得奇怪为什么不把传回值定义得有所区别。您也可能得出结论: CreateDIBSection应该称之为CreateDIBitmap,并且如同我前面所指出的CreateDIBitmap应该称之为 CreateDDBitmap。

首先让我们检查一下如何简化CreateDIBSection,并正确地使用它。首先,把最后两个参数hSection和dwOffset,分别设定为NULL和0,我将在本章最后讨论这些参数的用法。第二,仅在fColorUse参数设定为DIB_ PAL_COLORS时,才使用hdc参数,如果fColorUse为DIB_RGB_COLORS(或0),hdc将被忽略(这与CreateDIBitmap不同,hdc参数用于取得与DDB兼容的设备的色彩格式)。

因此,CreateDIBSection最简单的形式仅需要第二和第四个参数。第二个参数是指向BITMAPINFO结构的指针,我们以前曾使用过。我希望指向第四个参数的指标定义的指标不会使您困惑,它实际上很简单。

假设要建立每图素24位的384×256位DIB,24位格式不需要色彩对照表,因此它是最简单的,所以我们可以为BITMAPINFO参数使用BITMAPINFOHEADER结构。

您需要定义三个变量:BITMAPINFOHEADER结构、BYTE指针和位图句柄:

BITMAPINFOHEADER   bmih ;

BYTE * pBits ;

HBITMAP hBitmap ;

现在初始化BITMAPINFOHEADER结构的字段

bmih->biSize              = sizeof (BITMAPINFOHEADER) ;

bmih->biWidth = 384 ;

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结构的地址。这是常见的,但一个BYIE指针pBits的地址,就不常见了。这样,第四个参数是函数需要的指向指标的指标。

这是函数呼叫所做的:CreateDIBSection检查BITMAPINFOHEADER结构并配置足够的内存块来加载DIB图素位。(在这个 例子里,内存块的大小为384×256×3字节。)它在您提供的pBits参数中储存了指向此内存块的指针。函数传回位图句柄,正如我说的,它与 CreateDIBitmap和其它位图建立函数传回的句柄不一样。

然而,我们还没有做完,位图图素是未初始化的。如果正在读取DIB文件,可以简单地把pBits参数传递给ReadFile函数并读取它们。或者可以使用一些程序代码「人工」设定。

程序15-7 DIBSECT除了呼叫CreateDIBSection而不是CreateDIBitmap之外,与DIBCONV程序相似。

程序15-7 DIBSECT
        
DIBSECT.C

/*----------------------------------------------------------------------------

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 (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, 0, 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 = 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, &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 bitmap, 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 (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(摘录)

//Microsoft Developer Studio generated resource script.

#include "resource.h"

#include "afxres.h"

/////////////////////////////////////////////////////////////////////////////

// Menu

DIBSECT MENU DISCARDABLE

BEGIN

POPUP "&File"

BEGIN

MENUITEM "&Open", IDM_FILE_OPEN

END

END
RESOURCE.H(摘录)

// Microsoft Developer Studio generated include file.

// Used by 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位。当应用程序透过API传递海量存储器块时,只要系统拥有这些内存块,在WINDOWS NT下就不会影响速度。

我之前曾说过,当在视讯显示器上显示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会转化为特定的色彩格式,通常是在用BitBlt或 StretchBlt显示位图时。

您也可以将位图句柄选入内存设备内容并使用GDI函数来绘制。在 pBits 变量指向的DIB图素内将反映出结果。因为Windows NT下的GDI函数分批呼叫,在内存设备背景上绘制之后和「人为」的存取位之前会呼叫GdiFlush。

在DIBSECT,我们清除pBits变量,因为程序不再需要这个变量了。您会使用CreateDIBSection的主要原因在于您有需要直接更改位值。在CreateDIBSection呼叫之后似乎就没有别的方法来取得位指针了。

DIB区块的其它区别

从CreateDIBitmap传回的位图句柄与函数的hdc参数引用的设备有相同的平面和图素字节织。您能通过具有BITMAP结构的GetObject呼叫来检验这一点。

CreateDIBSection就不同了。如果以该函数传回的位图句柄的BITMAP结构呼叫GetObject,您会发现位图具有的色彩组织与BITMAPINFOHEADER结构的字段指出的色彩组织相同。您能将这个句柄选入与视讯显示器兼容的内存设备内容。这与 上一章关于DDB的内容相矛盾,但这也就是我说此DIB区块位图句柄不同的原因。

另一个奇妙之处是:你可能还记得,DIB中图素数据行的位组长度始终是4的倍数。GDI位图对象中行的位组长度,就是使用GetObject从 BITMAP结构的bmWidthBytes字段中得到的长度,始终是2的倍数。如果用每图素24位和宽度2图素设定BITMAPINFOHEADER结 构并随后呼叫GetObject,您就会发现bmWidthBytes字段是8而不是6。

使用从CreateDIBSection传回的位图句柄,也可以使用DIBSECTION结构呼叫GetObject:

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

此函数不能处理其它位图建立函数传回的位图句柄。DIBSECTION结构定义如下:

typedef struct tagDIBSECTION  // ds

{

BITMAP dsBm ; // BITMAP structure

BITMAPINFOHEADER dsBmih ; // DIB information header

DWORD dsBitfields [3] ; // color masks

HANDLE dshSection ; // file-mapping object handle

DWORD dsOffset ; // offset to bitmap bits

}

DIBSECTION, * PDIBSECTION ;

此结构包含BITMAP结构和BITMAPINFOHEADER结构。最后两个字段是传递给CreateDIBSection的最后两个参数,等一下将会讨论它们。

DIBSECTION结构中包含除了色彩对照表以外有关位图的许多内容。当把DIB区块位图句柄选入内存设备内容时,可以通过呼叫GetDIBColorTable来得到色彩对照表:

hdcMem = CreateCompatibleDC (NULL) ;

SelectObject (hdcMem, hBitmap) ;

GetDIBColorTable (hdcMem, uFirstIndex, uNumEntries, &rgb) ;

DeleteDC (hdcMem) ;

同样,您可以通过呼叫SetDIBColorTable来设定色彩对照表中的项目。

文件映像选项

我们还没有讨论CreateDIBSection的最后两个参数,它们是文件映像对象的句柄和文件中位图位开始的偏移量。文件映像对象使您能够像文件位于内存中一样处理文件。也就是说,可以通过使用内存指针来存取文件,但文件不需要整个加载内存中。

在大型DIB的情况下,此技术对于减少内存需求是很有帮助的。DIB图素位能够储存在磁盘上,但仍然可以当作位于内存中一样进行存取,虽然会影响程序执行效能。问题是,当图素位实际上储存在磁盘上时,它们不可能是实际DIB文件的一部分。它们必须位于其它的文件内。

为了展示这个程序,下面显示的函数除了不把图素位读入内存以外,与DIBSECT中建立DIB区块的函数很相似。然而,它提供了文件映像对象和传递给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, 0, 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 = 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, &pBits,hFileMap, bmfh.bfOffBits) ;

free (pbmi) ;

return hBitmap ;

}

啊哈!这个程序不会动。CreateDIBSection的文件指出「dwOffset [函数的最后一个参数]必须是DWORD大小的倍数」。尽管信息表头的大小始终是4的倍数并且色彩对照表的大小也始终是4的倍数,但位图文件表头却不是,它是14字节。因此bmfh.bfOffBits永远不会是4的倍数。

总结

如果您有小型的DIB并且需要频繁地操作图素位,您可以使用SetDIBitsToDevice和StretchDIBits来显示它们。然而,对于大型的DIB,此技术会遇到显示效能的问题,尤其在8位视讯显示器上和Windows NT环境下。

您可以使用CreateDIBitmap和SetDIBits把DIB转化为DDB。现在,显示位图可以使用快速的BitBlt和StretchBlt函数来进行了。然而,您不能直接存取这些与设备无关的图素位。

CreateDIBSection是一个很好的折衷方案。在Windows NT下通过BitBlt和StretchBlt使用位图句柄比使用SetDIBitsToDevice和StretchDIBits(但没有DDB的缺陷)会得到更好的效能。您仍然可以存取DIB图素位。

下一章,在讨论「Windows调色盘管理器」之后会进入位图的探索。

原创粉丝点击