16. 調色盤管理器

Post date: 2012/3/23 上午 05:41:02

16. 調色盤管理器

如果硬體允許,本章就沒有存在的必要。儘管許多現代的顯示卡提供24位元顏色(也稱「true color」或「數百萬色」)或16位元顏色(「增強色」或「數萬種顏色」),一些顯示卡-尤其是在攜帶型電腦上或高解析度模式中-每個圖素只允許8位元。這意味著僅有256種顏色。

我們用256種顏色能做什麼呢?很明顯,要顯示真實世界的圖像,僅16種顏色是不夠的,至少要使用數千或數百萬種顏色,256種顏色位於中間狀態。是的,用256種顏色來顯示真實世界的圖像足夠了,但需要根據特定的圖像來指定這些顏色。這意味著作業系統不能簡單地選擇「標準」系列的256種顏色,就希望它們對每個應用程式都是理想的顏色。

這就是Windows調色盤管理器所要涉及的全部內容。它用於指定程式在8位元顯示模式下執行時所需要的顏色。如果知道程式肯定不會在8位元顯示模式下執行,那麼您也不需要使用調色盤管理器。不過,由於補充了點陣圖的一些細節,所以本章還是包含重要資訊的。

使用調色盤

傳統上講,調色盤是畫家用來混合顏色的板子。這個詞也可以指畫家在繪畫過程中使用的所有顏色。在電腦圖形中,調色盤是在圖形輸出設備(例如視訊顯示器)上可用的顏色範圍。這個名詞也可以指支援256色模式的顯示卡上的對照表。

視頻硬體

顯示卡上的調色盤對照表運作過程如下圖所示:

在8位元顯示模式中,每個圖素占8位元。圖素值查詢包含256RGB值的對照表的位址。這些RGB值可以正好24位元寬,或者小一點,通常是18位元寬(即主要的紅、綠和藍各6位元)。每種顏色的值都輸入到數位類比轉換器,以得到發送給監視器的紅、綠和藍三個類比信號。

通常,軟體可以用任意值來載入調色盤對照表,但這對裝置無關的視窗介面,例如Microsoft Windows,會有一些干擾。首先,Windows必須提供軟體介面,以便在不直接干擾硬體的情況下,應用程式就可以存取調色盤管理器。第二個問題更嚴重:因為所有的應用程式都共用同一個視訊顯示器,而且同時執行,所以一個應用程式使用了調色盤對照表可能會影響其他程式的使用。

這時就需要使用Windows調色盤管理器(在Windows 3.0中提出)了。Windows保留了256種顏色中的20種,而允許應用程式修改其餘的236種。(在某些情況下,應用程式最多可以改變256種顏色中的254種-只有黑色和白色除外-但這有一點麻煩)。Windows為系統保留的20種顏色(有時稱為20種「靜態」顏色)如表16-1所示。

表16-1 256種顏色顯示模式中的20種保留的顏色

在256種顏色顯示模式下執行時,由Windows維護系統調色盤,此調色盤與顯示卡上的硬體調色盤對照表相同。內定的系統調色盤如表16-1所示。應用程式可以通過指定「邏輯調色盤(logical palettes)」來修改其餘236種顏色。如果有多個應用程式使用邏輯調色盤,那麼Windows就給活動視窗最高優先權(我們知道,活動視窗有高亮顯示標題列,並且顯示在其他所有視窗的前面)。我們將用一些簡單的範例程式來檢查它是如何工作的。

要執行本章其他部分的程式,您可能需要將顯示卡切換成256色模式。在桌面上單擎滑鼠右鍵,從功能表中選擇「屬性」,然後選擇「設定」頁面標籤。

顯示灰階

程式16-1所示的GRAYS1程式沒有使用Windows調色盤管理器,而嘗試用正常顯示的65級種階作為從黑到白的多種彩色的「來源」。

程式16-1 GRAYS1 GRAYS1.C /*--------------------------------------------------------------------------- GRAYS1.C -- Gray Shades (c) Charles Petzold, 1998 -----------------------------------------------------------------------------*/ #include <windows.h> LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ; int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { static TCHAR szAppName[] = TEXT ("Grays1") ; 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 = NULL ; wndclass.lpszClassName = szAppName ; if (!RegisterClass (&wndclass)) { MessageBox ( NULL, TEXT ("This program requires Windows NT!"), szAppName, MB_ICONERROR) ; return 0 ; } hwnd = CreateWindow ( szAppName, TEXT ("Shades of Gray #1"), 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 ; } LRESULT CALLBACK WndProc ( HWND hwnd, UINT message, WPARAM wParam,LPARAM lParam) { static int cxClient, cyClient ; HBRUSH hBrush ; HDC hdc ; int i ; PAINTSTRUCT ps ; RECT rect ; switch (message) { case WM_SIZE: cxClient = LOWORD (lParam) ; cyClient = HIWORD (lParam) ; return 0 ; case WM_PAINT: hdc = BeginPaint (hwnd, &ps) ; // Draw the fountain of grays for (i = 0 ; i < 65 ; i++) { rect.left = i * cxClient / 65 ; rect.top = 0 ; rect.right = (i + 1) * cxClient / 65 ; rect.bottom = cyClient ; hBrush = CreateSolidBrush (RGB(min (255, 4 * i), min (255, 4 * i), min (255, 4 * i))) ; FillRect (hdc, &rect, hBrush) ; DeleteObject (hBrush) ; } EndPaint (hwnd, &ps) ; return 0 ; case WM_DESTROY: PostQuitMessage (0) ; return 0 ; } return DefWindowProc (hwnd, message, wParam, lParam) ; }

在WM_PAINT訊息處理期間,程式呼叫了65次FillRect函式,每次都使用不同灰階建立的畫刷。灰階值是RGB值(0,0,0)、(4,4,4)、(8,8,8)等等,直到最後一個值(255,255,255)。最後一個值來自CreateSolidBrush函式中的min巨集。

如果在256色顯示模式下執行該程式,您將看到從黑到白的65種灰階,而且它們幾乎都用混色著色。純顏色只有黑色、暗灰色(128,128,128)、亮灰色(192,192,192)和白色。其他顏色是混合了這些純顏色的多位元模式。如果我們在顯示行或文字,而不是用這65種灰階填充區域,Windows將不使用混色而只使用這四種純色。如果我們正在顯示點陣圖,則圖像將用20種標準Windows顏色近似。這時正如同您在執行 最後一章 中的程式的同時又載入了彩色或灰階DIB所見到的一樣。通常,Windows在點陣圖中不使用混色。

程式16-2所示的GRAYS2程式用較少的外部程式碼驗證了調色盤管理器中最重要的函式和訊息。

程式16-2 GRAYS2 GRAYS2.C /*--------------------------------------------------------------------------- GRAYS2.C -- Gray Shades Using Palette Manager (c) Charles Petzold, 1998 -----------------------------------------------------------------------------*/ #include <windows.h> LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ; int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { static TCHAR szAppName[] = TEXT ("Grays2") ; 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 = NULL ; wndclass.lpszClassName = szAppName ; if (! RegisterClass (&wndclass)) { MessageBox ( NULL, TEXT ("This program requires Windows NT!"), szAppName, MB_ICONERROR) ; return 0 ; } hwnd = CreateWindow ( szAppName, TEXT ("Shades of Gray #2"), 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 ; } LRESULT CALLBACK WndProc ( HWND hwnd, UINT message, WPARAM wParam,LPARAM lParam) { static HPALETTE hPalette ; static int cxClient, cyClient ; HBRUSH hBrush ; HDC hdc ; int i ; LOGPALETTE * plp ; PAINTSTRUCT ps ; RECT rect ; switch (message) { case WM_CREATE: // Set up a LOGPALETTE structure and create a palette plp = malloc (sizeof (LOGPALETTE) + 64 * sizeof (PALETTEENTRY)) ; plp->palVersion = 0x0300 ; plp->palNumEntries = 65 ; for (i = 0 ; i < 65 ; i++) { plp->palPalEntry[i].peRed = (BYTE) min (255, 4 * i) ; plp->palPalEntry[i].peGreen = (BYTE) min (255, 4 * i) ; plp->palPalEntry[i].peBlue = (BYTE) min (255, 4 * i) ; plp->palPalEntry[i].peFlags = 0 ; } hPalette = CreatePalette (plp) ; free (plp) ; return 0 ; case WM_SIZE: cxClient = LOWORD (lParam) ; cyClient = HIWORD (lParam) ; return 0 ; case WM_PAINT: hdc = BeginPaint (hwnd, &ps) ; // Select and realize the palette in the device context SelectPalette (hdc, hPalette, FALSE) ; RealizePalette (hdc) ; // Draw the fountain of grays for (i = 0 ; i < 65 ; i++) { rect.left = i * cxClient / 64 ; rect.top = 0 ; rect.right = (i + 1) * cxClient / 64 ; rect.bottom = cyClient ; hBrush = CreateSolidBrush (PALETTERGB( min (255, 4 * i), min (255, 4 * i), min (255, 4 * i))) ; FillRect (hdc, &rect, hBrush) ; DeleteObject (hBrush) ; } EndPaint (hwnd, &ps) ; return 0 ; case WM_QUERYNEWPALETTE: if (!hPalette) return FALSE ; hdc = GetDC (hwnd) ; SelectPalette (hdc, hPalette, FALSE) ; RealizePalette (hdc) ; InvalidateRect (hwnd, NULL, TRUE) ; ReleaseDC (hwnd, hdc) ; return TRUE ; case WM_PALETTECHANGED: if (!hPalette || (HWND) wParam == hwnd) break ; hdc = GetDC (hwnd) ; SelectPalette (hdc, hPalette, FALSE) ; RealizePalette (hdc) ; UpdateColors (hdc) ; ReleaseDC (hwnd, hdc) ; break ; case WM_DESTROY: DeleteObject (hPalette) ; PostQuitMessage (0) ; return 0 ; } return DefWindowProc (hwnd, message, wParam, lParam) ; }

通常,使用調色盤管理器的第一步就是呼叫CreatePalette函式來建立邏輯調色盤。邏輯調色盤包含程式所需要的全部顏色-即236種顏色。GRAYS1程式在WM_CREATE訊息處理期間處理此作業。它初始化LOGPALETTE(「logical palette:邏輯調色盤」)結構的欄位,並將這個結構的指標傳遞給CreatePalette函式。CreatePalette傳回邏輯調色盤的代號,並將此代號儲存在靜態變數hPalette中。

LOGPALETTE結構定義如下:

typedef struct { WORD palVersion ; WORD palNumEntries ; PALETTEENTRY palPalEntry[1] ; } LOGPALETTE, * PLOGPALETTE ;

第一個欄位通常設為0x0300,表示相容Windows 3.0。第二個欄位設定為調色盤表中的項目數。LOGPALETTE結構中的第三個欄位是一個PALETTEENTRY結構的陣列,此結構也是一個調色盤項目。PALETTEENTRY結構定義如下:

typedef struct { BYTE peRed ; BYTE peGreen ; BYTE peBlue ; BYTE peFlags ; } PALETTEENTRY, * PPALETTEENTRY ;

每個PALETTEENTRY結構都定義了一個我們要在調色盤中使用的RGB顏色值。

注意,LOGPALETTE中只能定義一個PALETTEENTRY結構的陣列。您需要為LOGPALETTE結構和附加的PALETTEENTRY結構配置足夠大的記憶體空間。GRAYS2需要65種灰階,因此它為LOGPALETTE結構和64個附加的PALETTEENTRY結構配置了足夠大的記憶體空間。GRAYS2將palNumEntries欄位設定為65,然後從0到64迴圈,計算灰階等級(一般是迴圈索引的4倍,但不超過255),將結構中的peRed、peGreen和peBlue欄位設定為此灰階等級。peFlags欄位設為0。程式將指向這個記憶體塊的指標傳遞給CreatePalette,在一個靜態變數中儲存該調色盤代號,然後釋放記憶體。

邏輯調色盤是GDI物件。程式應該刪除它們建立的所有邏輯調色盤。WndProc透過在WM_DESTROY訊息處理期間呼叫DeleteObject,仔細地刪除了邏輯調色盤。

注意邏輯調色盤是獨立的裝置內容。在真正使用之前,必須確保將其選進裝置內容。在WM_PAINT訊息處理期間,SelectPalette將邏輯調色盤選進裝置內容。除了含有第三個參數以外,此函式與SelectObject函式相似。通常,第三個參數設為FALSE。如果SelectPalette的第三個參數設為TRUE,那麼調色盤將始終是「背景調色盤」,這意味著當其他所有程式都顯現了各自的調色盤之後,該調色盤才可以獲得仍位於系統調色盤中的一個未使用項目。

在任何時候都只有一個邏輯調色盤能選進裝置內容。函式將傳回前一個選進裝置內容的邏輯調色盤代號。如果您希望將此邏輯調色盤重新選進裝置內容,則可以儲存此代號。

通過將顏色映射到系統調色盤,RealizePalette函式使Windows在裝置內容中「顯現」邏輯調色盤,而系統調色盤是與顯示卡實際的實際調色盤相對應。實際工作在此函式呼叫期間進行。Windows必須決定呼叫函式的視窗是活動的還是非活動的,並盡可能將系統調色盤已改變通知給其他視窗(我們將簡要說明一下通知的程序)。

回憶一下GRAYS1,它用RGB巨集來指定純色畫刷的顏色。RGB巨集建構一個32位元長整數(記作COLORREF值),其中高位元組是0,3個低位元組是紅、綠和藍的亮度。

使用Windows調色盤管理器的程式可以繼續使用RGB顏色值來指定顏色。不過,這些RGB顏色值將不能存取邏輯調色盤中的附加顏色。它們的作用與沒有使用調色盤管理器相同。要在邏輯調色盤中使用附加的顏色,就要用到PALETTERGB巨集。除了COLORREF值的高位元組設為2而不是0以外,「調色盤RGB」顏色與RGB顏色很相似。

下面是重要的規則:

  • 為了使用邏輯調色盤中的顏色,請用調色盤RGB值或調色盤索引來指定(我將簡要討論調色盤索引)。不要使用常規的RGB值。如果使用了常規的RGB值,您將得到一種標準顏色,而不是邏輯調色盤中的顏色。
  • 沒有將調色盤選進裝置內容時,不要使用調色盤RGB值或調色盤索引。
  • 儘管可以使用調色盤RGB值來指定邏輯調色盤中沒有的顏色,但您還是要從邏輯調色盤獲得顏色。

例如,在GRAYS2中處理WM_PAINT期間,當您選擇並顯現了邏輯調色盤之後,如果試圖顯示紅色,則將顯示灰階。您必須用RGB顏色值來選擇不在邏輯調色盤中的顏色。

注意,GRAYS2從不檢查視訊顯示驅動程式是否支援調色盤管理程式。在不支援調色盤管理程式的顯示模式(即所有非256種顏色的顯示模式)下執行GRAYS2時,GRAYS2的功能與GRASY1相同。

調色盤資訊

如果程式在邏輯調色盤中指定一種顏色,該顏色又是20種保留顏色之一,那麼Windows將把邏輯調色盤項目映射給該顏色。另外,如果兩個或多個應用程式都在它們的邏輯調色盤中指定了同一種顏色,那麼這些應用程式將共用系統調色盤項目。程式可以通過將PALETTEENTRY結構的peFlags欄位指定為常數PC_NOCOLLAPSE來忽略該內定狀態(其餘兩個可能的標記是PC_EXPLICIT(用於顯示系統調色盤)和PC_RESERVED(用於調色盤動畫),我將在本章的後面展示這兩個標記)。

要幫助組織系統調色盤,Windows調色盤管理器含有兩個發送給主視窗的訊息。

第一個是QM_QUERYNEWPALETTE。當主視窗活動時,該訊息發送給主視窗。如果程式在您的視窗上繪畫時使用了調色盤管理器,則它必須處理該訊息。GRAYS2展示具體的作法。程式獲得裝置內容代號,並選進調色盤,呼叫RealizePalette,然後使視窗失效以產生WM_PAINT訊息。如果顯現了邏輯調色盤,則視窗訊息處理程式從該訊息傳回TRUE,否則傳回FALSE。

當系統調色盤改成與WM_QUERYNEWPALETTE訊息的結果相同時,Windows將WM_PALETTECHANGED訊息發送給由目前活動的視窗來啟動並終止處理視窗鏈的所有主視窗。這允許前臺視窗有優先權。傳遞給視窗訊息處理程式的wParam值是活動視窗的代號。只有當wParam不等於程式的視窗代號時,使用調色盤管理器的程式才會處理該訊息。

通常,在處理WM_PALETTECHANGED時,使用自訂調色盤的任何程式都呼叫SelectPalette和RealizePalette。後續的視窗在訊息處理期間呼叫RealizePalette時,Windows首先檢查邏輯調色盤中的RGB顏色是否與已載入到系統調色盤中的RGB顏色相匹配。如果兩個程式需要相同的顏色,那麼這兩個程式就共同使用一個系統調色盤項目。接下來,Windows檢查未使用的系統調色盤項目。如果都已使用,則邏輯調色盤中的顏色從20種保留項目映射到最近的顏色。

如果不關心程式非活動時顯示區域的外觀,那麼您不必處理WM_PALETTECHANGED訊息。否則,您有兩個選擇。GRAYS2顯示其中之一:在處理WM_QUERYNEWPALETTE訊息時,它獲得裝置內容,選進調色盤,然後呼叫RealizePalette。這時就可以在處理WM_QUERYNEWPALETTE時呼叫InvalidateRect了。相反地,GRAYS2呼叫UpdateColors。這個函式通常比重新繪製視窗更有效,同時它改變視窗中圖素的值來幫助保護以前的顏色。

使用調色盤管理器的許多程式都將讓WM_QUERYNEWPALETTE和WM_PALETTECHANGED訊息用GRAYS2所顯示的方法來處理。

調色盤索引方法

程式16-3所示的GRAYS3程式與GRAYS2非常相似,只是在處理WM_PAINT期間使用了呼叫PALETTEINDEX的巨集,而不是PALETTERGB。

程式16-3 GRAYS3 GRAYS3.C /*--------------------------------------------------------------------------- GRAYS3.C -- Gray Shades Using Palette Manager (c) Charles Petzold, 1998 -----------------------------------------------------------------------------*/ #include <windows.h> LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ; int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { static TCHAR szAppName[] = TEXT ("Grays3") ; 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 = NULL ; wndclass.lpszClassName = szAppName ; if (!RegisterClass (&wndclass)) { MessageBox ( NULL, TEXT ("This program requires Windows NT!"), szAppName, MB_ICONERROR) ; return 0 ; } hwnd = CreateWindow ( szAppName, TEXT ("Shades of Gray #3"), 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 ; } LRESULT CALLBACK WndProc ( HWND hwnd, UINT message, WPARAM wParam,LPARAM lParam) { static HPALETTE hPalette ; static int cxClient, cyClient ; HBRUSH hBrush ; HDC hdc ; int i ; LOGPALETTE * plp ; PAINTSTRUCT ps ; RECT rect ; switch (message) { case WM_CREATE: // Set up a LOGPALETTE structure and create a palette plp = malloc (sizeof (LOGPALETTE) + 64 * sizeof (PALETTEENTRY)) ; plp->palVersion = 0x0300 ; plp->palNumEntries = 65 ; for (i = 0 ; i < 65 ; i++) { plp->palPalEntry[i].peRed = (BYTE) min (255, 4 * i) ; plp->palPalEntry[i].peGreen = (BYTE) min (255, 4 * i) ; plp->palPalEntry[i].peBlue = (BYTE) min (255, 4 * i) ; plp->palPalEntry[i].peFlags = 0 ; } hPalette = CreatePalette (plp) ; free (plp) ; return 0 ; case WM_SIZE: cxClient = LOWORD (lParam) ; cyClient = HIWORD (lParam) ; return 0 ; case WM_PAINT: hdc = BeginPaint (hwnd, &ps) ; // Select and realize the palette in the device context SelectPalette (hdc, hPalette, FALSE) ; RealizePalette (hdc) ; // Draw the fountain of grays for (i = 0 ; i < 65 ; i++) { rect.left = i * cxClient / 64 ; rect.top = 0 ; rect.right = (i + 1) * cxClient / 64 ; rect.bottom = cyClient ; hBrush = CreateSolidBrush (PALETTEINDEX (i)) ; FillRect (hdc, &rect, hBrush) ; DeleteObject (hBrush) ; } EndPaint (hwnd, &ps) ; return 0 ; case WM_QUERYNEWPALETTE: if (!hPalette) return FALSE ; hdc = GetDC (hwnd) ; SelectPalette (hdc, hPalette, FALSE) ; RealizePalette (hdc) ; InvalidateRect (hwnd, NULL, FALSE) ; ReleaseDC (hwnd, hdc) ; return TRUE ; case WM_PALETTECHANGED: if (!hPalette || (HWND) wParam == hwnd) break ; hdc = GetDC (hwnd) ; SelectPalette (hdc, hPalette, FALSE) ; RealizePalette (hdc) ; UpdateColors (hdc) ; ReleaseDC (hwnd, hdc) ; break ; case WM_DESTROY: PostQuitMessage (0) ; return 0 ; } return DefWindowProc (hwnd, message, wParam, lParam) ; }

「調色盤」索引的顏色不同於調色盤RGB顏色,其高位元組是1,而低位元組的值是目前在裝置內容中選擇的、邏輯調色盤中的索引。在GRAYS3中,邏輯調色盤有65個項目,用於這些項目的索引從0到64。值

指黑色,

指灰色,而

指白色。

因為Windows不需要執行最近顏色的搜索,所以使用調色盤索引比使用RGB值更有效。

查詢調色盤支援

您可以容易地驗證:當Windows在16位元或24位元顯示模式下執行時,GRAYS2和GRAYS3程式執行良好。但是在某些情況下,要使用調色盤管理器的Windows應用程式可能要先確定裝置驅動程式是否支援它。這時,您可以呼叫GetDeviceCaps,並以視訊顯示的裝置內容代號和PASTERCAPS作為參數。函式將傳回由一系列旗標組成的整數。通過在傳回值和常數RC_PALETTE之間執行位元操作來檢驗支援的調色盤:

如果此值非零,則視訊顯示器裝置驅動程式將支援調色盤操作。在這種情況之下,來自GetDeviceCaps的其他三個重要項目也是可用的。函式呼叫

將傳回在顯示卡上調色盤表的總尺寸。這與同時顯示的顏色總數相同。因為調色盤管理器只用於每圖素8位元的視訊顯示模式,所以此值將是256。

函式呼叫

傳回在調色盤表中的顏色數,該表是裝置驅動程式為系統保留的,此值是20。不呼叫調色盤管理器,這些只是Windows應用程式在256色顯示模式下使用的純色。要使用其餘的236種顏色,程式必須使用調色盤管理器函式。

一個附加項目也可用:

此值告訴您載入到硬體調色盤表的RGB顏色值解析度(以位元計)。這些是進入數位類比轉換器的位元。某些視訊顯示卡只使用6位元ADC,所以該值是18。其餘使用8位元的ADC,所以值是24。

Windows程式注意顏色解析度並因此採取一些動作是很有用的。例如,如果該顏色解析度是18,那麼程式將不可能要求到128種灰階,因為只有64個離散的灰階可用。要求到128種灰階就不必用多餘的項目來填充硬體調色盤表。

系統調色盤

我在前面提過,Windows系統調色盤直接與顯示卡上的硬體調色盤查詢表相符(然而,硬體調色盤查詢表可能比系統調色盤的顏色解析度低)。程式可以通過呼叫下面的函式來獲得系統調色盤中的某些或全部的RGB項目:

只有顯示卡模式支援調色盤操作時,該函式才能執行。第二個和第三個參數是無正負號整數,顯示第一個調色盤項目的索引和調色盤項目數。最後一個參數是指向PALETTEENTRY型態的指標。

您可以在幾種情況下使用該函式。程式可以定義PALETTEENTRY結構如下:

然後可按下面的方法多次呼叫GetSystemPaletteEntries:

其中的i從0到某個值,該值小於從GetDeviceCaps(帶有SIZEPALETTE索引255)傳回的值。或者,程式要獲得所有的系統調色盤項目,可以通過定義指向PALETTEENTRY結構的指標,然後重新配置足夠的記憶體塊,以儲存與調色盤大小指定同樣多的PALETTEENTRY結構。

GetSystemPaletteEntries函式確實允許您檢驗硬體調色盤表。系統調色盤中的項目按圖素值增加的順序排列,這些值用於表示視訊顯示緩衝區中的顏色。我將簡單地討論一下具體作法。

其他調色盤函式

我們在前面看過,Windows程式能夠改變系統調色盤,但只是間接改變:第一步建立邏輯調色盤,它基本上是程式要使用的RGB顏色值陣列。CreatePalette函式不會導致系統調色盤或者顯示卡調色盤表的任何變化。邏輯調色盤必須在任何事情發生之前就選進裝置內容並顯現。

程式可以通過呼叫

來查詢邏輯調色盤中的RGB顏色值。您可以按使用GetSystemPaletteEntries的方法來使用此函式。但是要注意,第一個參數是邏輯調色盤的代號,而不是裝置內容的代號。

建立邏輯調色盤以後,讓您改變其中的值的相應函式是:

另外,記住呼叫此函式不引起系統調色盤的任何變化-即使目前調色盤選進了裝置內容。此函式也不改變邏輯調色盤的尺寸。要改變邏輯調色盤的尺寸,請使用ResizePalette。

下面的函式接受RGB顏色引用值作為最後的參數,並將索引傳回給邏輯調色盤,該邏輯調色盤與和它最接近的RGB顏色值相對應:

第二個參數是COLORREF值。如果希望的話,呼叫GetPaletteEntries就可以獲得邏輯調色盤中實際的RGB顏色值。

如果程式在8位元顯示模式下需要多於236種自訂顏色,則可以呼叫GetSystemPaletteUse。這允許程式設定254種自訂顏色;系統僅保留黑色和白色。不過,程式僅在最大化充滿全螢幕時才允許這樣,而且它還將一些系統顏色設為黑色和白色,以便標題列和功能表等仍然可見。

位元映射操作問題

第五章 可以瞭解到,GDI允許使用不同的「繪畫模式」或「位元映射操作」來畫線並填充區域。用SetROP2設定繪畫模式,其中的「2」表示兩個物件之間的二元(binary)位元映射操作。三元位元映射操作用於處理BitBlt和類似功能。這些位元映射操作決定了正在畫的物件圖素與表面圖素的結合方式。例如,您可以畫一條直線,以便線上的圖素與顯示的圖素按位元異或的方式相結合。

位元映射操作就是在圖素位元上照著各個位元的順序進行操作。改變調色盤會影響到這些位元映射操作。位元映射操作的操作物件是圖素位元,而這些圖素位元可能與實際顏色沒有關聯。

透過執行GRAYS2或GRAYS3程式,您自己就可以得出這個結論。調整尺寸時,拖動頂部或底部的邊界穿過視窗,Windows利用反轉背景圖素位元的位元映射操作來顯示拖動尺寸的邊界,其目的是使拖動尺寸邊界總是可見的。但在GRAYS2和GRAYS3程式中,您將看到各種隨機變換的顏色,這些顏色恰好與對應於調色盤表中未使用的項目,那是反轉顯示圖素位元的結果。可視顏色沒有反轉-只有圖素位元反轉了。

正如您在 表16-1 中所看到的一樣,20種標準保留顏色位於系統調色盤的頂部和底部,以便位元映射操作的結果仍然正常。然而,一旦您開始修改調色盤-尤其是替換了保留顏色-那麼顏色物件的位元映射操作就變得沒有意義了。

唯一保證的是位元映射操作將用黑色和白色運作。黑色是系統調色盤中的第一個項目(所有的圖素位元都設為0),而白色是最後的項目(所有的圖素位元都設為1)。這兩個項目不能改變。如果需要預知在顏色物件上進行位元映射操作的結果,則可以先獲得系統調色盤表,然後查看不同圖素位元值的RGB顏色值。

查看系統調色盤

在Windows下執行的程式將處理邏輯調色盤,為使邏輯調色盤更好地服務於所有使用邏輯調色盤的程式,Windows將在系統調色盤中設定顏色。該系統調色盤複製了顯示卡的硬體對照表內容。這樣,查看系統調色盤有助於調適調色盤應用程式。

因為對於這個問題有三種截然不同的處理方式,所以我將向您展示三個程式,以顯示系統調色盤的內容。

SYSPAL1程式,如程式16-4所示,使用了前面所講的GetSystemPaletteEntries函式。

PALETTEINDEX (0)

PALETTEINDEX (32)

PALETTEINDEX (64)

RC_PALETTE & GetDeviceCaps (hdc, RASTERCAPS)

GetDeviceCaps (hdc, SIZEPALETTE)

GetDeviceCaps (hdc, NUMRESERVED)

GetDeviceCaps (hdc, COLORRES)

GetSystemPaletteEntries (hdc, uStart, uNum, &pe) ;

PALETTEENTRY pe ;

GetSystemPaletteEntries (hdc, i, 1, &pe) ;

GetPaletteEntries (hPalette, uStart, uNum, &pe) ;

SetPaletteEntries (hPalette, uStart, uNum, &pe) ;

iIndex = GetNearestPaletteIndex (hPalette, cr) ;

