第十五章 與設備無關的位圖(DIB 和 DDB 的結合2)

與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調色盤管理器」之後會進入位圖的探索。

發佈了60 篇原創文章 · 獲贊 1 · 訪問量 4萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章