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,&pBits,NULL,0) ;
注意,我们为第二个参数赋予BITMAPINFOHEADER结构的位址。这是常见的,但一个BYIE指标pBits的位址,就不常见了。这样,第四个参数是函式需要的指向指标的指标。
这是函式呼叫所做的:CreateDIBSection检查BITMAPINFOHEADER结构并配置足够的记忆体块来载入DIB图素位元。(在这个例子里,记忆体块的大小为384×256×3位元组。)它在您提供的pBits参数中储存了指向此记忆体块的指标。函式传回点阵图代号,正如我说的,它与CreateDIBitmap和其他点阵图建立函式传回的代号不一样。
然而,我们还没有做完,点阵图图素是未初始化的。如果正在读取DIB档案,可以简单地把pBits参数传递给ReadFile函式并读取它们。或者可以使用一些程式码「人工」设定。
有意思的是ShowDib函数,它依赖于菜单选择以四种不同的方式之一在程序的显示区域显示DIB。可以使用SetDIBitsToDevice从显示区域的左上角或在显示区域的中心显示DIB。程序也有两个使用StretchDIBits的选项,DIB能放大填充整个显示区域。在此情况下它可能会变形,或它能等比例显示,也就是说不会变形。
把DIB复制到剪贴簿包括:在整体共享内存中制作packed DIB内存块的副本。剪贴簿数据型态为CF_DIB。程序没有列出从剪贴簿复制DIB的方法,因为在仅有指向packed DIB的指标的情况下这样做需要更多步骤来确定图素位的偏移量。我将在下一章的末尾示范如何做到这点的方法。
附件中还有一个 SHOWDIB2prb.asm 程序,这个程序有一个BUG,就是当你选择显示模式之后,菜单中前面的小对号就会不翼而飞。请尝试调试吧!
色彩转换、调色盘和显示效能
记得在虎豹小霸王编剧William Goldman的另一出电影剧本《All the President's Men》中,Deep Throat告诉Bob Woodward揭开水门秘密的关键是「跟着钱走」。那么在位图显示中获得高级性能的关键就是「跟着图素位走」以及理解色彩转换发生的时机。DIB是设备无关的格式,视频显示器内存几乎总是与图素格式不同。在SetDIBitsToDevice或StretchDIBits函数呼叫期间,每个图素(可能有几百万个)必须从设备无关的格式转换成设备相关格式。
在许多情况下,这种转换是很繁琐的。例如,在24位视频显示器上显示24位DIB,显示驱动程序最多是切换红、绿、蓝的字节顺序而已。在24位设备上显示16位DIB就需要位的搬移和修剪了。在24位设备上显示4位或8位DIB要求在DIB色彩对照表内查找DIB图素位,然后对字节重新排列。
但是要在4位或8位视频显示器上显示16位、24位或32位DIB时,会发生什么事情呢?一种完全不一样的颜色转换发生了。对于DIB内的每个图素,设备驱动程序必须在图素和显示器上可用的颜色之间「找寻最接近的色彩」,这包括循环和计算。(GDI函数GetNearestColor进行「最接近色彩搜寻」。)
整个RGB色彩的三维数组可用立方体表示。曲线内任意两点之间的距离是:
在这里两个颜色是R1G1B1和R2G2B2。执行最接近色彩搜寻包括从一种颜色到其它颜色集合中找寻最短距离。幸运的是,在RGB颜色立方体中「比较」距离时,并不需要计算平方根部分。但是需转换的每个图素必须与设备的所有颜色相比较以发现最接近的颜色。这是个工作量相当大的工作。(尽管在8位设备上显示8位DIB也得进行最接近色彩搜寻,但它不必对每个图素都进行,它仅需对DIB色彩对照表中的每种颜色进行寻找。)
正是由于以上原因,应该避免使用SetDIBitsToDevice或StretchDIBits在8位视频显示卡上显示16位、24位或32位DIB。DIB应转换为8位DIB,或者8位DDB,以求得更好的显示效能。实际上,您可以经由将DIB转换为DDB并使用BitBlt和StretchBlt显示图像,来加快显示任何DIB的速度。
如果在8位视频显示器上执行Windows(或仅仅切换到8位模式来观察在显示True-ColorDIB时的效能变化),您可能会注意到另一个问题:DIB不会使用所有颜色来显示。任何在8位视频显示器上的DIB刚好限制在以20种颜色显示。如何获得多于20种颜色是「调色盘管理器」的任务,这将在下一章提到。
最后,如果在同一台机器上执行Windows 98和Windows NT,您可能会注意到:对于同样的显示模式,Windows NT显示大型DIB花费的时间较长。这是Windows NT的客户/服务器体系结构的结果,它使大量数据在传输给API函数时耗费更多时间。解决方法是将DIB转换为DDB。而我等一下将谈到的CreateDIBSection函数对这种情况特别有用。
DIB 和 DDB 的结合
您可以做许多事情去发掘DIB的格式,并呼叫两个DIB绘图函数:SetDIBitsToDevice和StretchDIBits。您可以直接存取DIB中的各个位、字节和图素,且一旦您有了一堆能让您以结构化的方式检查和更改数据的函数,您要怎么处理DIB就没人管了。
实际上,我们发现还是有一些限制。在上一章,我们了解了使用GDI函数在DDB上绘制图像的方法。到目前为止,还没有在DIB上绘图的方法。另一个问题是SetDIBitsToDevice和StretchDIBits没有BitBlt和StretchBlt速度快,尤其在Windows NT环境下以及执行许多最接近颜色搜寻(例如,在8位视频卡上显示24位DIB)时。
因此,在DIB和DDB之间进行转换是有好处的。例如,如果我们有一个需要在屏幕上显示许多次的DIB,那么把DIB转换为DDB就很有意义,这样我们就能够使用快速的BitBlt和StretchBlt函数来显示它了。
从DIB建立DDB
从DIB中建立GDI位图对象可能吗?基本上我们已经知道了方法:如果有DIB,您就能够使用CreateCompatibleBitmap来建立与DIB大小相同并与视频显示器兼容的GDI位图对象。然后将该位图对象选入内存设备内容并呼叫SetDIBitsToDevice在那个内存DC上绘图。结果就是DDB具有与DIB相同的图像,但具有与视频显示器兼容的颜色组织。
您也可以通过呼叫CreateDIBitmap用几个步骤完成上述工作。函数的语法为:
hBitmap = CreateDIBitmap ( hdc,// device context handle pInfoHdr,// pointer to DIB information header fInit,// 0 or CBM_INIT pBits,// pointer to DIB pixel bits pInfo,// pointer to DIB information fClrUse) ; // color use flag
请注意pInfoHdr和pInfo这两个参数,它们分别定义为指向BITMAPINFOHEADER结构和BITMAPINFO结构的指针。正如我们所知,BITMAPINFO结构是后面紧跟色彩对照表的BITMAPINFOHEADER结构。我们一会儿会看到这种区别所起的作用。最后一个参数是DIB_RGB_ COLORS(等于0)或DIB_PAL_COLORS,它们在SetDIBitsToDevice函数中使用。下一章我将讨论更多这方面的内容。
理解Windows中位图函数的作用是很重要的。不要考虑CreateDIBitmap函数的名称,它不建立与「设备无关的位图」,它从设备无关的规格中建立「设备相关的位图」。注意该函数传回GDI位图对象的句柄,CreateBitmap、CreateBitmapIndirect和CreateCompatibleBitmap也与它一样。
hBitmap = CreateDIBitmap (NULL,pbmih,0) ;
唯一的参数是指向BITMAPINFOHEADER结构(不带色彩对照表)的指标。在这个形式中,函数建立单色GDI位图对象。第二种简单的方法是:
hBitmap = CreateDIBitmap (hdc,0) ;
在这个形式中,函数建立了与设备内容兼容的DDB,该设备内容由hdc参数指出。到目前为止,我们都是透过CreateBitmap(建立单色位图)或CreateCompatibleBitmap(建立与视频显示器兼容的位图)来完成一些工作。
在CreateDIBitmap的这两个简化模式中,图素还未被初始化。如果CreateDIBitmap的第三个参数是CBM_INIT,Windows就会建立DDB并使用最后三个参数初始化位图位。pInfo参数是指向包括色彩对照表的BITMAPINFO结构的指针。pBits参数是指向由BITMAPINFO结构指出的色彩格式中的位数组的指针,根据色彩对照表这些位被转换为设备的颜色格式,这与SetDIBitsToDevice的情况相同。实际上,整个CreateDIBitmap函数可以用下列程序代码来实作:
HBITMAP CreateDIBitmap ( HDC hdc,CONST BITMAPINFOHEADER * pbmih,DWORD fInit,CONST VOID * pBits,CONST BITMAPINFO * pbmi,UINT fUsage) { HBITMAP hBitmap ; HDC hdc ; int cx,cy,iBitCount ; if (pbmih->biSize == sizeof (BITMAPCOREHEADER)) { 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,1,NULL) ; if (fInit == CBM_INIT) { hdcMem = CreateCompatibleDC (hdc) ; SelectObject (hdcMem,hBitmap) ; SetDIBitsToDevice ( hdcMem,0 cy,pBits,pbmi,fUsage) ; DeleteDC (hdcMem) ; } return hBitmap ; }
如果仅需显示DIB一次,并担心SetDIBitsToDevice显示太慢,则呼叫CreateDIBitmap并使用BitBlt或StretchBlt来显示DDB就没有什么意义。因为SetDIBitsToDevice和CreateDIBitmap都执行颜色转换,这两个工作会占用同样长的时间。只有在多次显示DIB时(例如在处理WM_PAINT消息时)进行这种转换才有意义。
程序15-6 DIBCONV展示了利用SetDIBitsToDevice把DIB文件转换为DDB的方法。
DIBCONV.ASM ;MASMPlus 代码模板 - 普通的 Windows 程序代码
.386
.Model Flat,StdCall
Option Casemap :None
Include windows.inc
Include user32.inc
Include kernel32.inc
Include gdi32.inc
Include comdlg32.inc
includelib gdi32.lib
IncludeLib user32.lib
IncludeLib kernel32.lib
IncludeLib comdlg32.lib
include macro.asm
WinMain PROTO :DWORD,:DWORD,:DWORD
IDM_FILE_OPEN equ 40001
.DATA
szAppName TCHAR "DibConv",0
.DATA?
hInstance HINSTANCE ?
hBitmap HBITMAP ?
cxClient DWORD ?
cyClient DWORD ?
ofn OPENFILENAME <?>
szFileName db MAX_PATH dup(?)
szTitleName db MAX_PATH dup(?)
;DOCINFOA STRUCT
; cbSize DWORD ?
; lpszDocName DWORD ?
; lpszOutput DWORD ?
; lpszDatatype DWORD ?
; fwType DWORD ?
;DOCINFOA ENDS
;BITMAPFILEHEADER STRUCT
; bfType WORD ?
; bfSize DWORD ?
; bfReserved1 WORD ?
; bfReserved2 WORD ?
; bfOffBits DWORD ?
;BITMAPFILEHEADER ENDS
;BITMAPINFO STRUCT
; bmiHeader BITMAPINFOHEADER <>
; bmiColors RGBQUAD <>
;BITMAPINFO ENDS
;BITMAPINFOHEADER STRUCT
; biSize DWORD ?
; biWidth DWORD ?
; biHeight DWORD ?
; biPlanes WORD ?
; biBitCount WORD ?
; biCompression DWORD ?
; biSizeImage DWORD ?
; biXPelsPerMeter DWORD ?
; biYPelsPerMeter DWORD ?
; biClrUsed DWORD ?
; biClrImportant DWORD ?
;BITMAPINFOHEADER ENDS
;BITMAPCOREHEADER STRUCT
; bcSize DWORD ?
; bcWidth WORD ?
; bcHeight WORD ?
; bcPlanes WORD ?
; bcBitCount WORD ?
;BITMAPCOREHEADER ENDS
.CODE
START:
invoke GetModuleHandle,NULL
invoke WinMain,eax,SW_SHOWDEFAULT
invoke ExitProcess,0
WinMain proc hInst:DWORD,hPrevInst:DWORD,szCmdLine:DWORD,iCmdShow:DWORD
LOCAL hWnd :HWND
LOCAL msg :MSG
LOCAL wndclass :WNDCLASSEX
mov wndclass.cbSize,sizeof WNDCLASSEX
mov wndclass.style,CS_HREDRAW or CS_VREDRAW
mov wndclass.lpfnWndProc,offset WndProc
mov wndclass.cbClsExtra,0
mov wndclass.cbWndExtra,0
push hInst
pop wndclass.hInstance
invoke LoadIcon,hInst,IDI_APPLICATION
mov wndclass.hIcon,eax
invoke LoadCursor,IDC_ARROW
mov wndclass.hCursor,eax
invoke GetStockObject,WHITE_BRUSH
mov wndclass.hbrBackground,EAX
lea eax,szAppName
mov wndclass.lpszMenuName,eax
mov wndclass.lpszClassName,eax
mov wndclass.hIconSm,0
invoke RegisterClassEx,ADDR wndclass
.if (eax==0)
invoke MessageBox,CTXT("This program requires Windows NT!"),szAppName,MB_ICONERROR
ret
.endif
invoke CreateWindowEx,
ADDR szAppName,;window class name
CTXT("DIB to DDB Conversion"),
WS_OVERLAPPEDWINDOW,;window style
CW_USEDEFAULT,;initial x position
CW_USEDEFAULT,;initial y position
CW_USEDEFAULT,;initial x size
CW_USEDEFAULT,;initial y size
NULL,;parent window handle
NULL,;window menu handle
hInst,;program instance handle
NULL ;creation parameters
mov hWnd,eax
invoke ShowWindow,hWnd,iCmdShow
invoke UpdateWindow,hWnd
StartLoop:
invoke GetMessage,ADDR msg,0
cmp eax,0
je ExitLoop
invoke TranslateMessage,ADDR msg
invoke DispatchMessage,ADDR msg
jmp StartLoop
ExitLoop:
mov eax,msg.wParam
ret
WinMain endp
CreateBitmapObjectFromDibFile proc hdc:HDC,FileName:DWORD
;FileName 是传过来的地址
LOCAL pbmfh:DWORD ;pbmfh指向BITMAPFILEHEADER结构体
LOCAL bSuccess:BOOL
LOCAL dwFileSize,dwHighSize,dwBytesRead:DWORD
LOCAL hFile:HANDLE
LOCAL hBitmapCB:HBITMAP
; Open the file: read access,prohibit write access
invoke CreateFile,FileName,GENERIC_READ,FILE_SHARE_READ,OPEN_EXISTING,FILE_FLAG_SEQUENTIAL_SCAN,NULL
mov hFile,eax
.if (hFile == INVALID_HANDLE_VALUE)
mov eax,NULL
ret
.endif
; Read in the whole file
invoke GetFileSize,hFile,addr dwHighSize
mov dwFileSize,eax
.if (dwHighSize!=0)
invoke CloseHandle,hFile
mov eax,NULL
ret
.endif
invoke LocalAlloc,LMEM_FIXED or LMEM_ZEROINIT,dwFileSize ;malloc,dwFileSize
mov pbmfh,eax ;pbmfh = malloc (dwFileSize) ;
.if (pbmfh==0)
invoke CloseHandle,NULL
ret
.endif
invoke ReadFile,pbmfh,dwFileSize,addr dwBytesRead,NULL
mov bSuccess,eax
invoke CloseHandle,hFile
; Verify the file
mov esi,pbmfh
mov ax,[esi]
mov ebx,[esi+2]
mov ecx,dwBytesRead
.if (bSuccess==0) || ( ecx!= dwFileSize) || (ax != "MB") || (ebx != dwFileSize) ;注意 “BM”需要调整为“MB”
invoke LocalFree,pbmfh
mov eax,NULL
ret
.endif
; Create the DDB
mov esi,pbmfh
mov edi,esi
add edi,sizeof (BITMAPFILEHEADER) ;edi = (BITMAPINFO *) (pbmfh + 1) ;
mov eax,esi ;esi中的已经是结构体的地址了
mov ecx,[eax+10]
add ecx,eax ;ecx = (BYTE *) pbmfh + pbmfh->bfOffBits ;
mov esi,pbmfh
mov ebx,esi
add ebx,sizeof (BITMAPINFO )
invoke CreateDIBitmap,hdc,edi,CBM_INIT,ecx,DIB_RGB_COLORS
mov hBitmapCB,eax
invoke LocalFree,pbmfh
mov eax,hBitmapCB
ret
CreateBitmapObjectFromDibFile endp
WndProc proc hwnd:DWORD,uMsg:DWORD,wParam :DWORD,lParam :DWORD
LOCAL bitmap:BITMAP
LOCAL hdc,hdcMem:HDC
LOCAL ps:PAINTSTRUCT
.if uMsg == WM_CREATE
mov eax,sizeof (OPENFILENAME)
mov ofn.lStructSize,eax
mov eax,hwnd
mov ofn.hwndOwner,NULL
mov ofn.hInstance,eax
mov ofn.lpstrFilter,CTEXT ("Bitmap Files (*.BMP)/0*.bmp/0/0All Files (*.*)/0*.*/0/0")
mov eax,NULL
mov ofn.lpstrCustomFilter,eax
xor eax,eax
mov ofn.nMaxCustFilter,eax
mov ofn.nFilterIndex,eax
lea eax,szFileName
mov ofn.lpstrFile,eax ; Set in Open and Close functions
mov eax,MAX_PATH
mov ofn.nMaxFile,NULL
mov ofn.lpstrFileTitle,eax ;Set in Open and Close functions
mov eax,MAX_PATH
mov ofn.nMaxFileTitle,NULL
mov ofn.lpstrInitialDir,eax
mov ofn.lpstrTitle,0
mov ofn.Flags,eax ; Set in Open and Close functions
mov ofn.nFileOffset,ax
mov ofn.nFileExtension,ax
mov ofn.lpstrDefExt,CTEXT ("bmp")
mov eax,0
mov ofn.lCustData,NULL
mov ofn.lpfnHook,eax
mov ofn.lpTemplateName,eax
xor eax,eax
ret
.elseif uMsg == WM_SIZE
mov eax,lParam
shl eax,16
shr eax,16
mov cxClient,lParam
shr eax,16
mov cyClient,eax
ret
.elseif uMsg == WM_COMMAND
mov eax,wParam
and eax,0FFFFh
.if ax == IDM_FILE_OPEN
; Show the File Open dialog Box
invoke GetOpenFileName,addr ofn
.if eax==0
xor eax,eax
ret
.endif
; If there's an existing DIB,delete it
.if (hBitmap!=0)
invoke DeleteObject,hBitmap
mov hBitmap,NULL
.endif
; Create the DDB from the DIB
invoke LoadCursor,IDC_WAIT
invoke SetCursor,eax
invoke ShowCursor,TRUE
invoke GetDC,hwnd
mov hdc,eax
invoke CreateBitmapObjectFromDibFile,addr szFileName
mov hBitmap,eax
invoke ReleaseDC,hwnd,hdc
invoke ShowCursor,FALSE
invoke LoadCursor,IDC_ARROW
invoke SetCursor,eax
; Invalidate the client area for later update
invoke InvalidateRect,TRUE
.if (hBitmap == NULL)
invoke MessageBox,CTEXT ("Cannot load DIB file"),addr szAppName,MB_OK or MB_ICONEXCLAMATION
.endif
xor eax,eax
ret
.endif
.elseif uMsg == WM_PAINT
invoke BeginPaint,addr ps
mov hdc,eax
.if (hBitmap!=0)
invoke GetObject,hBitmap,sizeof (BITMAP),addr bitmap
invoke CreateCompatibleDC,hdc
mov hdcMem,eax
invoke SelectObject,hdcMem,hBitmap
invoke BitBlt,bitmap.bmWidth,bitmap.bmHeight,SRCCOPY
invoke DeleteDC,hdcMem
.endif
invoke EndPaint,addr ps
xor eax,eax
ret
.elseif uMsg == WM_DESTROY
.if hBitmap!=0
invoke DeleteObject,hBitmap
.endif
invoke PostQuitMessage,0
xor eax,eax
ret
.endif
invoke DefWindowProc,uMsg,wParam,lParam
ret
WndProc endp
END START
DIBCONV.RC (摘录) #include "resource.h"
#define IDM_FILE_OPEN 40001
// Menu
DIBCONV MENU DISCARDABLE
BEGIN
POPUP "&File"
BEGIN
MENUITEM "&Open",IDM_FILE_OPEN
END
END
DIBCONV.ASM 本身就是完整的,并不需要前面的文件。在对它仅有的菜单命令(「File Open」)的响应中,WndProc呼叫程序的CreateBitmapObjectFromDibFile函数。此函数将整个文件读入内存并将指向内存块的指针传递给CreateDIBitmap函数,函数传回位图的句柄,然后包含DIB的内存块被释放。在WM_PAINT消息处理期间,WndProc将位图选入兼容的内存设备内容并使用BitBlt(不是SetDIBitsToDevice)在显示区域显示位图。它通过使用位图句柄呼叫带有BITMAP结构的GetObject函数来取得位图的宽度和高度。
在从CreateDIBitmap建立位图时不必初始化DDB图素位,之后您可以呼叫SetDIBits初始化图素位。该函数的语法如下:
iLines = SetDIBits ( 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 pInfo,// pointer to DIB information fClrUse) ; // color use flag
函数使用了BITMAPINFO结构中的色彩对照表把位转换为设备相关的格式。只有在最后一个参数设定为DIB_PAL_COLORS时,才需要设备内容句柄。
从DDB到DIB
与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) ;
注意,我们为第二个参数赋予BITMAPINFOHEADER结构的地址。这是常见的,但一个BYIE指针pBits的地址,就不常见了。这样,第四个参数是函数需要的指向指标的指标。
这是函数呼叫所做的:CreateDIBSection检查BITMAPINFOHEADER结构并配置足够的内存块来加载DIB图素位。(在这个例子里,内存块的大小为384×256×3字节。)它在您提供的pBits参数中储存了指向此内存块的指针。函数传回位图句柄,正如我说的,它与CreateDIBitmap和其它位图建立函数传回的句柄不一样。
然而,我们还没有做完,位图图素是未初始化的。如果正在读取DIB文件,可以简单地把pBits参数传递给ReadFile函数并读取它们。或者可以使用一些程序代码「人工」设定。
程序15-7 DIBSECT除了呼叫CreateDIBSection而不是CreateDIBitmap之外,与DIBCONV程序相似。
DIBSECT.ASM ;MASMPlus 代码模板 - 普通的 Windows 程序代码
.386
.Model Flat,:DWORD
IDM_FILE_OPEN equ 40001
.DATA
szAppName TCHAR "DIBsect",msg.wParam
ret
WinMain endp
CreateDIBsectionFromDibFile proc hdc:HDC,FileName:DWORD
;FileName 是传过来的地址
LOCAL bmfh:BITMAPFILEHEADER
LOCAL pbmi:DWORD ;pbmi指向BITMAPINFO结构体
LOCAL pBits:DWORD ;pBits指向Byte
LOCAL pbmfh:DWORD ;pbmfh指向BITMAPFILEHEADER结构体
LOCAL bSuccess:BOOL
LOCAL dwInfoSize,NULL
ret
.endif
; Read in the BITMAPFILEHEADER
invoke ReadFile,addr bmfh,sizeof(BITMAPFILEHEADER),eax
mov ax,bmfh.bfType
mov ecx,dwBytesRead
.if (bSuccess==0) || ( ecx!= sizeof (BITMAPFILEHEADER)) || (ax != "MB") ;注意 “BM”需要调整为“MB”
invoke CloseHandle,NULL
ret
.endif
; Allocate memory for the BITMAPINFO structure & read it in
mov eax,bmfh.bfOffBits
sub eax,sizeof (BITMAPFILEHEADER)
mov dwInfoSize,eax
invoke LocalAlloc,dwInfoSize ;malloc,dwFileSize
mov pbmi,eax ;pbmi = malloc (dwInfoSize) ;
invoke ReadFile,dwInfoSize,eax
mov eax,dwBytesRead
.if (bSuccess==0) || ( eax!= dwInfoSize)
invoke LocalFree,pbmi
invoke CloseHandle,NULL
ret
.endif
; Create the DIB Section
invoke CreateDIBSection,DIB_RGB_COLORS,addr pBits,0
mov hBitmapCB,eax
.if (hBitmapCB == NULL)
invoke LocalFree,NULL
ret
.endif
; Read in the bitmap bits
mov ebx,bmfh.bfSize
sub ebx,bmfh.bfOffBits
invoke ReadFile,ebx,NULL
invoke LocalFree,hBitmapCB
ret
CreateDIBsectionFromDibFile endp
WndProc proc hwnd:DWORD,eax
ret
.elseif uMsg == WM_SIZE
mov eax,eax
invoke CreateDIBsectionFromDibFile,eax
ret
.elseif uMsg == WM_DESTROY
.if hBitmap!=0
invoke DeleteObject,lParam
ret
WndProc endp
END START
DIBSECT.RC(摘录) #include "resource.h"
#define IDM_FILE_OPEN 40001
// Menu
DIBsect MENU DISCARDABLE
BEGIN
POPUP "&File"
BEGIN
MENUITEM "&Open",IDM_FILE_OPEN
END
END
注意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,// No sharing! NULL,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,NULL) ; if (!bSuccess || (dwBytesRead != dwInfoSize)) { free (pbmi) ; CloseHandle (hFile) ; return NULL ; } hFileMap = CreateFileMapping (hFile,PAGE_READWRITE,NULL) ; hBitmap = CreateDIBSection ( NULL,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图素位。