程式16-4 SYSPAL1 SYSPAL1.C /*--------------------------------------------------------------------------- SYSPAL1.C -- Displays system palette (c) Charles Petzold, 1998 -----------------------------------------------------------------------------*/ #include <windows.h> LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ; TCHAR szAppName [] = TEXT ("SysPal1") ; 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 = NULL ; wndclass.lpszClassName = szAppName ; if (!RegisterClass (&wndclass)) { MessageBox ( NULL, TEXT ("This program requires Windows NT!"), szAppName, MB_ICONERROR) ; return 0 ; } hwnd = CreateWindow ( szAppName, TEXT ("System Palette #1"), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL) ; if (!hwnd) return 0 ; ShowWindow (hwnd, iCmdShow) ; UpdateWindow (hwnd) ; while (GetMessage (&msg, NULL, 0, 0)) { TranslateMessage (&msg) ; DispatchMessage (&msg) ; } return msg.wParam ; } BOOL CheckDisplay (HWND hwnd) { HDC hdc ; int iPalSize ; hdc = GetDC (hwnd) ; iPalSize = GetDeviceCaps (hdc, SIZEPALETTE) ; ReleaseDC (hwnd, hdc) ; if (iPalSize != 256) { MessageBox (hwnd,TEXT ("This program requires that the video ") TEXT ("display mode have a 256-color palette."), szAppName, MB_ICONERROR) ; return FALSE ; } return TRUE ; } LRESULT CALLBACK WndProc ( HWND hwnd, UINT message, WPARAM wParam,LPARAM lParam) { static int cxClient, cyClient ; static SIZE sizeChar ; HDC hdc ; HPALETTE hPalette ; int i, x, y ; PAINTSTRUCT ps ; PALETTEENTRY pe [256] ; TCHAR szBuffer [16] ; switch (message) { case WM_CREATE: if (!CheckDisplay (hwnd)) return -1 ; hdc = GetDC (hwnd) ; SelectObject (hdc, GetStockObject (SYSTEM_FIXED_FONT)) ; GetTextExtentPoint32 (hdc, TEXT ("FF-FF-FF"), 10, &sizeChar) ; ReleaseDC (hwnd, hdc) ; return 0 ; case WM_DISPLAYCHANGE: if (!CheckDisplay (hwnd)) DestroyWindow (hwnd) ; return 0 ; case WM_SIZE: cxClient = LOWORD (lParam) ; cyClient = HIWORD (lParam) ; return 0 ; case WM_PAINT: hdc = BeginPaint (hwnd, &ps) ; SelectObject (hdc, GetStockObject (SYSTEM_FIXED_FONT)) ; GetSystemPaletteEntries (hdc, 0, 256, pe) ; for (i = 0, x = 0, y = 0 ; i < 256 ; i++) { wsprintf ( szBuffer, TEXT ("%02X-%02X-%02X"), pe[i].peRed, pe[i].peGreen, pe[i].peBlue) ; TextOut (hdc, x, y, szBuffer, lstrlen (szBuffer)) ; if (( x += sizeChar.cx) + sizeChar.cx > cxClient) { x = 0 ; if (( y += sizeChar.cy) > cyClient) break ; } } EndPaint (hwnd, &ps) ; return 0 ; case WM_PALETTECHANGED: InvalidateRect (hwnd, NULL, FALSE) ; return 0 ; case WM_DESTROY: PostQuitMessage (0) ; return 0 ; } return DefWindowProc (hwnd, message, wParam, lParam) ; }

與SYSPAL系列中的其他程式一樣,除非帶有SIZEPALETTE參數的GetDeviceCaps傳回值為256,否則SYSPAL1不會執行。

注意無論SYSPAL1的顯示區域什麼時候收到WM_PALETTECHANGED訊息,它都是無效的。在合併WM_PAINT訊息處理期間,SYSPAL1呼叫GetSystemPaletteEntries,並用一個含256個PALETTEENTRY結構的陣列作為參數。RGB值作為文字字串顯示在顯示區域。程式執行時,注意20種保留顏色是RGB值列表中的前10個和後10個,這與表16-1所示相同。

當SYSPAL1顯示有用的資訊時,它與實際看到的256種顏色不同。那就是SYSPAL2的作業,如程式16-5所示。

程式16-5 SYSPAL2 SYSPAL2.C /*--------------------------------------------------------------------------- SYSPAL2.C -- Displays system palette (c) Charles Petzold, 1998 ----------------------------------------------------------------------------*/ #include <windows.h> LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ; TCHAR szAppName [] = TEXT ("SysPal2") ; 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 = NULL ; wndclass.lpszClassName = szAppName ; if (!RegisterClass (&wndclass)) { MessageBox ( NULL, TEXT ("This program requires Windows NT!"), szAppName, MB_ICONERROR) ; return 0 ; } hwnd = CreateWindow ( szAppName, TEXT ("System Palette #2"), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL) ; if (!hwnd) return 0 ; ShowWindow (hwnd, iCmdShow) ; UpdateWindow (hwnd) ; while (GetMessage (&msg, NULL, 0, 0)) { TranslateMessage (&msg) ; DispatchMessage (&msg) ; } return msg.wParam ; } BOOL CheckDisplay (HWND hwnd) { HDC hdc ; int iPalSize ; hdc = GetDC (hwnd) ; iPalSize = GetDeviceCaps (hdc, SIZEPALETTE) ; ReleaseDC (hwnd, hdc) ; if (iPalSize != 256) { MessageBox (hwnd, TEXT ("This program requires that the video ") TEXT ("display mode have a 256-color palette."), szAppName, MB_ICONERROR) ; return FALSE ; } return TRUE ; } LRESULT CALLBACK WndProc ( HWND hwnd, UINT message, WPARAM wParam,LPARAM lParam) { static HPALETTE hPalette ; static int cxClient, cyClient ; HBRUSH hBrush ; HDC hdc ; int i, x, y ; LOGPALETTE * plp ; PAINTSTRUCT ps ; RECT rect ; switch (message) { case WM_CREATE: if (!CheckDisplay (hwnd)) return -1 ; plp = malloc (sizeof (LOGPALETTE) + 255 * sizeof (PALETTEENTRY)) ; plp->palVersion = 0x0300 ; plp->palNumEntries = 256 ; for (i = 0 ; i < 256 ; i++) { plp->palPalEntry[i].peRed = i ; plp->palPalEntry[i].peGreen = 0 ; plp->palPalEntry[i].peBlue = 0 ; plp->palPalEntry[i].peFlags = PC_EXPLICIT ; } hPalette = CreatePalette (plp) ; free (plp) ; return 0 ; case WM_DISPLAYCHANGE: if (!CheckDisplay (hwnd)) DestroyWindow (hwnd) ; return 0 ; case WM_SIZE: cxClient = LOWORD (lParam) ; cyClient = HIWORD (lParam) ; return 0 ; case WM_PAINT: hdc = BeginPaint (hwnd, &ps) ; SelectPalette (hdc, hPalette, FALSE) ; RealizePalette (hdc) ; for (y = 0 ; y < 16 ; y++) for (x = 0 ; x < 16 ; x++) { hBrush = CreateSolidBrush (PALETTEINDEX (16 * y + x)) ; SetRect (&rect, x * cxClient /16, y * cyClient / 16, (x + 1)` * cxClient / 16,(y+1) * cyClient / 16); FillRect (hdc, &rect, hBrush) ; DeleteObject (hBrush) ; } EndPaint (hwnd, &ps) ; return 0 ; case WM_PALETTECHANGED: if ((HWND) wParam != hwnd) InvalidateRect (hwnd, NULL, FALSE) ; return 0 ; case WM_DESTROY: DeleteObject (hPalette) ; PostQuitMessage (0) ; return 0 ; } return DefWindowProc (hwnd, message, wParam, lParam) ; }

SYSPAL2在WM_CREATE訊息處理期間建立了邏輯調色盤。但是請注意:邏輯調色盤中所有的256個值都是從0到255的調色盤索引,並且peFlags欄位是PC_EXPLICIT。該旗標是這樣定義的:「邏輯調色盤項目的較低字組指定了一個硬體調色盤索引。此旗標允許應用程式顯示硬體調色盤的內容。」該旗標就是專為我們要做的這件事情而設計的。

在WM_PAINT訊息處理期間,SYSPAL2將該調色盤選進裝置內容並顯現它。這不會引起系統調色盤的任何重組,而是允許程式使用PALETTEINDEX巨集來指定系統調色盤中的顏色。按此方法,SYSPAL2顯示了256個矩形。另外,當您執行該程式時,注意頂行和底行的前10種和後10種顏色是20種保留顏色,如 表16-1 所示。當您執行使用自己邏輯調色盤的程式時,顯示就改變了。

如果您既喜歡看SYSPAL2中的顏色,又喜歡RGB的值,那麼請與 第八章的WHATCLR程式 同時執行。

SYSPAL系列中的第三版使用的技術對我來說是最近才出現的-從我開始研究Windows調色盤管理器七年多後,才出現了那些技術。

事實上,所有的GDI函式都直接或間接地指定顏色作為RGB值。在GDI內部,這將轉換成與那個顏色相關的圖素位元。在某些顯示模式中(例如,16位元或24位元顏色模式),這些轉換是相當直接的。在其他顯示模式中(4位元或8位元顏色),這可能涉及最接近顏色的搜索。

然而,有兩個GDI函式讓您直接指定圖素位元中的顏色。當然在這種方式中使用的這兩個函式都與設備高度相關。它們太依賴設備了,以至於它們可以直接顯示視訊顯示卡上實際的調色盤對照表。這兩個函式是BitBlt和StretchBlt。

程式16-6所示的SYSPAL3程式顯示了使用StretchBlt顯示系統調色盤中顏色的方法。

程式16-6 SYSPAL3 SYSPAL3.C /*--------------------------------------------------------------------------- SYSPAL3.C -- Displays system palette (c) Charles Petzold, 1998 ----------------------------------------------------------------------------*/ #include <windows.h> LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ; TCHAR szAppName [] = TEXT ("SysPal3") ; 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 = NULL ; wndclass.lpszClassName = szAppName ; if (!RegisterClass (&wndclass)) { MessageBox ( NULL, TEXT ("This program requires Windows NT!"), szAppName, MB_ICONERROR) ; return 0 ; } hwnd = CreateWindow ( szAppName, TEXT ("System Palette #3"), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL) ; if (!hwnd) return 0 ; ShowWindow (hwnd, iCmdShow) ; UpdateWindow (hwnd) ; while (GetMessage (&msg, NULL, 0, 0)) { TranslateMessage (&msg) ; DispatchMessage (&msg) ; } return msg.wParam ; } BOOL CheckDisplay (HWND hwnd) { HDC hdc ; int iPalSize ; hdc = GetDC (hwnd) ; iPalSize = GetDeviceCaps (hdc, SIZEPALETTE) ; ReleaseDC (hwnd, hdc) ; if (iPalSize != 256) { MessageBox (hwnd,TEXT("This program requires that the video ") TEXT("display mode have a 256-color palette."), szAppName, MB_ICONERROR) ; return FALSE ; } return TRUE ; } LRESULT CALLBACK WndProc ( HWND hwnd, UINT message, WPARAM wParam,LPARAM lParam) { static HBITMAP hBitmap ; static int cxClient, cyClient ; BYTE bits [256] ; HDC hdc, hdcMem ; int i ; PAINTSTRUCT ps ; switch (message) { case WM_CREATE: if (! CheckDisplay (hwnd)) return -1 ; for ( i = 0 ; i < 256 ; i++) bits [i] = i ; hBitmap = CreateBitmap (16, 16, 1, 8, &bits) ; return 0 ; case WM_DISPLAYCHANGE: if (!CheckDisplay (hwnd)) DestroyWindow (hwnd) ; return 0 ; case WM_SIZE: cxClient = LOWORD (lParam) ; cyClient = HIWORD (lParam) ; return 0 ; case WM_PAINT: hdc = BeginPaint (hwnd, &ps) ; hdcMem = CreateCompatibleDC (hdc) ; SelectObject (hdcMem, hBitmap) ; StretchBlt (hdc, 0, 0, cxClient, cyClient, hdcMem, 0, 0, 16, 16, SRCCOPY) ; DeleteDC (hdcMem) ; EndPaint (hwnd, &ps) ; return 0 ; case WM_DESTROY: DeleteObject (hBitmap) ; PostQuitMessage (0) ; return 0 ; } return DefWindowProc (hwnd, message, wParam, lParam) ; }

在WM_CREATE訊息處理期間,SYSPAL3使用CreateBitmap來建立16×16的每圖素8位元的點陣圖。該函式的最後一個參數是包括數值0到255的256位元組陣列。這些是256種可能的圖素位元值。在處理WM_PAINT訊息的程序中,程式將這個點陣圖選進記憶體裝置內容,用StretchBlt來顯示並填充該顯示區域。Windows僅將點陣圖中的圖素位元傳輸到視訊顯示器硬體,從而允許這些圖素位元存取調色盤對照表中的256個項目。程式的顯示區域甚至不必使接收WM_PALETTECHANGED訊息無效-對於對照表的任何修改都會立即影響到SYSPAL3的顯示。

調色盤動畫

在本節的標題中看到「動畫」一詞,並開始考慮螢幕周圍執行的「電腦寵物」時,您的眼前可能會為之一亮。是的,您可以使用Windows調色盤管理器作一些動畫,而且是有一定專業水平的動畫。

通常,Windows下的動畫就是快速連續地顯示一系列點陣圖。調色盤動畫與這種方法有很大的區別。您透過在螢幕上繪製您所需要的每件東西開始,然後您處理調色盤來改變這些物件的顏色,可能是畫一些相對於螢幕背景來說是不可見的圖像。您用這種方法就可以獲得動畫效果,而不必重畫任何東西。調色盤動畫的速度是相當快的。

對於調色盤動畫,最初的建立工作與我們前面看見的有些不同:對於動畫期間要修改的每種RGB顏色值,PALETTEENTRY結構的peFlags欄位必須設定為PC_RESERVED。

通常,就像我們所看到的一樣,在建立邏輯調色盤時,您將peFlags標記設為0。這允許GDI將多個邏輯調色盤中同樣的顏色映射到相同的系統調色盤項目。例如,假設兩個Windows程式都建立了包含RGB項目10-10-10的邏輯調色盤,那麼在系統調色盤表中,Windows只需要一個10-10-10項目。但如果這兩個程式中的一個使用調色盤動畫,那您就不要再讓GDI使用調色盤了。調色盤動畫意味著速度非常快-而且如果不重畫,它也只可能提高速度。當使用調色盤動畫的程式修改調色盤時,它不會影響其他程式,或者迫使GDI重組系統調色盤表。PC_RESERVED的peFlags值為單個邏輯調色盤儲存系統調色盤項目。

使用調色盤動畫時,通常您可以在WM_PAINT訊息處理期間呼叫SelectPalette和RealizePalette,使用PALETTEINDEX巨集來指定顏色。該巨集將一個索引帶進邏輯調色盤表。

對於動畫,您可能要通過改變調色盤來回應WM_TIMER訊息。要改變邏輯調色盤中的RGB顏色值,請使用一個PALETTEENTRY結構的陣列來呼叫函式AnimatePalette。此函式速度很快,因為它只需要改變系統調色盤以及顯示卡硬體調色盤表中的項目。

跳動的球

程式16-7顯示了BOUNCE程式的元件,但還有一個程式可顯示跳動的球。為了簡單起見,根據顯示區域的大小將球畫成了橢圓形。因為本章有幾個調色盤動畫程式,所以PALANIM.C(「調色盤動畫」)檔案包含一些通用內容。

程式16-7 BOUNCE PALANIM.C /*-------------------------------------------------------------------------- PALANIM.C -- Palette Animation Shell Program s(c) Charles Petzold, 1998 ---------------------------------------------------------------------------*/ #include <windows.h> extern HPALETTE CreateRoutine (HWND) ; extern void PaintRoutine (HDC, int, int) ; extern void TimerRoutine (HDC, HPALETTE) ; extern void DestroyRoutine (HWND, HPALETTE) ; LRESULT CALLBA CK WndProc (HWND, UINT, WPARAM, LPARAM) ; extern TCHAR szAppName [] ; extern TCHAR szTitle [] ; 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 = NULL ; wndclass.lpszClassName = szAppName ; if (!RegisterClass (&wndclass)) { MessageBox ( NULL, TEXT ("This program requires Windows NT!"), szAppName, MB_ICONERROR) ; return 0 ; } hwnd = CreateWindow ( szAppName, szTitle, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL) ; if (!hwnd) return 0 ; ShowWindow (hwnd, iCmdShow) ; UpdateWindow (hwnd) ; while (GetMessage (&msg, NULL, 0, 0)) { TranslateMessage (&msg) ; DispatchMessage (&msg) ; } return msg.wParam ; } BOOL CheckDisplay (HWND hwnd) { HDC hdc ; int iPalSize ; hdc = GetDC (hwnd) ; iPalSize = GetDeviceCaps (hdc, SIZEPALETTE) ; ReleaseDC (hwnd, hdc) ; if (iPalSize != 256) { MessageBox (hwnd, TEXT ("This program requires that the video ") TEXT ("display mode have a 256-color palette."), szAppName, MB_ICONERROR) ; return FALSE ; } return TRUE ; } LRESULT CALLBACK WndProc ( HWND hwnd, UINT message, WPARAM wParam,LPARAM lParam) { static HPALETTE hPalette ; static int cxClient, cyClient ; HDC hdc ; PAINTSTRUCT ps ; switch (message) { case WM_CREATE: if (!CheckDisplay (hwnd)) return -1 ; hPalette = CreateRoutine (hwnd) ; return 0 ; case WM_DISPLAYCHANGE: if (!CheckDisplay (hwnd)) DestroyWindow (hwnd) ; return 0 ; case WM_SIZE: cxClient = LOWORD (lParam) ; cyClient = HIWORD (lParam) ; return 0 ; case WM_PAINT: hdc = BeginPaint (hwnd, &ps) ; SelectPalette (hdc, hPalette, FALSE) ; RealizePalette (hdc) ; PaintRoutine (hdc, cxClient, cyClient) ; EndPaint (hwnd, &ps) ; return 0 ; case WM_TIMER: hdc = GetDC (hwnd) ; SelectPalette (hdc, hPalette, FALSE) ; TimerRoutine (hdc, hPalette) ; ReleaseDC (hwnd, hdc) ; return 0 ; case WM_QUERYNEWPALETTE: if (!hPalette) return FALSE ; hdc = GetDC (hwnd) ; SelectPalette (hdc, hPalette, FALSE) ; RealizePalette (hdc) ; InvalidateRect (hwnd, NULL, TRUE) ; ReleaseDC (hwnd, hdc) ; return TRUE ; case WM_PALETTECHANGED: if (!hPalette || (HWND) wParam == hwnd) break ; hdc = GetDC (hwnd) ; SelectPalette (hdc, hPalette, FALSE) ; RealizePalette (hdc) ; UpdateColors (hdc) ; ReleaseDC (hwnd, hdc) ; break ; case WM_DESTROY: DestroyRoutine (hwnd, hPalette) ; PostQuitMessage (0) ; return 0 ; } return DefWindowProc (hwnd, message, wParam, lParam) ; }

BOUNCE.C /*-------------------------------------------------------------------------- BOUNCE.C -- Palette Animation Demo (c) Charles Petzold, 1998 ----------------------------------------------------------------------------*/ #include <windows.h> #define ID_TIMER 1 TCHAR szAppName [] = TEXT ("Bounce") ; TCHAR szTitle [] = TEXT ("Bounce: Palette Animation Demo") ; static LOGPALETTE * plp ; HPALETTE CreateRoutine (HWND hwnd) { HPALETTE hPalette ; int i ; plp = malloc (sizeof (LOGPALETTE) + 33 * sizeof (PALETTEENTRY)) ; plp->palVersion = 0x0300 ; plp->palNumEntries = 34 ; for (i = 0 ; i < 34 ; i++) { plp->palPalEntry[i].peRed = 255 ; plp->palPalEntry[i].peGreen = (i == 0 ? 0 : 255) ; plp->palPalEntry[i].peBlue = (i == 0 ? 0 : 255) ; plp->palPalEntry[i].peFlags = (i == 33 ? 0 : PC_RESERVED) ; } hPalette = CreatePalette (plp) ; SetTimer (hwnd, ID_TIMER, 50, NULL) ; return hPalette ; } void PaintRoutine (HDC hdc, int cxClient, int cyClient) { HBRUSH hBrush ; int i, x1, x2, y1, y2 ; RECT rect ; // Draw window background using palette index 33 SetRect (&rect, 0, 0, cxClient, cyClient) ; hBrush = CreateSolidBrush (PALETTEINDEX (33)) ; FillRect (hdc, &rect, hBrush) ; DeleteObject (hBrush) ; // Draw the 33 balls SelectObject (hdc, GetStockObject (NULL_PEN)) ; for (i = 0 ; i < 33 ; i++) { x1 = i * cxClient / 33 ; x2 = (i + 1)* cxClient / 33 ; if (i < 9) { y1 = i * cyClient / 9 ; y2 = (i + 1) * cyClient / 9 ; } else if (i < 17) { y1 = (16 - i) * cyClient / 9 ; y2 = (17 - i) * cyClient / 9 ; } else if (i < 25) { y1 = (i - 16) * cyClient / 9 ; y2 = (i - 15) * cyClient / 9 ; } else { y1 = (32 - i) * cyClient / 9 ; y2 = (33 - i) * cyClient / 9 ; } hBrush = CreateSolidBrush (PALETTEINDEX (i)) ; SelectObject (hdc, hBrush) ; Ellipse (hdc, x1, y1, x2, y2) ; DeleteObject (SelectObject (hdc, GetStockObject (WHITE_BRUSH))) ; } return ; } void TimerRoutine (HDC hdc, HPALETTE hPalette) { static BOOL bLeftToRight = TRUE ; static int iBall ; // Set old ball to white plp->palPalEntry[iBall].peGreen = 255 ; plp->palPalEntry[iBall].peBlue = 255 ; iBall += (bLeftToRight ? 1 : -1) ; if ( iBall == (bLeftToRight ? 33 : -1)) { iBall = (bLeftToRight ? 31 : 1) ; bLeftToRight ^= TRUE ; } // Set new ball to red plp->palPalEntry[iBall].peGreen = 0 ; plp->palPalEntry[iBall].peBlue = 0 ; // Animate the palette AnimatePalette (hPalette, 0, 33, plp->palPalEntry) ; return ; } void DestroyRoutine (HWND hwnd, HPALETTE hPalette) { KillTimer (hwnd, ID_TIMER) ; DeleteObject (hPalette) ; free (plp) ; return ; }

除非Windows處於支援調色盤的顯示模式下,否則調色盤動畫將不能工作。因此,PALANIM.C通過呼叫CheckDisplay函式(與SYSPAL程式中的函式相同)來開始處理WM_CREATE。

PALANIM.C呼叫BOUNCE.C中的四個函式:在WM_CREATE訊息處理期間呼叫CreateRoutine(在BOUNCE中用於建立邏輯調色盤);在WM_PAINT訊息處理期間呼叫PaintRoutine;在WM_TIMER訊息處理期間呼叫TimerRoutine;在WM_DESTROY訊息處理期間呼叫DestroyRoutine(在BOUNCE中用於清除)。在呼叫PaintRoutine和TimerRoutine之前,PALANIM.C獲得裝置內容,並將其選進邏輯調色盤。在呼叫PaintRoutine之前,它也顯現調色盤。PALANIM.C期望TimerRoutine呼叫AnimatePalette。儘管AnimatePalette需要從裝置內容中選擇調色盤,但它不需要呼叫RealizePalette。

BOUNCE中的球按「W」路線在顯示區域中來回跳動。顯示區域背景是白色,球是紅色。任何時候,都可以在33個不重疊的位置之一看見球。這需要34個調色盤項目:一個用於背景,其他33個用於不同位置的球。在CreateRoutine中,BOUNCE初始化PALETTEENTRY結構的一個陣列,將第一個調色盤項目(與球在左上角的位置對應)設定為紅色,其他的設定為白色。注意,對於除背景以外的所有項目,peFlags欄位都設定為PC_RESERVED(背景是最後的一個調色盤項目)。BOUNCE通過將Windows計時器的間隔設定為50毫秒來終止CreateRoutine。

BOUNCE在PaintRoutine完成所有的繪畫工作。視窗背景用一個實心畫刷和調色盤索引33所指定的顏色來繪製。33個球的顏色是依據從0到32的調色盤索引的顏色。當BOUNCE第一次在顯示區域內繪畫時,0的調色盤索引映射成紅色,其他調色盤索引映射到白色。這導致球出現在左上角。

當WndProc處理WM_TIMER訊息並呼叫TimerRoutine時,動畫就發生了。TimerRoutine通過呼叫AnimatePalette來結束,語法如下:

其中,第一個參數是調色盤代號,最後一個參數是指向陣列的指標,該陣列由一個或多個PALETTEENTRY結構組成。該函式改變邏輯調色盤中從uStart項目到uNum項目之間的若干項目。邏輯調色盤中新的uStart項目是PALETTEENTRY結構中的第一個成員。當心!uStart參數是進入原始邏輯調色盤表的索引,而不是進入PALETTEENTRY陣列的索引。

為了方便起見,BOUNCE使用PALETTEENTRY結構的陣列,該結構是建立邏輯調色盤時使用的LOGPALETTE結構的一部分。球的目前位置(從0到32)儲存在靜態變數iBall中。在TimerRoutine期間,BOUNCE將PALETTEENTRY成員設為白色。然後計算球的下一個位置,並將該元素設為紅色。用下面的呼叫來改變調色盤:

GDI改變33邏輯調色盤項目中的第一個(儘管實際上只改變了兩個),使它與系統調色盤表中的變化相對應,然後修改顯示卡上的硬體調色盤表。這樣,不用重畫球就開始移動了。

BOUNCE執行時,您會發現同時執行SYSPAL2或SYSPAL3效果會更好。

儘管AnimatePalette執行得非常快,但是當只有一兩個項目改變時,您還應該儘量避免改變所有的邏輯調色盤項目。這在BOUNCE中有點複雜,因為球要來回地跳-iBall要先增加,然後再減少。一種方法是使用兩個變數:分別稱為iBallOld(設定球的目前位置)和iBallMin(iBall和iBallOld中較小的)。然後您就可以像下面這樣呼叫AnimatePalette來改變兩個項目了:

還有另一種方法:我們先假定您定義了一個PALETTEENTRY結構:

在TimerRoutine期間,您將PALETTEENTRY欄位設為白色,並呼叫AnimatePalette來改變邏輯調色盤中iBall位置的一個項目:

AnimatePalette (hPalette, uStart, uNum, &pe) ;

AnimatePalette (hPalette, 0, 33, plp->palPalEntry) ;

iBallMin = min (iBall, iBallOld) ; AnimatePalette (hPal, iBallMin, 2, plp->palPalEntry + iBallMin) ;

PALETTEENTRY pe ;

pe.peRed = 255 ; pe.peGreen = 255 ; pe.peBlue = 255 ; pe.peFlags = PC_RESERVED ; AnimatePalette (hPalette, iBall, 1, &pe) ;

然後計算顯示在BOUNCE中的iBall的新值,將PALETTEENTRY結構的欄位定義為紅色,然後再次呼叫AnimatePalette:

pe.peRed = 255 ; pe.peGreen = 0 ; pe.peBlue = 0 ; pe.peFlags = PC_RESERVED ; AnimatePalette (hPalette, iBall, 1, &pe) ;

儘管跳動的球是對動畫的一個傳統的簡單說明,但它實際上並不適合調色盤動畫,因為必須先畫出球的所有可能位置。調色盤動畫更適合於顯示運動的重複圖案。

一個項目的調色盤動畫

調色盤動畫中一個更有趣的方面就是,可以只使用一個調色盤項目來完成一些有趣的技術。例如程式16-8所示的FADER程式。這個程式也需要前面的PALANIM.C檔案。

程式16-8 FADER FADER.C /*-------------------------------------------------------------------------- FADER.C -- Palette Animation Demo (c) Charles Petzold, 1998 --------------------------------------------------------------------------*/ #include <windows.h> #define ID_TIMER 1 TCHAR szAppName [] = TEXT ("Fader") ; TCHAR szTitle [] = TEXT ("Fader: Palette Animation Demo") ; static LOGPALETTE lp ; HPALETTE CreateRoutine (HWND hwnd) { HPALETTE hPalette ; lp.palVersion = 0x0300 ; lp.palNumEntries = 1 ; lp.palPalEntry[0].peRed = 255 ; lp.palPalEntry[0].peGreen = 255 ; lp.palPalEntry[0].peBlue = 255 ; lp.palPalEntry[0].peFlags = PC_RESERVED ; hPalette = CreatePalette (&lp) ; SetTimer (hwnd, ID_TIMER, 50, NULL) ; return hPalette ; } void PaintRoutine (HDC hdc, int cxClient, int cyClient) { static TCHAR szText [] = TEXT (" Fade In and Out ") ; int x, y ; SIZE sizeText ; SetTextColor (hdc, PALETTEINDEX (0)) ; GetTextExtentPoint32 (hdc, szText, lstrlen (szText), &sizeText) ; for (x = 0 ; x < cxClient ; x += sizeText.cx) for (y = 0 ; y < cyClient ; y += sizeText.cy) { TextOut (hdc, x, y, szText, lstrlen (szText)) ; } return ; } void TimerRoutine (HDC hdc, HPALETTE hPalette) { static BOOL bFadeIn = TRUE ; if (bFadeIn) { lp.palPalEntry[0].peRed -= 4 ; lp.palPalEntry[0].peGreen -= 4 ; if ( lp.palPalEntry[0].peRed == 3) bFadeIn = FALSE ; } else { lp.palPalEntry[0].peRed += 4 ; lp.palPalEntry[0].peGreen += 4 ; if (lp.palPalEntry[0].peRed == 255) bFadeIn = TRUE ; } AnimatePalette (hPalette, 0, 1, lp.palPalEntry) ; return ; } void DestroyRoutine (HWND hwnd, HPALETTE hPalette) { KillTimer (hwnd, ID_TIMER) ; DeleteObject (hPalette) ; return ; }

FADER在顯示區域上顯示滿了文字字串「Fade In And Out」。文字首先顯示為白色,這對於白色背景的視窗來說是看不出來的。通過使用調色盤動畫,FADER慢慢地將文字的顏色改為藍色,然後再改回白色,這樣一遍一遍地重複。文字就有漸現漸隱的顯示效果了。

FADER用CreateRoutine函式建立了邏輯調色盤,它只需要一個調色盤項目,並將顏色初始化為白色-紅色、綠色和藍色值都設為255。在PaintRoutine中(您可能想起,當邏輯調色盤選進裝置內容並顯現以後,PALANIM呼叫過此函式),FADER呼叫SetTextColor將文字顏色設定為PALETTEINDEX(0)。這意味著文字顏色設定為調色盤表格中的第一個項目,此項目初始為白色。然後FADER用「Fade In And Out」文字字串填充顯示區域。這時,視窗背景是白色,文字也是白色,所以文字不可見。

在TimerRoutine函式中,FADER通過改變PALETTEENTRY結構並將其傳遞給AnimatePalette來完成調色盤動畫。最初,對每一個WM_TIMER訊息,程式都將紅色和綠色值減4,直到等於3;然後將這些值加4,直到等於255。這將使文字顏色逐漸從白色變到藍色,然後又回到白色。

程式16-9所示的ALLCOLOR程式只用了邏輯調色盤的一個項目來顯示顯示卡可以著色的所有顏色。當然,程式不是同時顯示這些顏色,而是連續顯示。如果顯示卡有18位元的解析度(這時能有262144種不同的顏色),那麼在兩種顏色間隔55毫秒的速度下,只需要4小時就可以在螢幕上看到所有的顏色。

程式16-9 ALLCOLOR ALLCOLOR.C /*--------------------------------------------------------------------------- ALLCOLOR.C -- Palette Animation Demo (c) Charles Petzold, 1998 -----------------------------------------------------------------------------*/ #include <windows.h> #define ID_TIMER 1 TCHAR szAppName [] = TEXT ("AllColor") ; TCHAR szTitle [] = TEXT ("AllColor: Palette Animation Demo") ; static int iIncr ; static PALETTEENTRY pe ; HPALETTE CreateRoutine (HWND hwnd) { HDC hdc ; HPALETTE hPalette ; LOGPALETTE lp ; // Determine the color resolution and set iIncr hdc = GetDC (hwnd) ; iIncr = 1 << (8 - GetDeviceCaps (hdc, COLORRES) / 3) ; ReleaseDC (hwnd, hdc) ; // Create the logical palette lp.palVersion = 0x0300 ; lp.palNumEntries = 1 ; lp.palPalEntry[0].peRed = 0 ; lp.palPalEntry[0].peGreen = 0 ; lp.palPalEntry[0].peBlue = 0 ; lp.palPalEntry[0].peFlags = PC_RESERVED ; hPalette = CreatePalette (&lp) ; // Save global for less typing pe = lp.palPalEntry[0] ; SetTimer (hwnd, ID_TIMER, 10, NULL) ; return hPalette ; } void DisplayRGB (HDC hdc, PALETTEENTRY * ppe) { TCHAR szBuffer [16] ; wsprintf (szBuffer, TEXT (" %02X-%02X-%02X "), ppe->peRed, ppe->peGreen, ppe->peBlue) ; TextOut (hdc, 0, 0, szBuffer, lstrlen (szBuffer)) ; } void PaintRoutine (HDC hdc, int cxClient, int cyClient) { HBRUSH hBrush ; RECT rect ; // Draw Palette Index 0 on entire window hBrush = CreateSolidBrush (PALETTEINDEX (0)) ; SetRect (&rect, 0, 0, cxClient, cyClient) ; FillRect (hdc, &rect, hBrush) ; DeleteObject (SelectObject (hdc, GetStockObject (WHITE_BRUSH))) ; // Display the RGB value DisplayRGB (hdc, &pe) ; return ; } void TimerRoutine (HDC hdc, HPALETTE hPalette) { static BOOL bRedUp = TRUE, bGreenUp = TRUE, bBlueUp = TRUE ; // Define new color value pe.peBlue += (bBlueUp ? iIncr : -iIncr) ; if ( pe.peBlue == (BYTE) (bBlueUp ? 0 : 256 - iIncr)) { pe.peBlue = (bBlueUp ? 256 - iIncr : 0) ; bBlueUp ^= TRUE ; pe.peGreen += (bGreenUp ? iIncr : -iIncr) ; if ( pe.peGreen == (BYTE) (bGreenUp ? 0 : 256 - iIncr)) { pe.peGreen = (bGreenUp ? 256 - iIncr : 0) ; bGreenUp ^= TRUE ; pe.peRed += (bRedUp ? iIncr : -iIncr) ; if ( pe.peRed == (BYTE) (bRedUp ? 0 : 256 - iIncr)) { pe.peRed = (bRedUp ? 256 - iIncr : 0) ; bRedUp ^= TRUE ; } } } // Animate the palette AnimatePalette (hPalette, 0, 1, &pe) ; DisplayRGB (hdc, &pe) ; return ; } void DestroyRoutine (HWND hwnd, HPALETTE hPalette) { KillTimer (hwnd, ID_TIMER) ; DeleteObject (hPalette) ; return ; }

在結構上,ALLCOLOR與FADER非常相似。在CreateRoutine中,ALLCOLOR只用一個設為黑色的調色盤項目(PALETTEENTRY結構的red、green和blue欄位設為0)來建立調色盤。在PaintRoutine中,ALLCOLOR用PALETTEINDEX(0)建立實心畫刷,並呼叫FillRect來用此畫刷為整個顯示區域著色。

在TimerRoutine中,ALLCOLOR通過改變PALETTEENTRY顏色並呼叫AnimatePalette來啟動調色盤。我編寫ALLCOLOR程式,以便顏色變化順暢。首先,藍色值漸漸增加。達到最大時,綠色值增加,而藍色值漸漸減少。紅色、綠色和藍色值的增加和減少取決於iIncr變數。在CreateRoutine期間,這將根據用COLORRES參數從GetDeviceCaps傳回的值來計算。例如,如果GetDeviceCaps傳回18,那麼iIncr設為4-獲得所有顏色所需要的最小值。

ALLCOLOR還在顯示區域的左上角顯示目前的RGB顏色值。我最初添加這個程式碼是出於測試目的,但是現在證明它是有用的,所以我保留了它。

工程應用程式

在工程應用程式中,動畫對於顯示機械或電的作用過程很有用。在電腦螢幕上顯示內燃引擎雖然簡單,但是動畫可以使它變得更加生動,且更清楚地顯示其工作程序。

使用調色盤動畫的一個好範例就是顯示流體通過管子的過程。這是一個例子,圖像不必十分精確-實際上,如果圖像很精確(就像看透明的管子),則很難說明管子裏的流體是如何運動的。這時用符號會更好一些。程式16-10所示的PIPES程式是此技術的簡單示範:在顯示區域有兩個水平的管子,流體在上面的管子裏從左向右流動,而在下面的管子裏從右向左移動。

程式16-10 PIPES

PIPES.C /*------------------------------------------------------------------------- PIPES.C -- Palette Animation Demo (c) Charles Petzold, 1998 ---------------------------------------------------------------------------*/ #include <windows.h> #define ID_TIMER 1 TCHAR szAppName [] = TEXT ("Pipes") ; TCHAR szTitle [] = TEXT ("Pipes: Palette Animation Demo") ; static LOGPALETTE * plp ; HPALETTE CreateRoutine (HWND hwnd) { HPALETTE hPalette ; int i ; plp = malloc (sizeof (LOGPALETTE) + 32 * sizeof (PALETTEENTRY)) ; // Initialize the fields of the LOGPALETTE structure plp->palVersion = 0x300 ; plp->palNumEntries = 16 ; for (i = 0 ; i <= 8 ; i++) { plp->palPalEntry[i].peRed = (BYTE) min (255, 0x20 * i) ; plp->palPalEntry[i].peGreen = 0 ; plp->palPalEntry[i].peBlue = (BYTE) min (255, 0x20 * i) ; plp->palPalEntry[i].peFlags = PC_RESERVED ; plp->palPalEntry[16 - i] = plp->palPalEntry[i] ; plp->palPalEntry[16 + i] = plp->palPalEntry[i] ; plp->palPalEntry[32 - i] = plp->palPalEntry[i] ; } hPalette = CreatePalette (plp) ; SetTimer (hwnd, ID_TIMER, 100, NULL) ; return hPalette ; } void PaintRoutine (HDC hdc, int cxClient, int cyClient) { HBRUSH hBrush ; int i ; RECT rect ; // Draw window background SetRect (&rect, 0, 0, cxClient, cyClient) ; hBrush = SelectObject (hdc, GetStockObject (WHITE_BRUSH)) ; FillRect (hdc, &rect, hBrush) ; // Draw the interiors of the pipes for (i = 0 ; i < 128 ; i++) { hBrush = CreateSolidBrush (PALETTEINDEX (i % 16)) ; SelectObject (hdc, hBrush) ; rect.left = (127 - i) * cxClient / 128 ; zrect.right = (128 - i) * cxClient / 128 ; rect.top = 4 * cyClient / 14 ; rect.bottom = 5 * cyClient / 14 ; FillRect (hdc, &rect, hBrush) ; rect.left = i * cxClient / 128 ; rect.right = ( i + 1) * cxClient / 128 ; rect.top = 9 * cyClient / 14 ; rect.bottom = 10 * cyClient / 14 ; FillRect (hdc, &rect, hBrush) ; DeleteObject (SelectObject (hdc, GetStockObject (WHITE_BRUSH))) ; } // Draw the edges of the pipes MoveToEx (hdc, 0, 4 * cyClient / 14, NULL) ; LineTo (hdc, cxClient, 4 * cyClient / 14) ; MoveToEx (hdc, 0, 5 * cyClient / 14, NULL) ; LineTo (hdc, cxClient, 5 * cyClient / 14) ; MoveToEx (hdc, 0, 9 * cyClient / 14, NULL) ; LineTo (hdc, cxClient, 9 * cyClient / 14) ; MoveToEx (hdc, 0, 10 * cyClient / 14, NULL) ; LineTo (hdc, cxClient, 10 * cyClient / 14) ; return ; } void TimerRoutine (HDC hdc, HPALETTE hPalette) { static int iIndex ; AnimatePalette (hPalette, 0, 16, plp->palPalEntry + iIndex) ; iIndex = (iIndex + 1) % 16 ; return ; } void DestroyRoutine (HWND hwnd, HPALETTE hPalette) { KillTimer (hwnd, ID_TIMER) ; DeleteObject (hPalette) ; free (plp) ; return ; }

PIPES為動畫使用了16個調色盤項目,而您可能會使用更少的項目。最小化時,真正需要的是有足夠的項目來顯示流動的方向。用三個調色盤項目要比用一個靜態箭頭好。

程式16-11所示的TUNNEL程式是這組程式中最貪心的程式,它為動畫使用了128個調色盤項目,但是從效果來看,值得這樣做。

程式16-11 TUNNEL TUNNEL.C /*--------------------------------------------------------------------------- TUNNEL.C -- Palette Animation Demo (c) Charles Petzold, 1998 ----------------------------------------------------------------------------*/ #include <windows.h> #define ID_TIMER 1 TCHAR szAppName [] = TEXT ("Tunnel") ; TCHAR szTitle [] = TEXT ("Tunnel: Palette Animation Demo") ; static LOGPALETTE * plp ; HPALETTE CreateRoutine (HWND hwnd) { BYTE byGrayLevel ; HPALETTE hPalette ; int i ; plp = malloc (sizeof (LOGPALETTE) + 255 * sizeof (PALETTEENTRY)) ; // Initialize the fields of the LOGPALETTE structure plp->palVersion = 0x0300 ; plp->palNumEntries = 128 ; for (i = 0 ; i < 128 ; i++) { if (i < 64) byGrayLevel = (BYTE) (4 * i) ; else byGrayLevel = (BYTE) min (255, 4 * (128 - i)) ; plp->palPalEntry[i].peRed = byGrayLevel ; plp->palPalEntry[i].peGreen = byGrayLevel ; plp->palPalEntry[i].peBlue = byGrayLevel ; plp->palPalEntry[i].peFlags = PC_RESERVED ; plp->palPalEntry[i + 128].peRed = byGrayLevel ; plp->palPalEntry[i + 128].peGreen = byGrayLevel ; plp->palPalEntry[i + 128].peBlue = byGrayLevel ; plp->palPalEntry[i + 128].peFlags = PC_RESERVED ; } hPalette = CreatePalette (plp) ; SetTimer (hwnd, ID_TIMER, 50, NULL) ; return hPalette ; } void PaintRoutine (HDC hdc, int cxClient, int cyClient) { HBRUSH hBrush ; int i ; RECT rect ; for (i = 0 ; i < 127 ; i++) { // Use a RECT structure for each of 128 rectangles rect.left = i * cxClient / 255 ; rect.top = i * cyClient / 255 ; rect.right = cxClient - i * cxClient / 255 ; rect.bottom = cyClient - i * cyClient / 255 ; hBrush = CreateSolidBrush (PALETTEINDEX (i)) ; // Fill the rectangle and delete the brush FillRect (hdc, &rect, hBrush) ; DeleteObject (hBrush) ; } return ; } void TimerRoutine (HDC hdc, HPALETTE hPalette) { static int iLevel ; iLevel = (iLevel + 1) % 128 ; AnimatePalette (hPalette, 0, 128, plp->palPalEntry + iLevel) ; return ; } void DestroyRoutine (HWND hwnd, HPALETTE hPalette) { KillTimer (hwnd, ID_TIMER) ; DeleteObject (hPalette) ; free (plp) ; return ; }

TUNNEL在128個調色盤項目中使用64種移動的灰階-從黑到白,再從白到黑-表現在隧道旅行的效果。

調色盤和真實世界圖像

當然,儘管我們已經完成了許多有趣的事:連續顯示色彩的網底、做了調色盤動畫,但調色盤管理器的真正目的是允許在8位元顯示模式下顯示真實世界中的圖像。對於本章的其餘部分,我們正好研究一下。正如您所期望的,在使用packed DIB、GDI點陣圖物件和DIB區塊時,必須按照不同的方法來使用調色盤。下面的六個程式闡明了用調色盤來處理點陣圖的各種技術。

調色盤和packed DIB

下面三個程式,有助於我們建立處理packed DIB記憶體塊的一系列函式。這些函式都在程式16-12所示的PACKEDIB檔案中。

程式16-12 PACKEDIB檔案 PACKEDIB.H /*------------------------------------------------------------------------- PACKEDIB.H -- Header file for PACKEDIB.C (c) Charles Petzold, 1998 --------------------------------------------------------------------------*/ #include <windows.h> BITMAPINFO * PackedDibLoad (PTSTR szFileName) ; int PackedDibGetWidth (BITMAPINFO * pPackedDib) ; int PackedDibGetHeight (BITMAPINFO * pPackedDib) ; int PackedDibGetBitCount (BITMAPINFO * pPackedDib) ; int PackedDibGetRowLength (BITMAPINFO * pPackedDib) ; int PackedDibGetInfoHeaderSize (BITMAPINFO * pPackedDib) ; int PackedDibGetColorsUsed (BITMAPINFO * pPackedDib) ; int PackedDibGetNumColors (BITMAPINFO * pPackedDib) ; int PackedDibGetColorTableSize (BITMAPINFO * pPackedDib) ; RGBQUAD * PackedDibGetColorTablePtr (BITMAPINFO * pPackedDib) ; RGBQUAD * PackedDibGetColorTableEntry (BITMAPINFO * pPackedDib, int i) ; BYTE * PackedDibGetBitsPtr (BITMAPINFO * pPackedDib) ; int PackedDibGetBitsSize (BITMAPINFO * pPackedDib) ; HPALETTE PackedDibCreatePalette (BITMAPINFO * pPackedDib) ;

PACKEDIB.C /*------------------------------------------------------------------------- PACKEDIB.C -- Routines for using packed DIBs (c) Charles Petzold, 1998 ---------------------------------------------------------------------------*/ #include <windows.h> /*--------------------------------------------------------------------------- PackedDibLoad: Load DIB File as Packed-Dib Memory Block ----------------------------------------------------------------------------*/ BITMAPINFO * PackedDibLoad (PTSTR szFileName) { BITMAPFILEHEADER bmfh ; BITMAPINFO * pbmi ; BOOL bSuccess ; DWORD dwPackedDibSize, dwBytesRead ; HANDLE hFile ; // 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 packed DIB & read it in dwPackedDibSize = bmfh.bfSize - sizeof (BITMAPFILEHEADER) ; pbmi = malloc (dwPackedDibSize) ; bSuccess = ReadFile (hFile, pbmi, dwPackedDibSize, &dwBytesRead, NULL) ; CloseHandle (hFile) ; if (!bSuccess || (dwBytesRead != dwPackedDibSize)) { free (pbmi) ; return NULL ; } return pbmi ; } /*-------------------------------------------------------------------------- Functions to get information from packed DIB ----------------------------------------------------------------------------*/ int PackedDibGetWidth (BITMAPINFO * pPackedDib) { if (pPackedDib->bmiHeader.biSize == sizeof (BITMAPCOREHEADER)) return ((PBITMAPCOREINFO)pPackedDib)->bmciHeader.bcWidth ; else return pPackedDib->bmiHeader.biWidth ; } int PackedDibGetHeight (BITMAPINFO * pPackedDib) { if (pPackedDib->bmiHeader.biSize == sizeof (BITMAPCOREHEADER)) return ((PBITMAPCOREINFO)pPackedDib)->bmciHeader.bcHeight ; else return abs (pPackedDib->bmiHeader.biHeight) ; } int PackedDibGetBitCount (BITMAPINFO * pPackedDib) { if (pPackedDib->bmiHeader.biSize == sizeof (BITMAPCOREHEADER)) return ((PBITMAPCOREINFO)pPackedDib)->bmciHeader.bcBitCount ; else return pPackedDib->bmiHeader.biBitCount ; } int PackedDibGetRowLength (BITMAPINFO * pPackedDib) { return (( PackedDibGetWidth (pPackedDib) * PackedDibGetBitCount (pPackedDib) + 31) & ~31) >> 3 ; } /*--------------------------------------------------------------------------- PackedDibGetInfoHeaderSize includes possible color masks! ----------------------------------------------------------------------------*/ int PackedDibGetInfoHeaderSize (BITMAPINFO * pPackedDib) { if ( pPackedDib->bmiHeader.biSize == sizeof (BITMAPCOREHEADER)) return ((PBITMAPCOREINFO)pPackedDib)->bmciHeader.bcSize ; else if (pPackedDib->bmiHeader.biSize == sizeof (BITMAPINFOHEADER)) return pPackedDib->bmiHeader.biSize + (pPackedDib->bmiHeader.biCompression == BI_BITFIELDS ? 12 : 0) ; else return pPackedDib->bmiHeader.biSize ; } /*-------------------------------------------------------------------------- PackedDibGetColorsUsed returns value in information header; could be 0 to indicate non-truncated color table! ----------------------------------------------------------------------------*/ int PackedDibGetColorsUsed (BITMAPINFO * pPackedDib) { if (pPackedDib->bmiHeader.biSize == sizeof (BITMAPCOREHEADER)) return 0 ; else return pPackedDib->bmiHeader.biClrUsed ; } /*---------------------------------------------------------------------------- PackedDibGetNumColors is actual number of entries in color table -----------------------------------------------------------------------------*/ int PackedDibGetNumColors (BITMAPINFO * pPackedDib) { int iNumColors ; iNumColors = PackedDibGetColorsUsed (pPackedDib) ; if ( iNumColors == 0 && PackedDibGetBitCount (pPackedDib) < 16) iNumColors =1 << PackedDibGetBitCount (pPackedDib) ; return iNumColors ; } int PackedDibGetColorTableSize (BITMAPINFO * pPackedDib) { if (pPackedDib->bmiHeader.biSize == sizeof (BITMAPCOREHEADER)) return PackedDibGetNumColors (pPackedDib) * sizeof (RGBTRIPLE) ; else return PackedDibGetNumColors (pPackedDib) * sizeof (RGBQUAD) ; } RGBQUAD * PackedDibGetColorTablePtr (BITMAPINFO * pPackedDib) { if (PackedDibGetNumColors (pPackedDib) == 0) return 0 ; return (RGBQUAD *) (((BYTE *) pPackedDib) + PackedDibGetInfoHeaderSize (pPackedDib)) ; } RGBQUAD * PackedDibGetColorTableEntry (BITMAPINFO * pPackedDib, int i) { if ( PackedDibGetNumColors (pPackedDib) == 0) return 0 ; if ( pPackedDib->bmiHeader.biSize == sizeof (BITMAPCOREHEADER)) return (RGBQUAD *) (((RGBTRIPLE *) PackedDibGetColorTablePtr (pPackedDib)) + i) ; else return PackedDibGetColorTablePtr (pPackedDib) + i ; } /*-------------------------------------------------------------------------- PackedDibGetBitsPtr finally! ----------------------------------------------------------------------------*/ BYTE * PackedDibGetBitsPtr (BITMAPINFO * pPackedDib) { return ((BYTE *) pPackedDib)+ PackedDibGetInfoHeaderSize (pPackedDib) + PackedDibGetColorTableSize (pPackedDib) ; } /*----------------------------------------------------------------------------- PackedDibGetBitsSize can be calculated from the height and row length if it's not explicitly in the biSizeImage field -----------------------------------------------------------------------------*/ int PackedDibGetBitsSize (BITMAPINFO * pPackedDib) { if ((pPackedDib->bmiHeader.biSize != sizeof (BITMAPCOREHEADER)) && (pPackedDib->bmiHeader.biSizeImage != 0)) return pPackedDib->bmiHeader.biSizeImage ; return PackedDibGetHeight (pPackedDib) * PackedDibGetRowLength (pPackedDib) ; } /*--------------------------------------------------------------------------- PackedDibCreatePalette creates logical palette from PackedDib -----------------------------------------------------------------------------*/ HPALETTE PackedDibCreatePalette (BITMAPINFO * pPackedDib) { HPALETTE hPalette ; int i, iNumColors ; LOGPALETTE * plp ; RGBQUAD * prgb ; if (0 == ( iNumColors = PackedDibGetNumColors (pPackedDib))) return NULL ; plp = malloc (sizeof (LOGPALETTE) * (iNumColors - 1) * sizeof (PALETTEENTRY)) ; plp->palVersion = 0x0300 ; plp->palNumEntries = iNumColors ; for (i = 0 ; i < iNumColors ; i++) { prgb = PackedDibGetColorTableEntry (pPackedDib, i) ; plp->palPalEntry[i].peRed = prgb->rgbRed ; plp->palPalEntry[i].peGreen = prgb->rgbGreen ; plp->palPalEntry[i].peBlue = prgb->rgbBlue ; plp->palPalEntry[i].peFlags = 0 ; } hPalette = CreatePalette (plp) ; free (plp) ; return hPalette ; }

第一個函式是PackedDibLoad,它將唯一的參數作為檔案名,並傳回指向記憶體中packed DIB的指標。其他所有函式都將這個packed DIB指標作為它們的第一個參數並傳回有關DIB的資訊。這些函式按「由下而上」順序排列到檔案中。每個函式都使用從前面函式獲得的資訊。

我不傾向於說這是在處理packed DIB時有用的「完整」函式集。而且,我也不想彙編一個真正的擴展集,因為我不認為這是處理packed DIB的一個好方法。在寫類似下面的函式時,您會很明顯地發現這一點:

這種函式包括太多的巢狀函式呼叫,以致於效率非常低而且很慢。本章的後面將討論一種我認為更好的方法。

另外,您將注意到,其中許多函式都需要對OS/2相容的DIB採取不同的處理程序;這樣,函式將頻繁地檢查BITMAPINFO結構的第一個欄位是否與BITMAPCOREHEADER結構的大小相同。

特別注意最後一個函式PackedDibCreatePalette。這個函式用DIB中的顏色表來建立調色盤。如果DIB中沒有顏色表(這意味著DIB的每圖素有16、24或32位元),那麼就不建立調色盤。我們有時會將從DIB顏色表建立的調色盤稱為DIB 自己的 調色盤。

PACKEDIB檔案都放在SHOWDIB3,如程式16-13所示。

dwPixel = PackedDibGetPixel (pPackedDib, x, y) ;

程式16-13 SHOWDIB3 SHOWDIB3.C /*-------------------------------------------------------------------------- SHOWDIB3.C -- Displays DIB with native palette (c) Charles Petzold, 1998 ----------------------------------------------------------------------------*/ #include <windows.h> #include "PackeDib.h" #include "resource.h" LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ; TCHAR szAppName[] = TEXT ("ShowDib3") ; 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 ("Show DIB #3: Native Palette"), 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 ; } LRESULT CALLBACK WndProc ( HWND hwnd, UINT message, WPARAM wParam,LPARAM lParam) { static BITMAPINFO * pPackedDib ; static HPALETTE hPalette ; 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") ; HDC hdc ; 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 packed DIB, free the memory if (pPackedDib) { free (pPackedDib) ; pPackedDib = NULL ; } // If there's an existing logical palette, delete it if (hPalette) { DeleteObject (hPalette) ; hPalette = NULL ; } // Load the packed DIB into memory SetCursor (LoadCursor (NULL, IDC_WAIT)) ; ShowCursor (TRUE) ; pPackedDib = PackedDibLoad (szFileName) ; ShowCursor (FALSE) ; SetCursor (LoadCursor (NULL, IDC_ARROW)) ; if (pPackedDib) { // Create the palette from the DIB color table hPalette = PackedDibCreatePalette (pPackedDib) ; } else { MessageBox ( hwnd, TEXT ("Cannot load DIB file"), szAppName, 0) ; } InvalidateRect (hwnd, NULL, TRUE) ; return 0 ; } break ; case WM_PAINT: hdc = BeginPaint (hwnd, &ps) ; if (hPalette) { SelectPalette (hdc, hPalette, FALSE) ; RealizePalette (hdc) ; } if (pPackedDib) SetDIBitsToDevice (hdc, 0,0,PackedDibGetWidth (pPackedDib), PackedDibGetHeight (pPackedDib), 0,0,0,PackedDibGetHeight (pPackedDib), PackedDibGetBitsPtr (pPackedDib), pPackedDib, DIB_RGB_COLORS) ; EndPaint (hwnd, &ps) ; return 0 ; case WM_QUERYNEWPALETTE: if (!hPalette) return FALSE ; hdc = GetDC (hwnd) ; SelectPalette (hdc, hPalette, FALSE) ; RealizePalette (hdc) ; InvalidateRect (hwnd, NULL, TRUE) ; ReleaseDC (hwnd, hdc) ; return TRUE ; case WM_PALETTECHANGED: if (!hPalette || (HWND) wParam == hwnd) break ; hdc = GetDC (hwnd) ; SelectPalette (hdc, hPalette, FALSE) ; RealizePalette (hdc) ; UpdateColors (hdc) ; ReleaseDC (hwnd, hdc) ; break ; case WM_DESTROY: if (pPackedDib) free (pPackedDib) ; if (hPalette) DeleteObject (hPalette) ; PostQuitMessage (0) ; return 0 ; } return DefWindowProc (hwnd, message, wParam, lParam) ; }

SHOWDIB3.RC (摘錄) //Microsoft Developer Studio generated resource script. #include "resource.h" #include "afxres.h" ///////////////////////////////////////////////////////////////////////////// // Menu SHOWDIB3 MENU DISCARDABLE BEGIN POPUP "&File" BEGIN MENUITEM "&Open", IDM_FILE_OPEN END END

RESOURCE.H (摘錄) // Microsoft Developer Studio generated include file. // Used by ShowDib3.rc #define IDM_FILE_OPEN 40001

SHOWDIB3中的視窗訊息處理程式將packed DIB指標作為靜態變數來維護,視窗訊息處理程式在「File Open」命令期間呼叫PACKEDIB.C中的PackedDibLoad函式時獲得了此指標。在處理此命令的過程中,SHOWDIB3也呼叫PackedDibCreatePalette來獲得可能用於DIB的調色盤。注意,無論SHOWDIB3什麼時候準備載入新的DIB,都應先釋放前一個DIB的記憶體,並刪除前一個DIB的調色盤。在處理WM_DESTROY訊息的程序中,最後的DIB最後釋放,最後的調色盤最後刪除。

處理WM_PAINT訊息很簡單:如果存在調色盤,則SHOWDIB3將它選進裝置內容並顯現它。然後它呼叫SetDIBitsToDevice,並傳遞有關DIB的函式資訊(例如寬、高和指向DIB圖素位元的指標 ),這些資訊從PACKEDIB中的函式獲得。

另外,請記住SHOWDIB3依據DIB中的顏色表建立了調色盤。如果在DIB中沒有顏色表-通常是16位元、24位元和32位元DIB的情況-就不建立調色盤。在8位元顯示模式下顯示DIB時,它只能用標準保留的20種顏色顯示。

對這個問題有兩種解決方法:第一種是簡單地使用「通用」調色盤,這種調色盤適用於許多圖形。您也可以自己建立調色盤。第二種解決方法是分析DIB的圖素位元,並決定要顯示圖像的最佳顏色。很明顯,第二種方法將涉及更多的工作(對於程式寫作者和處理器都是如此),但是我將在本章結束之前告訴您如何使用第二種方法。

「通用」調色盤

程式16-14所示的SHOWDIB4程式建立了一個通用的調色盤,它用於顯示載入到程式中的所有DIB。另外,SHOWDIB4與SHOWDIB3非常相似。

程式16-14 SHOWDIB4 SHOWDIB4.C /*--------------------------------------------------------------------------- SHOWDIB4.C -- Displays DIB with "all-purpose" palette (c) Charles Petzold, 1998 -----------------------------------------------------------------------------*/ #include <windows.h> #include "..\\ShowDib3\\PackeDib.h" #include "resource.h" LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ; TCHAR szAppName[] = TEXT ("ShowDib4") ; 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 ("Show DIB #4: All-Purpose Palette"), 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 ; } /*----------------------------------------------------------------------------- CreateAllPurposePalette: Creates a palette suitable for a wide variety of images; the palette has 247 entries, but 15 of them are duplicates or match the standard 20 colors. -----------------------------------------------------------------------------*/ HPALETTE CreateAllPurposePalette (void) { HPALETTE hPalette ; int i, incr, R, G, B ; LOGPALETTE * plp ; plp = malloc (sizeof (LOGPALETTE) + 246 * sizeof (PALETTEENTRY)) ; plp->palVersion = 0x0300 ; plp->palNumEntries = 247 ; // The following loop calculates 31 gray shades, but 3 of them // will match the standard 20 colors for (i = 0, G = 0, incr = 8 ; G <= 0xFF ; i++, G += incr) { plp->palPalEntry[i].peRed = (BYTE) G ; plp->palPalEntry[i].peGreen = (BYTE) G ; plp->palPalEntry[i].peBlue = (BYTE) G ; plp->palPalEntry[i].peFlags = 0 ; incr = (incr == 9 ? 8 : 9) ; } // The following loop is responsible for 216 entries, but 8 of // them will match the standard 20 colors, and another // 4 of them will match the gray shades above. for (R = 0 ; R <= 0xFF ; R += 0x33) for (G = 0 ; G <= 0xFF ; G += 0x33) for (B = 0 ; B <= 0xFF ; B += 0x33) { plp->palPalEntry [i].peRed = (BYTE) R ; plp->palPalEntry [i].peGreen = (BYTE) G ; plp->palPalEntry [i].peBlue = (BYTE) B ; plp->palPalEntry [i].peFlags = 0 ; i++ ; } hPalette = CreatePalette (plp) ; free (plp) ; return hPalette ; } LRESULT CALLBACK WndProc ( HWND hwnd, UINT message, WPARAM wParam,LPARAM lParam) { static BITMAPINFO * pPackedDib ; static HPALETTE hPalette ; 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") ; HDC hdc ; 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 ; // Create the All-Purpose Palette hPalette = CreateAllPurposePalette () ; 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 packed DIB, free the memory if (pPackedDib) { free (pPackedDib) ; pPackedDib = NULL ; } // Load the packed DIB into memory SetCursor (LoadCursor (NULL, IDC_WAIT)) ; ShowCursor (TRUE) ; pPackedDib = PackedDibLoad (szFileName) ; ShowCursor (FALSE) ; SetCursor (LoadCursor (NULL, IDC_ARROW)) ; if (!pPackedDib) { MessageBox ( hwnd, TEXT ("Cannot load DIB file"), szAppName, 0) ; } InvalidateRect (hwnd, NULL, TRUE) ; return 0 ; } break ; case WM_PAINT: hdc = BeginPaint (hwnd, &ps) ; if (pPackedDib) { SelectPalette (hdc, hPalette, FALSE) ; RealizePalette (hdc) ; SetDIBitsToDevice (hdc,0,0,PackedDibGetWidth (pPackedDib), PackedDibGetHeight (pPackedDib), 0,0,0,PackedDibGetHeight (pPackedDib), PackedDibGetBitsPtr (pPackedDib), pPackedDib, DIB_RGB_COLORS) ; } EndPaint (hwnd, &ps) ; return 0 ; case WM_QUERYNEWPALETTE: hdc = GetDC (hwnd) ; SelectPalette (hdc, hPalette, FALSE) ; RealizePalette (hdc) ; InvalidateRect (hwnd, NULL, TRUE) ; ReleaseDC (hwnd, hdc) ; return TRUE ; case WM_PALETTECHANGED: if ((HWND) wParam != hwnd) hdc = GetDC (hwnd) ; SelectPalette (hdc, hPalette, FALSE) ; RealizePalette (hdc) ; UpdateColors (hdc) ; ReleaseDC (hwnd, hdc) ; break ; case WM_DESTROY: if (pPackedDib) free (pPackedDib) ; DeleteObject (hPalette) ; PostQuitMessage (0) ; return 0 ; } return DefWindowProc (hwnd, message, wParam, lParam) ; }

SHOWDIB4.RC (摘錄) //Microsoft Developer Studio generated resource script. #include "resource.h" #include "afxres.h" ///////////////////////////////////////////////////////////////////////////// // Menu SHOWDIB4 MENU DISCARDABLE BEGIN POPUP "&Open" BEGIN MENUITEM "&File", IDM_FILE_OPEN END END

RESOURCE.H (摘錄) // Microsoft Developer Studio generated include file. // Used by ShowDib4.rc #define IDM_FILE_OPEN 40001

在處理WM_CREATE訊息時,SHOWDIB4將呼叫CreateAllPurposePalette,並在程式中保留該調色盤,而在WM_DESTROY訊息處理期間刪除它。因為程式知道調色盤一定存在,所以在處理WM_PAINT、WM_QUERYNEWPALETTE或WM_PALETTECHANGED訊息時,不必檢查調色盤的存在。

CreateAllPurposePalette函式似乎是用247個項目來建立邏輯調色盤,它超出了系統調色盤中允許程式正常存取的236個項目。的確如此,不過這樣做很方便。這些項目中有15個被複製或者映射到20種標準的保留顏色中。

CreateAllPurposePalette從建立31種灰階開始,即0x00、0x09、0x11、0x1A、0x22、0x2B、0x33、0x3C、0x44、0x4D、0x55、0x5E、0x66、0x6F、0x77、0x80、0x88、0x91、0x99、0xA2、0xAA、0xB3、0xBB、0xC4、0xCC、0xD5、0xDD、0xE6、0xEE、0xF9和0xFF的紅色、綠色和藍色值。注意,第一個、最後一個和中間的項目都在標準的20種保留顏色中。下一個函式用紅色、綠色和藍色值的所有組合建立了顏色0x00、0x33、0x66、0x99、0xCC和0xFF。這樣就共有216種顏色,但是其中8種顏色複製了標準的20種保留顏色,而另外4個複製了前面計算的灰階。如果將PALETTEENTRY結構的peFlags欄位設為0,則Windows將不把複製的項目放進系統調色盤。

顯然地,實際的程式不希望計算16位元、24位元或者32位元DIB的最佳調色盤,程式將繼續使用DIB顏色表來顯示8位元DIB。SHOWDIB4不完成這項工作,它只對每件事都使用通用調色盤。因為SHOWDIB4是一個展示程式,而且您可以與SHOWDIB3顯示的8位元DIB進行比較。如果看一些人像的彩色DIB,那麼您可能會得出這樣的結論:SHOWDIB4沒有足夠的顏色來精確地表示鮮豔的色調。

如果用SHOWDIB4中的CreateAllPurposePalette函式來試驗(可能是通過將邏輯調色盤的大小減少到只有幾個項目的方法),您將發現當調色盤選進裝置內容時,Windows將只使用調色盤中的顏色,而不使用標準的20種顏色調色盤的顏色。

中間色調色盤

Windows API包括一個通用調色盤,程式可以通過呼叫CreateHalftonePalette來獲得該調色盤。使用此調色盤的方法與使用從SHOWDIB4中的CreateAllPurposePalette獲得調色盤的方法相同,或者您也可以與點陣圖縮放模式中的HALFTONE設定-用SetStretchBltMode設定-一起使用。程式16-15所示的SHOWDIB5程式展示了使用中間色調色盤的方法。

程式16-15 SHOWDIB5 SHOWDIB5.C /*-------------------------------------------------------------------------- SHOWDIB5.C -- Displays DIB with halftone palette (c) Charles Petzold, 1998 --------------------------------------------------------------------------*/ #include <windows.h> #include "..\\ShowDib3\\PackeDib.h" #include "resource.h" LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ; TCHAR szAppName[] = TEXT ("ShowDib5") ; 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 ("Show DIB #5: Halftone Palette"), 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 ; } LRESULT CALLBACK WndProc ( HWND hwnd, UINT message, WPARAM wParam,LPARAM lParam) { static BITMAPINFO * pPackedDib ; static HPALETTE hPalette ; 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") ; HDC hdc ; 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 ; // Create the All-Purpose Palette hdc = GetDC (hwnd) ; hPalette = CreateHalftonePalette (hdc) ; ReleaseDC (hwnd, hdc) ; 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 packed DIB, free the memory if (pPackedDib) { free (pPackedDib) ; pPackedDib = NULL ; } // Load the packed DIB into memory SetCursor (LoadCursor (NULL, IDC_WAIT)) ; ShowCursor (TRUE) ; pPackedDib = PackedDibLoad (szFileName) ; ShowCursor (FALSE) ; SetCursor (LoadCursor (NULL, IDC_ARROW)) ; if (!pPackedDib) { MessageBox ( hwnd, TEXT ("Cannot load DIB file"), szAppName, 0) ; } InvalidateRect (hwnd, NULL, TRUE) ; return 0 ; } break ; case WM_PAINT: hdc = BeginPaint (hwnd, &ps) ; if (pPackedDib) { // Set halftone stretch mode SetStretchBltMode (hdc, HALFTONE) ; SetBrushOrgEx (hdc, 0, 0, NULL) ; // Select and realize halftone palette SelectPalette (hdc, hPalette, FALSE) ; RealizePalette (hdc) ; // StretchDIBits rather than SetDIBitsToDevice StretchDIBits ( hdc,0,0,PackedDibGetWidth (pPackedDib), PackedDibGetHeight (pPackedDib), 0,0,PackedDibGetWidth (pPackedDib), PackedDibGetHeight (pPackedDib), PackedDibGetBitsPtr (pPackedDib), pPackedDib, DIB_RGB_COLORS, SRCCOPY) ; } EndPaint (hwnd, &ps) ; return 0 ; case WM_QUERYNEWPALETTE: hdc = GetDC (hwnd) ; SelectPalette (hdc, hPalette, FALSE) ; RealizePalette (hdc) ; InvalidateRect (hwnd, NULL, TRUE) ; ReleaseDC (hwnd, hdc) ; return TRUE ; case WM_PALETTECHANGED: if ((HWND) wParam != hwnd) hdc = GetDC (hwnd) ; SelectPalette (hdc, hPalette, FALSE) ; RealizePalette (hdc) ; UpdateColors (hdc) ; ReleaseDC (hwnd, hdc) ; break ; case WM_DESTROY: if (pPackedDib) free (pPackedDib) ; DeleteObject (hPalette) ; PostQuitMessage (0) ; return 0 ; } return DefWindowProc (hwnd, message, wParam, lParam) ; }

SHOWDIB5.RC (摘錄) //Microsoft Developer Studio generated resource script. #include "resource.h" #include "afxres.h" ///////////////////////////////////////////////////////////////////////////// // Menu SHOWDIB5 MENU DISCARDABLE BEGIN POPUP "&Open" BEGIN MENUITEM "&File", IDM_FILE_OPEN END END

RESOURCE.H (摘錄) // Microsoft Developer Studio generated include file. // Used by ShowDib5.rc #define IDM_FILE_OPEN 40001

SHOWDIB5程式類似於SHOWDIB4,SHOWDIB4中不使用DIB中的顏色表,而使用適用於圖像範圍更大的調色盤。為此,SHOWDIB5使用了由Windows支援的邏輯調色盤,其代號可以從CreateHalftonePalette函式獲得。

中間色調色盤並不比SHOWDIB4中的CreateAllPurposePalette函式所建立的調色盤更複雜。的確,如果只是拿來自用,結果是相似的。然而,如果您呼叫下面兩個函式:

其中,x和y是DIB左上角的裝置座標,並且如果您用StretchDIBits而不是SetDIBitsToDevice來顯示DIB,那麼結果會讓您吃驚:顏色色調要比不設定點陣圖縮放模式來使用CreateAllPurposePalette或者CreateHalftonePalette更精確。Windows使用一種混色圖案來處理中間色調色盤上的顏色,以使其更接近8位元顯示卡上原始圖像的顏色。與您所想像的一樣,這樣做的缺點是需要更多的處理時間。

索引調色盤顏色

現在開始處理SetDIBitsToDevice、StretchDIBits、CreateDIBitmap、SetDIBits、GetDIBits和CreateDIBSection的fClrUse參數。通常,您將這個參數設定為DIB_RGB_COLORS(等於0)。不過,您也能將它設定為DIB_PAL_COLORS。在這種情況下,假定BITMAPINFO結構中的顏色表不包括RGB顏色值,而是包括邏輯調色盤中顏色項目的16位元索引。邏輯調色盤是作為第一個參數傳遞給函式的裝置內容中目前選擇的那個。實際上,在CreateDIBSection中,之所以需要指定一個非NULL的裝置內容代號作為第一個參數,只是因為使用了DIB_PAL_COLORS。

DIB_PAL_COLORS能為您做些什麼呢?它可能提高一些性能。考慮一下在8位元顯示模式下呼叫SetDIBitsToDevice顯示的8位元DIB。Windows首先必須在DIB顏色表的所有顏色中搜索與設備可用顏色最接近的顏色。然後設定一個小表,以便將DIB圖素值映射到設備圖素。也就是說,最多需要搜索256次最接近的顏色。但是如果DIB顏色表中含有從裝置內容中選擇顏色的邏輯調色盤項目索引,那麼就可能跳過搜索。

除了使用調色盤索引以外,程式16-16所示的SHOWDIB6程式與SHOWDIB3相似。

SetStretchBltMode (hdc, HALFTONE) ; SetBrushOrgEx (hdc, x, y, NULL) ;

程式16-16 SHOWDIB6 SHOWDIB6.C /*--------------------------------------------------------------------------- SHOWDIB6.C -- Display DIB with palette indices (c) Charles Petzold, 1998 ----------------------------------------------------------------------------*/ #include <windows.h> #include "..\\ShowDib3\\PackeDib.h" #include "resource.h" LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ; TCHAR szAppName[] = TEXT ("ShowDib6") ; 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 ("Show DIB #6: Palette Indices"), 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 ; } LRESULT CALLBACK WndProc ( HWND hwnd, UINT message, WPARAM wParam,LPARAM lParam) { static BITMAPINFO * pPackedDib ; static HPALETTE hPalette ; 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") ; HDC hdc ; int i, iNumColors ; PAINTSTRUCT ps ; WORD * pwIndex ; 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 packed DIB, free the memory if (pPackedDib) { free (pPackedDib) ; pPackedDib = NULL ; } // If there's an existing logical palette, delete it if (hPalette) { DeleteObject (hPalette) ; hPalette = NULL ; } // Load the packed DIB into memory SetCursor (LoadCursor (NULL, IDC_WAIT)) ; ShowCursor (TRUE) ; pPackedDib = PackedDibLoad (szFileName) ; ShowCursor (FALSE) ; SetCursor (LoadCursor (NULL, IDC_ARROW)) ; if (pPackedDib) { // Create the palette from the DIB color table hPalette = PackedDibCreatePalette (pPackedDib) ; // Replace DIB color table with indices if (hPalette) { iNumColors = PackedDibGetNumColors (pPackedDib) ; pwIndex = (WORD *) PackedDibGetColorTablePtr (pPackedDib) ; for (i = 0 ; i < iNumColors ; i++) pwIndex[i] = (WORD) i ; } } else { MessageBox ( hwnd, TEXT ("Cannot load DIB file"), szAppName, 0) ; } InvalidateRect (hwnd, NULL, TRUE) ; return 0 ; } break ; case WM_PAINT: hdc = BeginPaint (hwnd, &ps) ; if (hPalette) { SelectPalette (hdc, hPalette, FALSE) ; RealizePalette (hdc) ; } if (pPackedDib) SetDIBitsToDevice (hdc,0,0,PackedDibGetWidth (pPackedDib), PackedDibGetHeight (pPackedDib), 0,0,0,PackedDibGetHeight (pPackedDib), PackedDibGetBitsPtr (pPackedDib), pPackedDib, DIB_PAL_COLORS) ; EndPaint (hwnd, &ps) ; return 0 ; case WM_QUERYNEWPALETTE: if (!hPalette) return FALSE ; hdc = GetDC (hwnd) ; SelectPalette (hdc, hPalette, FALSE) ; RealizePalette (hdc) ; InvalidateRect (hwnd, NULL, TRUE) ; ReleaseDC (hwnd, hdc) ; return TRUE ; case WM_PALETTECHANGED: if (!hPalette || (HWND) wParam == hwnd) break ; hdc = GetDC (hwnd) ; SelectPalette (hdc, hPalette, FALSE) ; RealizePalette (hdc) ; UpdateColors (hdc) ; ReleaseDC (hwnd, hdc) ; break ; case WM_DESTROY: if (pPackedDib) free (pPackedDib) ; if (hPalette) DeleteObject (hPalette) ; PostQuitMessage (0) ; return 0 ; } return DefWindowProc (hwnd, message, wParam, lParam) ; }

SHOWDIB6.RC (摘錄) //Microsoft Developer Studio generated resource script. #include "resource.h" #include "afxres.h" ///////////////////////////////////////////////////////////////////////////// // Menu SHOWDIB6 MENU DISCARDABLE BEGIN POPUP "&File" BEGIN MENUITEM "&Open", IDM_FILE_OPEN END END

RESOURCE.H (摘錄) // Microsoft Developer Studio generated include file. // Used by ShowDib6.rc // #define IDM_FILE_OPEN 40001

SHOWDIB6將DIB載入到記憶體並由此建立了調色盤以後,SHOWDIB6簡單地用以0開始的WORD索引替換了DIB顏色表中的顏色。PackedDibGetNumColors函式將表示有多少種顏色,而PackedDibGetColorTablePtr函式傳回指向DIB顏色表起始位置的指標。

注意,只有直接從DIB顏色表來建立調色盤時,此技術才可行。如果使用通用調色盤,則必須搜索最接近的顏色,以獲得放入DIB的索引。

如果要使用調色盤索引,那麼請在將DIB儲存到磁片之前,確實替換掉DIB中的顏色表。另外,不要將包含調色盤索引的DIB放入剪貼簿。實際上,在顯示之前,將調色盤索引放入DIB,然後將RGB顏色值放回,會更安全一些。

調色盤和點陣圖物件

程式16-17中的SHOWDIB7程式顯示了如何使用與DIB相關聯的調色盤,這些DIB是使用CreateDIBitmap函式轉換成GDI點陣圖物件的。

程式16-17 SHOWDIB7 SHOWDIB7.C /*-------------------------------------------------------------------------- SHOWDIB7.C -- Shows DIB converted to DDB (c) Charles Petzold, 1998 ----------------------------------------------------------------------------*/ #include <windows.h> #include "..\\ShowDib3\\PackeDib.h" #include "resource.h" LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ; TCHAR szAppName[] = TEXT ("ShowDib7") ; 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 ("Show DIB #7: Converted to DDB"), 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 ; } LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { static HBITMAP hBitmap ; static HPALETTE hPalette ; 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 ; BITMAPINFO * pPackedDib ; 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 packed DIB, free the memory if (hBitmap) { DeleteObject (hBitmap) ; hBitmap = NULL ; } // If there's an existing logical palette, delete it if (hPalette) { DeleteObject (hPalette) ; hPalette = NULL ; } // Load the packed DIB into memory SetCursor (LoadCursor (NULL, IDC_WAIT)) ; ShowCursor (TRUE) ; pPackedDib = PackedDibLoad (szFileName) ; ShowCursor (FALSE) ; SetCursor (LoadCursor (NULL, IDC_ARROW)) ; if (pPackedDib) { // Create palette from the DIB and select it into DC hPalette = PackedDibCreatePalette (pPackedDib) ; hdc = GetDC (hwnd) ; if (hPalette) { SelectPalette (hdc, hPalette, FALSE) ; RealizePalette (hdc) ; } // Create the DDB from the DIB hBitmap = CreateDIBitmap(hdc,(PBITMAPINFOHEADER) pPackedDib, CBM_INIT,PackedDibGetBitsPtr (pPackedDib), pPackedDib, DIB_RGB_COLORS) ; ReleaseDC (hwnd, hdc) ; // Free the packed-DIB memory free (pPackedDib) ; } else { MessageBox ( hwnd, TEXT ("Cannot load DIB file"), szAppName, 0) ; } InvalidateRect (hwnd, NULL, TRUE) ; return 0 ; } break ; case WM_PAINT: hdc = BeginPaint (hwnd, &ps) ; if (hPalette) { SelectPalette (hdc, hPalette, FALSE) ; RealizePalette (hdc) ; } 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_QUERYNEWPALETTE: if (!hPalette) return FALSE ; hdc = GetDC (hwnd) ; SelectPalette (hdc, hPalette, FALSE) ; RealizePalette (hdc) ; InvalidateRect (hwnd, NULL, TRUE) ; ReleaseDC (hwnd, hdc) ; return TRUE ; case WM_PALETTECHANGED: if (!hPalette || (HWND) wParam == hwnd) break ; hdc = GetDC (hwnd) ; SelectPalette (hdc, hPalette, FALSE) ; RealizePalette (hdc) ; UpdateColors (hdc) ; ReleaseDC (hwnd, hdc) ; break ; case WM_DESTROY: if (hBitmap) DeleteObject (hBitmap) ; if (hPalette) DeleteObject (hPalette) ; PostQuitMessage (0) ; return 0 ; } return DefWindowProc (hwnd, message, wParam, lParam) ; }

SHOWDIB7.RC (摘錄) //Microsoft Developer Studio generated resource script. #include "resource.h" #include "afxres.h" ///////////////////////////////////////////////////////////////////////////// // Menu SHOWDIB7 MENU DISCARDABLE BEGIN POPUP "&File" BEGIN MENUITEM "&Open", IDM_FILE_OPEN END END

RESOURCE.H (摘錄) // Microsoft Developer Studio generated include file. // Used by ShowDib7.rc #define IDM_FILE_OPEN 40001

與前面的程式一樣,SHOWDIB7獲得了一個指向packed DIB的指標,該DIB回應功能表的「File」、「Open」命令。程式從packed DIB建立了調色盤,然後-還是在WM_COMMAND訊息的處理過程中-獲得了用於視訊顯示的裝置內容,並選進調色盤,顯現調色盤。然後SHOWDIB7呼叫CreateDIBitmap以便從DIB建立DDB。如果調色盤沒有選進裝置內容並顯現,那麼CreateDIBitmap建立的DDB將不使用邏輯調色盤中的附加顏色。

呼叫CreateDIBitmap以後,該程式將釋放packed DIB佔用的記憶體空間。pPackedDib變數不是靜態變數。相反的,SHOWDIB7按靜態變數保留了點陣圖代號(hBitmap)和邏輯調色盤代號(hPalette)。

在WM_PAINT訊息處理期間,調色盤再次選進裝置內容並顯現。GetObject函式可獲得點陣圖的寬度和高度。然後,程式通過建立相容的記憶體裝置內容在顯示區域顯示點陣圖,選進點陣圖,並執行BitBlt。顯示DDB時所用的調色盤,必須與從CreateDIBitmap呼叫建立時所用的一樣。

如果將點陣圖複製到剪貼簿,則最好使用packed DIB格式。然後Windows可以將點陣圖物件提供給希望使用這些點陣圖的程式。然而,如果需要將點陣圖物件複製到剪貼簿,則首先要獲得視訊裝置內容並顯現調色盤。這允許Windows依據目前的系統調色盤將DDB轉換為DIB。

調色盤和DIB區塊

最後,程式16-18所示的SHOWDIB8說明了如何使用帶有DIB區塊的調色盤。

程式16-18 SHOWDIB8 SHOWDIB8.C /*-------------------------------------------------------------------------- SHOWDIB8.C -- Shows DIB converted to DIB section (c) Charles Petzold, 1998 ---------------------------------------------------------------------------*/ #include <windows.h> #include "..\\ShowDib3\\PackeDib.h" #include "resource.h" LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ; TCHAR szAppName[] = TEXT ("ShowDib8") ; 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 ("Show DIB #8: DIB Section"), 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 ; } LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { static HBITMAP hBitmap ; static HPALETTE hPalette ; static int cxClient, cyClient ; static OPENFILENAME ofn ; static PBYTE pBits ; 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 ; BITMAPINFO * pPackedDib ; 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 packed DIB, free the memory if (hBitmap) { DeleteObject (hBitmap) ; hBitmap = NULL ; } // If there's an existing logical palette, delete it if (hPalette) { DeleteObject (hPalette) ; hPalette = NULL ; } // Load the packed DIB into memory SetCursor (LoadCursor (NULL, IDC_WAIT)) ; ShowCursor (TRUE) ; pPackedDib = PackedDibLoad (szFileName) ; ShowCursor (FALSE) ; SetCursor (LoadCursor (NULL, IDC_ARROW)) ; if (pPackedDib) { // Create the DIB section from the DIB hBitmap = CreateDIBSection (NULL,pPackedDib,DIB_RGB_COLORS,&pBits,NULL, 0) ; // Copy the bits CopyMemory (pBits, PackedDibGetBitsPtr (pPackedDib), PackedDibGetBitsSize (pPackedDib)) ; // Create palette from the DIB hPalette = PackedDibCreatePalette (pPackedDib) ; // Free the packed-DIB memory free (pPackedDib) ; } else { MessageBox ( hwnd, TEXT ("Cannot load DIB file"), szAppName, 0) ; } InvalidateRect (hwnd, NULL, TRUE) ; return 0 ; } break ; case WM_PAINT: hdc = BeginPaint (hwnd, &ps) ; if (hPalette) { SelectPalette (hdc, hPalette, FALSE) ; RealizePalette (hdc) ; } 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_QUERYNEWPALETTE: if (!hPalette) return FALSE ; hdc = GetDC (hwnd) ; SelectPalette (hdc, hPalette, FALSE) ; RealizePalette (hdc) ; InvalidateRect (hwnd, NULL, TRUE) ; ReleaseDC (hwnd, hdc) ; return TRUE ; case WM_PALETTECHANGED: if (!hPalette || (HWND) wParam == hwnd) break ; hdc = GetDC (hwnd) ; SelectPalette (hdc, hPalette, FALSE) ; RealizePalette (hdc) ; UpdateColors (hdc) ; ReleaseDC (hwnd, hdc) ; break ; case WM_DESTROY: if (hBitmap) DeleteObject (hBitmap) ; if (hPalette) DeleteObject (hPalette) ; PostQuitMessage (0) ; return 0 ; } return DefWindowProc (hwnd, message, wParam, lParam) ; }

SHOWDIB8.RC (摘錄) //Microsoft Developer Studio generated resource script. #include "resource.h" #include "afxres.h" ///////////////////////////////////////////////////////////////////////////// // Menu SHOWDIB8 MENU DISCARDABLE BEGIN POPUP "&File" BEGIN MENUITEM "&Open", IDM_FILE_OPEN END END

RESOURCE.H (摘錄) // Microsoft Developer Studio generated include file. // Used by ShowDib8.rc #define IDM_FILE_OPEN 40001

在SHOWDIB7和SHOWDIB8中的WM_PAINT處理是一樣的:兩個程式都將點陣圖代號(hBitmap)和邏輯調色盤代號(hPalette)作為靜態變數。調色盤被選進裝置內容並顯現,點陣圖的寬度和高度從GetObject函式獲得,程式建立記憶體裝置內容並選進點陣圖,然後通過呼叫BitBlt將點陣圖顯示到顯示區域。

兩個程式之間最大的差別在於處理「File」、「Open」功能表命令的程序。在獲得指向packed DIB的指標並建立了調色盤以後,SHOWDIB7必須將調色盤選進視訊裝置內容,並在呼叫CreateDIBitmap之前顯現。SHOWDIB8在獲得packed DIB指標以後呼叫CreateDIBSection。不必將調色盤選進裝置內容,這是因為CreateDIBSection不將DIB轉換成設備相關的格式。的確,CreateDIBSection的第一個參數(即裝置內容代號)的唯一用途在於您是否使用DIB_PAL_COLORS旗標。

呼叫CreateDIBSection以後,SHOWDIB8將圖素位元從packed DIB複製到從CreateDIBSection函式傳回的記憶體位置,然後呼叫PackedDibCreatePalette。儘管此函式便於程式使用,但是SHOWDIB8將依據從GetDIBColorTable函式傳回的資訊建立調色盤。

DIB處理程式庫

就是現在-經過我們長時間地學習GDI點陣圖物件、裝置無關點陣圖、DIB區塊和Windows調色盤管理器之後-我們才做好了開發一套有助於處理點陣圖的函式的準備。

前面的PACKEDIB檔案展示了一種可能的方法:記憶體中的packed DIB只用指向它的指標表示。程式所的有關DIB的全部資訊都可以從存取表頭資訊結構的函式獲得。然而,實際上到「get pixel」和「set pixel」常式時,這種方法就會產生嚴重的執行問題。圖像處理任務當然需要存取點陣圖位元,並且這些函式也應該盡可能地快。

可能的C++的解決方式中包括建立DIB類別,這時指向packed DIB的指標正好是一個成員變數。其他成員變數和成員函式有助於更快地執行獲得和設定DIB中的圖素的常式。不過,因為我在第一章已經指出,對於本書您只需要瞭解C,使用C++將是其他書的範圍。

當然,用C++能做的事情用C也能做。一個好的例子就是許多Windows函式都使用代號。除了將代號當作數值以外,應用程式對它還瞭解什麼呢?程式知道代號引用特殊的函式物件,還知道函式用於處理現存的物件。顯然,作業系統按某種方式用代號來引用物件的內部資訊。代號可以與結構指標一樣簡單。

例如,假設有一個函式集,這些函式都使用一個稱為HDIB的代號。HDIB是什麼呢?它可能在某個表頭檔案中定義如下:

此定義用「不關您的事」回答了「HDIB是什麼」這個問題。

然而,實際上HDIB可能是結構指標,該結構不僅包括指向packed DIB的指標,還包括其他資訊:

typedef void * HDIB ;

typedef struct { BITMAPINFO * pPackedDib ; int cx, cy, cBitsPerPixel, cBytesPerRow ; BYTE * pBits ; { DIBSTRUCTURE, * PDIBSTRUCTURE ;

此結構的其他五個欄位包括從packed DIB中引出的資訊。當然,結構中這些值允許更快速地存取它們。不同的DIB程式庫函式都可以處理這個結構,而不是pPackedDib指標。可以按下面的方法來執行DibGetPixelPointer函式:

BYTE * DibGetPixelPointer (HDIB hdib, int x, int y) { PDIBSTRUCTURE pdib = hdib ; return pdib->pBits + y * pdib->cBytesPerRow + x * pdib->cBitsPerPixel / 8 ; }

當然,這種方法可能要比PACKEDIB.C中執行「get pixel」常式快。

由於這種方法非常合理,所以我決定放棄packed DIB,並改用處理DIB區塊的DIB程式庫。這實際上使我們對packed DIB的處理有更大的彈性(也就是說,能夠在裝置無關的方式下操縱DIB圖素位元),而且在Windows NT下執行時將更有效。

DIBSTRUCT結構

DIBHELP.C檔案-如此命名是因為對處理DIB提供幫助-有上千行,並在幾個小部分中顯示。但是首先讓我們看一下DIBHELP函式所處理的結構,該結構在DIBHELP.C中定義如下:

typedef struct { PBYTE * ppRow ; // array of row pointers int iSignature ; // = "Dib " HBITMAP hBitmap ; // handle returned from CreateDIBSection BYTE * pBits ; // pointer to bitmap bits DIBSECTION ds ; // DIBSECTION structure int iRShift[3] ; // right-shift values for color masks int iLShift[3] ; // left-shift values for color masks } DIBSTRUCT, * PDIBSTRUCT ;

現在跳過第一個欄位。它之所以為第一個欄位是因為它使某些巨集更易於使用-在討論完其他欄位以後再來理解第一個欄位就更容易了。

在DIBHELP.C中,當DIB建立的函式首先設定了此結構時,第二個欄位就設定為文字字串「Dib」的二進位值。通過一些DIBHELP函式,第二個欄位將用於結構有效指標的一個標記。

第三個欄位,即hBitmap,是從CreateDIBSection函式傳回的點陣圖代號。您將想起該代號可有多種使用方式,它與我們在 第十四章 遇到的GDI點陣圖物件的代號用法一樣。不過,從CreateDIBSection傳回的代號將涉及按裝置無關格式儲存的點陣圖,該點陣圖格式一直儲存到通過呼叫BitBlt和StretchBlt來將位元圖畫到輸出設備。

DIBSTRUCT的第四個欄位是指向點陣圖位元的指標。此值也可由CreateDIBSection函式設定。您將想起,作業系統將控制這個記憶體塊,但應用程式有存取它的許可權。在刪除點陣圖代號時,記憶體塊將自動釋放。

DIBSTRUCT的第五個欄位是DIBSECTION結構。如果您有從CreateDIBSection傳回的點陣圖代號,那麼您可以將代號傳遞給GetObject函式以獲得有關DIBSECTION結構中的點陣圖資訊:

作為提示,DIBSECTION結構在WINGDI.H中定義如下:

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

typedef struct tagDIBSECTION { BITMAP dsBm ; BITMAPINFOHEADER dsBmih ; DWORD dsBitfields[3] ; // Color masks HANDLE dshSection ; DWORD dsOffset ; } DIBSECTION, * PDIBSECTION ;

第一個欄位是BITMAP結構,它與CreateBitmapIndirect一起建立點陣圖物件,與GetObject一起傳回關於DDB的資訊。第二個欄位是BITMAPINFOHEADER結構。不管點陣圖資訊結構是否傳遞給CreateDIBSection函式,DIBSECTION結構總有BITMAPINFOHEADER結構而不是其他結構,例如BITMAPCOREHEADER結構。這意味著在存取此結構時,DIBHELP.C中的許多函式都不必檢查與OS/2相容的DIB。

對於16位元和32位元的DIB,如果BITMAPINFOHEADER結構的biCompression欄位是BI_BITFIELDS,那麼在資訊表頭結構後面通常有三個遮罩值。這些遮罩值決定如何將16位元和32位圖素值轉換成RGB顏色。遮罩儲存在DIBSECTION結構的第三個欄位中。

DIBSECTION結構的最後兩個欄位指的是DIB區塊,此區塊由檔案映射建立。DIBHELP不使用CreateDIBSection的這個特性,因此可以忽略這些欄位。

DIBSTRUCT的最後兩個欄位儲存左右移位值,這些值用於處理16位元和32位元DIB的顏色遮罩。我們將在 第十五章 討論這些移位值。

讓我們再回來看一下DIBSTRUCT的第一個欄位。正如我們所看到的一樣,在開始建立DIB時,此欄位設定為指向一個指標陣列的指標,該陣列中的每個指標都指向DIB中的一行圖素。這些指標允許以更快的方式來獲得DIB圖素位元,同時也被定義,以便頂行可以首先引用DIB圖素位元。此陣列的最後一個元素-引用DIB圖像的最底行-通常等於DIBSTRUCT的pBits欄位。

資訊函式

DIBHELP.C以定義DIBSTRUCT結構開始,然後提供一個函式集,此函式集允許應用程式獲得有關DIB區塊的資訊。程式16-19顯示了DIBHELP.C的第一部分。

程式16-19 DIBHELP.C檔案的第一部分 DIBHELP.C (第一部分) /*-------------------------------------------------------------------------- DIBHELP.C -- DIB Section Helper Routines (c) Charles Petzold, 1998 ----------------------------------------------------------------------------*/ #include <windows.h> #include "dibhelp.h" #define HDIB_SIGNATURE (* (int *) "Dib ") typedef struct { PBYTE * ppRow ; // must be first field for macros! int iSignature ; HBITMAP hBitmap ; BYTE * pBits ; DIBSECTION ds ; int iRShift[3] ; int iLShift[3] ; } DIBSTRUCT, * PDIBSTRUCT ; /*---------------------------------------------------------------------------- DibIsValid: Returns TRUE if hdib points to a valid DIBSTRUCT -----------------------------------------------------------------------------*/ BOOL DibIsValid (HDIB hdib) { PDIBSTRUCT pdib = hdib ; if (pdib == NULL) return FALSE ; if (IsBadReadPtr (pdib, sizeof (DIBSTRUCT))) return FALSE ; if (pdib->iSignature != HDIB_SIGNATURE) return FALSE ; return TRUE ; } /*---------------------------------------------------------------------------- DibBitmapHandle: Returns the handle to the DIB section bitmap object -----------------------------------------------------------------------------*/ HBITMAP DibBitmapHandle (HDIB hdib) { if (!DibIsValid (hdib)) return NULL ; return ((PDIBSTRUCT) hdib)->hBitmap ; } /*--------------------------------------------------------------------------- DibWidth: Returns the bitmap pixel width -----------------------------------------------------------------------------*/ int DibWidth (HDIB hdib) { if (!DibIsValid (hdib)) return 0 ; return ((PDIBSTRUCT) hdib)->ds.dsBm.bmWidth ; } /*--------------------------------------------------------------------------- DibHeight: Returns the bitmap pixel height ----------------------------------------------------------------------------*/ int DibHeight (HDIB hdib) { if (!DibIsValid (hdib)) return 0 ; return ((PDIBSTRUCT) hdib)->ds.dsBm.bmHeight ; } /*--------------------------------------------------------------------------- DibBitCount: Returns the number of bits per pixel ----------------------------------------------------------------------------*/ int DibBitCount (HDIB hdib) { if (!DibIsValid (hdib)) return 0 ; return ((PDIBSTRUCT) hdib)->ds.dsBm.bmBitsPixel ; } /*---------------------------------------------------------------------------- DibRowLength: Returns the number of bytes per row of pixels -----------------------------------------------------------------------------*/ int DibRowLength (HDIB hdib) { if (!DibIsValid (hdib)) return 0 ; return 4 * ((DibWidth (hdib) * DibBitCount (hdib) + 31) / 32) ; } /*--------------------------------------------------------------------------- DibNumColors: Returns the number of colors in the color table ----------------------------------------------------------------------------*/ int DibNumColors (HDIB hdib) { PDIBSTRUCT pdib = hdib ; if (!DibIsValid (hdib)) return 0 ; if (pdib->ds.dsBmih.biClrUsed != 0) { return pdib->ds.dsBmih.biClrUsed ; } else if (DibBitCount (hdib) <= 8) { return 1 << DibBitCount (hdib) ; } return 0 ; } /*--------------------------------------------------------------------------- DibMask: Returns one of the color masks ---------------------------------------------------------------------------*/ DWORD DibMask (HDIB hdib, int i) { PDIBSTRUCT pdib = hdib ; if (!DibIsValid (hdib) || i < 0 || i > 2) return 0 ; return pdib->ds.dsBitfields[i] ; } /*---------------------------------------------------------------------------- DibRShift: Returns one of the right-shift values -----------------------------------------------------------------------------*/ int DibRShift (HDIB hdib, int i) { PDIBSTRUCT pdib = hdib ; if (!DibIsValid (hdib) || i < 0 || i > 2) return 0 ; return pdib->iRShift[i] ; } /*---------------------------------------------------------------------------- DibLShift: Returns one of the left-shift values ----------------------------------------------------------------------------*/ int DibLShift (HDIB hdib, int i) { PDIBSTRUCT pdib = hdib ; if (!DibIsValid (hdib) || i < 0 || i > 2) return 0 ; return pdib->iLShift[i] ; } /*--------------------------------------------------------------------------- DibCompression: Returns the value of the biCompression field ----------------------------------------------------------------------------*/ int DibCompression (HDIB hdib) { if (!DibIsValid (hdib)) return 0 ; return ((PDIBSTRUCT) hdib)->ds.dsBmih.biCompression ; } /*--------------------------------------------------------------------------- DibIsAddressable: Returns TRUE if the DIB is not compressed ----------------------------------------------------------------------------*/ BOOL DibIsAddressable (HDIB hdib) { int iCompression ; if (!DibIsValid (hdib)) return FALSE ; iCompression = DibCompression (hdib) ; if ( iCompression == BI_RGB || iCompression == BI_BITFIELDS) return TRUE ; return FALSE ; } /*--------------------------------------------------------------------------- These functions return the sizes of various components of the DIB section AS THEY WOULD APPEAR in a packed DIB. These functions aid in converting the DIB section to a packed DIB and in saving DIB files. -----------------------------------------------------------------------------*/ DWORD DibInfoHeaderSize (HDIB hdib) { if (!DibIsValid (hdib)) return 0 ; return ((PDIBSTRUCT) hdib)->ds.dsBmih.biSize ; } DWORD DibMaskSize (HDIB hdib) { PDIBSTRUCT pdib = hdib ; if (!DibIsValid (hdib)) return 0 ; if (pdib->ds.dsBmih.biCompression == BI_BITFIELDS) return 3 * sizeof (DWORD) ; return 0 ; } DWORD DibColorSize (HDIB hdib) { return DibNumColors (hdib) * sizeof (RGBQUAD) ; } DWORD DibInfoSize (HDIB hdib) { return DibInfoHeaderSize(hdib) + DibMaskSize(hdib) + DibColorSize(hdib) ; } DWORD DibBitsSize (HDIB hdib) { PDIBSTRUCT pdib = hdib ; if (!DibIsValid (hdib)) return 0 ; if (pdib->ds.dsBmih.biSizeImage != 0) { return pdib->ds.dsBmih.biSizeImage ; } return DibHeight (hdib) * DibRowLength (hdib) ; } DWORD DibTotalSize (HDIB hdib) { return DibInfoSize (hdib) + DibBitsSize (hdib) ; } /*--------------------------------------------------------------------------- These functions return pointers to the various components of the DIB section. -----------------------------------------------------------------------------*/ BITMAPINFOHEADER * DibInfoHeaderPtr (HDIB hdib) { if (!DibIsValid (hdib)) return NULL ; return & (((PDIBSTRUCT) hdib)->ds.dsBmih) ; } DWORD * DibMaskPtr (HDIB hdib) { PDIBSTRUCT pdib = hdib ; if (!DibIsValid (hdib)) return 0 ; return pdib->ds.dsBitfields ; } void * DibBitsPtr (HDIB hdib) { if (!DibIsValid (hdib)) return NULL ; return ((PDIBSTRUCT) hdib)->pBits ; } /*--------------------------------------------------------------------------- DibSetColor: Obtains entry from the DIB color table -----------------------------------------------------------------------------*/ BOOL DibGetColor (HDIB hdib, int index, RGBQUAD * prgb) { PDIBSTRUCT pdib = hdib ; HDC hdcMem ; int iReturn ; if (!DibIsValid (hdib)) return 0 ; hdcMem = CreateCompatibleDC (NULL) ; SelectObject (hdcMem, pdib->hBitmap) ; iReturn = GetDIBColorTable (hdcMem, index, 1, prgb) ; DeleteDC (hdcMem) ; return iReturn ? TRUE : FALSE ; } /*---------------------------------------------------------------------------- DibGetColor: Sets an entry in the DIB color table ----------------------------------------------------------------------------*/ BOOL DibSetColor (HDIB hdib, int index, RGBQUAD * prgb) { PDIBSTRUCT pdib = hdib ; HDC hdcMem ; int iReturn ; if (!DibIsValid (hdib)) return 0 ; hdcMem = CreateCompatibleDC (NULL) ; SelectObject (hdcMem, pdib->hBitmap) ; iReturn = SetDIBColorTable (hdcMem, index, 1, prgb) ; DeleteDC (hdcMem) ; return iReturn ? TRUE : FALSE ; }

DIBHELP.C中的大部分函式是不用解釋的。DibIsValid函式能有助於保護整個系統。在試圖引用DIBSTRUCT中的資訊之前,其他函式都呼叫DibIsValid。所有這些函式都有(而且通常是只有)HDIB型態的第一個參數,( 我們將立即看到)該參數在DIBHELP.H中定義為空指標。這些函式可以將此參數儲存到PDIBSTRUCT,然後再存取結構中的欄位。

注意傳回BOOL值的DibIsAddressable函式。DibIsNotCompressed函式也可以呼叫此函式。傳回值表示獨立的DIB圖素能否定址。

以DibInfoHeaderSize開始的函式集將取得DIB區塊中不同元件出現在packed DIB中的大小。與我們所看到的一樣,這些函式有助於將DIB區塊轉換成packed DIB,並儲存DIB檔案。這些函式的後面是獲得指向不同DIB元件的指標的函式集。

儘管DIBHELP.C包括名稱為DibInfoHeaderPtr的函式,而且該函式將獲得指向BITMAPINFOHEADER結構的指標,但還是沒有函式可以獲得BITMAPINFO結構指標-即接在DIB顏色表後面的資訊結構。這是因為在處理DIB區塊時,應用程式並不直接存取這種型態的結構。BITMAPINFOHEADER結構和顏色遮罩都在DIBSECTION結構中有效,而且從CreateDIBSection函式傳回指向圖素位元的指標,這時通過呼叫GetDIBColorTable和SetDIBColorTable,就只能間接存取DIB顏色表。這些功能都封裝到DIBHELP的DibGetColor和DibSetColor函式裡頭了。

在DIBHELP.C的後面,檔案DibCopyToInfo配置一個指向BITMAPINFO結構的指標,並填充資訊,但是那與獲得指向記憶體中現存結構的指標不完全相同。

讀、寫圖素

應用程式維護packed DIB或DIB區塊的一個引人注目的優點是能夠直接操作DIB圖素位元。程式16-20所示的DIBHELP.C第二部分列出了提供此功能的函式。

程式16-20 DIBHELP.C檔案的第二部分 DIBHELP.C (第二部分) /*---------------------------------------------------------------------------- DibPixelPtr: Returns a pointer to the pixel at position (x, y) -----------------------------------------------------------------------------*/ BYTE * DibPixelPtr (HDIB hdib, int x, int y) { if (!DibIsAddressable (hdib)) return NULL ; if (x < 0 || x >= DibWidth (hdib) || y < 0 || y >= DibHeight (hdib)) return NULL ; return (((PDIBSTRUCT) hdib)->ppRow)[y] + (x * DibBitCount (hdib) >> 3) ; } /*--------------------------------------------------------------------------- DibGetPixel: Obtains a pixel value at (x, y) -----------------------------------------------------------------------------*/ DWORD DibGetPixel (HDIB hdib, int x, int y) { PBYTE pPixel ; if (!(pPixel = DibPixelPtr (hdib, x, y))) return 0 ; switch (DibBitCount (hdib)) { case 1: return 0x01 & (* pPixel >> (7 - (x & 7))) ; case 4: return 0x0F & (* pPixel >> (x & 1 ? 0 : 4)) ; case 8: return * pPixel ; case 16: return * (WORD *) pPixel ; case 24: return 0x00FFFFFF & * (DWORD *) pPixel ; case 32: return * (DWORD *) pPixel ; } return 0 ; } /*------------------------------------------------------------------------- DibSetPixel: Sets a pixel value at (x, y) ---------------------------------------------------------------------------*/ BOOL DibSetPixel (HDIB hdib, int x, int y, DWORD dwPixel) { PBYTE pPixel ; if (!(pPixel = DibPixelPtr (hdib, x, y))) return FALSE ; switch (DibBitCount (hdib)) { case 1: * pPixel &= ~(1 << (7 - (x & 7))) ; * pPixel |= dwPixel << (7 - (x & 7)) ; break ; case 4: * pPixel &= 0x0F << (x & 1 ? 4 : 0) ; * pPixel |= dwPixel << (x & 1 ? 0 : 4) ; break ; case 8: * pPixel = (BYTE) dwPixel ; break ; case 16: * (WORD *) pPixel = (WORD) dwPixel ; break ; case 24: * (RGBTRIPLE *) pPixel = * (RGBTRIPLE *) &dwPixel ; break ; case 32: * (DWORD *) pPixel = dwPixel ; break ; default: return FALSE ; } return TRUE ; } /*--------------------------------------------------------------------------- DibGetPixelColor: Obtains the pixel color at (x, y) ----------------------------------------------------------------------------*/ BOOL DibGetPixelColor (HDIB hdib, int x, int y, RGBQUAD * prgb) { DWORD dwPixel ; int iBitCount ; PDIBSTRUCT pdib = hdib ; // Get bit count; also use this as a validity check if (0 == (iBitCount = DibBitCount (hdib))) return FALSE ; // Get the pixel value dwPixel = DibGetPixel (hdib, x, y) ; // If the bit-count is 8 or less, index the color table if (iBitCount <= 8) return DibGetColor (hdib, (int) dwPixel, prgb) ; // If the bit-count is 24, just use the pixel else if (iBitCount == 24) { * (RGBTRIPLE *) prgb = * (RGBTRIPLE *) & dwPixel ; prgb->rgbReserved = 0 ; } // If the bit-count is 32 and the biCompression field is BI_RGB, // just use the pixel else if (iBitCount == 32 && pdib->ds.dsBmih.biCompression == BI_RGB) { * prgb = * (RGBQUAD *) & dwPixel ; } // Otherwise, use the mask and shift values // (for best performance, don't use DibMask and DibShift functions) else { prgb->rgbRed = (BYTE)(((pdib->ds.dsBitfields[0] & dwPixel) >> pdib->iRShift[0]) << pdib->iLShift[0]) ; prgb->rgbGreen=(BYTE((pdib->ds.dsBitfields[1] & dwPixel) >> pdib->iRShift[1]) << pdib->iLShift[1]) ; prgb->rgbBlue=(BYTE)(((pdib->ds.dsBitfields[2] & dwPixel) >> pdib->iRShift[2]) << pdib->iLShift[2]) ; } return TRUE ; } /*----------------------------------------------------------------------------- DibSetPixelColor: Sets the pixel color at (x, y) -----------------------------------------------------------------------------*/ BOOL DibSetPixelColor (HDIB hdib, int x, int y, RGBQUAD * prgb) { DWORD dwPixel ; int iBitCount ; PDIBSTRUCT pdib = hdib ; // Don't do this function for DIBs with color tables iBitCount = DibBitCount (hdib) ; if (iBitCount <= 8) return FALSE ; // The rest is just the opposite of DibGetPixelColor else if (iBitCount == 24) { * (RGBTRIPLE *) & dwPixel = * (RGBTRIPLE *) prgb ; dwPixel &= 0x00FFFFFF ; } else if (iBitCount == 32 && pdib->ds.dsBmih.biCompression == BI_RGB) { * (RGBQUAD *) & dwPixel = * prgb ; } else { dwPixel = (((DWORD) prgb->rgbRed >> pdib->iLShift[0]) << pdib->iRShift[0]) ; dwPixel |= (((DWORD) prgb->rgbGreen >> pdib->iLShift[1]) << pdib->iRShift[1]) ; dwPixel |= (((DWORD) prgb->rgbBlue >> pdib->iLShift[2]) << pdib->iRShift[2]) ; } DibSetPixel (hdib, x, y, dwPixel) ; return TRUE ; }

這部分DIBHELP.C從DibPixelPtr函式開始,該函式獲得指向儲存(或部分儲存)有特殊圖素的位元組的指標。回想一下DIBSTRUCT結構的ppRow欄位,那是個指向DIB中由頂行開始排列的圖素行位址的指標。這樣,

就是指向DIB頂行最左端圖素的指標,而

是指向位於(x,y)的圖素的指標。注意,如果DIB中的圖素不可被定址(即如果已壓縮),或者如果函式的x和y參數是負數或相對位於DIB外面的區域,則函式將傳回NULL。此檢查降低了函式(和所有依賴於DibPixelPtr的函式)的執行速度,下面我將講述一些更快的常式。

檔案後面的DibGetPixel和DibSetPixel函式利用了DibPixelPtr。對於8位元、16位元和32位元DIB,這些函式只記錄指向合適資料尺寸的指標,並存取圖素值。對於1位元和4位元的DIB,則需要遮罩和移位角度。

DibGetColor函式按RGBQUAD結構獲得圖素顏色。對於1位元、4位元和8位元DIB,這包括使用圖素值來從DIB顏色表獲得顏色。對於16位元、24位元和32位元DIB,通常必須將圖素值遮罩和移位以得到RGB顏色。DibSetPixel函式則相反,它允許從RGBQUAD結構設定圖素值。該函式只為16位元、24位元和32位元DIB定義。

建立和轉換

程式16-21所示的DIBHELP第三部分和最後部分展示了如何建立DIB區塊,以及如何將DIB區塊與packed DIB相互轉換。

((PDIBSTRUCT) hdib)->pprow)[0]

(((PDIBSTRUCT) hdib)->ppRow)[y] + (x * DibBitCount (hdib) >> 3)

程式16-21 DIBHELP.C檔案的第三部分和最後部分 DIBHELP.C (第三部分) /*-------------------------------------------------------------------------- Calculating shift values from color masks is required by the DibCreateFromInfo function. ----------------------------------------------------------------------------*/ static int MaskToRShift (DWORD dwMask) { int iShift ; if (dwMask == 0) return 0 ; for (iShift = 0 ; !(dwMask & 1) ; iShift++) dwMask >>= 1 ; return iShift ; } static int MaskToLShift (DWORD dwMask) { int iShift ; if (dwMask == 0) return 0 ; while (!(dwMask & 1)) dwMask >>= 1 ; for (iShift = 0 ; dwMask & 1 ; iShift++) dwMask >>= 1 ; return 8 - iShift ; } /*---------------------------------------------------------------------------- DibCreateFromInfo: All DIB creation functions ultimately call this one. This function is responsible for calling CreateDIBSection, allocating memory for DIBSTRUCT, and setting up the row pointer. -----------------------------------------------------------------------------*/ HDIB DibCreateFromInfo (BITMAPINFO * pbmi) { BYTE * pBits ; DIBSTRUCT * pdib ; HBITMAP hBitmap ; int i, iRowLength, cy, y ; hBitmap = CreateDIBSection (NULL, pbmi, DIB_RGB_COLORS, &pBits, NULL, 0) ; if (hBitmap == NULL) return NULL ; if (NULL == (pdib = malloc (sizeof (DIBSTRUCT)))) { DeleteObject (hBitmap) ; return NULL ; } pdib->iSignature = HDIB_SIGNATURE ; pdib->hBitmap = hBitmap ; pdib->pBits = pBits ; GetObject (hBitmap, sizeof (DIBSECTION), &pdib->ds) ; // Notice that we can now use the DIB information functions // defined above. // If the compression is BI_BITFIELDS, calculate shifts from masks if (DibCompression (pdib) == BI_BITFIELDS) { for (i = 0 ; i < 3 ; i++) { pdib->iLShift[i] = MaskToLShift (pdib->ds.dsBitfields[i]) ; pdib->iRShift[i] = MaskToRShift (pdib->ds.dsBitfields[i]) ; } } // If the compression is BI_RGB, but bit-count is 16 or 32, // set the bitfields and the masks else if (DibCompression (pdib) == BI_RGB) { if (DibBitCount (pdib) == 16) { pdib->ds.dsBitfields[0] = 0x00007C00 ; pdib->ds.dsBitfields[1] = 0x000003E0 ; pdib->ds.dsBitfields[2] = 0x0000001F ; pdib->iRShift [0] = 10 ; pdib->iRShift [1] = 5 ; pdib->iRShift [2] = 0 ; pdib->iLShift [0] = 3 ; pdib->iLShift [1] = 3 ; pdib->iLShift [2] = 3 ; } else if (DibBitCount (pdib) == 24 || DibBitCount (pdib) == 32) { pdib->ds.dsBitfields[0] = 0x00FF0000 ; pdib->ds.dsBitfields[1] = 0x0000FF00 ; pdib->ds.dsBitfields[2] = 0x000000FF ; pdib->iRShift [0] = 16 ; pdib->iRShift [1] = 8 ; pdib->iRShift [2] = 0 ; pdib->iLShift [0] = 0 ; pdib->iLShift [1] = 0 ; pdib->iLShift [2] = 0 ; } } // Allocate an array of pointers to each row in the DIB cy = DibHeight (pdib) ; if (NULL == (pdib->ppRow = malloc (cy * sizeof (BYTE *)))) { free (pdib) ; DeleteObject (hBitmap) ; return NULL ; } // Initialize them. iRowLength = DibRowLength (pdib) ; if (pbmi->bmiHeader.biHeight > 0) // ie, bottom up { for (y = 0 ; y < cy ; y++) pdib->ppRow[y] = pBits + (cy - y - 1) * iRowLength ; } else // top down { for (y = 0 ; y < cy ; y++) pdib->ppRow[y] = pBits + y * iRowLength ; } return pdib ; } /*-------------------------------------------------------------------------- DibDelete: Frees all memory for the DIB section ----------------------------------------------------------------------------*/ BOOL DibDelete (HDIB hdib) { DIBSTRUCT * pdib = hdib ; if (!DibIsValid (hdib)) return FALSE ; free (pdib->ppRow) ; DeleteObject (pdib->hBitmap) ; free (pdib) ; return TRUE ; } /*---------------------------------------------------------------------------- DibCreate: Creates an HDIB from explicit arguments -----------------------------------------------------------------------------*/ HDIB DibCreate (int cx, int cy, int cBits, int cColors) { BITMAPINFO * pbmi ; DWORD dwInfoSize ; HDIB hDib ; int cEntries ; if (cx <= 0 || cy <= 0 || ((cBits != 1) && (cBits != 4) && (cBits != 8) && (cBits != 16) && (cBits != 24) && (cBits != 32))) { return NULL ; } if ( cColors != 0) cEntries = cColors ; else if (cBits <= 8) cEntries = 1 << cBits ; dwInfoSize = sizeof (BITMAPINFOHEADER) + (cEntries - 1) * sizeof (RGBQUAD); if (NULL == (pbmi = malloc (dwInfoSize))) { return NULL ; } ZeroMemory (pbmi, dwInfoSize) ; pbmi->bmiHeader.biSize = sizeof (BITMAPINFOHEADER) ; pbmi->bmiHeader.biWidth = cx ; pbmi->bmiHeader.biHeight = cy ; pbmi->bmiHeader.biPlanes = 1 ; pbmi->bmiHeader.biBitCount = cBits ; pbmi->bmiHeader.biCompression = BI_RGB ; pbmi->bmiHeader.biSizeImage = 0 ; pbmi->bmiHeader.biXPelsPerMeter = 0 ; pbmi->bmiHeader.biYPelsPerMeter = 0 ; pbmi->bmiHeader.biClrUsed = cColors ; pbmi->bmiHeader.biClrImportant = 0 ; hDib = DibCreateFromInfo (pbmi) ; free (pbmi) ; return hDib ; } /*---------------------------------------------------------------------------- DibCopyToInfo: Builds BITMAPINFO structure. Used by DibCopy and DibCopyToDdb -----------------------------------------------------------------------------*/ static BITMAPINFO * DibCopyToInfo (HDIB hdib) { BITMAPINFO * pbmi ; int i, iNumColors ; RGBQUAD * prgb ; if (!DibIsValid (hdib)) return NULL ; // Allocate the memory if (NULL == (pbmi = malloc (DibInfoSize (hdib)))) return NULL ; // Copy the information header CopyMemory (pbmi, DibInfoHeaderPtr (hdib), sizeof (BITMAPINFOHEADER)); // Copy the possible color masks prgb = (RGBQUAD *) ((BYTE *) pbmi + sizeof (BITMAPINFOHEADER)) ; if (DibMaskSize (hdib)) { CopyMemory (prgb, DibMaskPtr (hdib), 3 * sizeof (DWORD)) ; prgb = (RGBQUAD *) ((BYTE *) prgb + 3 * sizeof (DWORD)) ; } // Copy the color table iNumColors = DibNumColors (hdib) ; for (i = 0 ; i < iNumColors ; i++) DibGetColor (hdib, i, prgb + i) ; return pbmi ; } /*-------------------------------------------------------------------------- DibCopy: Creates a new DIB section from an existing DIB section, possibly swapping the DIB width and height. ---------------------------------------------------------------------------*/ HDIB DibCopy (HDIB hdibSrc, BOOL fRotate) { BITMAPINFO * pbmi ; BYTE * pBitsSrc, * pBitsDst ; HDIB hdibDst ; if (!DibIsValid (hdibSrc)) return NULL ; if (NULL == (pbmi = DibCopyToInfo (hdibSrc))) return NULL ; if (fRotate) { pbmi->bmiHeader.biWidth = DibHeight (hdibSrc) ; pbmi->bmiHeader.biHeight = DibWidth (hdibSrc) ; } hdibDst = DibCreateFromInfo (pbmi) ; free (pbmi) ; if ( hdibDst == NULL) return NULL ; // Copy the bits if (!fRotate) { pBitsSrc = DibBitsPtr (hdibSrc) ; pBitsDst = DibBitsPtr (hdibDst) ; CopyMemory (pBitsDst, pBitsSrc, DibBitsSize (hdibSrc)) ; } return hdibDst ; } /*---------------------------------------------------------------------------- DibCopyToPackedDib is generally used for saving DIBs and for transferring DIBs to the clipboard. In the second case, the second argument should be set to TRUE so that the memory is allocated with the GMEM_SHARE flag. -----------------------------------------------------------------------------*/ BITMAPINFO * DibCopyToPackedDib (HDIB hdib, BOOL fUseGlobal) { BITMAPINFO * pPackedDib ; BYTE * pBits ; DWORD dwDibSize ; HDC hdcMem ; HGLOBAL hGlobal ; int iNumColors ; PDIBSTRUCT pdib = hdib ; RGBQUAD * prgb ; if (!DibIsValid (hdib)) return NULL ; // Allocate memory for packed DIB dwDibSize = DibTotalSize (hdib) ; if (fUseGlobal) { hGlobal = GlobalAlloc (GHND | GMEM_SHARE, dwDibSize) ; pPackedDib = GlobalLock (hGlobal) ; } else { pPackedDib = malloc (dwDibSize) ; } if (pPackedDib == NULL) return NULL ; // Copy the information header CopyMemory (pPackedDib, &pdib->ds.dsBmih, sizeof (BITMAPINFOHEADER)) ; prgb = (RGBQUAD *) ((BYTE *) pPackedDib + sizeof (BITMAPINFOHEADER)) ; // Copy the possible color masks if (pdib->ds.dsBmih.biCompression == BI_BITFIELDS) { CopyMemory (prgb, pdib->ds.dsBitfields, 3 * sizeof (DWORD)) ; prgb = (RGBQUAD *) ((BYTE *) prgb + 3 * sizeof (DWORD)) ; } // Copy the color table if (iNumColors = DibNumColors (hdib)) { hdcMem = CreateCompatibleDC (NULL) ; SelectObject (hdcMem, pdib->hBitmap) ; GetDIBColorTable (hdcMem, 0, iNumColors, prgb) ; DeleteDC (hdcMem) ; } pBits = (BYTE *) (prgb + iNumColors) ; // Copy the bits CopyMemory (pBits, pdib->pBits, DibBitsSize (pdib)) ; // If last argument is TRUE, unlock global memory block and // cast it to pointer in preparation for return if (fUseGlobal) { GlobalUnlock (hGlobal) ; pPackedDib = (BITMAPINFO *) hGlobal ; } return pPackedDib ; } /*-------------------------------------------------------------------------- DibCopyFromPackedDib is generally used for pasting DIBs from the clipboard. ------------------------------------------------------------------------*/ HDIB DibCopyFromPackedDib (BITMAPINFO * pPackedDib) { BYTE * pBits ; DWORD dwInfoSize, dwMaskSize, dwColorSize ; int iBitCount ; PDIBSTRUCT pdib ; // Get the size of the information header and do validity check dwInfoSize = pPackedDib->bmiHeader.biSize ; if ( dwInfoSize != sizeof (BITMAPCOREHEADER) && dwInfoSize != sizeof (BITMAPINFOHEADER) && dwInfoSize != sizeof (BITMAPV4HEADER) && dwInfoSize != sizeof (BITMAPV5HEADER)) { return NULL ; } // Get the possible size of the color masks if (dwInfoSize == sizeof (BITMAPINFOHEADER) && pPackedDib->bmiHeader.biCompression == BI_BITFIELDS) { dwMaskSize = 3 * sizeof (DWORD) ; } else { dwMaskSize = 0 ; } // Get the size of the color table if (dwInfoSize == sizeof (BITMAPCOREHEADER)) { iBitCount = ((BITMAPCOREHEADER *) pPackedDib)->bcBitCount ; if (iBitCount <= 8) { dwColorSize = (1 << iBitCount) * sizeof (RGBTRIPLE) ; } else dwColorSize = 0 ; } else // all non-OS/2 compatible DIBs { if (pPackedDib->bmiHeader.biClrUsed > 0) { dwColorSize = pPackedDib->bmiHeader.biClrUsed * sizeof (RGBQUAD); } else if (pPackedDib->bmiHeader.biBitCount <= 8) { dwColorSize = (1 << pPackedDib->bmiHeader.biBitCount) * sizeof (RGBQUAD) ; } else { dwColorSize = 0 ; } } // Finally, get the pointer to the bits in the packed DIB pBits = (BYTE *) pPackedDib + dwInfoSize + dwMaskSize + dwColorSize ; // Create the HDIB from the packed-DIB pointer pdib = DibCreateFromInfo (pPackedDib) ; // Copy the pixel bits CopyMemory (pdib->pBits, pBits, DibBitsSize (pdib)) ; return pdib ; } /*---------------------------------------------------------------------------- DibFileLoad: Creates a DIB section from a DIB file -----------------------------------------------------------------------------*/ HDIB DibFileLoad (const TCHAR * szFileName) { BITMAPFILEHEADER bmfh ; BITMAPINFO * pbmi ; BOOL bSuccess ; DWORD dwInfoSize, dwBitsSize, dwBytesRead ; HANDLE hFile ; HDIB hDib ; // 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 information structure & read it in dwInfoSize = bmfh.bfOffBits - sizeof (BITMAPFILEHEADER) ; if (NULL == (pbmi = malloc (dwInfoSize))) { CloseHandle (hFile) ; return NULL ; } bSuccess = ReadFile (hFile, pbmi, dwInfoSize, &dwBytesRead, NULL) ; if (!bSuccess || (dwBytesRead != dwInfoSize)) { CloseHandle (hFile) ; free (pbmi) ; return NULL ; } // Create the DIB hDib = DibCreateFromInfo (pbmi) ; free (pbmi) ; if (hDib == NULL) { CloseHandle (hFile) ; return NULL ; } // Read in the bits dwBitsSize = bmfh.bfSize - bmfh.bfOffBits ; bSuccess = ReadFile ( hFile, ((PDIBSTRUCT) hDib)->pBits, dwBitsSize, &dwBytesRead, NULL) ; CloseHandle (hFile) ; if (!bSuccess || (dwBytesRead != dwBitsSize)) { DibDelete (hDib) ; return NULL ; } return hDib ; } /*-------------------------------------------------------------------------- DibFileSave: Saves a DIB section to a file ----------------------------------------------------------------------------*/ BOOL DibFileSave (HDIB hdib, const TCHAR * szFileName) { BITMAPFILEHEADER bmfh ; BITMAPINFO * pbmi ; BOOL bSuccess ; DWORD dwTotalSize, dwBytesWritten ; HANDLE hFile ; hFile = CreateFile (szFileName, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL) ; if (hFile == INVALID_HANDLE_VALUE) return FALSE ; dwTotalSize = DibTotalSize (hdib) ; bmfh.bfType = * (WORD *) "BM" ; bmfh.bfSize = sizeof (BITMAPFILEHEADER) + dwTotalSize ; bmfh.bfReserved1 = 0 ; bmfh.bfReserved2 = 0 ; bmfh.bfOffBits = bmfh.bfSize - DibBitsSize (hdib) ; // Write the BITMAPFILEHEADER bSuccess = WriteFile ( hFile, &bmfh, sizeof (BITMAPFILEHEADER), &dwBytesWritten, NULL) ; if (!bSuccess || (dwBytesWritten != sizeof (BITMAPFILEHEADER))) { CloseHandle (hFile) ; DeleteFile (szFileName) ; return FALSE ; } // Get entire DIB in packed-DIB format if (NULL == (pbmi = DibCopyToPackedDib (hdib, FALSE))) { CloseHandle (hFile) ; DeleteFile (szFileName) ; return FALSE ; } // Write out the packed DIB bSuccess = WriteFile (hFile, pbmi, dwTotalSize, &dwBytesWritten, NULL) ; CloseHandle (hFile) ; free (pbmi) ; if (!bSuccess || (dwBytesWritten != dwTotalSize)) { DeleteFile (szFileName) ; return FALSE ; } return TRUE ; } /*--------------------------------------------------------------------------- DibCopyToDdb: For more efficient screen displays ---------------------------------------------------------------------------*/ HBITMAP DibCopyToDdb (HDIB hdib, HWND hwnd, HPALETTE hPalette) { BITMAPINFO * pbmi ; HBITMAP hBitmap ; HDC hdc ; if (!DibIsValid (hdib)) return NULL ; if (NULL == (pbmi = DibCopyToInfo (hdib))) return NULL ; hdc = GetDC (hwnd) ; if (hPalette) { SelectPalette (hdc, hPalette, FALSE) ; RealizePalette (hdc) ; } hBitmap = CreateDIBitmap (hdc, DibInfoHeaderPtr (hdib), CBM_INIT, DibBitsPtr (hdib), pbmi, DIB_RGB_COLORS) ; ReleaseDC (hwnd, hdc) ; free (pbmi) ; return hBitmap ; }

這部分的DIBHELP.C檔案從兩個小函式開始,這兩個函式根據16位元和32位元DIB的顏色遮罩得到左、右移位值。這些函式在 第十五章「顏色遮罩」 一節說明。

DibCreateFromInfo函式是DIBHELP中唯一呼叫CreateDIBSection並為DIBSTRUCT結構配置記憶體的函式。其他所有建立和複製函式都重複此函式。DibCreateFromInfo唯一的參數是指向BITMAPINFO結構的指標。此結構的顏色表必須存在,但是它不必用有效的值填充。呼叫CreateDIBSection之後,該函式將初始化DIBSTRUCT結構的所有欄位。注意,在設定DIBSTRUCT結構的ppRow欄位的值時(指向DIB行位址的指標),DIB有由下而上和由上而下的不同儲存方式。ppRow開頭的元素就是DIB的頂行。

DibDelete刪除DibCreateFromInfo中建立的點陣圖,同時釋放在該函式中配置的記憶體。

DibCreate可能比DibCreateFromInfo更像一個從應用程式呼叫的函式。前三個參數提供圖素的寬度、高度和每圖素的位數。最後一個參數可以設定為0(用於顏色表的內定尺寸),或者設定為非0(表示比每圖素位元數所需要的顏色表更小的顏色表)。

DibCopy函式根據現存的DIB區塊建立新的DIB區塊,並用DibCreateInfo函式為BITMAPINFO結構配置了記憶體,還填了所有的資料。DibCopy函式的一個BOOL參數指出是否在建立新的DIB時交換了DIB的寬度和高度。我們將在後面看到此函式的用法。

DibCopyToPackedDib和DibCopyFromPackedDib函式的使用通常與透過剪貼簿傳遞DIB相關。DibFileLoad函式從DIB檔案建立DIB區塊;DibFileSave函式將資料儲存到DIB檔案。

最後,DibCopyToDdb函式根據DIB建立GDI點陣圖物件。注意,該函式需要目前調色盤的代號和程式視窗的代號。程式視窗代號用於獲得選進並顯現調色盤的裝置內容。只有這樣,函式才可以呼叫CreateDIBitmap。這曾在本章前面的SHOWDIB7中展示。

DIBHELP表頭檔案和巨集

DIBHELP.H表頭檔案如程式16-22所示。

程式16-22 DIBHELP.H檔案 DIBHELP.H /*-------------------------------------------------------------------------- DIBHELP.H header file for DIBHELP.C ----------------------------------------------------------------------------*/ typedef void * HDIB ; // Functions in DIBHELP.C BOOL DibIsValid (HDIB hdib) ; HBITMAP DibBitmapHandle (HDIB hdib) ; int DibWidth (HDIB hdib) ; int DibHeight (HDIB hdib) ; int DibBitCount (HDIB hdib) ; int DibRowLength (HDIB hdib) ; int DibNumColors (HDIB hdib) ; DWORD DibMask (HDIB hdib, int i) ; int DibRShift (HDIB hdib, int i) ; int DibLShift (HDIB hdib, int i) ; int DibCompression (HDIB hdib) ; BOOL DibIsAddressable (HDIB hdib) ; DWORD DibInfoHeaderSize (HDIB hdib) ; DWORD DibMaskSize (HDIB hdib) ; DWORD DibColorSize (HDIB hdib) ; DWORD DibInfoSize (HDIB hdib) ; DWORD DibBitsSize (HDIB hdib) ; DWORD DibTotalSize (HDIB hdib) ; BITMAPINFOHEADER * DibInfoHeaderPtr (HDIB hdib) ; DWORD * DibMaskPtr (HDIB hdib) ; void * DibBitsPtr (HDIB hdib) ; BOOL DibGetColor (HDIB hdib, int index, RGBQUAD * prgb) ; BOOL DibSetColor (HDIB hdib, int index, RGBQUAD * prgb) ; BYTE * DibPixelPtr (HDIB hdib, int x, int y) ; DWORD DibGetPixel (HDIB hdib, int x, int y) ; BOOL DibSetPixel (HDIB hdib, int x, int y, DWORD dwPixel) ; BOOL DibGetPixelColor (HDIB hdib, int x, int y, RGBQUAD * prgb) ; BOOL DibSetPixelColor (HDIB hdib, int x, int y, RGBQUAD * prgb) ; HDIB DibCreateFromInfo (BITMAPINFO * pbmi) ; BOOL DibDelete (HDIB hdib) ; HDIB DibCreate (int cx, int cy, int cBits, int cColors) ; HDIB DibCopy (HDIB hdibSrc, BOOL fRotate) ; BITMAPINFO * DibCopyToPackedDib (HDIB hdib, BOOL fUseGlobal) ; HDIB DibCopyFromPackedDib (BITMAPINFO * pPackedDib) ; HDIB DibFileLoad (const TCHAR * szFileName) ; BOOL DibFileSave (HDIB hdib, const TCHAR * szFileName) ; HBITMAP DibCopyToDdb (HDIB hdib, HWND hwnd, HPALETTE hPalette) ; HDIB DibCreateFromDdb (HBITMAP hBitmap) ; /*--------------------------------------------------------------------------- Quickie no-bounds-checked pixel gets and sets -----------------------------------------------------------------------------*/ #define DibPixelPtr1(hdib, x, y) (((* (PBYTE **) hdib) [y]) + ((x) >> 3)) #define DibPixelPtr4(hdib, x, y) (((* (PBYTE **) hdib) [y]) + ((x) >> 1)) #define DibPixelPtr8(hdib, x, y) (((* (PBYTE **) hdib) [y]) + (x) ) #define DibPixelPtr16(hdib, x, y) \ ((WORD *) (((* (PBYTE **) hdib) [y]) + (x) * 2)) #define DibPixelPtr24(hdib, x, y) \ ((RGBTRIPLE *) (((* (PBYTE **) hdib) [y]) + (x) * 3)) #define DibPixelPtr32(hdib, x, y) \ ((DWORD *) (((* (PBYTE **) hdib) [y]) + (x) * 4)) #define DibGetPixel1(hdib, x, y) \ (0x01 & (* DibPixelPtr1 (hdib, x, y) >> (7 - ((x) & 7)))) #define DibGetPixel4(hdib, x, y) \ (0x0F & (* DibPixelPtr4 (hdib, x, y) >> ((x) & 1 ? 0 : 4))) #define DibGetPixel8(hdib, x, y) (* DibPixelPtr8 (hdib, x, y)) #define DibGetPixel16(hdib, x, y) (* DibPixelPtr16 (hdib, x, y)) #define DibGetPixel24(hdib, x, y) (* DibPixelPtr24 (hdib, x, y)) #define DibGetPixel32(hdib, x, y) (* DibPixelPtr32 (hdib, x, y)) #define DibSetPixel1(hdib, x, y, p) \ ((* DibPixelPtr1 (hdib, x, y) &= ~( 1 << (7 - ((x) & 7)))), \ (* DibPixelPtr1 (hdib, x, y) |= ((p) << (7 - ((x) & 7))))) #define DibSetPixel4(hdib, x, y, p) \ ((* DibPixelPtr4 (hdib, x, y) &= (0x0F << ((x) & 1 ? 4 : 0))), \ (* DibPixelPtr4 (hdib, x, y) |= ((p) << ((x) & 1 ? 0 : 4)))) #define DibSetPixel8(hdib, x, y, p) (* DibPixelPtr8 (hdib, x, y) = p) #define DibSetPixel16(hdib, x, y, p) (* DibPixelPtr16 (hdib, x, y) = p) #define DibSetPixel24(hdib, x, y, p) (* DibPixelPtr24 (hdib, x, y) = p) #define DibSetPixel32(hdib, x, y, p) (* DibPixelPtr32 (hdib, x, y) = p)

這個表頭檔案將HDIB定義為空指標(void* )。應用程式的確不需要瞭解HDIB所指結構的內部結構。此表頭檔案還包括DIBHELP.C中所有函式的說明,還有一些巨集-非常特殊的巨集。

如果再看一看DIBHELP.C中的DibPixelPtr、DibGetPixel和DibSetPixel函式,並試圖提高它們的執行速度表現,那麼您將看到兩種可能的解決方法。第一種,可以刪除所有的檢查保護,並相信應用程式不會使用無效參數呼叫函式。還可以刪除一些函式呼叫,例如DibBitCount,並使用指向DIBSTRUCT結構內部的指標來直接獲得資訊。

提高執行速度表現另一項較不明顯的方法是刪除所有對每圖素位元數的處理方式,同時分離出處理不同DIB函式-例如DibGetPixel1、DibGetPixel4、DibGetPixel8等等。下一個最佳化步驟是刪除整個函式呼叫,將其處理動作透過inline function或巨集中進行合併。

DIBHELP.H採用巨集的方法。它依據DibPixelPtr、DibGetPixel和DibSetPixel函式提出了三套巨集。這些巨集都明確對應於特殊的圖素位元數。

DIBBLE程式

DIBBLE程式,如程式16-23所示,使用DIBHELP函式和巨集工作。儘管DIBBLE是本書中最長的程式,它確實只是一些作業的粗略範例,這些作業可以在簡單的數位影像處理程式中找到。對DIBBLE的明顯改進是轉換成了多重文件介面(MDI:multiple document interface),我們將在 第十九章 學習有關多重文件介面的知識。

程式16-23 DIBBLE DIBBLE.C /*--------------------------------------------------------------------------- DIBBLE.C -- Bitmap and Palette Program (c) Charles Petzold, 1998 -----------------------------------------------------------------------------*/ #include <windows.h> #include "dibhelp.h" #include "dibpal.h" #include "dibconv.h" #include "resource.h" #define WM_USER_SETSCROLLS (WM_USER + 1) #define WM_USER_DELETEDIB (WM_USER + 2) #define WM_USER_DELETEPAL (WM_USER + 3) #define WM_USER_CREATEPAL (WM_USER + 4) LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ; TCHAR szAppName[] = TEXT ("Dibble") ; int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { HACCEL hAccel ; 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, szAppName, WS_OVERLAPPEDWINDOW | WM_VSCROLL | WM_HSCROLL, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL) ; ShowWindow (hwnd, iCmdShow) ; UpdateWindow (hwnd) ; hAccel = LoadAccelerators (hInstance, szAppName) ; while (GetMessage (&msg, NULL, 0, 0)) { if (!TranslateAccelerator (hwnd, hAccel, &msg)) { TranslateMessage (&msg) ; DispatchMessage (&msg) ; } } return msg.wParam ; } /*---------------------------------------------------------------------------- DisplayDib: Displays or prints DIB actual size or stretched depending on menu selection -----------------------------------------------------------------------------*/ int DisplayDib ( HDC hdc, HBITMAP hBitmap, int x, int y, int cxClient, int cyClient, WORD wShow, BOOL fHalftonePalette) { BITMAP bitmap ; HDC hdcMem ; int cxBitmap, cyBitmap, iReturn ; GetObject (hBitmap, sizeof (BITMAP), &bitmap) ; cxBitmap = bitmap.bmWidth ; cyBitmap = bitmap.bmHeight ; SaveDC (hdc) ; if (fHalftonePalette) SetStretchBltMode (hdc, HALFTONE) ; else SetStretchBltMode (hdc, COLORONCOLOR) ; hdcMem = CreateCompatibleDC (hdc) ; SelectObject (hdcMem, hBitmap) ; switch (wShow) { case IDM_SHOW_NORMAL: if (fHalftonePalette) iReturn = StretchBlt (hdc, 0, 0, min (cxClient, cxBitmap - x), min (cyClient, cyBitmap - y), hdcMem, x, y, min (cxClient, cxBitmap - x), min (cyClient, cyBitmap - y), SRCCOPY); else iReturn = BitBlt (hdc,0, 0, min (cxClient, cxBitmap - x), min (cyClient, cyBitmap - y), hdcMem, x, y, SRCCOPY) ; break ; case IDM_SHOW_CENTER: if (fHalftonePalette) iReturn = StretchBlt ( hdc, (cxClient - cxBitmap) / 2, (cyClient - cyBitmap) / 2, cxBitmap, cyBitmap, hdcMem, 0, 0, cxBitmap, cyBitmap, SRCCOPY); else iReturn = BitBlt (hdc, (cxClient - cxBitmap) / 2, cyClient - cyBitmap) / 2, cxBitmap, cyBitmap, hdcMem, 0, 0, SRCCOPY) ; break ; case IDM_SHOW_STRETCH: iReturn = StretchBlt (hdc, 0, 0, cxClient, cyClient, hdcMem, 0, 0, cxBitmap, cyBitmap, SRCCOPY) ; break ; case IDM_SHOW_ISOSTRETCH: SetMapMode (hdc, MM_ISOTROPIC) ; SetWindowExtEx (hdc, cxBitmap, cyBitmap, NULL) ; SetViewportExtEx (hdc, cxClient, cyClient, NULL) ; SetWindowOrgEx (hdc, cxBitmap / 2, cyBitmap / 2, NULL) ; SetViewportOrgEx (hdc, cxClient / 2, cyClient / 2, NULL) ; iReturn = StretchBlt (hdc, 0, 0, cxBitmap, cyBitmap, hdcMem, 0, 0, cxBitmap, cyBitmap, SRCCOPY) ; break ; } DeleteDC (hdcMem) ; RestoreDC (hdc, -1) ; return iReturn ; } /*--------------------------------------------------------------------------- DibFlipHorizontal: Calls non-optimized DibSetPixel and DibGetPixel ----------------------------------------------------------------------------*/ HDIB DibFlipHorizontal (HDIB hdibSrc) { HDIB hdibDst ; int cx, cy, x, y ; if (!DibIsAddressable (hdibSrc)) return NULL ; if (NULL == (hdibDst = DibCopy (hdibSrc, FALSE))) return NULL ; cx = DibWidth (hdibSrc) ; cy = DibHeight (hdibSrc) ; for (x = 0 ; x < cx ; x++) for (y = 0 ; y < cy ; y++) { DibSetPixel (hdibDst, x, cy - 1 - y, DibGetPixel (hdibSrc, x, y)) ; } return hdibDst ; } /*--------------------------------------------------------------------------- DibRotateRight: Calls optimized DibSetPixelx and DibGetPixelx -----------------------------------------------------------------------------*/ HDIB DibRotateRight (HDIB hdibSrc) { HDIB hdibDst ; int cx, cy, x, y ; if (!DibIsAddressable (hdibSrc)) return NULL ; if (NULL == (hdibDst = DibCopy (hdibSrc, TRUE))) return NULL ; cx = DibWidth (hdibSrc) ; cy = DibHeight (hdibSrc) ; switch (DibBitCount (hdibSrc)) { case 1: for ( x = 0 ; x < cx ; x++) for ( y = 0 ; y < cy ; y++) DibSetPixel1 (hdibDst, cy - y - 1, x, DibGetPixel1 (hdibSrc, x, y)) ; break ; case 4: for ( x = 0 ; x < cx ; x++) for ( y = 0 ; y < cy ; y++) DibSetPixel4 (hdibDst, cy - y - 1, x, DibGetPixel4 (hdibSrc, x, y)) ; break ; case 8: for ( x = 0 ; x < cx ; x++) for ( y = 0 ; y < cy ; y++) DibSetPixel8 (hdibDst, cy - y - 1, x, DibGetPixel8 (hdibSrc, x, y)) ; break ; case 16: for (x = 0 ; x < cx ; x++) for (y = 0 ; y < cy ; y++) DibSetPixel16 (hdibDst, cy - y - 1, x, DibGetPixel16 (hdibSrc, x, y)) ; break ; case 24: for (x = 0 ; x < cx ; x++) for (y = 0 ; y < cy ; y++) DibSetPixel24 (hdibDst, cy - y - 1, x, DibGetPixel24 (hdibSrc, x, y)) ; break ; case 32: for (x = 0 ; x < cx ; x++) for (y = 0 ; y < cy ; y++) DibSetPixel32 (hdibDst, cy - y - 1, x, DibGetPixel32 (hdibSrc, x, y)) ; break ; } return hdibDst ; } /*------------------------------------------------------------------------ PaletteMenu: Uncheck and check menu item on palette menu --------------------------------------------------------------------------*/ void PaletteMenu (HMENU hMenu, WORD wItemNew) { static WORD wItem = IDM_PAL_NONE ; CheckMenuItem (hMenu, wItem, MF_UNCHECKED) ; wItem = wItemNew ; CheckMenuItem (hMenu, wItem, MF_CHECKED) ; } LRESULT CALLBACK WndProc ( HWND hwnd, UINT message, WPARAM wParam,LPARAM lParam) { static BOOL fHalftonePalette ; static DOCINFO di = {sizeof(DOCINFO),TEXT("Dibble:Printing")} ; static HBITMAP hBitmap ; static HDIB hdib ; static HMENU hMenu ; static HPALETTE hPalette ; static int cxClient, cyClient, iVscroll, iHscroll ; static OPENFILENAME ofn ; static PRINTDLG printdlg = { sizeof (PRINTDLG) } ; static TCHAR szFileName [MAX_PATH], szTitleName [MAX_PATH] ; static TCHAR szFilter[]= TEXT ("Bitmap Files (*.BMP)\0*.bmp\0") TEXT ("All Files (*.*)\0*.*\0\0") ; static TCHAR * szCompression[] = { TEXT("BI_RGB"),TEXT("BI_RLE8"),TEXT("BI_RLE4"), TEXT("BI_BITFIELDS"),TEXT("Unknown")} ; static WORD wShow = IDM_SHOW_NORMAL ; BOOL fSuccess ; BYTE * pGlobal ; HDC hdc, hdcPrn ; HGLOBAL hGlobal ; HDIB hdibNew ; int iEnable, cxPage, cyPage, iConvert ; PAINTSTRUCT ps ; SCROLLINFO si ; TCHAR szBuffer [256] ; switch (message) { case WM_CREATE: // Save the menu handle in a static variable hMenu = GetMenu (hwnd) ; // Initialize the OPENFILENAME structure for the File Open // and File Save dialog boxes. 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 = OFN_OVERWRITEPROMPT ; ofn.nFileOffset = 0 ; ofn.nFileExtension = 0 ; ofn.lpstrDefExt = TEXT ("bmp") ; ofn.lCustData = 0 ; ofn.lpfnHook = NULL ; ofn.lpTemplateName = NULL ; return 0 ; case WM_DISPLAYCHANGE: SendMessage (hwnd, WM_USER_DELETEPAL, 0, 0) ; SendMessage (hwnd, WM_USER_CREATEPAL, TRUE, 0) ; return 0 ; case WM_SIZE: // Save the client area width and height in static variables. cxClient = LOWORD (lParam) ; cyClient = HIWORD (lParam) ; wParam = FALSE ; // fall through // WM_USER_SETSCROLLS: Programmer-defined Message! // Set the scroll bars. If the display mode is not normal, // make them invisible. If wParam is TRUE, reset the // scroll bar position. case WM_USER_SETSCROLLS: if (hdib == NULL || wShow != IDM_SHOW_NORMAL) { si.cbSize = sizeof (SCROLLINFO) ; si.fMask = SIF_RANGE ; si.nMin = 0 ; si.nMax = 0 ; SetScrollInfo (hwnd, SB_VERT, &si, TRUE) ; SetScrollInfo (hwnd, SB_HORZ, &si, TRUE) ; } else { // First the vertical scroll si.cbSize = sizeof (SCROLLINFO) ; si.fMask = SIF_ALL ; GetScrollInfo (hwnd, SB_VERT, &si) ; si.nMin = 0 ; si.nMax = DibHeight (hdib) ; si.nPage = cyClient ; if ((BOOL) wParam) si.nPos = 0 ; SetScrollInfo (hwnd, SB_VERT, &si, TRUE) ; GetScrollInfo (hwnd, SB_VERT, &si) ; iVscroll = si.nPos ; // Then the horizontal scroll GetScrollInfo (hwnd, SB_HORZ, &si) ; si.nMin = 0 ; si.nMax = DibWidth (hdib) ; si.nPage = cxClient ; if ((BOOL) wParam) si.nPos = 0 ; SetScrollInfo (hwnd, SB_HORZ, &si, TRUE) ; GetScrollInfo (hwnd, SB_HORZ, &si) ; iHscroll = si.nPos ; } return 0 ; // WM_VSCROLL: Vertically scroll the DIB case WM_VSCROLL: si.cbSize = sizeof (SCROLLINFO) ; si.fMask = SIF_ALL ; GetScrollInfo (hwnd, SB_VERT, &si) ; iVscroll = si.nPos ; switch (LOWORD (wParam)) { case SB_LINEUP: si.nPos - = 1 ; break ; case SB_LINEDOWN: si.nPos + = 1 ; break ; case SB_PAGEUP: si.nPos - = si.nPage ;break ; case SB_PAGEDOWN: si.nPos + = si.nPage ;break ; case SB_THUMBTRACK:si.nPos = si.nTrackPos ;break ; default: break ; } si.fMask = SIF_POS ; SetScrollInfo (hwnd, SB_VERT, &si, TRUE) ; GetScrollInfo (hwnd, SB_VERT, &si) ; if (si.nPos != iVscroll) { ScrollWindow (hwnd, 0, iVscroll - si.nPos, NULL, NULL) ; iVscroll = si.nPos ; UpdateWindow (hwnd) ; } return 0 ; // WM_HSCROLL: Horizontally scroll the DIB case WM_HSCROLL: si.cbSize = sizeof (SCROLLINFO) ; si.fMask = SIF_ALL ; GetScrollInfo (hwnd, SB_HORZ, &si) ; iHscroll = si.nPos ; switch (LOWORD (wParam)) { case SB_LINELEFT: si.nPos -=1 ; break ; case SB_LINERIGHT: si.nPos +=1 ; break ; case SB_PAGELEFT: si.nPos -=si.nPage ;break ; case SB_PAGERIGHT: si.nPos +=si.nPage ;break ; case SB_THUMBTRACK:si.nPos =si.nTrackPos ;break ; default: break ; } si.fMask = SIF_POS ; SetScrollInfo (hwnd, SB_HORZ, &si, TRUE) ; GetScrollInfo (hwnd, SB_HORZ, &si) ; if (si.nPos != iHscroll) { ScrollWindow (hwnd, iHscroll - si.nPos, 0, NULL, NULL) ; iHscroll = si.nPos ; UpdateWindow (hwnd) ; } return 0 ; // WM_INITMENUPOPUP: Enable or Gray menu items case WM_INITMENUPOPUP: if (hdib) iEnable = MF_ENABLED ; else iEnable = MF_GRAYED ; EnableMenuItem (hMenu, IDM_FILE_SAVE, iEnable) ; EnableMenuItem (hMenu, IDM_FILE_PRINT, iEnable) ; EnableMenuItem (hMenu, IDM_FILE_PROPERTIES, iEnable) ; EnableMenuItem (hMenu, IDM_EDIT_CUT, iEnable) ; EnableMenuItem (hMenu, IDM_EDIT_COPY, iEnable) ; EnableMenuItem (hMenu, IDM_EDIT_DELETE, iEnable) ; if (DibIsAddressable (hdib)) iEnable = MF_ENABLED ; else iEnable = MF_GRAYED ; EnableMenuItem (hMenu, IDM_EDIT_ROTATE, iEnable) ; EnableMenuItem (hMenu, IDM_EDIT_FLIP, iEnable) ; EnableMenuItem (hMenu, IDM_CONVERT_01, iEnable) ; EnableMenuItem (hMenu, IDM_CONVERT_04, iEnable) ; EnableMenuItem (hMenu, IDM_CONVERT_08, iEnable) ; EnableMenuItem (hMenu, IDM_CONVERT_16, iEnable) ; EnableMenuItem (hMenu, IDM_CONVERT_24, iEnable) ; EnableMenuItem (hMenu, IDM_CONVERT_32, iEnable) ; switch (DibBitCount (hdib)) { case 1: EnableMenuItem (hMenu, IDM_CONVERT_01, MF_GRAYED) ; break ; case 4: EnableMenuItem (hMenu, IDM_CONVERT_04, MF_GRAYED) ; break ; case 8: EnableMenuItem (hMenu, IDM_CONVERT_08, MF_GRAYED) ; break ; case 16: EnableMenuItem (hMenu, IDM_CONVERT_16, MF_GRAYED) ; break ; case 24: EnableMenuItem (hMenu, IDM_CONVERT_24, MF_GRAYED) ; break ; case 32: EnableMenuItem (hMenu, IDM_CONVERT_32, MF_GRAYED) ; break ; } if (hdib && DibColorSize (hdib) > 0) iEnable = MF_ENABLED ; else iEnable = MF_GRAYED ; EnableMenuItem (hMenu, IDM_PAL_DIBTABLE, iEnable) ; if (DibIsAddressable (hdib) && DibBitCount (hdib) > 8) iEnable = MF_ENABLED ; else iEnable = MF_GRAYED ; EnableMenuItem (hMenu, IDM_PAL_OPT_POP4, iEnable) ; EnableMenuItem (hMenu, IDM_PAL_OPT_POP5, iEnable) ; EnableMenuItem (hMenu, IDM_PAL_OPT_POP6, iEnable) ; EnableMenuItem (hMenu, IDM_PAL_OPT_MEDCUT, iEnable) ; EnableMenuItem (hMenu, IDM_EDIT_PASTE, IsClipboardFormatAvailable (CF_DIB) ? MF_ENABLED : MF_GRAYED) ; return 0 ; // WM_COMMAND: Process all menu commands. case WM_COMMAND: iConvert = 0 ; switch (LOWORD (wParam)) { case IDM_FILE_OPEN: // Show the File Open dialog box if (!GetOpenFileName (&ofn)) return 0 ; // If there's an existing DIB and palette, delete them SendMessage (hwnd, WM_USER_DELETEDIB, 0, 0) ; // Load the DIB into memory SetCursor (LoadCursor (NULL, IDC_WAIT)) ; ShowCursor (TRUE) ; hdib = DibFileLoad (szFileName) ; ShowCursor (FALSE) ; SetCursor (LoadCursor (NULL, IDC_ARROW)) ; // Reset the scroll bars SendMessage (hwnd, WM_USER_SETSCROLLS, TRUE, 0) ; // Create the palette and DDB SendMessage (hwnd, WM_USER_CREATEPAL, TRUE, 0) ; if (!hdib) { MessageBox (hwnd, TEXT ("Cannot load DIB file!"), szAppName, MB_OK | MB_ICONEXCLAMATION) ; } InvalidateRect (hwnd, NULL, TRUE) ; return 0 ; case IDM_FILE_SAVE: // Show the File Save dialog box if (! GetSaveFileName (&ofn)) return 0 ; // Save the DIB to memory SetCursor (LoadCursor (NULL, IDC_WAIT)) ; ShowCursor (TRUE) ; fSuccess = DibFileSave (hdib, szFileName) ; ShowCursor (FALSE) ; SetCursor (LoadCursor (NULL, IDC_ARROW)) ; if (!fSuccess) MessageBox ( hwnd, TEXT ("Cannot save DIB file!"), szAppName, MB_OK | MB_ICONEXCLAMATION) ; return 0 ; case IDM_FILE_PRINT: if (!hdib) return 0 ; // Get printer DC printdlg.Flags = PD_RETURNDC | PD_NOPAGENUMS | PD_NOSELECTION ; if (!PrintDlg (&printdlg)) return 0 ; if (NULL == (hdcPrn = printdlg.hDC)) { MessageBox( hwnd, TEXT ("Cannot obtain Printer DC"), szAppName, MB_ICONEXCLAMATION | MB_OK) ; return 0 ; } // Check if the printer can print bitmaps if (!(RC_BITBLT & GetDeviceCaps (hdcPrn, RASTERCAPS))) { DeleteDC (hdcPrn) ; MessageBox ( hwnd, TEXT ("Printer cannot print bitmaps"), szAppName, MB_ICONEXCLAMATION | MB_OK) ; return 0 ; } // Get size of printable area of page cxPage = GetDeviceCaps (hdcPrn, HORZRES) ; cyPage = GetDeviceCaps (hdcPrn, VERTRES) ; fSuccess = FALSE ; // Send the DIB to the printer SetCursor (LoadCursor (NULL, IDC_WAIT)) ; ShowCursor (TRUE) ; if ((StartDoc (hdcPrn, &di) > 0) && (StartPage (hdcPrn) > 0)) { DisplayDib (hdcPrn, DibBitmapHandle (hdib), 0, 0, cxPage, cyPage, wShow, FALSE) ; if (EndPage (hdcPrn) > 0) { fSuccess = TRUE ; EndDoc (hdcPrn) ; } } ShowCursor (FALSE) ; SetCursor (LoadCursor (NULL, IDC_ARROW)) ; DeleteDC (hdcPrn) ; if ( !fSuccess) MessageBox ( hwnd, TEXT ("Could not print bitmap"), szAppName, MB_ICONEXCLAMATION | MB_OK) ; return 0 ; case IDM_FILE_PROPERTIES: if (!hdib) return 0 ; wsprintf (szBuffer, TEXT ("Pixel width:\t%i\n") TEXT ("Pixel height:\t%i\n") TEXT ("Bits per pixel:\t%i\n") TEXT ("Number of colors:\t%i\n") TEXT ("Compression:\t%s\n"), DibWidth (hdib), DibHeight (hdib), DibBitCount (hdib), DibNumColors (hdib), szCompression [min (3, DibCompression (hdib))]) ; MessageBox ( hwnd, szBuffer, szAppName, MB_ICONEXCLAMATION | MB_OK) ; return 0 ; case IDM_APP_EXIT: SendMessage (hwnd, WM_CLOSE, 0, 0) ; return 0 ; case IDM_EDIT_COPY: case IDM_EDIT_CUT: if (!(hGlobal = DibCopyToPackedDib (hdib, TRUE))) return 0 ; OpenClipboard (hwnd) ; EmptyClipboard () ; SetClipboardData (CF_DIB, hGlobal) ; CloseClipboard () ; if (LOWORD (wParam) == IDM_EDIT_COPY) return 0 ; // fall through for IDM_EDIT_CUT case IDM_EDIT_DELETE: SendMessage (hwnd, WM_USER_DELETEDIB, 0, 0) ; InvalidateRect (hwnd, NULL, TRUE) ; return 0 ; case IDM_EDIT_PASTE: OpenClipboard (hwnd) ; hGlobal = GetClipboardData (CF_DIB) ; pGlobal = GlobalLock (hGlobal) ; // If there's an existing DIB and palette,delete them. // Then convert the packed DIB to an HDIB. if (pGlobal) { SendMessage (hwnd, WM_USER_DELETEDIB, 0, 0) ; hdib = DibCopyFromPackedDib ((BITMAPINFO *) pGlobal) ; SendMessage (hwnd, WM_USER_CREATEPAL, TRUE, 0) ; } GlobalUnlock (hGlobal) ; CloseClipboard () ; // Reset the scroll bars SendMessage (hwnd, WM_USER_SETSCROLLS, TRUE, 0) ; InvalidateRect (hwnd, NULL, TRUE) ; return 0 ; case IDM_EDIT_ROTATE: if (hdibNew = DibRotateRight (hdib)) { DibDelete (hdib) ; DeleteObject (hBitmap) ; hdib = hdibNew ; hBitmap = DibCopyToDdb (hdib, hwnd, hPalette) ; SendMessage (hwnd, WM_USER_SETSCROLLS, TRUE, 0) ; InvalidateRect (hwnd, NULL, TRUE) ; } else { MessageBox ( hwnd, TEXT ("Not enough memory"), szAppName, MB_OK | MB_ICONEXCLAMATION) ; } return 0 ; case IDM_EDIT_FLIP: if (hdibNew = DibFlipHorizontal (hdib)) { DibDelete (hdib) ; DeleteObject (hBitmap) ; hdib = hdibNew ; hBitmap = DibCopyToDdb (hdib, hwnd, hPalette) ; InvalidateRect (hwnd, NULL, TRUE) ; } else { MessageBox ( hwnd, TEXT ("Not enough memory"), szAppName, MB_OK | MB_ICONEXCLAMATION) ; } return 0 ; case IDM_SHOW_NORMAL: case IDM_SHOW_CENTER: case IDM_SHOW_STRETCH: case IDM_SHOW_ISOSTRETCH: CheckMenuItem (hMenu, wShow, MF_UNCHECKED) ; wShow = LOWORD (wParam) ; CheckMenuItem (hMenu, wShow, MF_CHECKED) ; SendMessage (hwnd, WM_USER_SETSCROLLS, FALSE, 0) ; InvalidateRect (hwnd, NULL, TRUE) ; return 0 ; case IDM_CONVERT_32: iConvert += 8 ; case IDM_CONVERT_24: iConvert += 8 ; case IDM_CONVERT_16: iConvert += 8 ; case IDM_CONVERT_08: iConvert += 4 ; case IDM_CONVERT_04: iConvert += 3 ; case IDM_CONVERT_01: iConvert += 1 ; SetCursor (LoadCursor (NULL, IDC_WAIT)) ; ShowCursor (TRUE) ; hdibNew = DibConvert (hdib, iConvert) ; ShowCursor (FALSE) ; SetCursor (LoadCursor (NULL, IDC_ARROW)) ; if (hdibNew) { SendMessage (hwnd, WM_USER_DELETEDIB, 0, 0) ; hdib = hdibNew ; SendMessage (hwnd, WM_USER_CREATEPAL, TRUE, 0) ; InvalidateRect (hwnd, NULL, TRUE) ; } else { MessageBox ( hwnd, TEXT ("Not enough memory"), szAppName, MB_OK | MB_ICONEXCLAMATION) ; } return 0 ; case IDM_APP_ABOUT: MessageBox ( hwnd, TEXT ("Dibble (c) Charles Petzold, 1998"), szAppName, MB_OK | MB_ICONEXCLAMATION) ; return 0 ; } // All the other WM_COMMAND messages are from the palette // items. Any existing palette is deleted, and the cursor // is set to the hourglass. SendMessage (hwnd, WM_USER_DELETEPAL, 0, 0) ; SetCursor (LoadCursor (NULL, IDC_WAIT)) ; ShowCursor (TRUE) ; // Notice that all messages for palette items are ended // with break rather than return. This is to allow // additional processing later on. switch (LOWORD (wParam)) { case IDM_PAL_DIBTABLE: hPalette = DibPalDibTable (hdib) ; break ; case IDM_PAL_HALFTONE: hdc = GetDC (hwnd) ; if (hPalette = CreateHalftonePalette (hdc)) fHalftonePalette = TRUE ; ReleaseDC (hwnd, hdc) ; break ; case IDM_PAL_ALLPURPOSE: hPalette = DibPalAllPurpose () ; break ; case IDM_PAL_GRAY2:hPalette = DibPalUniformGrays (2) ; break; case IDM_PAL_GRAY3:hPalette = DibPalUniformGrays (3) ; break; case IDM_PAL_GRAY4:hPalette = DibPalUniformGrays (4) ; break; case IDM_PAL_GRAY8:hPalette = DibPalUniformGrays (8) ; break; case IDM_PAL_GRAY16:hPalette = DibPalUniformGrays (16) ; break; case IDM_PAL_GRAY32:hPalette = DibPalUniformGrays (32) ; break; case IDM_PAL_GRAY64:hPalette = DibPalUniformGrays (64) ; break; case IDM_PAL_GRAY128:hPalette = DibPalUniformGrays (128) ; break; case IDM_PAL_GRAY256:hPalette = DibPalUniformGrays (256) ; break; case IDM_PAL_RGB222:hPalette = DibPalUniformColors (2,2,2); break; case IDM_PAL_RGB333:hPalette = DibPalUniformColors (3,3,3); break; case IDM_PAL_RGB444:hPalette = DibPalUniformColors (4,4,4); break; case IDM_PAL_RGB555:hPalette = DibPalUniformColors (5,5,5); break; case IDM_PAL_RGB666:hPalette = DibPalUniformColors (6,6,6); break; case IDM_PAL_RGB775:hPalette = DibPalUniformColors (7,7,5); break; case IDM_PAL_RGB757:hPalette = DibPalUniformColors (7,5,7); break; case IDM_PAL_RGB577:hPalette = DibPalUniformColors (5,7,7); break; case IDM_PAL_RGB884:hPalette = DibPalUniformColors (8,8,4); break; case IDM_PAL_RGB848:hPalette = DibPalUniformColors (8,4,8); break; case IDM_PAL_RGB488:hPalette = DibPalUniformColors (4,8,8); break; case IDM_PAL_OPT_POP4:hPalette = DibPalPopularity (hdib, 4) ; break ; case IDM_PAL_OPT_POP5:hPalette = DibPalPopularity (hdib, 5) ; break ; case IDM_PAL_OPT_POP6:hPalette = DibPalPopularity (hdib, 6) ; break ; case IDM_PAL_OPT_MEDCUT:hPalette = DibPalMedianCut (hdib, 6) ; break ; } // After processing Palette items from the menu, the cursor // is restored to an arrow, the menu item is checked, and // the window is invalidated. hBitmap = DibCopyToDdb (hdib, hwnd, hPalette) ; ShowCursor (FALSE) ; SetCursor (LoadCursor (NULL, IDC_ARROW)) ; if (hPalette) PaletteMenu (hMenu, (LOWORD (wParam))) ; InvalidateRect (hwnd, NULL, TRUE) ; return 0 ; // This programmer-defined message deletes an existing DIB // in preparation for getting a new one. Invoked during // File Open command, Edit Paste command, and others. case WM_USER_DELETEDIB: if (hdib) { DibDelete (hdib) ; hdib = NULL ; } SendMessage (hwnd, WM_USER_DELETEPAL, 0, 0) ; return 0 ; // This programmer-defined message deletes an existing palette // in preparation for defining a new one. case WM_USER_DELETEPAL: if (hPalette) { DeleteObject (hPalette) ; hPalette = NULL ; fHalftonePalette = FALSE ; PaletteMenu (hMenu, IDM_PAL_NONE) ; } if (hBitmap) DeleteObject (hBitmap) ; return 0 ; // Programmer-defined message to create a new palette based on // a new DIB. If wParam == TRUE, create a DDB as well. case WM_USER_CREATEPAL: if (hdib) { hdc = GetDC (hwnd) ; if (!(RC_PALETTE & GetDeviceCaps (hdc, RASTERCAPS))) { PaletteMenu (hMenu, IDM_PAL_NONE) ; } else if (hPalette = DibPalDibTable (hdib)) { PaletteMenu (hMenu, IDM_PAL_DIBTABLE) ; } else if (hPalette = CreateHalftonePalette (hdc)) { fHalftonePalette = TRUE ; PaletteMenu (hMenu, IDM_PAL_HALFTONE) ; } ReleaseDC (hwnd, hdc) ; if ((BOOL) wParam) hBitmap = DibCopyToDdb (hdib, hwnd, hPalette) ; } return 0 ; case WM_PAINT: hdc = BeginPaint (hwnd, &ps) ; if (hPalette) { SelectPalette (hdc, hPalette, FALSE) ; RealizePalette (hdc) ; } if (hBitmap) { DisplayDib ( hdc, fHalftonePalette ? DibBitmapHandle (hdib) : hBitmap, iHscroll, iVscroll, cxClient, cyClient, wShow, fHalftonePalette) ; } EndPaint (hwnd, &ps) ; return 0 ; case WM_QUERYNEWPALETTE: if (!hPalette) return FALSE ; hdc = GetDC (hwnd) ; SelectPalette (hdc, hPalette, FALSE) ; RealizePalette (hdc) ; InvalidateRect (hwnd, NULL, TRUE) ; ReleaseDC (hwnd, hdc) ; return TRUE ; case WM_PALETTECHANGED: if (!hPalette || (HWND) wParam == hwnd) break ; hdc = GetDC (hwnd) ; SelectPalette (hdc, hPalette, FALSE) ; RealizePalette (hdc) ; UpdateColors (hdc) ; ReleaseDC (hwnd, hdc) ; break ; case WM_DESTROY: if (hdib) DibDelete (hdib) ; if (hBitmap) DeleteObject (hBitmap) ; if (hPalette) DeleteObject (hPalette) ; PostQuitMessage (0) ; return 0 ; } return DefWindowProc (hwnd, message, wParam, lParam) ; }

DIBBLE.RC (摘錄) //Microsoft Developer Studio generated resource script. #include "resource.h" #include "afxres.h" ///////////////////////////////////////////////////////////////////////////// // Menu DIBBLE MENU DISCARDABLE BEGIN POPUP "&File" BEGIN MENUITEM "&Open...\tCtrl+O", IDM_FILE_OPEN MENUITEM "&Save...\tCtrl+S", IDM_FILE_SAVE MENUITEM SEPARATOR MENUITEM "&Print...\tCtrl+P", IDM_FILE_PRINT MENUITEM SEPARATOR MENUITEM "Propert&ies...", IDM_FILE_PROPERTIES MENUITEM SEPARATOR MENUITEM "E&xit", IDM_APP_EXIT END POPUP "&Edit" BEGIN MENUITEM "Cu&t\tCtrl+X", IDM_EDIT_CUT MENUITEM "&Copy\tCtrl+C", IDM_EDIT_COPY MENUITEM "&Paste\tCtrl+V", IDM_EDIT_PASTE MENUITEM "&Delete\tDelete", IDM_EDIT_DELETE MENUITEM SEPARATOR MENUITEM "&Flip", IDM_EDIT_FLIP MENUITEM "&Rotate", IDM_EDIT_ROTATE END POPUP "&Show" BEGIN MENUITEM "&Actual Size", IDM_SHOW_NORMAL, CHECKED MENUITEM "&Center", IDM_SHOW_CENTER MENUITEM "&Stretch to Window", IDM_SHOW_STRETCH MENUITEM "Stretch &Isotropically", IDM_SHOW_ISOSTRETCH END POPUP "&Palette" BEGIN MENUITEM "&None", IDM_PAL_NONE, CHECKED MENUITEM "&Dib ColorTable", IDM_PAL_DIBTABLE MENUITEM "&Halftone", IDM_PAL_HALFTONE MENUITEM "&All-Purpose", IDM_PAL_ALLPURPOSE POPUP "&Gray Shades" BEGIN MENUITEM "&1. 2 Grays", IDM_PAL_GRAY2 MENUITEM "&2. 3 Grays", IDM_PAL_GRAY3 MENUITEM "&3. 4 Grays", IDM_PAL_GRAY4 MENUITEM "&4. 8 Grays", IDM_PAL_GRAY8 MENUITEM "&5. 16 Grays", IDM_PAL_GRAY16 MENUITEM "&6. 32 Grays", IDM_PAL_GRAY32 MENUITEM "&7. 64 Grays", IDM_PAL_GRAY64 MENUITEM "&8. 128 Grays", IDM_PAL_GRAY128 MENUITEM "&9. 256 Grays", IDM_PAL_GRAY256 END POPUP "&Uniform Colors" BEGIN MENUITEM "&1. 2R x 2G x 2B (8)", IDM_PAL_RGB222 MENUITEM "&2. 3R x 3G x 3B (27)", IDM_PAL_RGB333 MENUITEM "&3. 4R x 4G x 4B (64)", IDM_PAL_RGB444 MENUITEM "&4. 5R x 5G x 5B (125)", IDM_PAL_RGB555 MENUITEM "&5. 6R x 6G x 6B (216)", IDM_PAL_RGB666 MENUITEM "&6. 7R x 7G x 5B (245)", IDM_PAL_RGB775 MENUITEM "&7. 7R x 5B x 7B (245)", IDM_PAL_RGB757 MENUITEM "&8. 5R x 7G x 7B (245)", IDM_PAL_RGB577 MENUITEM "&9. 8R x 8G x 4B (256)", IDM_PAL_RGB884 MENUITEM "&A. 8R x 4G x 8B (256)", IDM_PAL_RGB848 MENUITEM "&B. 4R x 8G x 8B (256)", IDM_PAL_RGB488 END POPUP "&Optimized" BEGIN MENUITEM "&1. Popularity Algorithm (4 bits)"IDM_PAL_OPT_POP4 MENUITEM "&2. Popularity Algorithm (5 bits)"IDM_PAL_OPT_POP5 MENUITEM "&3. Popularity Algorithm (6 bits)"IDM_PAL_OPT_POP6 MENUITEM "&4. Median Cut Algorithm ", IDM_PAL_OPT_MEDCUT END END POPUP "Con&vert" BEGIN MENUITEM "&1. to 1 bit per pixel", IDM_CONVERT_01 MENUITEM "&2. to 4 bits per pixel", IDM_CONVERT_04 MENUITEM "&3. to 8 bits per pixel", IDM_CONVERT_08 MENUITEM "&4. to 16 bits per pixel", IDM_CONVERT_16 MENUITEM "&5. to 24 bits per pixel", IDM_CONVERT_24 MENUITEM "&6. to 32 bits per pixel", IDM_CONVERT_32 END POPUP "&Help" BEGIN MENUITEM "&About", IDM_APP_ABOUT END END ///////////////////////////////////////////////////////////////////////////// // Accelerator DIBBLE ACCELERATORS DISCARDABLE BEGIN "C", IDM_EDIT_COPY, VIRTKEY, CONTROL, NOINVERT "O", IDM_FILE_OPEN, VIRTKEY, CONTROL, NOINVERT "P", IDM_FILE_PRINT, VIRTKEY, CONTROL, NOINVERT "S", IDM_FILE_SAVE, VIRTKEY, CONTROL, NOINVERT "V", IDM_EDIT_PASTE, VIRTKEY, CONTROL, NOINVERT VK_DELETE, IDM_EDIT_DELETE, VIRTKEY, NOINVERT "X", IDM_EDIT_CUT, VIRTKEY, CONTROL, NOINVERT END

RESOURCE.H (摘錄) // Microsoft Developer Studio generated include file. // Used by Dibble.rc #define IDM_FILE_OPEN 40001 #define IDM_FILE_SAVE 40002 #define IDM_FILE_PRINT 40003 #define IDM_FILE_PROPERTIES 40004 #define IDM_APP_EXIT 40005 #define IDM_EDIT_CUT 40006 #define IDM_EDIT_COPY 40007 #define IDM_EDIT_PASTE 40008 #define IDM_EDIT_DELETE 40009 #define IDM_EDIT_FLIP 40010 #define IDM_EDIT_ROTATE 40011 #define IDM_SHOW_NORMAL 40012 #define IDM_SHOW_CENTER 40013 #define IDM_SHOW_STRETCH 40014 #define IDM_SHOW_ISOSTRETCH 40015 #define IDM_PAL_NONE 40016 #define IDM_PAL_DIBTABLE 40017 #define IDM_PAL_HALFTONE 40018 #define IDM_PAL_ALLPURPOSE 40019 #define IDM_PAL_GRAY2 40020 #define IDM_PAL_GRAY3 40021 #define IDM_PAL_GRAY4 40022 #define IDM_PAL_GRAY8 40023 #define IDM_PAL_GRAY16 40024 #define IDM_PAL_GRAY32 40025 #define IDM_PAL_GRAY64 40026 #define IDM_PAL_GRAY128 40027 #define IDM_PAL_GRAY256 40028 #define IDM_PAL_RGB222 40029 #define IDM_PAL_RGB333 40030 #define IDM_PAL_RGB444 40031 #define IDM_PAL_RGB555 40032 #define IDM_PAL_RGB666 40033 #define IDM_PAL_RGB775 40034 #define IDM_PAL_RGB757 40035 #define IDM_PAL_RGB577 40036 #define IDM_PAL_RGB884 40037 #define IDM_PAL_RGB848 40038 #define IDM_PAL_RGB488 40039 #define IDM_PAL_OPT_POP4 40040 #define IDM_PAL_OPT_POP5 40041 #define IDM_PAL_OPT_POP6 40042 #define IDM_PAL_OPT_MEDCUT 40043 #define IDM_CONVERT_01 40044 #define IDM_CONVERT_04 40045 #define IDM_CONVERT_08 40046 #define IDM_CONVERT_16 40047 #define IDM_CONVERT_24 40048 #define IDM_CONVERT_32 40049 #define IDM_APP_ABOUT 40050

DIBBLE使用了兩個其他檔案,我將簡要地說明它們。DIBCONV檔案(DIBCONV.C和DIBCONV.H)在兩種不同格式之間轉換-例如,從每圖素24位元轉換成每圖素8位元。DIBPAL檔案(DIBPAL.C和DIBPAL.H)建立調色盤。

DIBBLE維護WndProc中的三個重要的靜態變數。這些是呼叫hdib的HDIB代號、呼叫hPalette的HPALETTE代號和呼叫hBitmap的HBITMAP代號。HDIB來自DIBHELP中的不同函式;HPALETTE來自DIBPAL中的不同函式或CreateHalftonePalette函式;而HBITMAP代號來自DIBHELP.C中的DibCopyToDdb函式並幫助加速螢幕顯示,特別是在256色顯示模式下。不過,無論在程式建立新的「DIB Section」(顯而易見地)或在程式建立不同的調色盤(不很明顯)時,這個代號都必須重新建立。

讓我們從功能上而非循序漸進地來介紹一下DIBBLE。

檔案載入和儲存

DIBBLE可以在回應IDM_FILE_LOAD和IDM_FILE_SAVE的WM_COMMAND訊息處理過程中載入DIB檔案並儲存這些檔案。在處理這些訊息處理期間,DIBBLE通過分別呼叫GetOpenFileName和GetSaveFileName來啟動公用檔案對話方塊。

對於「File」、「Save」功能表命令,DIBBLE只需要呼叫DibFileSave。對於「File」、「Open」功能表命令,DIBBLE必須首先刪除前面的HDIB、調色盤和點陣圖物件。它透過發送一個WM_USER_DELETEDIB訊息來完成這件事,此訊息通過呼叫DibDelete和DeleteObject來處理。然後DIBBLE呼叫DIBHELP中的DibFileLoad函式,發送WM_USER_SETSCROLLS和WM_USER_CREATEPAL訊息來重新設定捲動列並建立調色盤。WM_USER_CREATEPAL訊息也位於程式從DIB區塊建立的新的DDB位置。

顯示、捲動和列印

DIBBLE的功能表允許它以實際尺寸在顯示區域左上角顯示DIB,或在顯示區域中間顯示DIB,或伸展到填充顯示區域,或者在保持縱橫比的情況下盡量填充顯示區域。您可以在DIBBLE的「Show」功能表上來選擇需要的選項。注意,這些與 上一章的SHOWDIB2程式 中四個選項相同。

在WM_PAINT訊息處理期間-也是處理「File」、「Print」命令的過程中-DIBBLE呼叫DisplayDib函式。注意,DisplayDib使用BitBlt和StretchBlt,而不是使用SetDIBitsToDevice和StretchDIBits。在WM_PAINT訊息處理期間,傳遞給函式的點陣圖代號由DibCopyToDdb函式建立,並在WM_USER_CREATEPAL訊息處理期間呼叫。其中DDB與視訊裝置內容相容。當處理「File」、「Print」命令時,DIBBLE呼叫DisplayDib,其中可用的DIB區塊代號來自DIBHELP.C中的DibBitmapHandle函式。

另外要注意,DIBBLE保留一個稱作fHalftonePalette的靜態BOOL變數,如果從CreateHalftonePalette函式中獲得hPalette,則此變數設定為TRUE。這將迫使DisplayDib函式呼叫StretchBlt而不是呼叫BitBlt,即使DIB被指定按實際尺寸顯示。fHalftonePalette變數也導致WM_PAINT處理程式將DIB區塊代號傳遞給DisplayDib函式,而不是由DibCopyToDdb函式建立的點陣圖代號。本章前面討論過中間色調色盤的使用,並在SHOWDIB5程式中進行了展示。

第一次使用範例程式時,DIBBLE允許在顯示區域中捲動DIB。只有按實際尺寸顯示DIB時,才顯示捲動列。在處理WM_PAINT時,WndProc簡單地將捲動列的目前位置傳遞給DisplayDib函式。

剪貼簿

對於「Cut」和「Copy」功能表項,DIBBLE呼叫DIBHELP中的DibCopyToPackedDib函式。該函式將獲得所有的DIB元件並將它們放入大的記憶體塊中。

對於第一次使用本書中的某些範例程式來說,DIBBLE從剪貼簿中粘貼DIB。這包括呼叫DibCopyFromPackedDib函式,並替換視窗訊息處理程式前面儲存的HDIB、調色盤和點陣圖。

翻轉和旋轉

DIBBLE中的「Edit」功能表中除了常見的「Cut」、「Copy」、「Paste」和「Delete」選項之外,還包括兩個附加項-「Flip」和「Rotate」。「Flip」選項使點陣圖繞水平軸翻轉-即上下顛倒翻轉。「Rotate」選項使點陣圖順時針旋轉90度。這兩個函式都需要透過將它們從一個DIB複製到另一個來存取所有的DIB圖素(因為這兩個函式不需要建立新的調色盤,所以不刪除和重新建立調色盤)。

「Flip」功能表選項使用DibFlipHorizontal函式,此函式也位於DIBBLE.C檔案。此函式呼叫DibCopy來獲得DIB精確的副本。然後,進入將原DIB中的圖素複製到新DIB的迴圈,但是複製這些圖素是為了上下翻轉圖像。注意,此函式呼叫DibGetPixel和DibSetPixel。這些是DIBHELP.C中的通用(但不像我們所希望的那麼快)函式。

為了說明DibGetPixel和DibSetPixel函式與DIBHELP.H中執行更快的DibGetPixel和DibSetPixel巨集之間的區別,DibRotateRight函式使用了巨集。然而,首先要注意的是,該函式呼叫DibCopy時,第二個參數設定為TRUE。這導致DibCopy翻轉原DIB的寬度和高度來建立新的DIB。另外,圖素位元不能由DibCopy函式複製。但是,DibRotateRight函式有六個不同的迴圈將圖素位元從原DIB複製到新的DIB-每一個都對應不同的DIB圖素寬度(1位元、4位元、8位元、16位元、24位元和32位元)。雖然包括了更多的程式碼,但是函式更快了。

儘管可以使用「Flip Horizontal」和「Rotate Right」選項來產生「Flip Vertical」、「Rotate Left」和「Rotate 180°」功能,但通常程式將直接執行所有選項。畢竟,DIBBLE只是個展示程式而已。

簡單調色盤;最佳化調色盤

在DIBBLE中,您可以在256色視訊顯示器上選擇不同的調色盤來顯示DIB。這些都在DIBBLE的「Palette」功能表中列出。除了中間色調色盤以外,其餘的都直接由Windows函式呼叫建立,建立不同調色盤的所有函式都由程式16-24所示的DIBPAL檔案提供。

程式16-24 DIBPAL檔案 DIBPAL.H /*-------------------------------------------------------------------------- DIBPAL.H header file for DIBPAL.C ----------------------------------------------------------------------------*/ HPALETTE DibPalDibTable (HDIB hdib) ; HPALETTE DibPalAllPurpose (void) ; HPALETTE DibPalUniformGrays (int iNum) ; HPALETTE DibPalUniformColors (int iNumR, int iNumG, int iNumB) ; HPALETTE DibPalVga (void) ; HPALETTE DibPalPopularity (HDIB hdib, int iRes) ; HPALETTE DibPalMedianCut (HDIB hdib, int iRes) ;

DIBPAL.C /*---------------------------------------------------------------------------- DIBPAL.C -- Palette-Creation Functions (c) Charles Petzold, 1998 -------------------------------------------------------------------------*/ #include <windows.h> #include "dibhelp.h" #include "dibpal.h" /*--------------------------------------------------------------------------- DibPalDibTable: Creates a palette from the DIB color table -----------------------------------------------------------------------------*/ HPALETTE DibPalDibTable (HDIB hdib) { HPALETTE hPalette ; int i, iNum ; LOGPALETTE * plp ; RGBQUAD rgb ; if (0 == (iNum = DibNumColors (hdib))) return NULL ; plp = malloc (sizeof (LOGPALETTE) + (iNum - 1) * sizeof (PALETTEENTRY)) ; plp->palVersion = 0x0300 ; plp->palNumEntries = iNum ; for (i = 0 ; i < iNum ; i++) { DibGetColor (hdib, i, &rgb) ; plp->palPalEntry[i].peRed = rgb.rgbRed ; plp->palPalEntry[i].peGreen = rgb.rgbGreen ; plp->palPalEntry[i].peBlue = rgb.rgbBlue ; plp->palPalEntry[i].peFlags = 0 ; } hPalette = CreatePalette (plp) ; free (plp) ; return hPalette ; } /*--------------------------------------------------------------------------- DibPalA llPurpose: Creates a palette suitable for a wide variety of images; the palette has 247 entries, but 15 of them are duplicates or match the standard 20 colors. ----------------------------------------------------------------------------*/ HPALETTE DibPalAllPurpose (void) { HPALETTE hPalette ; int i, incr, R, G, B ; LOGPALETTE * plp ; plp = malloc (sizeof (LOGPALETTE) + 246 * sizeof (PALETTEENTRY)) ; plp->palVersion = 0x0300 ; plp->palNumEntries = 247 ; // The following loop calculates 31 gray shades, but 3 of them // will match the standard 20 colors for (i = 0, G = 0, incr = 8 ; G <= 0xFF ; i++, G += incr) { plp->palPalEntry[i].peRed = (BYTE) G ; plp->palPalEntry[i].peGreen = (BYTE) G ; plp->palPalEntry[i].peBlue = (BYTE) G ; plp->palPalEntry[i].peFlags = 0 ; incr = (incr == 9 ? 8 : 9) ; } // The following loop is responsible for 216 entries, but 8 of // them will match the standard 20 colors, and another // 4 of them will match the gray shades above. for (R = 0 ; R <= 0xFF ; R += 0x33) for (G = 0 ; G <= 0xFF ; G += 0x33) for (B = 0 ; B <= 0xFF ; B += 0x33) { plp->palPalEntry[i].peRed = (BYTE) R ; plp->palPalEntry[i].peGreen = (BYTE) G ; plp->palPalEntry[i].peBlue = (BYTE) B ; plp->palPalEntry[i].peFlags = 0 ; i++ ; } hPalette = CreatePalette (plp) ; free (plp) ; return hPalette ; } /*--------------------------------------------------------------------------- DibPalUniformGrays: Creates a palette of iNum grays, uniformly spaced ----------------------------------------------------------------------------*/ HPALETTE DibPalUniformGrays (int iNum) { HPALETTE hPalette ; int i ; LOGPALETTE * plp ; plp = malloc (sizeof (LOGPALETTE) + (iNum - 1) * sizeof (PALETTEENTRY)) ; plp->palVersion = 0x0300 ; plp->palNumEntries = iNum ; for (i = 0 ; i < iNum ; i++) { plp->palPalEntry[i].peRed = plp->palPalEntry[i].peGreen = plp->palPalEntry[i].peBlue = (BYTE) (i * 255 / (iNum - 1)) ; plp->palPalEntry[i].peFlags = 0 ; } hPalette = CreatePalette (plp) ; free (plp) ; return hPalette ; } /*-------------------------------------------------------------------------- DibPalUniformColors: Creates a palette of iNumR x iNumG x iNumB colors ----------------------------------------------------------------------------*/ HPALETTE DibPalUniformColors (int iNumR, int iNumG, int iNumB) { HPALETTE hPalette ; int i, iNum, R, G, B ; LOGPALETTE * plp ; iNum = iNumR * iNumG * iNumB ; plp = malloc (sizeof (LOGPALETTE) + (iNum - 1) * sizeof (PALETTEENTRY)) ; plp->palVersion = 0x0300 ; plp->palNumEntries = iNumR * iNumG * iNumB ; i = 0 ; for (R = 0 ; R < iNumR ; R++) for (G = 0 ; G < iNumG ; G++) for (B = 0 ; B < iNumB ; B++) { plp->palPalEntry[i].peRed = (BYTE) (R * 255 / (iNumR - 1)) ; plp->palPalEntry[i].peGreen = (BYTE) (G * 255 / (iNumG - 1)) ; plp->palPalEntry[i].peBlue = (BYTE) (B * 255 / (iNumB - 1)) ; plp->palPalEntry[i].peFlags = 0 ; i++ ; } hPalette = CreatePalette (plp) ; free (plp) ; return hPalette ; } /*--------------------------------------------------------------------------- DibPalVga: Creates a palette based on standard 16 VGA colors ----------------------------------------------------------------------------*/ HPALETTE DibPalVga (void) { static RGBQUAD rgb [16] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x80, 0x00, 0x80, 0x00, 0x00, 0x00, 0x80, 0x00, 0x80, 0x00, 0x80, 0x80, 0x00, 0x00, 0x80, 0x80, 0x80, 0x00, 0xC0, 0xC0, 0xC0, 0x00, 0x00, 0x00, 0xFF, 0x00, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0xFF, 0x00, 0xFF, 0x00, 0x00, 0x00, 0xFF, 0x00, 0xFF, 0x00, 0xFF, 0xFF, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0x00 } ; HPALETTE hPalette ; int i ; LOGPALETTE * plp ; plp = malloc (sizeof (LOGPALETTE) + 15 * sizeof (PALETTEENTRY)) ; plp->palVersion = 0x0300 ; plp->palNumEntries = 16 ; for (i = 0 ; i < 16 ; i++) { plp->palPalEntry[i].peRed = rgb[i].rgbRed ; plp->palPalEntry[i].peGreen = rgb[i].rgbGreen ; plp->palPalEntry[i].peBlue = rgb[i].rgbBlue ; plp->palPalEntry[i].peFlags = 0 ; } hPalette = CreatePalette (plp) ; free (plp) ; return hPalette ; } /*--------------------------------------------------------------------------- Macro used in palette optimization routines -------------------------------------------------------------------------*/ #define PACK_RGB(R,G,B,iRes) ((int) (R) | ((int) (G) << (iRes)) | \ ((int) (B) << ((iRes) + (iRes)))) /*---------------------------------------------------------------------------- AccumColorCounts: Fills up piCount (indexed by a packed RGB color) with counts of pixels of that color. -----------------------------------------------------------------------------*/ static void AccumColorCounts (HDIB hdib, int * piCount, int iRes) { int x, y, cx, cy ; RGBQUAD rgb ; cx = DibWidth (hdib) ; cy = DibHeight (hdib) ; for (y = 0 ; y < cy ; y++) for (x = 0 ; x < cx ; x++) { DibGetPixelColor (hdib, x, y, &rgb) ; rgb.rgbRed >>= (8 - iRes) ; rgb.rgbGreen >>= (8 - iRes) ; rgb.rgbBlue >>= (8 - iRes) ; ++piCount [PACK_RGB (rgb.rgbRed, rgb.rgbGreen, rgb.rgbBlue, iRes)] ; } } /*--------------------------------------------------------------------------- DibPalPopularity: Popularity algorithm for optimized colors -----------------------------------------------------------------------------*/ HPALETTE DibPalPopularity (HDIB hdib, int iRes) { HPALETTE hPalette ; int i, iArraySize, iEntry, iCount, iIndex, iMask, R, G, B ; int * piCount ; LOGPALETTE * plp ; // Validity checks if (DibBitCount (hdib) < 16) return NULL ; if (iRes < 3 || iRes > 8) return NULL ; // Allocate array for counting pixel colors iArraySize = 1 << (3 * iRes) ; iMask = (1 << iRes) - 1 ; if (NULL == (piCount = calloc (iArraySize, sizeof (int)))) return NULL ; // Get the color counts AccumColorCounts (hdib, piCount, iRes) ; // Set up a palette plp = malloc (sizeof (LOGPALETTE) + 235 * sizeof (PALETTEENTRY)) ; plp->palVersion = 0x0300 ; for (iEntry = 0 ; iEntry < 236 ; iEntry++) { for (i = 0, iCount = 0 ; i < iArraySize ; i++) if (piCount[i] > iCount) { iCount = piCount[i] ; iIndex = i ; } if (iCount == 0) break ; R = (iMask & iIndex) << (8 - iRes) ; G = (iMask & (iIndex >> iRes )) << (8 - iRes) ; B = (iMask & (iIndex >> (iRes + iRes)))<< (8 - iRes) ; plp->palPalEntry[iEntry].peRed = (BYTE) R ; plp->palPalEntry[iEntry].peGreen = (BYTE) G ; plp->palPalEntry[iEntry].peBlue = (BYTE) B ; plp->palPalEntry[iEntry].peFlags = 0 ; piCount [iIndex] = 0 ; } // On exit from the loop iEntry will be the number of stored entries plp->palNumEntries = iEntry ; // Create the palette, clean up, and return the palette handle hPalette = CreatePalette (plp) ; free (piCount) ; free (plp) ; return hPalette ; } /*-------------------------------------------------------------------------- Structures used for implementing median cut algorithm ----------------------------------------------------------------------------*/ typedef struct // defines dimension of a box { int Rmin, Rmax, Gmin, Gmax, Bmin, Bmax ; } MINMAX ; typedef struct // for Compare routine for qsort { int iBoxCount ; RGBQUAD rgbBoxAv ; } BOXES ; /*---------------------------------------------------------------------------- FindAverageColor: In a box -----------------------------------------------------------------------------*/ static int FindAverageColor ( int * piCount, MINMAX mm, int iRes, RGBQUAD * prgb) { int R, G, B, iR, iG, iB, iTotal, iCount ; // Initialize some variables iTotal = iR = iG = iB = 0 ; // Loop through all colors in the box for (R = mm.Rmin ; R <= mm.Rmax ; R++) for (G = mm.Gmin ; G <= mm.Gmax ; G++) for (B = mm.Bmin ; B <= mm.Bmax ; B++) { // Get the number of pixels of that color iCount = piCount [PACK_RGB (R, G, B, iRes)] ; // Weight the pixel count by the color value iR += iCount * R ; iG += iCount * G ; iB += iCount * B ; iTotal += iCount ; } // Find the average color prgb->rgbRed = (BYTE) ((iR / iTotal) << (8 - iRes)) ; prgb->rgbGreen = (BYTE) ((iG / iTotal) << (8 - iRes)) ; prgb->rgbBlue = (BYTE) ((iB / iTotal) << (8 - iRes)) ; // Return the total number of pixels in the box return iTotal ; } /*--------------------------------------------------------------------------- CutBox: Divide a box in two ----------------------------------------------------------------------------*/ static void CutBox (int * piCount, int iBoxCount, MINMAX mm, int iRes, int iLevel, BOXES * pboxes, int * piEntry) { int iCount, R, G, B ; MINMAX mmNew ; // If the box is empty, return if (iBoxCount == 0) return ; // If the nesting level is 8, or the box is one pixel, we're ready // to find the average color in the box and save it along with // the number of pixels of that color if (iLevel == 8 || ( mm.Rmin == mm.Rmax && mm.Gmin == mm.Gmax && mm.Bmin == mm.Bmax)) { pboxes[*piEntry].iBoxCount = FindAverageColor (piCount, mm, iRes, &pboxes[*piEntry].rgbBoxAv) ; (*piEntry) ++ ; } // Otherwise, if blue is the largest side, split it else if ((mm.Bmax - mm.Bmin > mm.Rmax - mm.Rmin) && (mm.Bmax - mm.Bmin > mm.Gmax - mm.Gmin)) { // Initialize a counter and loop through the blue side iCount = 0 ; for (B = mm.Bmin ; B < mm.Bmax ; B++) { // Accumulate all the pixels for each successive blue value for ( R = mm.Rmin ; R <= mm.Rmax ; R++) for ( G = mm.Gmin ; G <= mm.Gmax ; G++) iCount += piCount [PACK_RGB (R, G, B, iRes)] ; // If it's more than half the box count, we're there if (i Count >= iBoxCount / 2) break ; // If the next blue value will be the max, we're there if ( B == mm.Bmax - 1) break ; } // Cut the two split boxes. // The second argument to CutBox is the new box count. // The third argument is the new min and max values. mmNew = mm ; mmNew.Bmin = mm.Bmin ; mmNew.Bmax = B ; CutBox ( piCount, iCount, mmNew, iRes, iLevel + 1, pboxes, piEntry) ; mmNew.Bmin = B + 1 ; mmNew.Bmax = mm.Bmax ; CutBox ( piCount, iBoxCount - iCount, mmNew, iRes, iLevel + 1, pboxes, piEntry) ; } // Otherwise, if red is the largest side, split it (just like blue) else if (mm.Rmax - mm.Rmin > mm.Gmax - mm.Gmin) { iCount = 0 ; for (R = mm.Rmin ; R < mm.Rmax ; R++) { for (B = mm.Bmin ; B <= mm.Bmax ; B++) for (G = mm.Gmin ; G <= mm.Gmax ; G++) iCount += piCount [PACK_RGB (R, G, B, iRes)] ; if (iCount >= iBoxCount / 2) break ; if (R == mm.Rmax - 1) break ; } mmNew = mm ; mmNew.Rmin = mm.Rmin ; mmNew.Rmax = R ; CutBox ( piCount, iCount, mmNew, iRes, iLevel + 1, pboxes, piEntry) ; mmNew.Rmin = R + 1 ; mmNew.Rmax = mm.Rmax ; CutBox ( piCount, iBoxCount - iCount, mmNew, iRes, iLevel + 1, pboxes, piEntry) ; } // Otherwise, split along the green size else { iCount = 0 ; for (G = mm.Gmin ; G < mm.Gmax ; G++) { for ( B = mm.Bmin ; B <= mm.Bmax ; B++) for ( R = mm.Rmin ; R <= mm.Rmax ; R++) iCount += piCount [PACK_RGB (R, G, B, iRes)] ; if ( iCount >= iBoxCount / 2) break ; if ( G == mm.Gmax - 1) break ; } mmNew = mm ; mmNew.Gmin = mm.Gmin ; mmNew.Gmax = G ; CutBox ( piCount, iCount, mmNew, iRes, iLevel + 1, pboxes, piEntry) ; mmNew.Gmin = G + 1 ; mmNew.Gmax = mm.Gmax ; CutBox ( piCount, iBoxCount - iCount, mmNew, iRes, iLevel + 1, pboxes, piEntry) ; } } /*--------------------------------------------------------------------------- Compare routine for qsort -----------------------------------------------------------------------------*/ static int Compare (const BOXES * pbox1, const BOXES * pbox2) { return pbox1->iBoxCount - pbox2->iBoxCount ; } /*--------------------------------------------------------------------------- DibPalMedianCut: Creates palette based on median cut algorithm -------------------------------------------------------------------------*/ HPALETTE DibPalMedianCut (HDIB hdib, int iRes) { BOXES boxes [256] ; HPALETTE hPalette ; int i, iArraySize, iCount, R, G, B, iTotCount, iDim, iEntry = 0 ; int * piCount ; LOGPALETTE * plp ; MINMAX mm ; // Validity checks if (DibBitCount (hdib) < 16) return NULL ; if (iRes < 3 || iRes > 8) return NULL ; // Accumulate counts of pixel colors iArraySize = 1 << (3 * iRes) ; if (NULL == (piCount = calloc (iArraySize, sizeof (int)))) return NULL ; AccumColorCounts (hdib, piCount, iRes) ; // Find the dimensions of the total box iDim = 1 << iRes ; mm.Rmin = mm.Gmin = mm.Bmin = iDim - 1 ; mm.Rmax = mm.Gmax = mm.Bmax = 0 ; iTotCount = 0 ; for (R = 0 ; R < iDim ; R++) for (G = 0 ; G < iDim ; G++) for (B = 0 ; B < iDim ; B++) if ((iCount = piCount [PACK_RGB (R, G, B, iRes)]) > 0) { iTotCount += iCount ; if (R < mm.Rmin) mm.Rmin = R ; if (G < mm.Gmin) mm.Gmin = G ; if (B < mm.Bmin) mm.Bmin = B ; if (R > mm.Rmax) mm.Rmax = R ; if (G > mm.Gmax) mm.Gmax = G ; if (B > mm.Bmax) mm.Bmax = B ; } // Cut the first box (iterative function). // On return, the boxes structure will have up to 256 RGB values, // one for each of the boxes, and the number of pixels in // each box. // The iEntry value will indicate the number of non-empty boxes. CutBox (piCount, iTotCount, mm, iRes, 0, boxes, &iEntry) ; free (piCount) ; // Sort the RGB table by the number of pixels for each color qsort (boxes, iEntry, sizeof (BOXES), Compare) ; plp = malloc (sizeof (LOGPALETTE) + (iEntry - 1) * sizeof (PALETTEENTRY)) ; if (plp == NULL) return NULL ; plp->palVersion = 0x0300 ; plp->palNumEntries = iEntry ; for (i = 0 ; i < iEntry ; i++) { plp->palPalEntry[i].peRed = boxes[i].rgbBoxAv.rgbRed ; plp->palPalEntry[i].peGreen= boxes[i].rgbBoxAv.rgbGreen ; plp->palPalEntry[i].peBlue = boxes[i].rgbBoxAv.rgbBlue ; plp->palPalEntry[i].peFlags= 0 ; } hPalette = CreatePalette (plp) ; free (plp) ; return hPalette ; }

第一個函式-DibPalDibTable-看起來應該很熟悉。它根據DIB的顏色表建立了調色盤。這與本章前面的SHOWDIB3中所用到的PACKEDIB.C裏的PackedDibCreatePalette函式相似。在SHOWDIB3中,只有當DIB有顏色表時才執行此函式。在8位元顯示模式下試圖顯示16位元、24位元或32位元DIB時,此函式就沒用了。

預設情況下,執行在256色顯示模式下時,DIBBLE將首先嘗試呼叫DibPalDibTable來根據DIB顏色表建立調色盤。如果DIB沒有顏色表,則DIBBLE將呼叫CreateHalftonePalette並將fHalftonePalette變數設定為TRUE。此邏輯發生在WM_USER_CREATEPAL訊息處理期間。

DIBPAL.C也執行函式DibPalAllPurpose,因為此函式與SHOWDIB4中的CreateAllPurposePalette函式非常相似,所以它看起來也很熟悉。您也可以從DIBBLE功能表中選擇此調色盤。

在256色模式下顯示點陣圖最有趣的是,您可以直接控制Windows用於顯示圖像的顏色。如果您選擇並顯現調色盤,則Winsows將使用此調色盤中的顏色,而不是其他調色盤中的顏色。

例如,您可以用DibPalUniformGrays函式來單獨建立一種灰階調色盤。使用兩種灰階的調色盤則只含有00-00-00(黑色)和FF-FF-FF(白色)。用此調色盤來輸出圖像將提供某些照片中常用的高對比「黑白」效果。使用3種灰階將在黑色和白色中間添加中間灰色,使用4種灰階將添加2種灰階。

用8種灰階,您就有可能看到明顯的輪廓-相同灰階的無規則斑點,雖然很明顯地執行了最接近顏色演算法,但是一般仍不帶有任何審美判斷。通常到16種灰階就可以明顯改善圖像畫質。使用32種灰階差不多就可以消除全部輪廓了。而目前普遍認為64種灰階是現在大多數顯示設備的極限。在這點以上,再提升也沒什麼邊際效益了。在6位元顏色解析度的設備上提供超過64種灰階看不出有什麼改進之處。

迄今為止,對於8位元顯示模式下顯示16位元、24位元和32位元彩色DIB,我們最多就是能夠設計通用調色盤(這對灰階圖像很有效,但通常不適於彩色圖像)或者使用中間色調色盤,它用混色顯示與通用顏色調色盤合用。

還應注意,當您在8位元顏色模式下為大張16位元、24位元或32位元DIB選擇通用調色盤時,為了要顯示這些圖像,程式將花費一些時間依據DIB的內容來建立GDI點陣圖物件。如果不需要調色盤,則程式根據DIB來建立DDB的時間會更少(用8位元彩色模式顯示大24位元DIB時,比較SHOWDIB1和SHOWDIB4的性能,您也能看出這點區別)。這是為什麼呢?

它按最接近顏色搜尋。通常,用8位元顯示模式顯示24位元DIB時(或者將DIB轉換為DDB),GDI必須將DIB中的每個圖素都與靜態20種顏色中的一種相貼近。完成此操作的唯一方法是決定哪種靜態顏色與圖素顏色最接近。這包括計算圖素與三維RGB顏色中每種靜態顏色的距離。這將花些時間,特別是在DIB圖像中有上百萬個圖素時。

在建立232色調色盤時,例如DIBBLE和SHOWDIB4中的通用調色盤,您會很快將搜索最接近顏色的時間增加到超過11倍!GDI現在必須徹底檢查232種顏色,而不是20種。那就是顯示DIB的整個作業放慢的原因。

這裏的教訓是避免在8位元顯示模式下顯示24位元(或16位元,或32位元)DIB。您應該找出最接近DIB圖像顏色範圍的256色調色盤,來將它們轉換成8位元DIB。這經常稱為「最佳調色盤」。當我研究這個問題的時候,Paul Heckbert編寫的〈Color Image Quantization for Frame Buffer Displays〉(刊登在1982年7月出版的《Computer Graphics》)對此問題有所幫助。

均勻分佈

建立256色調色盤最簡單的方法是選擇範圍統一的RGB顏色值,它與DibPalAllPurpose中的方法相似。此方法的優點是您不必檢查DIB中的實際圖素。這個函式是DibPalCreateUniformColors,它依據範圍統一的RGB三原色索引建立調色盤。

一個合理的分佈包括8階紅色和綠色以及4階藍色(肉眼對藍色較不敏感)。調色盤是RGB顏色值的集合,它是紅色和綠色值0x00、0x24、0x49、0x6D、0x92、0xB6、0xDB和0xFF以及藍色值0x00、0x55、0xAA和0xFF的所有可能的組合,共有256種顏色。另一種可能的統一分佈調色盤使用6階紅色、綠色和藍色。此調色盤是紅色、綠色和藍色值為0x00、0x33、0x66、0x99、0xCC和0xFF的所有可能的組合,調色盤中的顏色數是6的3次方,即216。

這兩個選項和其他幾個選項都由DIBBLE提供。

「Popularity」演算法

「Popularity」演算法是256色調色盤問題相當明顯的解決方法。您要做的就是走遍點陣圖中的所有圖素,並找出256種最普通的RGB顏色值。這些就是您在調色盤中使用的值。DIBPAL的DibPalPopularity函式中實作了這種演算法。

不過,如果每種顏色都使用整個24位元,而且假設需要用整數來計算所有的顏色,那麼陣列將佔據64MB記憶體。另外,您可以發現點陣圖中實際上沒有(或很少)重複的24位元圖素值,這樣就沒有所謂常見的顏色了。

要解決這個問題,您可以只使用每個紅色、綠色和藍色值中最重要的n位元-例如,6位元而不是8位元。因為大多數的彩色掃描器和視訊顯示卡都只有6位元的解析度,所以這樣規定更有意義。這將陣列減少到大小更合理的256KB或1MB。只使用5位元能將可用的顏色總數減少到32,768。通常,使用5位元要比6位元的性能更好。對此,您可以用DIBBLE和一些圖像顏色來自己檢驗。

「Median Cut」演算法

DIBPAL.C中的DibPalMedianCut函式執行Paul Heckbert的Median Cut演算法。此演算法在概念上相當簡單,但在程式碼中實作要比Popularity演算法更困難,它適合遞迴函式。

畫出RGB顏色立方體。圖像中的每個圖素都是此立方體中的一個點。一些點可能代表圖像中的多個圖素。找出包括圖像中所有圖素的立體方塊,找出此方塊的最大尺寸,並將方塊分成兩個,每個方塊都包括相同數量的圖素。對於這兩個方塊,執行相同的操作。現在您就有4個方塊,將這4個方塊分成8個,然後再分成16個、32個、64個、128個和256個。

現在您有256個方塊,每個方塊都包括相同數量的圖素。取每個方塊中圖素RGB顏色值的平均值,並將結果用於調色盤。

實際上,這些方塊通常包含圖素的數量並不相同。例如,通常包括單個點的方塊會有更多的圖素。這發生在黑色和白色上。有時,一些方塊裡頭根本沒有圖素。如果這樣,您就可以省下更多的方塊,但是我決定不這樣做。

另一種最佳化調色盤的技術稱為「octree quantization」,此技術由Jeff Prosise提出,並於1996年8月發表在《Microsoft Systems Journal》上(包含在MSDN的CD中)。

轉換格式

DIBBLE還允許將DIB從一種格式轉換到另一種格式。這用到了DIBCONV檔案中的DibConvert函式,如程式16-25所示。

程式16-25 DIBCONV檔案 DIBCONV.H /*---------------------------------------------------------------------------- DIBCONV.H header file for DIBCONV.C -----------------------------------------------------------------------------*/ HDIB DibConvert (HDIB hdibSrc, int iBitCountDst) ;

DIBCONV.C /*--------------------------------------------------------------------------- DIBCONV.C -- Converts DIBs from one format to another (c) Charles Petzold, 1998 ----------------------------------------------------------------------------*/ #include <windows.h> #include "dibhelp.h" #include "dibpal.h" #include "dibconv.h" HDIB DibConvert (HDIB hdibSrc, int iBitCountDst) { HDIB hdibDst ; HPALETTE hPalette ; int i, x, y, cx, cy, iBitCountSrc, cColors ; PALETTEENTRY pe ; RGBQUAD rgb ; WORD wNumEntries ; cx = DibWidth (hdibSrc) ; cy = DibHeight (hdibSrc) ; iBitCountSrc = DibBitCount (hdibSrc) ; if (iBitCountSrc == iBitCountDst) return NULL ; // DIB with color table to DIB with larger color table: if ((iBitCountSrc < iBitCountDst) && (iBitCountDst <= 8)) { cColors = DibNumColors (hdibSrc) ; hdibDst = DibCreate (cx, cy, iBitCountDst, cColors) ; for (i = 0 ; i < cColors ; i++) { DibGetColor (hdibSrc, i, &rgb) ; DibSetColor (hdibDst, i, &rgb) ; } for (x = 0 ; x < cx ; x++) for (y = 0 ; y < cy ; y++) { DibSetPixel (hdibDst, x, y, DibGetPixel (hdibSrc, x, y)) ; } } // Any DIB to DIB with no color table else if (iBitCountDst >= 16) { hdibDst = DibCreate (cx, cy, iBitCountDst, 0) ; for (x = 0 ; x < cx ; x++) for (y = 0 ; y < cy ; y++) { DibGetPixelColor (hdibSrc, x, y, &rgb) ; DibSetPixelColor (hdibDst, x, y, &rgb) ; } } // DIB with no color table to 8-bit DIB else if (iBitCountSrc >= 16 && iBitCountDst == 8) { hPalette = DibPalMedianCut (hdibSrc, 6) ; GetObject (hPalette, sizeof (WORD), &wNumEntries) ; hdibDst = DibCreate (cx, cy, 8, wNumEntries) ; for (i = 0 ; i < (int) wNumEntries ; i++) { GetPaletteEntries (hPalette, i, 1, &pe) ; rgb.rgbRed = pe.peRed ; rgb.rgbGreen = pe.peGreen ; rgb.rgbBlue = pe.peBlue ; rgb.rgbReserved = 0 ; DibSetColor (hdibDst, i, &rgb) ; } for (x = 0 ; x < cx ; x++) for (y = 0 ; y < cy ; y++) { DibGetPixelColor (hdibSrc, x, y, &rgb) ; DibSetPixel (hdibDst, x, y, GetNearestPaletteIndex (hPalette, RGB (rgb.rgbRed, rgb.rgbGreen, rgb.rgbBlue))) ; } DeleteObject (hPalette) ; } // Any DIB to monochrome DIB else if (iBitCountDst == 1) { hdibDst = DibCreate (cx, cy, 1, 0) ; hPalette = DibPalUniformGrays (2) ; for (i = 0 ; i < 2 ; i++) { GetPaletteEntries (hPalette, i, 1, &pe) ; rgb.rgbRed = pe.peRed ; rgb.rgbGreen = pe.peGreen ; rgb.rgbBlue = pe.peBlue ; rgb.rgbReserved = 0 ; DibSetColor (hdibDst, i, &rgb) ; } for (x = 0 ; x < cx ; x++) for (y = 0 ; y < cy ; y++) { DibGetPixelColor (hdibSrc, x, y, &rgb) ; DibSetPixel (hdibDst, x, y, GetNearestPaletteIndex (hPalette, RGB (rgb.rgbRed, rgb.rgbGreen, rgb.rgbBlue))) ; } DeleteObject (hPalette) ; } // All non-monochrome DIBs to 4-bit DIB else if (iBitCountSrc >= 8 && iBitCountDst == 4) { hdibDst = DibCreate (cx, cy, 4, 0) ; hPalette = DibPalVga () ; for (i = 0 ; i < 16 ; i++) { GetPaletteEntries (hPalette, i, 1, &pe) ; rgb.rgbRed = pe.peRed ; rgb.rgbGreen = pe.peGreen ; rgb.rgbBlue = pe.peBlue ; rgb.rgbReserved = 0 ; DibSetColor (hdibDst, i, &rgb) ; } for (x = 0 ; x < cx ; x++) for (y = 0 ; y < cy ; y++) { DibGetPixelColor (hdibSrc, x, y, &rgb) ; DibSetPixel (hdibDst, x, y, GetNearestPaletteIndex (hPalette, RGB (rgb.rgbRed, rgb.rgbGreen, rgb.rgbBlue))) ; } DeleteObject (hPalette) ; } // Should not be necessary else hdibDst = NULL ; return hdibDst ; }

將DIB從一種格式轉換成另一種格式需要幾種不同的方法。

要將帶有顏色表的DIB轉換成另一種也帶有顏色表但有較大的圖素寬度的DIB(亦即,將1位元DIB轉換成4位元或8位元DIB,或將4位元DIB轉換成8位元DIB),所需要做的就是透過呼叫DibCreate來建立新的DIB,並在呼叫時帶有希望的位元數以及與原始DIB中的顏色數相等的顏色數。然後函式複製圖素位元和顏色表項目。

如果新的DIB沒有顏色表(即位元數是16、24或32),那麼DIB只需要按新格式建立,而且通過呼叫DibGetPixelColor和DibSetPixelColor從現有的DIB中複製圖素位元。

下面的情況可能更普遍:現有的DIB沒有顏色表(即位元數是16、24或32),而新的DIB每圖素佔8位元。這種情況下,DibConvert呼叫DibPalMedianCut來為圖像建立最佳化的調色盤。新DIB的顏色表設定為調色盤中的RGB值。DibGetPixelColor函式從現有的DIB中獲得圖素顏色。透過呼叫GetNearestPaletteIndex來轉換成8位元DIB中的圖素值,並透過呼叫DibSetPixel將圖素值儲存到DIB。

當DIB需要轉換成單色DIB時,用包括兩個項目-黑色和白色-的顏色表建立新的DIB。另外,GetNearestPaletteIndex有助於將現有DIB中的顏色轉換成圖素值0或1。類似地,當8個圖素位元或更多位元的DIB要轉換成4位元DIB時,可從DibPalVga函式獲得DIB顏色表,同時GetNearestPaletteIndex也有助於計算圖素值。

儘管DIBBLE示範了如何開始寫一個圖像處理程式基礎,但是程式最後還是沒有全部完成,我們總是會想到還有些功能沒有加進去裡頭。但是很可惜的是,我們現在得停止繼續研究這些東西,而往下討論別的東西了。