14. 點陣圖和Bitblt

Post date: 2012/3/23 上午 05:40:16

14. 點陣圖和Bitblt

點陣圖是一個二維的位元陣列,它與圖像的圖素一一對應。當現實世界的圖像被掃描成點陣圖以後,圖像被分割成網格,並以圖素作為取樣單位。在點陣圖中的每個圖素值指明了一個單位網格內圖像的平均顏色。單色點陣圖每個圖素只需要一位元,灰色或彩色點陣圖中每個圖素需要多個位元。

點陣圖代表了Windows程式內儲存圖像資訊的兩種方法之一。儲存圖像資訊的另一種形式是metafile,我將在 第十八章 討論。Metafile儲存的就是對圖像如何生成的描述,而不是將圖像以數位化的圖示代表。

以後我將更詳細地討論,Microsoft Windows 3.0定義了一種稱為裝置無關點陣圖(DIB:device-independent bitmap)。我將在 下一章 討論DIB。本章主要討論GDI點陣圖物件,這是一種在Windows中比DIB更早支援的點陣圖形資料。如同本章大量的範例程式所說明的,這種比DIB點陣圖更早被Windows支援的圖形格式仍然有其利用價值。

點陣圖入門

點陣圖和metafile在電腦圖形處理世界中都佔有一席之地。點陣圖經常用來表示來自真實世界的複雜圖像,例如數位化的照片或者視訊圖像。Metafile更適合於描述由人或者機器產生的圖像,比如建築藍圖。點陣圖和metafile都能存於記憶體或作為檔案存於磁片上,並且都能通過剪貼簿在Windows應用程式之間傳輸。

點陣圖和metafile的區別在於位元映射圖像和向量圖像之間的差別。位元映射圖像用離散的圖素來處理輸出設備;而向量圖像用笛卡爾座標系統來處理輸出設備,其線條和填充物件能被個別拖移。現在大多數的圖像輸出設備是位元映射設備,這包括視訊顯示、點陣印表機、雷射印表機和噴墨印表機。而筆式繪圖機則是向量輸出設備。

點陣圖有兩個主要的缺點。第一個問題是容易受裝置依賴性的影響。最明顯的就是對顏色的依賴性,在單色設備上顯示彩色點陣圖的效果總是不能令人滿意的。另一個問題是點陣圖經常暗示了特定的顯示解析度和圖像縱橫比。儘管點陣圖能被拉伸和縮小,但是這樣的處理通常包括複製或刪除圖素的某些行和列,這樣會破壞圖像的大小。而metafile在放大縮小後仍然能保持圖形樣貌不受破壞。

點陣圖的第二個缺點是需要很大的儲存空間。例如,描述完整的640×480圖素,16色的視頻圖形陣列(VGA:Video Graphics Array)螢幕的一幅點陣圖需要大於150 KB的空間;一幅1024×768,並且每個圖素為24位元顏色的圖像則需要大於2 MB的空間。Metafile需要通常比點陣圖來得少的空間。點陣圖的儲存空間由圖像的大小及其包含的顏色決定,而metafile的儲存空間則由圖像的複雜程度和它所包含的GDI指令數決定。

然而,點陣圖優於metafile之處在於速度。將點陣圖複製給視訊顯示器通常比複製基本圖形檔案的速度要快。最近幾年,壓縮技術允許壓縮點陣圖的檔案大小,以使它能有效地通過電話線傳輸並廣泛地用於Internet的網頁上。

點陣圖的來源

點陣圖可以手工建立,例如,使用Windows 98附帶的「小畫家」程式。一些人寧願使用位元映射繪圖軟體也不使用向量繪圖軟體。他們假定:圖形最後一定會複雜到不能用線條跟填充區域來表達。

點陣圖圖像也能由電腦程式計算生成。儘管大多數計算生成的圖像能按向量圖形metafile儲存,但是高清晰度的畫面或碎形圖樣通常還是需要點陣圖。

現在,點陣圖通常用於描述真實世界的圖像,並且有許多硬體設備能讓您把現實世界的圖像輸入到電腦。這類硬體通常使用 電荷耦合裝置 (CCD:charge-coupled device),這種裝置接觸到光就釋放電荷。有時這些CCD單元能排列成一組,一個圖素對應一個CCD;為節約開支,只用一行CCD掃描圖像。

在這些電腦CCD設備中, 掃描器 是最古老的。它用一行CCD沿著紙上圖像(例如照片)的表面掃描。CCD根據光的強度產生電荷。類比數位轉換器(ADC:Analog-to-digital converters)把電荷轉換為數位訊號,然後排列成點陣圖。

攜帶型攝像機也利用CCD單元組來捕捉影像。通常,這些影像是記錄到錄影帶上。不過,這些視訊輸出也能直接進入 影像捕捉器 (frame grabber),該裝置能把類比視訊信號轉換為一組圖素值。這些影像捕捉器與任何相容的視訊信號來源都能同時使用,例如VCR、光碟、DVD播放機或有線電視解碼器。

最近,數位照相機的價位對於家庭使用者來說開始變得負擔得起了。它看起來很像普通照相機。但是數位照相機不使用底片,而用一組CCD來攔截圖像,並且在ADC內部把數位圖像直接儲存在照相機內的記憶體中。通常,數位照相機與電腦的介面要通過序列埠。

點陣圖尺寸

點陣圖呈矩形,並有空間尺寸,圖像的高度和寬度都以圖素為單位。例如,此網格可描述一個很小的點陣圖:寬度為9圖素,高度為6圖素,或者更簡單地計為9×6:

習慣上,點陣圖的速記尺寸是先給出寬度。點陣圖總數為9×6或者54圖素。我將經常使用符號cx和cy來表示點陣圖的寬度和高度。c表示計數,因此cx和cy是沿著x軸(水平)和y軸(垂直)的圖素數。

我們能根據x和y座標來描述點陣圖上具體的圖素。一般(並不都是這樣),在網格內計算圖素時,點陣圖開始於圖像的左上角。這樣,在此點陣圖右下角的圖素座標就是(8, 5)。因為從0開始計數,所以此值比圖像的寬度和高度小1。

點陣圖的空間尺寸通常也指定了解析度,但這是一個有爭議的詞。我們說我們的視訊顯示有640×480的解析度,但是雷射印表機的解析度只有每英寸300點。我喜歡用後一種情況中解析度的意思作為每單位圖素的數量。點陣圖在這種意義上的解析度指的是點陣圖在特定測量單位中的圖素數。不管怎樣,當我使用解析度這個詞語時,其定義的內容應該是明確的。

點陣圖是矩形的,但是電腦記憶體空間是線性的。通常(但並不都是這樣)點陣圖按列儲存在記憶體中,且從頂列圖素開始到底列結束。(DIB是此規則的一個主要例外)。每一列,圖素都從最左邊的圖素開始依次向右儲存。這就好像儲存幾列文字中的各個字元。

顏色和點陣圖

除空間尺寸以外,點陣圖還有顏色尺寸。這裡指的是每個圖素所需要的位元數,有時也稱為點陣圖的 顏色深度 (color depth)、 位元數 (bit-count)或 位元/圖素 (bpp:bits per pixel)數。點陣圖中的每個圖素都有相同數量的顏色位元。

每圖素1位元的點陣圖稱為 二階 (bilevel)、 二色 (bicolor)或者 單色 (monochrome)點陣圖。每圖素可以是0或1,0表示黑色,1可以表示白色,但並不總是這樣。對於其他顏色,一個圖素就需要有多個位元。可能的顏色值等於2位元數值。用2位元可以得到4種顏色,用4位元可以得16種顏色,8位元可得到256種顏色,16位元可得到65,536種顏色,而24位元可得到16,777,216種顏色。

如何將顏色位元的組合與人們所熟悉的顏色相對應是目前處理點陣圖時經常碰到(而且常常是災難)的問題。

實際的設備

點陣圖可按其顏色位元數來分類;在Windows的發展過程中,不同的點陣圖顏色格式取決於常用視訊顯示卡的功能。實際上,我們可把視訊顯示記憶體看作是一幅巨大的點陣圖-我們從顯示器上就可以看見。

Windows 1.0多數採用的顯示卡是IBM的彩色圖像適配器(CGA:Color Graphics Adapter)和單色圖形卡(HGC:Hercules Graphics Card)。HGC是單色設備,而CGA也只能在Windows以單色圖形模式使用。單色點陣圖現在還很常用(例如,滑鼠的游標一般為單色),而且單色點陣圖除顯示圖像以外還有其他用途。

隨著增強型圖形顯示卡(EGA:Enhanced Graphics Adapter)的出現,Windows使用者開始接觸16色的圖形。每個圖素需要4個顏色位元。(實際上,EGA比這裏所講的更複雜,它還包括一個64種顏色的調色盤,應用程式可以從中選擇任意的16種顏色,但Windows只按較簡單的方法使用EGA)。在EGA中使用的16種顏色是黑、白、兩種灰色、高低亮度的紅色、綠和藍(三原色)、青色(藍和綠組合的顏色)。現在認為這16種顏色是Windows的最低顏色標準。同樣,其他16色點陣圖也可以在Windows中顯示。大多數的圖示都是16色的點陣圖。通常,簡單的卡通圖像也可以用這16種顏色製作。

在16色點陣圖中的顏色編碼有時稱為IRGB(高亮紅綠藍:Intensity-Red-Green-Blue),並且實際上是源自IBM CGA文字模式下最初使用的十六種顏色。每個圖素所用的4個IRGB顏色位元都映射為表14-1所示的Windows十六進位RGB顏色。

表14-1

EGA的記憶體組成了四個「顏色面」,也就是說,定義每個圖素顏色的四位元在記憶體中是不連續的。然而,這樣組織顯示記憶體便於使所有的亮度位元都排列在一起、所有的紅色位元都排在一起,等等。這樣聽起來就好像一種設備依賴特性,即Windows程式寫作者不需要瞭解所有細節,但這時應或多或少地知道一些。不過,這些顏色面會出現在一些API呼叫中,例如GetDeviceCaps和CreateBitmap。

Windows 98和Microsoft Windows NT需要VGA或解析度更高的圖形卡。這是目前公認的顯示卡的最低標準。

1987年,IBM最早發表視訊圖像陣列(Video Graphics Array:VGA)以及PS/2系列的個人電腦。它提供了許多不同的顯示模式,但最好的圖像模式(Windows也使用其中之一)是水平顯示640個圖素,垂直顯示480個圖素,帶有16種顏色。要顯示256種顏色,最初的VGA必須切換到320×240的圖形模式,這種圖素數不適合Windows的正常工作。

一般人們已經忘記了最初VGA卡的顏色限制,因為其他硬體製造商很快就開發了「Super-VGA」(SVGA)顯示卡,它包括更多的視訊記憶體,可顯示256種顏色並有多於640×480的模式。這是現在的標準,而且也是一件好事,因為對於現實世界中的圖像來說,16種顏色過於簡單,有些不適合。

顯示256種顏色的顯示卡模式採用每圖素8位元。不過,這些8位元值都不必與實際的顏色相符。事實上,顯示卡提供了「調色盤對照表(palette lookup table)」,該表允許軟體指定這8位元的顏色值,以便與實際顏色相符合。在Windows中,應用程式不能直接存取調色盤對照表。實際上,Windows儲存了256種顏色中的20種,而應用程式可以通過「Windows調色盤管理器」來自訂其餘的236種顏色。關於這些內容,我將在 第十六章 詳細介紹。調色盤管理器允許應用程式在256色顯示器上顯示實際點陣圖。Windows所儲存的20種顏色如表14-2所示。

表14-2

最近幾年,True-Color顯示卡很普遍,它們在每圖素使用16位元或24位元。有時每圖素雖然用了16位元,其中有1位元不用,而其他15位元主要近似於紅、綠和藍。這樣紅、綠和藍每種都有32色階,組合起來就可以達到32,768種顏色。更普遍的是,6位元用於綠色(人類對此顏色最敏感),這樣就可得到65,536種顏色。對於非技術性的PC使用者來說,他們並不喜歡看到諸如32,768或65,536之類的數字,因此通常將這種視訊顯示卡稱為Hi-Color顯示卡,它能提供數以千計的顏色。

到了每個圖素24位元時,我們總共有了16,777,216種顏色(或者True Color、數百萬的顏色),每個圖素使用3位元組。這與今後的標準很相似,因為它大致代表了人類感官的極限而且也很方便。

在呼叫GetDeviceCaps時(參見 第五章的DEVCAPS程式 ),您能利用BITSPIXEL和PLANES常數來獲得顯示卡的顏色單位,這些值顯示如表14-3所示

表14-3

最近,您應該不會再碰到單色顯示器了,但即便碰到了,您的應用程式也應該不會發生問題。

GDI支援的點陣圖

Windows圖形裝置介面(GDI:Graphics Device Interface)從1.0版開始支援點陣圖。不過,一直到Windows 3.0以前,Windows下唯一支援GDI物件的只有點陣圖,以點陣圖代號來使用。這些GDI點陣圖物件是單色的,或者與實際的圖像輸出設備(例如視訊顯示器)有相同的顏色單位。例如,與16色VGA相容的點陣圖有四個顏色面。問題是這些顏色點陣圖不能儲存,也不能用於顏色單位不同的圖像輸出設備(如每圖素占8位元就可以產生256種顏色的設備)上。

從Windows 3.0開始,定義了一種新的點陣圖格式,我們稱之為裝置無關點陣圖(device-independent bitmap),或者DIB。DIB包括了自己的調色盤,其中顯示了與RGB顏色相對應的圖素位元。DIB能顯示在任何位元映射輸出設備上。這裏唯一的問題是DIB的顏色通常一定會轉換成設備實際表現出來的顏色。

與DIB同時,Windows 3.0還介紹了「Windows調色盤管理器」,它讓程式能夠從顯示的256種顏色中自訂顏色。就像我們在 第十六章 所看到的那樣,應用程式通常在顯示DIB時使用「調色盤管理器」。

Microsoft在Windows 95(和Windows NT 4.0)中擴展了DIB的定義,並且在Windows 98(和Windows NT 5.0)中再次擴展。這些擴展增加了所謂的「圖像顏色管理器(ICM:Image Color Management),並允許DIB更精確地指定圖像所需要的顏色。我將在 第十五章 簡要討論ICM。

不論DIB多麼重要,在處理點陣圖時,早期的GDI點陣圖物件依然扮演了重要的角色。掌握點陣圖使用方式的最好方法是按各種用法在演進發展的時間順序來學習,先從GDI點陣圖物件和位元塊傳輸的概念開始。

位元塊傳輸

我前面提到過,您可以把整個視訊顯示器看作是一幅大點陣圖。您在螢幕上見到的圖素由儲存在視訊顯示卡上記憶體中的位元來描述。任何視訊顯示的矩形區域也都是一個點陣圖,其大小是它所包含的行列數。

讓我們從將圖像從視訊顯示的一個區域複製到另一個區域,開始我們在點陣圖世界的旅行吧!這個是強大的BitBlt函式的工作。

Bitblt(讀作「bit blit」)代表「位元塊傳輸(bit-block transfer)」。BLT起源於一條組合語言指令,該指令在DEC PDP-10上用來傳輸記憶體塊。術語「bitblt」第一次用在圖像上與Xerox Palo Alto Research Center(PARC)設計的SmallTalk系統有關。在SmallTalk中,所有的圖形輸出操作都使用bitblt。程式寫作者有時將blt用作動詞,例如:「Then I wrote some code to blt the happy face to the screen and play a wave file.」

BitBlt函式移動的是圖素,或者(更明確地)是一個位元映射圖塊。您將看到,術語「傳輸(transfer)」與BitBlt函式不盡相同。此函式實際上對圖素執行了一次位元操作,而且可以產生一些有趣的結果。

簡單的BitBlt

程式14-1所示的BITBLT程式用BitBlt函式將程式系統的功能表圖示(位於程式Windows的左上角)複製到它的顯示區域。

程式14-1 BITBLT BITBLT.C /*---------------------------------------------------------------------- BITBLT.C -- BitBlt Demonstration (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 ("BitBlt") ; 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_INFORMATION) ; 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 ("BitBlt Demo"), 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, cxSource, cySource ; HDC hdcClient, hdcWindow ; int x, y ; PAINTSTRUCT ps ; switch (message) { case WM_CREATE: cxSource = GetSystemMetrics (SM_CXSIZEFRAME) + GetSystemMetrics (SM_CXSIZE) ; cySource = GetSystemMetrics (SM_CYSIZEFRAME) + GetSystemMetrics (SM_CYCAPTION) ; return 0 ; case WM_SIZE: cxClient = LOWORD (lParam) ; cyClient = HIWORD (lParam) ; return 0 ; case WM_PAINT: hdcClient = BeginPaint (hwnd, &ps) ; hdcWindow = GetWindowDC (hwnd) ; for (y = 0 ; y < cyClient ; y += cySource) for (x = 0 ; x < cxClient ; x += cxSource) { BitBlt (hdcClient, x, y, cxSource, cySource, hdcWindow, 0, 0, SRCCOPY) ; } ReleaseDC (hwnd, hdcWindow) ; EndPaint (hwnd, &ps) ; return 0 ; case WM_DESTROY: PostQuitMessage (0) ; return 0 ; } return DefWindowProc (hwnd, message, wParam, lParam) ; }

但為什麼只用了一個BitBlt呢?實際上,那個BITBLT用系統功能表圖示的多個副本來填滿顯示區域(在此情況下是資訊方塊中普遍使用的IDI_INFORMATION圖示),如圖14-1所示。

BitBlt函式從稱為「來源」的裝置內容中將一個矩形區的圖素傳輸到稱為「目的(destination)」的另一個裝置內容中相同大小的矩形區。此函式的語法如下:

來源和目的裝置內容可以相同。

在BITBLT程式中,目的裝置內容是視窗的顯示區域,裝置內容代號從BeginPaint函式獲得。來源裝置內容是應用程式的整個視窗,此裝置內容代號從GetWindowDC獲得的。很明顯地,這兩個裝置內容指的是同一個實際設備(視訊顯示器)。不過,這兩個裝置內容的座標原點不同。

xSrc和ySrc參數指明了來源圖像左上角的座標位置。在BITBLT中,這兩個參數設為0,表示圖像從來源裝置內容(也就是整個視窗)的左上角開始,cx和cy參數是圖像的寬度和高度。BITBLT根據從GetSytemMetrics函式獲得的資訊來計算這些值。

xDst和yDst參數表示了複製圖像位置左上角的座標位置。在BITBLT中,這兩個參數設定為不同的值以便多次複製圖像。對於第一次BitBlt呼叫,這兩個參數設制為0,將圖像複製到顯示區域的左上角位置。

BitBlt的最後一個參數是位元映射操作型態。我將簡短地討論一下這個值。

請注意,BitBlt是從實際視訊顯示記憶體傳輸圖素,而不是從系統功能表圖示的其他圖像傳輸。如果您移動BITBLT視窗以使部分系統功能表圖示移出螢幕,然後調整BITBLT視窗的尺寸使其重畫,這時您將發現BITBLT顯示區域中顯示的是功能表圖示的一部分。BitBlt函式不再存取整個圖像。

在BitBlt函式中,來源和目的裝置內容可以相同。您可以重新編寫BITBLT以使WM_PAINT處理執行以下內容:

BitBlt (hdcDst, xDst, yDst, cx, cy, hdcSrc, xSrc, ySrc, dwROP) ;

圖14-1 BITBLT的螢幕顯示

BitBlt (hdcClient, 0, 0, cxSource, cySource, hdcWindow, 0, 0, SRCCOPY) ; for (y = 0 ; y < cyClient ; y += cySource) for (x = 0 ; x < cxClient ; x += cxSource) { if (x > 0 || y > 0) BitBlt (hdcClient, x, y, cxSource, cySource, hdcClient, 0, 0, SRCCOPY) ; }

這將與前面顯示的BITBLT一樣產生相同的效果,只是顯示區域左上角比較模糊。

在BitBlt內的最大限制是兩個裝置內容必須是相容的。這意味著或者其中之一必須是單色的,或者兩者的每個圖素都相同的位元數。總而言之,您不能用此方法將螢幕上的某些圖形複製到印表機。

拉伸點陣圖

在BitBlt函式中,目的圖像與來源圖像的尺寸是相同的,因為函式只有兩個參數來說明寬度和高度。如果您想在複製時拉伸或者壓縮圖像尺寸,可以使用StretchBlt函式。StretchBlt函式的語法如下:

此函式添加了兩個參數。現在的函式就分別包含了目的和來源各自的寬度和高度。STRETCH程式展示了StretchBlt函式,如程式14-2所示。

StretchBlt ( hdcDst, xDst, yDst, cxDst, cyDst, hdcSrc, xSrc, ySrc, cxSrc, cySrc, dwROP) ;

程式14-2 STRETCH STRETCH.C /*-------------------------------------------------------------------------- STRETCH.C -- StretchBlt Demonstration (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 ("Stretch") ; 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_INFORMATION) ; 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 ("StretchBlt Demo"), 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, cxSource, cySource ; HDC hdcClient, hdcWindow ; PAINTSTRUCT ps ; switch (message) { case WM_CREATE: cxSource = GetSystemMetrics (SM_CXSIZEFRAME) + GetSystemMetrics (SM_CXSIZE) ; cySource = GetSystemMetrics (SM_CYSIZEFRAME) + GetSystemMetrics (SM_CYCAPTION) ; return 0 ; case WM_SIZE: cxClient = LOWORD (lParam) ; cyClient = HIWORD (lParam) ; return 0 ; case WM_PAINT: hdcClient = BeginPaint (hwnd, &ps) ; hdcWindow = GetWindowDC (hwnd) ; StretchBlt (hdcClient, 0, 0, cxClient, cyClient, hdcWindow, 0, 0, cxSource, cySource, MERGECOPY) ; ReleaseDC (hwnd, hdcWindow) ; EndPaint (hwnd, &ps) ; return 0 ; case WM_DESTROY: PostQuitMessage (0) ; return 0 ; } return DefWindowProc (hwnd, message, wParam, lParam) ; }

此程式只有呼叫了StretchBlt函式一次,但是利用此函式以系統功能表圖示填充了整個顯示區域,如圖14-2所示。

BitBlt和StretchBlt函式中所有的座標與大小都是依據邏輯單位的。但是當您在BitBlt函式中定義了兩個不同的裝置內容,而這兩個裝置內容雖然參考同一個實際設備,卻各自有著不同的映射模式,這時將發生什麼結果呢?如果出現這種情況,呼叫BitBlt產生的結果就顯得不明確了:cx和cy參數都是邏輯單位,而它們同樣應用於來源裝置內容和目的裝置內容中的矩形區。所有的座標和尺寸必須在實際的位元傳輸之前轉換為裝置座標。因為cx和cy值同時用於來源和目的裝置內容,所以此值必須轉換為裝置內容自己的單位。

當來源和目的裝置內容相同,或者兩個裝置內容都使用MM_TEXT圖像模式時,裝置單位下的矩形尺寸在兩個裝置內容中會是相同的,然後才由Windows進行圖素對圖素的轉換。不過,如果裝置單位下的矩形尺寸在兩個裝置內容中不同時,則Windows就把此工作轉交給更通用的StretchBlt函式。

StretchBlt也允許水平或垂直翻轉圖像。如果cxSrc和cxDst標記(轉換成裝置單位以後)不同,那麼StretchBlt就建立一個鏡像:左右翻轉。在STRETCH程式中,通過將xDst參數改為cxClient並將cxDst參數改成-cxClient,您就可以做到這一點。如果cySrc和cyDst不同,則StretchBlt會上下翻轉圖像。要在STRETCH程式中測試這一點,可將yDst參數改為cyClient並將cyDst參數改成-cyClient。

StretchBlt模式

使用StretchBlt會碰到一些與點陣圖大小縮放相關的一些根本問題。在擴展一個點陣圖時,StretchBlt必須複製圖素行或列。如果放大倍數不是原圖的整數倍,那麼此操作會造成產生的圖像有些失真。

如果目的矩形比來源矩形小,那麼StretchBlt在縮小圖像時就必須把兩行(或列)或者多行(或列)的圖素合併到一行(或列)。完成此操作有四種方法,它根據裝置內容伸展模式屬性來選擇其中一種方法。您可使用SetStretchBltMode函式來修改這個屬性。

iMode可取下列值:

SetStretchBltMode (hdc, iMode) ;

圖14-2 STRETCH的螢幕顯示

  • BLACKONWHITE或者STRETCH_ANDSCANS(內定) 如果兩個或多個圖素得合併成一個圖素,那麼StretchBlt會對圖素執行一個邏輯AND運算。這樣的結果是只有全部的原始圖素是白色時該圖素才為白色,其實際意義是黑色圖素控制了白色圖素。這適用於白背景中主要是黑色的單色點陣圖。
  • WHITEONBLACK或STRETCH_ORSCANS 如果兩個或多個圖素得合併成一個圖素,那麼StretchBlt執行邏輯OR運算。這樣的結果是只有全部的原始圖素都是黑色時才是黑色,也就是說由白色圖素決定顏色。這適用於黑色背景中主要是白色的單色點陣圖。
  • COLORONCOLOR或STRETCH_DELETESCANS StretchBlt簡單地消除圖素行或列,而沒有任何邏輯組合。這是通常是處理彩色點陣圖的最佳方法。
  • HALFTONE或STRETCH_HALFTONE Windows根據組合起來的來源顏色來計算目的的平均顏色。這將與半調調色盤聯合使用, 第十六章 將展示這一程序。

Windows還包括用於取得目前伸展模式的GetStretchBltMode函式。

位元映射操作

BITBLT和STRETCH程式簡單地將來源點陣圖複製給了目的點陣圖,在過程中也可能進行了縮放。這是把SRCCOPY作為BitBlt和StretchBlt函式最後一個參數的結果。SRCCOPY只是您能在這些函式中使用的256個位元映射操作中的一個。讓我們先在STRETCH程式中做一個別的實驗,然後再系統地研究位元映射操作。

儘量用NOTSRCCOPY來代替SRCCOPY。與它們名稱一樣,位元映射操作在複製點陣圖時轉換其顏色。在顯示區域視窗,所有的顏色轉換:黑色變成白色、白色變成黑色,藍色變成黃色。現在試一下SRCINVERT,您將得到同樣效果。如果試一下BLACKNESS,正如其名稱一樣,整個顯示區域都將變成黑色,而WHITENESS則使其變成白色。

現在試一試用下列三條敘述來代替StretchBlt呼叫:

SelectObject (hdcClient, CreateHatchBrush (HS_DIAGCROSS, RGB (0, 0, 0))); StretchBlt ( hdcClient, 0, 0, cxClient, cyClient, hdcWindow, 0, 0, cxSource, cySource, MERGECOPY) ; DeleteObject (hdcClient, GetStockObject (WHITE_BRUSH)) ;

這次,您將在圖像上看到一個菱形的畫刷,這是什麼?

我在前面說過,BitBlt和StretchBlt函式不是簡單的位元塊傳輸。此函式實際在下面三種圖像間執行位元操作。

  • Source 來源點陣圖,拉伸或壓縮(如果有必要)到目的矩形的尺寸。
  • Destination 在BitBlt或StretchBlt呼叫之前的目的矩形。
  • Pattern 在目的裝置內容中選擇的目前畫刷,水平或垂直地複製到目的矩形範圍內。

結果是複製到了目的矩形中。

位元映射操作與我們在 第五章 遇到的繪圖模式在概念上相似。繪圖模式採用圖像物件的控制項方式,例如一條線就組合成一個目的。我們知道有16種繪圖模式-也就是說,物件中的0和1畫出時,唯一結果就是目的中0和1的組合。

使用BitBlt和StretchBlt的位元映射操作包含了三個物件的組合,這將產生256種位元映射操作。有256種方法來組合來源點陣圖、目的點陣圖和圖案。有15種位元映射操作已經命名-其中一些名稱其實還不能夠清楚清楚說明其意義-它們定義在WINGDI.H裡頭,其餘的都有數值,列在/Platform SDK/Graphics and Multimedia Services/GDI/Raster Operation Codes/Ternary Raster Operations之中。

有名稱的15種ROP代碼見表14-4。

表14-4

此表格對於理解和使用位元映射操作非常重要,因此我們應花點時間來研究。

在這個表格中,「ROP代碼」行的值將傳遞給BitBlt或StretchBlt的最後一個參數;在「名稱」行中的值在WINGDI.H定義。ROP代碼的低字組協助裝置驅動程式傳輸位元映射操作。高字組是0到255之間的數值。此數值與第2列的圖案的位元相同,這是在圖案、來源和顯示在頂部的目的之間進行位元操作的結果。「布林運算」列按C語法顯示圖案、來源和目的的組合方式。

要開始瞭解此表,最簡單的辦法是假定您正處理一個單色系統(每圖素1位元)其中0代表黑色,1代表白色。BLACKNESS操作的結果是不管是來源、目的和圖案是什麼,全部為零,因此目的將顯示黑色。類似地,WHITENESS總導致目的呈白色。

現在假定您使用位元映射操作PATCOPY。這導致結果位元與圖案位元相同,而忽略了來源和目的點陣圖。換句話說,PATCOPY簡單地將目前圖案複製給了目的矩形。

PATPAINT位元映射操作包含一個更複雜的操作。其結果相同於在圖案、目的和反轉的來源之間進行位元或操作。當來源點陣圖是黑色(0)時,其結果總是白色(1);當來源是白色(1)時,只要圖案或目的為白色,則結果就是白色。換句話說,只有來源為白色而圖案和目的都是黑色時,結果才是黑色。

彩色顯示時每個圖素都使用了多個位元。BitBlt和StretchBlt函式對每個顏色位元都分別提供了位元操作。例如,如果目的是紅色而來源為藍色,SRCPAINT位元映射操作把目的變成洋紅色。注意,操作實際是按顯示卡內儲存的位元執行的。這些位元所對應的顏色取決於顯示卡的調色盤的設定。Windows完成了此操作,以便位元映射操作能達到您預計的結果。不過,如果您修改了調色盤(我將在 第十六章 討論),位元映射操作將產生無法預料的結果。

如要得到位元映射操作較好的應用程式,請參見本章後面的「 非矩形點陣圖圖像 」一節。

圖案Blt

除了BitBlt和StretchBlt以外,Windows還包括一個稱為PatBlt (「pattern block transfer:圖案塊傳輸」)的函式。這是三個「blt」函式中最簡單的。與BitBlt和StretchBlt不同,它只使用一個目的裝置內容。PatBlt語法是:

x、y、cx和cy參數位於邏輯單位。邏輯點(x,y)指定了矩形的左上角。矩形寬為cx單位,高為cy單位。這是PatBlt修改的矩形區域。PatBlt在畫刷與目的裝置內容上執行的邏輯操作由dwROP參數決定,此參數是ROP代碼的子集-也就是說,您可以只使用那些不包括來源目的裝置內容的ROP代碼。下表列出了PatBlt支援的16個位元映射操作:

PatBlt (hdc, x, y, cx, cy, dwROP) ;

表14-5

下面列出了PatBlt一些更常見用途。如果想畫一個黑色矩形,您可呼叫

要畫一個白色矩形,請用

函式

用於改變矩形的顏色。如果目前裝置內容中選擇了WHITE_BRUSH,那麼函式

也改變矩形。

您可以再次呼叫FillRect函式來用畫筆充滿一個矩形區域:

FillRect函式相同於下列代碼:

PatBlt (hdc, x, y, cx, cy, BLACKNESS) ;

PatBlt (hdc, x, y, cx, cy, WHITENESS) ;

PatBlt (hdc, x, y, cx, cy, DSTINVERT) ;

PatBlt (hdc, x, y, cx, cy, PATINVERT) ;

FillRect (hdc, &rect, hBrush) ;

hBrush = SelectObject (hdc, hBrush) ; PatBlt (hdc, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, PATCOPY) ; SelectObject (hdc, hBrush) ;

實際上,此程式碼是Windows用於執行FillRect函式的動作。如果您呼叫

Windows將其轉換成函式:

InvertRect (hdc, &rect) ;

PatBlt (hdc, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, DSTINVERT) ;

在介紹PatBlt函式的語法時,我說過點(x,y)指出了矩形的左上角,而且此矩形寬度為cx單位,高度為cy單位。此敘述並不完全正確。BitBlt、PatBlt和StretchBlt是最合適的GDI畫圖函式,它們根據從一個角測得的邏輯寬度和高度來指定邏輯直角座標。矩形邊框用到的其他所有GDI畫圖函式都要求根據左上角和右下角的座標來指定座標。對於MM_TEXT映射模式,上面講述的PatBlt參數就是正確的。然而對於公制的映射模式來說,就不正確。如果您使用一的cx和cy值,那麼點(x,y)將是矩形的左下角。如果希望點(x,y)是矩形的左上角,那麼cy參數必須設為矩形的負高度。

如果想更精確,用PatBlt修改顏色的矩形將通過cx的絕對值獲得邏輯寬度,通過cy的絕對值獲得邏輯高度。這兩個參數可以是負值。由邏輯點(x, y)和(x + cx, y + cy)給定的兩個角定義了矩形。矩形的左上角通常屬於PatBlt修改的區域。右上角則超出了矩形的範圍。根據映射模式和cx、cy參數的符號,矩形左上角的點應為(x, y)、(x, y + cy)、(x + cx, y)或者(x + cx, y + cy)。

如果給MM_LOENGLISH設定了映射模式,並且您想在顯示區域左上角的一小塊正方形上使用PatBlt,您可以使用

給PatBlt設定正確參數最容易的方法是將x和y設為矩形左上角。如果映射模式定義y座標隨著向上捲動顯示而增加,那麼請使用負的cy參數。如果映射模式定義x座標向左增加(很少有人用),則需要使用負的cx參數。

GDI點陣圖物件

我在本章前面已提到過Windows從1.0開始就支援GDI點陣圖物件。因為在Windows 3.0發表了裝置無關點陣圖,GDI點陣圖物件有時也稱為裝置相關點陣圖,或者DDB。我儘量不全部引用device-dependent bitmap的全文,因為它看上去與device-independent bitmap類似。縮寫DDB會好一些,因為我們很容易把它與DIB區別開來。

對程式寫作者來說,現存的兩種不同型態的點陣圖從Windows 3.0開始就更為混亂。許多有經驗的Windows程式寫作者都不能準確地理解DIB和DDB之間的關係。(恐怕本書的Windows 3.0版本不能澄清這個問題)。誠然,DIB和DDB在許多方面是相關的:DIB與DDB能相互轉換(儘管轉換程序中會丟失一些資訊)。然而DIB和DDB是不可以相互替換的,並且不能簡單地選擇一種方法來表示同一個可視資料。

如果我們能假設說DIB一定會替代DDB,那以後就會很方便了。但現實並不是如此,DDB還在Windows中扮演著很重要角色,尤其是您在乎程式執行表現好壞時。

建立DDB

DDB是Windows圖形裝置介面的圖形物件之一(其中還包括繪圖筆、畫刷、字體、metafile和調色盤)。這些圖形物件儲存在GDI模組內部,由應用程式軟體以代號數字的方式引用。您可以將DDB代號儲存在一個HBITMAP(「handle to a bitmap:點陣圖代號」)型態的變數中,例如:

然後通過呼叫DDB建立的一個函式來獲得代號,例如:CreateBitmap。這些函式配置並初始化GDI記憶體中的一些記憶體來儲存關於點陣圖的資訊,以及實際點陣圖位元的資訊。應用程式不能直接存取這段記憶體。點陣圖與裝置內容無關。當程式使用完點陣圖以後,就要清除這段記憶體:

如果程式執行時您使用了DDB,那麼程式終止時,您可以完成上面的操作。

CreateBitmap函式用法如下:

前兩個參數是點陣圖的寬度和高度(以圖素為單位),第三個參數是顏色面的數目,第四個參數是每圖素的位元數,第五個參數是指向一個以特定顏色格式存放的位元陣列的指標,陣列內存放有用來初始化該DDB的圖像。如果您不想用一張現有的圖像來初始化DDB,可以將最後一個參數設為NULL。以後您還是可以設定該DDB內圖素的內容。

使用此函式時,Windows也允許建立您喜歡的特定型態GDI點陣圖物件。例如,假設您希望點陣圖寬7個圖素、高9個圖素、5個?色位元面,並且每個圖素占3位元,您只需要執行下面的操作:

這時Windows會好好給您一個有效的點陣圖代號。

在此函式呼叫期間,Windows將儲存您傳遞給函式的資訊,並為圖素位元配置記憶體。粗略的計算是此點陣圖需要7×9×5×3,即945位元,這需要比118個位元組還多幾個位元。

然而,Windows為點陣圖配置好記憶體以後,每行圖素都佔用許多連貫的位元組,這樣

或者C程式寫作者更傾向於寫成:

因此,為DDB配置的記憶體就是:

本例中,iWidthBytes占4位元組,iBitmapBytes占180位元組。

現在,知道一張點陣圖有5個顏色位元面,每圖素占3個顏色位有什麼意義嗎?真是見鬼了,這甚至不能把它稱作一個習題作業。雖然您讓GDI內部配置了些記憶體,並且讓這些記憶體有一定結構的內容,但是您這張點陣圖完全作不出任何有用的事情來。

實際上,您將用兩種型態的參數來呼叫CreateBitmap。

更現實的情況下,您只會在第一種情況下呼叫CreateBitmap。對於第二種情況,您可以用CreateCompatibleBitmap來簡化問題:

此函式建立了一個與設備相容的點陣圖,此設備的裝置內容代號由第一個參數給出。CreateCompatibleBitmap用裝置內容代號來獲得GetDeviceCaps資訊,然後將此資訊傳遞給CreateBitmap。除了與實際的裝置內容有相同的記憶體組織之外,DDB與裝置內容沒有其他聯繫。

CreateDiscardableBitmap函式與CreateCompatibleBitmap的參數相同,並且功能上相同。在早期的Windows版本中,CreateDiscardableBitmap建立的點陣圖可以在記憶體減少時由Windows將其從記憶體中清除,然後程式再重建點陣圖資料。

第三個點陣圖建立函式是CreateBitmapIndirect:

其中bitmap是BITMAP型態的結構。BITMAP結構定義如下:

PatBlt (hdc, 0, 0, 100, -100, dwROP) ;

PatBlt (hdc, 0, -100, 100, 100, dwROP) ;

PatBlt (hdc, 100, 0, -100, -100, dwROP) ;

PatBlt (hdc, 100, -100, -100, 100, dwROP) ;

HBITMAP hBitmap ;

DeleteObject (hBitmap) ;

hBitmap = CreateBitmap (cx, cy, cPlanes, cBitsPixel, bits) ;

hBitmap = CreateBitmap (7, 9, 5, 3, NULL) ;

iWidthBytes = 2 * ((cx * cBitsPixel + 15) / 16) ;

iWidthBytes = (cx * cBitsPixel + 15) & ~15) >> 3 ;

iBitmapBytes = cy * cPlanes * iWidthBytes ;

hBitmap = CreateCompatibleBitmap (hdc, cx, cy) ;

hBitmap CreateBitmapIndirect (&bitmap) ;

  • cPlanes和cBitsPixel都等於1(表示單色點陣圖);或者
  • cPlanes和cBitsPixel都等於某個特定裝置內容的值,您可以使用PLANES和BITSPIXEL索引來從GetDeviceCaps函式獲得。

typedef struct _tagBITMAP { LONG bmType ; // set to 0 LONG bmWidth ; // width in pixels LONG bmHeight ; // height in pixels LONG bmWidthBytes ; // width of row in bytes WORD bmPlanes ; // number of color planes WORD bmBitsPixel ; // number of bits per pixel LPVOID bmBits ; // pointer to pixel bits } BITMAP, * PBITMAP ;

在呼叫CreateBitmapIndirect函式時,您不需要設定bmWidthBytes欄位。Windows將為您計算,您也可以將bmBits欄位設定為NULL,或者設定為初始化點陣圖時用的圖素位元位址。

GetObject函式內也使用BITMAP結構,首先定義一個BITMAP型態的結構。

並呼叫函式如下:

Windows將用點陣圖資訊填充BITMAP結構的欄位,不過,bmBits欄位等於NULL。

您最後應呼叫DeleteObject來清除程式內建立的所有點陣圖。

點陣圖位元

用CreateBitmap或CreateBitmapIndirect來建立設備相關GDI點陣圖物件時,您可以給點陣圖圖素位元指定一個指標。或者您也可以讓點陣圖維持未初始化的狀態。在建立點陣圖以後,Windows還提供兩個函式來獲得並設定圖素位元。

要設定圖素位元,請呼叫:

GetBitmapBits函式有相同的語法:

在這兩個函式中,cBytes指明要複製的位元組數,bits是最少cBytes大小的緩衝區。

DDB中的圖素位元從頂列開始排列。我在前面說過,每列的位元組數都是偶數。除此之外,沒什麼好說明的了。如果點陣圖是單色的,也就是說它有1個位元面並且每個圖素占1位元,則每個圖素不是1就是0。每列最左邊的圖素是本列第一個位元組最高位元的位元。我們在本章的後面講完如何顯示單色DDB之後,將做一個單色的DDB。

對於非單色點陣圖,應避免出現您需要知道圖素位元含義的狀況。例如,假定在8位顏色的VGA上執行Windows,您可以呼叫CreateCompatibleBitmap。通過GetDeviceCaps,您能夠確定您正處理一個有1個顏色位元面和每圖素8位元的設備。一個位元組儲存一個圖素。但是圖素值0x37是什麼意思呢?很明顯是某種顏色,但到底是什麼顏色呢?

圖素實際上並不涉及任何固定的顏色,它只是一個值。DDB沒有顏色表。問題的關鍵在於:當DDB顯示在螢幕上時,圖素的顏色是什麼。它肯定是某種顏色,但具體是什麼顏色呢?顯示的圖素將與在顯示卡上的調色盤查看表裏的0x37索引值代表的RGB顏色有關。這就是您現在碰到的裝置依賴性。

不過,不要只因為我們不知道圖素值的含義,就假定非單色DDB沒用。我們將簡要看一下它們的用途。 下一章 ,我們將看到SetBitmapBits和GetBitmapBits函式是如何被更有用的SetDIBits和GetDIBits函式所取代的。

因此,基本的規則是這樣的:不要用CreateBitmap、CreateBitmapIndirect或SetBitmapBits來設定彩色DDB的位元,您只能安全地使用這些函式來設定單色DDB的位元。(如果您在呼叫GetBitmapBits期間,從其他相同格式的DDB中獲得位元,那麼這些規則例外。)

在繼續之前,讓我再討論一下SetBitmapDimensionEx和GetBitmapDimensionEx函式。這些函式讓您設定(和獲得)點陣圖的測量尺寸(以0.1毫米為單位)。這些資訊與點陣圖解析度一起儲存在GDI中,但不用於任何操作。它只是您與DDB聯繫的一個測量尺寸標識。

記憶體裝置內容

我們必須解決的下一個概念是記憶體裝置內容。您需要用記憶體裝置內容來處理GDI點陣圖物件。

通常,裝置內容指的是特殊的圖形輸出設備(例如視訊顯示器或者印表機)及其裝置驅動程式。記憶體裝置內容只位於記憶體中,它不是真正的圖形輸出設備,但可以說與指定的真正設備「相容」。

要建立一個記憶體裝置內容,您必須首先有實際設備的裝置內容代號。如果是hdc,那麼您可以像下面那樣建立記憶體裝置內容:

通常,函式的呼叫比這更簡單。如果您將參數設為NULL,那麼Windows將建立一個與視訊顯示器相相容的記憶體裝置內容。應用程式建立的任何記憶體裝置內容最終都通過呼叫DeleteDC來清除。

記憶體裝置內容有一個與實際位元映射設備相同的顯示平面。不過,最初此顯示平面非常小-單色、1圖素寬、1圖素高。顯示平面就是單獨1位元。

當然,用1位元的顯示平面,您不能做更多的工作,因此下一步就是擴大顯示平面。您可以通過將一個GDI點陣圖物件選進記憶體裝置內容來完成這項工作,例如:

此函式與您將畫筆、畫刷、字體、區域和調色盤選進裝置內容的函式相同。然而,記憶體裝置內容是您可以選進點陣圖的唯一一種裝置內容型態。(如果需要,您也可以將其他GDI物件選進記憶體裝置內容。)

只有選進記憶體裝置內容的點陣圖是單色的,或者與記憶體裝置內容相容設備有相同的色彩組織時,SelectObject才會起作用。這也是建立特殊的DDB(例如有5個位元面,且每圖素3位元)沒有用的原因。

現在情況是這樣:SelectObject呼叫以後,DDB就是記憶體裝置內容的顯示平面。處理實際裝置內容的每項操作,您幾乎都可以用於記憶體裝置內容。例如,如果用GDI畫圖函式在記憶體裝置內容中畫圖,那麼圖像將畫在點陣圖上。這是非常有用的。還可以將記憶體裝置內容作為來源,把視訊裝置內容作為目的來呼叫BitBlt。這就是在顯示器上繪製點陣圖的方法。如果把視訊裝置內容作為來源,把記憶體裝置內容作為目的,那麼呼叫BitBlt可將螢幕上的一些內容複製給點陣圖。我們將看到這些都是可能的。

載入點陣圖資源

除了各種各樣的點陣圖建立函式以外,獲得GDI點陣圖物件代號的另一個方法就是呼叫LoadBitmap函式。使用此函式,您不必擔心點陣圖格式。在程式中,您只需簡單地按資源來建立點陣圖,這與建立圖示或者滑鼠游標的方法類似。LoadBitmap函式的語法與LoadIcon和LoadCursor相同:

如果想載入系統點陣圖,那麼將第一個參數設為NULL。這些不同的點陣圖是Windows視覺介面(例如關閉方塊和勾選標記)的一小部分,它們的識別字以字母OBM開始。如果點陣圖與整數識別字而不是與名稱有聯繫,那麼第二個參數就可以使用MAKEINTRESOURCE巨集。由LoadBitmap載入的所有點陣圖最終應用DeleteObject清除。

如果點陣圖資源是單色的,那麼從LoadBitmap傳回的代號將指向一個單色的點陣圖物件。如果點陣圖資源不是單色,那麼從LoadBitmap傳回的代號將指向一個GDI點陣圖物件,該物件與執行程式的視訊顯示器有相同的色彩組織。因此,點陣圖始終與視訊顯示器相容,並且總是選進與視訊顯示器相容的記憶體裝置內容中。採用LoadBitmap呼叫後,就不用擔心任何色彩轉換的問題了。在下一章中,我們就知道LoadBitmap的具體運作方式了。

程式14-3所示的BRICKS1程式示範了載入一小張單色點陣圖資源的方法。此點陣圖本身不像磚塊,但當它水平和垂直重複時,就與磚牆相似了。

BITMAP bitmap ;

GetObject (hBitmap, sizeof (BITMAP), &bitmap) ;

SetBitmapBits (hBitmap, cBytes, &bits) ;

GetBitmapBits (hBitmap, cBytes, &bits) ;

hdcMem = CreateCompatibleDC (hdc) ;

SelectObject (hdcMem, hBitmap) ;

hBitmap = LoadBitmap (hInstance, szBitmapName) ;

程式14-3 BRICKS1 BRICKS1.C /*-------------------------------------------------------------------------- BRICKS1.C -- LoadBitmap Demonstration (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 ("Bricks1") ; 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 ("LoadBitmap Demo"), 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 int cxClient, cyClient, cxSource, cySource ; BITMAP bitmap ; HDC hdc, hdcMem ; HINSTANCE hInstance ; int x, y ; PAINTSTRUCT ps ; switch (message) { case WM_CREATE: hInstance = ((LPCREATESTRUCT) lParam)->hInstance ; hBitmap = LoadBitmap (hInstance, TEXT ("Bricks")) ; GetObject (hBitmap, sizeof (BITMAP), &bitmap) ; cxSource = bitmap.bmWidth ; cySource = bitmap.bmHeight ; 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) ; for (y = 0 ; y < cyClient ; y += cySource) for (x = 0 ; x < cxClient ; x += cxSource) { BitBlt (hdc, x, y, cxSource, cySource, hdcMem, 0, 0, SRCCOPY) ; } DeleteDC (hdcMem) ; EndPaint (hwnd, &ps) ; return 0 ; case WM_DESTROY: DeleteObject (hBitmap) ; PostQuitMessage (0) ; return 0 ; } return DefWindowProc (hwnd, message, wParam, lParam) ; }

BRICKS1.RC (摘錄) //Microsoft Developer Studio generated resource script. #include "resource.h" #include "afxres.h" ///////////////////////////////////////////////////////////////////////////// // Bitmap BRICKS BITMAP DISCARDABLE "Bricks.bmp"

BRICKS.BMP

在Visual C++ Developer Studio中建立點陣圖時,應指明點陣圖的高度和寬度都是8個圖素,是單色,名稱是「Bricks」。BRICKS1程式在WM_CREATE訊息處理期間載入了點陣圖並用GetObject來確定點陣圖的圖素尺寸(以便當點陣圖不是8圖素見方時程式仍能繼續工作)。以後,BRICKS1將在WM_DESTROY訊息中刪除此點陣圖。

在WM_PAINT訊息處理期間,BRICKS1建立了一個與顯示器相容的記憶體裝置內容,並且選進了點陣圖。然後是從記憶體裝置內容到顯示區域裝置內容一系列的BitBlt函式呼叫,再刪除記憶體裝置內容。圖14-3顯示了程式的執行結果。

順便說一下,Developer Studio建立的BRICKS.BMP檔案是一個裝置無關點陣圖。您可能想在Developer Studio內建立一個彩色的BRICKS.BMP檔案(您可自己選定顏色),並且保證一切工作正常。

我們看到DIB能轉換成與視訊顯示器相容的GDI點陣圖物件。我們將在下一章看到這是如何操作的。

單色點陣圖格式

如果您在處理小塊單色圖像,那麼您不必把它們當成資源來建立。與彩色點陣圖物件不同,單色位元的格式相對簡單一些,而且幾乎能全部從您要建立的圖像中分離出來。例如,假定您要建立下圖所示的點陣圖:

您能寫下一系列的位元(0代表黑色,1代表白色),這些位元直接對應於網格。從左到右讀這些位元,您能給每8位元組配置一個十六進位元的位元組值。如果點陣圖的寬度不是16的倍數,在位元組的右邊用零填充,以得到偶數個位元組:

圖14-3 BRICKS1的螢幕顯示

0 1 0 1 0 0 0 1 0 1 1 1 0 1 1 1 0 0 0 1 = 51 77 10 00 0 1 0 1 0 1 1 1 0 1 1 1 0 1 1 1 0 1 0 1 = 57 77 50 00 0 0 0 1 0 0 1 1 0 1 1 1 0 1 1 1 0 1 0 1 = 13 77 50 00 0 1 0 1 0 1 1 1 0 1 1 1 0 1 1 1 0 1 0 1 = 57 77 50 00 0 1 0 1 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 1 = 51 11 10 00

圖素寬為20,掃描線高為5,位元組寬為4。您可以用下面的敘述來設定此點陣圖的BITMAP結構:

並且可以將位元儲存在BYTE陣列中:

static BITMAP bitmap = { 0, 20, 5, 4, 1, 1 } ;

static BYTE bits [] = { 0x51, 0x77, 0x10, 0x00, 0x57, 0x77, 0x50, 0x00, 0x13, 0x77, 0x50, 0x00, 0x57, 0x77, 0x50, 0x00, 0x51, 0x11, 0x10, 0x00 } ;

用CreateBitmapIndirect來建立點陣圖需要下面兩條敘述:

另一種方法是:

您也可以用一道敘述來建立點陣圖:

在程式14-4顯示的BRICKS2程式利用此技術直接建立了磚塊點陣圖,而沒有使用資源。

bitmap.bmBits = (PSTR) bits ; hBitmap = CreateBitmapIndirect (&bitmap) ;

hBitmap = CreateBitmapIndirect (&bitmap) ; SetBitmapBits (hBitmap, sizeof bits, bits) ;

hBitmap = CreateBitmap (20, 5, 1, 1, bits) ;

程式14-4 BRICKS2 BRICKS2.C /*-------------------------------------------------------------------------- BRICKS2.C -- CreateBitmap Demonstration (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 ("Bricks2") ; 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 ("CreateBitmap Demo"), 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 BITMA Pbitmap = { 0, 8, 8, 2, 1, 1 } ; static BYTE bits [8][2]={ 0xFF, 0, 0x0C, 0, 0x0C, 0, 0x0C, 0, 0xFF, 0, 0xC0, 0, 0xC0, 0, 0xC0, 0 } ; static HBITMAP hBitmap ; static int cxClient, cyClient, cxSource, cySource ; HDC hdc, hdcMem ; int x, y ; PAINTSTRUCT ps ; switch (message) { case WM_CREATE: bitmap.bmBits = bits ; hBitmap = CreateBitmapIndirect (&bitmap) ; cxSource = bitmap.bmWidth ; cySource = bitmap.bmHeight ; 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) ; for (y = 0 ; y < cyClient ; y += cySource) for (x = 0 ; x < cxClient ; x += cxSource) { BitBlt (hdc, x, y, cxSource, cySource, hdcMem, 0, 0, SRCCOPY) ; } DeleteDC (hdcMem) ; EndPaint (hwnd, &ps) ; return 0 ; case WM_DESTROY: DeleteObject (hBitmap) ; PostQuitMessage (0) ; return 0 ; } return DefWindowProc (hwnd, message, wParam, lParam) ; }

您可以嘗試一下與彩色點陣圖相似的物件。例如,如果您的視訊顯示器執行在256色模式下,那麼您可以根據表14-2來定義彩色磚的每個圖素。不過,當程式執行在其他顯示模式下時,此程式碼不起作用。以裝置無關方式處理彩色點陣圖需要使用 下章 討論的DIB。

點陣圖中的畫刷

BRICKS系列的最後一個專案是BRICKS3,如程式14-5所示。乍看此程式,您可能會有這種感覺:程式碼哪裡去了呢?

程式14-5 BRICKS3 BRICKS3.C /*------------------------------------------------------------------------- BRICKS3.C -- CreatePatternBrush Demonstration (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 ("Bricks3") ; HBITMAP hBitmap ; HBRUSH hBrush ; HWND hwnd ; MSG msg ; WNDCLASS wndclass ; hBitmap = LoadBitmap (hInstance, TEXT ("Bricks")) ; hBrush = CreatePatternBrush (hBitmap) ; DeleteObject (hBitmap) ; 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 ; 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 ("CreatePatternBrush Demo"), 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) ; } DeleteObject (hBrush) ; return msg.wParam ; } LRESULT CALLBACK WndProc ( HWND hwnd, UINT message, WPARAM wParam,LPARAM lParam) { switch (message) { case WM_DESTROY: PostQuitMessage (0) ; return 0 ; } return DefWindowProc (hwnd, message, wParam, lParam) ; }

BRICKS3.RC (摘錄) //Microsoft Developer Studio generated resource script. #include "resource.h" #include "afxres.h" ///////////////////////////////////////////////////////////////////////////// // Bitmap BRICKS BITMAP DISCARDABLE "Bricks.bmp"

此程式與BRICKS1使用同一個BRICKS.BMP檔案,而且視窗看上去也相同。

正如您看到的一樣,視窗訊息處理程式沒有更多的內容。BRICKS3實際上使用磚塊圖案作為視窗類別背景畫刷,它在WNDCLASS結構的hbrBackground欄位中定義。

您現在可能猜想GDI畫刷是很小的點陣圖,通常是8個圖素見方。如果將LOGBRUSH結構的lbStyle欄位設定為BS_PATTERN,然後呼叫CreatePatternBrush或CreateBrushIndirect,您就可以在點陣圖外面來建立畫刷了。此點陣圖至少是寬高各8個圖素。如果再大,Windows 98將只使用點陣圖的左上角作為畫刷。而Windows NT不受此限制,它會使用整個點陣圖。

請記住,畫刷和點陣圖都是GDI物件,而且您應該在程式終止前刪除您在程式中建立畫刷和點陣圖。如果您依據點陣圖建立畫刷,那麼在用畫刷畫圖時,Windows將複製點陣圖位元到畫刷所繪製的區域內。呼叫CreatePatternBrush(或者CreateBrushIndirect)之後,您可以立即刪除點陣圖而不會影響到畫筆。類似地,您也可以刪除畫刷而不會影響到您選進的原始點陣圖。注意,BRICKS3在建立畫刷後刪除了點陣圖,並在程式終止前刪除了畫刷。

繪製點陣圖

在視窗中繪圖時,我們已經將點陣圖當成繪圖來源使用過了。這要求先將點陣圖選進記憶體裝置內容,並呼叫BitBlt或者StretchBlt。您也可以用記憶體裝置內容代號作為所有實際呼叫的GDI函式中的第一參數。記憶體裝置內容的動作與實際的裝置內容相同,除非顯示平面是點陣圖。

程式14-6所示的HELLOBIT程式展示了此項技術。程式在一個小點陣圖上顯示了字串「Hello, world!」,然後從點陣圖到程式顯示區域執行BitBlt或StretchBlt(依照選擇的功能表選項而定)。

程式14-6 HELLOBIT HELLOBIT.C /*----------------------------------------------------------------------- HELLOBIT.C -- Bitmap Demonstration (c) Charles Petzold, 1998 -------------------------------------------------------------------------*/ #include <windows.h> #include "resource.h" LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ; int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { static TCHAR szAppName [] = TEXT ("HelloBit") ; 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 ("HelloBit"), 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 HDC hdcMem ; static int cxBitmap, cyBitmap, cxClient, cyClient, iSize = IDM_BIG ; static TCHAR * szText = TEXT (" Hello, world! ") ; HDC hdc ; HMENU hMenu ; int x, y ; PAINTSTRUCT ps ; SIZE size ; switch (message) { case WM_CREATE: hdc = GetDC (hwnd) ; hdcMem = CreateCompatibleDC (hdc) ; GetTextExtentPoint32 (hdc, szText, lstrlen (szText), &size) ; cxBitmap = size.cx ; cyBitmap = size.cy ; hBitmap = CreateCompatibleBitmap (hdc, cxBitmap, cyBitmap) ; ReleaseDC (hwnd, hdc) ; SelectObject (hdcMem, hBitmap) ; TextOut (hdcMem, 0, 0, szText, lstrlen (szText)) ; return 0 ; case WM_SIZE: cxClient = LOWORD (lParam) ; cyClient = HIWORD (lParam) ; return 0 ; case WM_COMMAND: hMenu = GetMenu (hwnd) ; switch (LOWORD (wParam)) { case IDM_BIG: case IDM_SMALL: CheckMenuItem (hMenu, iSize, MF_UNCHECKED) ; iSize = LOWORD (wParam) ; CheckMenuItem (hMenu, iSize, MF_CHECKED) ; InvalidateRect (hwnd, NULL, TRUE) ; break ; } return 0 ; case WM_PAINT: hdc = BeginPaint (hwnd, &ps) ; switch (iSize) { case IDM_BIG: StretchBlt (hdc, 0, 0, cxClient, cyClient, hdcMem, 0, 0, cxBitmap, cyBitmap, SRCCOPY) ; break ; case IDM_SMALL: for (y = 0 ; y < cyClient ; y += cyBitmap) for (x = 0 ; x < cxClient ; x += cxBitmap) { BitBlt (hdc, x, y, cxBitmap, cyBitmap, hdcMem, 0, 0, SRCCOPY) ; } break ; } EndPaint (hwnd, &ps) ; return 0 ; case WM_DESTROY: DeleteDC (hdcMem) ; DeleteObject (hBitmap) ; PostQuitMessage (0) ; return 0 ; } return DefWindowProc (hwnd, message, wParam, lParam) ; }

HELLOBIT.RC (摘錄) //Microsoft Developer Studio generated resource script. #include "resource.h" #include "afxres.h" ///////////////////////////////////////////////////////////////////////////// // Menu HELLOBIT MENU DISCARDABLE BEGIN POPUP "&Size" BEGIN MENUITEM "&Big", IDM_BIG, CHECKED MENUITEM "&Small", IDM_SMALL END END

RESOURCE.H (摘錄) // Microsoft Developer Studio generated include file. // Used by HelloBit.rc #define IDM_BIG 40001 #define IDM_SMALL 40002

程式從呼叫GetTextExtentPoint32確定字串的圖素尺寸開始。這些尺寸將成為與視訊顯示相容的點陣圖尺寸。當此點陣圖被選進記憶體裝置內容(也與視訊顯示相容)後,再呼叫TextOut將文字顯示在點陣圖上。記憶體裝置內容在程式執行期間保留。在處理WM_DESTROY資訊期間,HELLOBIT刪除了點陣圖和記憶體裝置內容。

HELLOBIT中的一條功能表選項允許您顯示點陣圖尺寸,此尺寸或者是顯示區域中水平和垂直方向平鋪的實際尺寸,或者是縮放成顯示區域大小的尺寸,如圖14-4所示。正與您所見到的一樣,這不是顯示大尺寸字元的好方法!它只是小字體的放大版,並帶有放大時產生的鋸齒線。

您可能想知道一個程式,例如HELLOBIT,是否需要處理WM_DISPLAYCHANGE訊息。只要使用者(或者其他應用程式)修改了視訊顯示大小或者顏色深度,應用程式就接收到此訊息。其中顏色深度的改變會導致記憶體裝置內容和視訊裝置內容不相容。但這並不會發生,因為當顯示模式修改後,Windows自動修改了記憶體裝置內容的顏色解析度。選進記憶體裝置內容的點陣圖仍然保持原樣,但不會造成任何問題。

陰影點陣圖

在記憶體裝置內容繪圖(也就是點陣圖)的技術是執行「陰影點陣圖(shadow bitmap)」的關鍵。此點陣圖包含視窗顯示區域中顯示的所有內容。這樣,對WM_PAINT訊息的處理就簡化到簡單的BitBlt。

陰影點陣圖在繪畫程式中最有用。程式14-7所示的SKETCH程式並不是一個最完美的繪畫程式,但它是一個開始。

圖14-4 HELLOBIT的螢幕顯示

程式14-7 SKETCH SKETCH.C /*------------------------------------------------------------------------- SKETCH.C -- Shadow Bitmap Demonstration (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 ("Sketch") ; 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 ("Sketch"), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL) ; if (hwnd == NULL) { MessageBox ( NULL, TEXT ("Not enough memory to create bitmap!"), szAppName, MB_ICONERROR) ; return 0 ; } ShowWindow (hwnd, iCmdShow) ; UpdateWindow (hwnd) ; while (GetMessage (&msg, NULL, 0, 0)) { TranslateMessage (&msg) ; DispatchMessage (&msg) ; } return msg.wParam ; } void GetLargestDisplayMode (int * pcxBitmap, int * pcyBitmap) { DEVMODE devmode ; int iModeNum = 0 ; * pcxBitmap = * pcyBitmap = 0 ; ZeroMemory (&devmode, sizeof (DEVMODE)) ; devmode.dmSize = sizeof (DEVMODE) ; while (EnumDisplaySettings (NULL, iModeNum++, &devmode)) { * pcxBitmap = max (* pcxBitmap, (int) devmode.dmPelsWidth) ; * pcyBitmap = max (* pcyBitmap, (int) devmode.dmPelsHeight) ; } } LRESULT CALLBACK WndProc ( HWND hwnd, UINT message, WPARAM wParam,LPARAM lParam) { static BOOL fLeftButtonDown, fRightButtonDown ; static HBITMAP hBitmap ; static HDC hdcMem ; static int cxBitmap, cyBitmap, cxClient, cyClient, xMouse, yMouse ; HDC hdc ; PAINTSTRUCT ps ; switch (message) { case WM_CREATE: GetLargestDisplayMode (&cxBitmap, &cyBitmap) ; hdc = GetDC (hwnd) ; hBitmap = CreateCompatibleBitmap (hdc, cxBitmap, cyBitmap) ; hdcMem = CreateCompatibleDC (hdc) ; ReleaseDC (hwnd, hdc) ; if (!hBitmap) // no memory for bitmap { DeleteDC (hdcMem) ; return -1 ; } SelectObject (hdcMem, hBitmap) ; PatBlt (hdcMem, 0, 0, cxBitmap, cyBitmap, WHITENESS) ; return 0 ; case WM_SIZE: cxClient = LOWORD (lParam) ; cyClient = HIWORD (lParam) ; return 0 ; case WM_LBUTTONDOWN: if (!fRightButtonDown) SetCapture (hwnd) ; xMouse = LOWORD (lParam) ; yMouse = HIWORD (lParam) ; fLeftButtonDown = TRUE ; return 0 ; case WM_LBUTTONUP: if (fLeftButtonDown) SetCapture (NULL) ; fLeftButtonDown = FALSE ; return 0 ; case WM_RBUTTONDOWN: if (!fLeftButtonDown) SetCapture (hwnd) ; xMouse = LOWORD (lParam) ; yMouse = HIWORD (lParam) ; fRightButtonDown = TRUE ; return 0 ; case WM_RBUTTONUP: if (fRightButtonDown) SetCapture (NULL) ; fRightButtonDown = FALSE ; return 0 ; case WM_MOUSEMOVE: if (!fLeftButtonDown && !fRightButtonDown) return 0 ; hdc = GetDC (hwnd) ; SelectObject (hdc, GetStockObject (fLeftButtonDown ? BLACK_PEN : WHITE_PEN)) ; SelectObject (hdcMem, GetStockObject (fLeftButtonDown ? BLACK_PEN : WHITE_PEN)) ; MoveToEx (hdc, xMouse, yMouse, NULL) ; MoveToEx (hdcMem, xMouse, yMouse, NULL) ; xMouse = (short) LOWORD (lParam) ; yMouse = (short) HIWORD (lParam) ; LineTo (hdc, xMouse, yMouse) ; LineTo (hdcMem, xMouse, yMouse) ; ReleaseDC (hwnd, hdc) ; return 0 ; case WM_PAINT: hdc = BeginPaint (hwnd, &ps) ; BitBlt (hdc, 0, 0, cxClient, cyClient, hdcMem, 0, 0, SRCCOPY) ; EndPaint (hwnd, &ps) ; return 0 ; case WM_DESTROY: DeleteDC (hdcMem) ; DeleteObject (hBitmap) ; PostQuitMessage (0) ; return 0 ; } return DefWindowProc (hwnd, message, wParam, lParam) ; }

要想在SKETCH中畫線,請按下滑鼠左鍵並拖動滑鼠。要擦掉畫過的東西(更確切地說,是畫白線),請按下滑鼠右鍵並拖動滑鼠。要清空整個視窗,請…結束程式,然後重新載入,一切從頭再來。圖14-5中顯示的SKETCH程式圖樣表達了對頻果公司的麥金塔電腦早期廣告的敬意。

此陰影點陣圖應多大?在本程式中,它應該大到能包含最大化視窗的整個顯示區域。這一問題很容易根據GetSystemMetrics資訊計算得出,但如果使用者修改了顯示設定後再顯示,進而擴大了最大化時視窗的尺寸,這時將發生什麼呢?SKETCH程式在EnumDisplaySettings函式的幫助下解決了此問題。此函式使用DEVMODE結構來傳回全部有效視訊顯示模式的資訊。第一次呼叫此函式時,應將EnumDisplaySettings的第二參數設為0,以後每次呼叫此值都增加。EnumDisplaySettings傳回FALSE時完成。

與此同時,SKETCH將建立一個陰影點陣圖,它比目前視訊顯示模式的表面還多四倍,而且需要幾百萬位元組的記憶體。由於如此,SKETCH將檢查點陣圖是否建立成功了,如果沒有建立,就從WM_CREATE傳回-1,以表示錯誤。

在WM_MOUSEMOVE訊息處理期間,按下滑鼠左鍵或者右鍵,並在記憶體裝置內容和顯示區域裝置內容中畫線時,SKETCH攔截滑鼠。如果畫線方式更複雜的話,您可能想在一個函式中實作,程式將呼叫此函式兩次-一次畫在視訊裝置內容上,一次畫在記憶體裝置內容上。

下面是一個有趣的實驗:使SKETCH視窗小於全畫面尺寸。隨著滑鼠左鍵的按下,將滑鼠拖出視窗的右下角。因為SKETCH攔截滑鼠,所以它繼續接收並處理WM_MOUSEMOVE訊息。現在擴大視窗,您將看到陰影點陣圖包含您在SKETCH視窗外所畫的內容。

在功能表中使用點陣圖

您也可以用點陣圖在功能表上顯示選項。如果您聯想起功能表中檔案夾、剪貼簿和資源回收筒的圖片,那麼不要再想那些圖片了。您應該考慮一下,功能表上顯示點陣圖對畫圖程式用途有多大,想像一下在功能表中使用不同字體和字體大小、線寬、陰影圖案以及顏色。

GRAFMENU是展示圖形功能表選項的範例程式。此程式頂層功能表如圖14-6所示。放大的字母來自於40×16圖素的單色點陣圖檔案,該檔案在Visual C++ Developer Studio建立。從功能表上選擇「FONT」將彈出三個選擇項-「Courier New」、「 Arial」和「Times New Roman」。它們是標準的Windows TrueType字體,並且每一個都按其相關的字體顯示,如圖14-7所示。這些點陣圖在程式中用記憶體裝置內容建立。

圖14-5 SKETCH的螢幕顯示

圖14-6 GRAFMENU程式的頂層功能表

圖14-7 GRAFMENU程式彈出的「FONT」功能表

最後,在拉下系統功能表時,您將獲得一些「輔助」資訊,用「HELP」表示了新使用者的線上求助項目(參見圖14-8)。此64×64圖素的單色點陣圖是在Developer Studio中建立的。

GRAFMENU程式,包括四個Developer Studio中建立的點陣圖,如程式14-8所示。

圖14-8 GRAFMENU程式系統功能表

程式14-8 GRAFMENU GRAFMENU.C /*---------------------------------------------------------------------------- GRAFMENU.C -- Demonstrates Bitmap Menu Items (c) Charles Petzold, 1998 -----------------------------------------------------------------------------*/ #include <windows.h> #include "resource.h" LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ; void AddHelpToSys (HINSTANCE, HWND) ; HMENU CreateMyMenu (HINSTANCE) ; HBITMAP StretchBitmap (HBITMAP) ; HBITMAP GetBitmapFont (int) ; void DeleteAllBitmaps (HWND) ; TCHAR szAppName[] = TEXT ("GrafMenu") ; 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 ("Bitmap Menu Demonstration"), 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 iMsg, WPARAM wParam,LPARAM lParam) { HMENU hMenu ; static int iCurrentFont = IDM_FONT_COUR ; switch (iMsg) { case WM_CREATE: AddHelpToSys (((LPCREATESTRUCT) lParam)->hInstance, hwnd) ; hMenu = CreateMyMenu (((LPCREATESTRUCT) lParam)->hInstance) ; SetMenu (hwnd, hMenu) ; CheckMenuItem (hMenu, iCurrentFont, MF_CHECKED) ; return 0 ; case WM_SYSCOMMAND: switch (LOWORD (wParam)) { case IDM_HELP: MessageBox (hwnd, TEXT ("Help not yet implemented!"), szAppName, MB_OK | MB_ICONEXCLAMATION) ; return 0 ; } break ; case WM_COMMAND: switch (LOWORD (wParam)) { case IDM_FILE_NEW: case IDM_FILE_OPEN: case IDM_FILE_SAVE: case IDM_FILE_SAVE_AS: case IDM_EDIT_UNDO: case IDM_EDIT_CUT: case IDM_EDIT_COPY: case IDM_EDIT_PASTE: case IDM_EDIT_CLEAR: MessageBeep (0) ; return 0 ; case IDM_FONT_COUR: case IDM_FONT_ARIAL: case IDM_FONT_TIMES: hMenu = GetMenu (hwnd) ; CheckMenuItem (hMenu, iCurrentFont, MF_UNCHECKED) ; iCurrentFont = LOWORD (wParam) ; CheckMenuItem (hMenu, iCurrentFont, MF_CHECKED) ; return 0 ; } break ; case WM_DESTROY: DeleteAllBitmaps (hwnd) ; PostQuitMessage (0) ; return 0 ; } return DefWindowProc (hwnd, iMsg, wParam, lParam) ; } /*-------------------------------------------------------------------------- AddHelpToSys: Adds bitmap Help item to system menu ------------------------------------------------------------------------*/ void AddHelpToSys (HINSTANCE hInstance, HWND hwnd) { HBITMAP hBitmap ; HMENU hMenu ; hMenu = GetSystemMenu (hwnd, FALSE); hBitmap = StretchBitmap (LoadBitmap (hInstance, TEXT ("BitmapHelp"))) ; AppendMenu (hMenu, MF_SEPARATOR, 0, NULL) ; AppendMenu (hMenu, MF_BITMAP, IDM_HELP, (PTSTR) (LONG) hBitmap) ; } /*------------------------------------------------------------------------ CreateMyMenu: Assembles menu from components --------------------------------------------------------------------------*/ HMENU CreateMyMenu (HINSTANCE hInstance) { HBITMAP hBitmap ; HMENU hMenu, hMenuPopup ; int i ; hMenu = CreateMenu () ; hMenuPopup = LoadMenu (hInstance, TEXT ("MenuFile")) ; hBitmap = StretchBitmap (LoadBitmap (hInstance, TEXT ("BitmapFile"))) ; AppendMenu (hMenu, MF_BITMAP | MF_POPUP, (int) hMenuPopup, (PTSTR) (LONG) hBitmap) ; hMenuPopup = LoadMenu (hInstance, TEXT ("MenuEdit")) ; hBitmap = StretchBitmap (LoadBitmap (hInstance, TEXT ("BitmapEdit"))) ; AppendMenu (hMenu, MF_BITMAP | MF_POPUP, (int) hMenuPopup, (PTSTR) (LONG) hBitmap) ; hMenuPopup = CreateMenu () ; for (i = 0 ; i < 3 ; i++) { hBitmap = GetBitmapFont (i) ; AppendMenu (hMenuPopup, MF_BITMAP, IDM_FONT_COUR + i, (PTSTR) (LONG) hBitmap) ; } hBitmap = StretchBitmap (LoadBitmap (hInstance, TEXT ("BitmapFont"))) ; AppendMenu (hMenu, MF_BITMAP | MF_POPUP, (int) hMenuPopup, (PTSTR) (LONG) hBitmap) ; return hMenu ; } /*------------------------------------------------------------------------- StretchBitmap: Scales bitmap to display resolution ---------------------------------------------------------------------------*/ HBITMAP StretchBitmap (HBITMAP hBitmap1) { BITMAP bm1, bm2 ; HBITMAP hBitmap2 ; HDC hdc, hdcMem1, hdcMem2 ; int cxChar, cyChar ; // Get the width and height of a system font character cxChar = LOWORD (GetDialogBaseUnits ()) ; cyChar = HIWORD (GetDialogBaseUnits ()) ; // Create 2 memory DCs compatible with the display hdc = CreateIC (TEXT ("DISPLAY"), NULL, NULL, NULL) ; hdcMem1 = CreateCompatibleDC (hdc) ; hdcMem2 = CreateCompatibleDC (hdc) ; DeleteDC (hdc) ; // Get the dimensions of the bitmap to be stretched GetObject (hBitmap1, sizeof (BITMAP), (PTSTR) &bm1) ; // Scale these dimensions based on the system font size bm2 = bm1 ; bm2.bmWidth = (cxChar * bm2.bmWidth) / 4 ; bm2.bmHeight = (cyChar * bm2.bmHeight) / 8 ; bm2.bmWidthBytes = ((bm2.bmWidth + 15) / 16) * 2 ; // Create a new bitmap of larger size hBitmap2 = CreateBitmapIndirect (&bm2) ; // Select the bitmaps in the memory DCs and do a StretchBlt SelectObject (hdcMem1, hBitmap1) ; SelectObject (hdcMem2, hBitmap2) ; StretchBlt (hdcMem2, 0, 0, bm2.bmWidth, bm2.bmHeight, hdcMem1, 0, 0, bm1.bmWidth, bm1.bmHeight, SRCCOPY) ; // Clean up DeleteDC (hdcMem1) ; DeleteDC (hdcMem2) ; DeleteObject (hBitmap1) ; return hBitmap2 ; } /*--------------------------------------------------------------------------- GetBitmapFont: Creates bitmaps with font names -----------------------------------------------------------------------------*/ HBITMAP GetBitmapFont (int i) { static TCHAR * szFaceName[3]= { TEXT ("Courier New"), TEXT ("Arial"), TEXT ("Times New Roman") } ; HBITMAP hBitmap ; HDC hdc, hdcMem ; HFONT hFont ; SIZE size ; TEXTMETRIC tm ; hdc = CreateIC (TEXT ("DISPLAY"), NULL, NULL, NULL) ; GetTextMetrics (hdc, &tm) ; hdcMem = CreateCompatibleDC (hdc) ; hFont = CreateFont (2 * tm.tmHeight, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, szFaceName[i]) ; hFont = (HFONT) SelectObject (hdcMem, hFont) ; GetTextExtentPoint32 (hdcMem, szFaceName[i], lstrlen (szFaceName[i]), &size); hBitmap = CreateBitmap (size.cx, size.cy, 1, 1, NULL) ; SelectObject (hdcMem, hBitmap) ; TextOut (hdcMem, 0, 0, szFaceName[i], lstrlen (szFaceName[i])) ; DeleteObject (SelectObject (hdcMem, hFont)) ; DeleteDC (hdcMem) ; DeleteDC (hdc) ; return hBitmap ; } /*--------------------------------------------------------------------------- DeleteAllBitmaps: Deletes all the bitmaps in the menu -------------------------------------------------------------------------*/ void DeleteAllBitmaps (HWND hwnd) { HMENU hMenu ; int i ; MENUITEMINFO mii = { sizeof (MENUITEMINFO), MIIM_SUBMENU | MIIM_TYPE } ; // Delete Help bitmap on system menu hMenu = GetSystemMenu (hwnd, FALSE); GetMenuItemInfo (hMenu, IDM_HELP, FALSE, &mii) ; DeleteObject ((HBITMAP) mii.dwTypeData) ; // Delete top-level menu bitmaps hMenu = GetMenu (hwnd) ; for (i = 0 ; i < 3 ; i++) { GetMenuItemInfo (hMenu, i, TRUE, &mii) ; DeleteObject ((HBITMAP) mii.dwTypeData) ; } // Delete bitmap items on Font menu hMenu = mii.hSubMenu ;; for (i = 0 ; i < 3 ; i++) { GetMenuItemInfo (hMenu, i, TRUE, &mii) ; DeleteObject ((HBITMAP) mii.dwTypeData) ; } }

GRAFMENU.RC (摘錄) //Microsoft Developer Studio generated resource script. #include "resource.h" #include "afxres.h" ///////////////////////////////////////////////////////////////////////////// // Menu MENUFILE MENU DISCARDABLE BEGIN MENUITEM "&New", IDM_FILE_NEW MENUITEM "&Open...", IDM_FILE_OPEN MENUITEM "&Save", IDM_FILE_SAVE MENUITEM "Save &As...", IDM_FILE_SAVE_AS END MENUEDIT MENU DISCARDABLE BEGIN MENUITEM "&Undo", IDM_EDIT_UNDO MENUITEM SEPARATOR MENUITEM "Cu&t", IDM_EDIT_CUT MENUITEM "&Copy", IDM_EDIT_COPY MENUITEM "&Paste", IDM_EDIT_PASTE MENUITEM "De&lete", IDM_EDIT_CLEAR END ///////////////////////////////////////////////////////////////////////////// // Bitmap BITMAPFONT BITMAP DISCARDABLE "Fontlabl.bmp" BITMAPHELP BITMAP DISCARDABLE "Bighelp.bmp" BITMAPEDIT BITMAP DISCARDABLE "Editlabl.bmp" BITMAPFILE BITMAP DISCARDABLE "Filelabl.bmp"

RESOURCE.H (摘錄) // Microsoft Developer Studio generated include file. // Used by GrafMenu.rc #define IDM_FONT_COUR 101 #define IDM_FONT_ARIAL 102 #define IDM_FONT_TIMES 103 #define IDM_HELP 104 #define IDM_EDIT_UNDO 40005 #define IDM_EDIT_CUT 40006 #define IDM_EDIT_COPY 40007 #define IDM_EDIT_PASTE 40008 #define IDM_EDIT_CLEAR 40009 #define IDM_FILE_NEW 40010 #define IDM_FILE_OPEN 40011 #define IDM_FILE_SAVE 40012 #define IDM_FILE_SAVE_AS 40013

要將點陣圖插入功能表,可以利用AppendMenu或InsertMenu。點陣圖有兩個來源:可以在Visual C++ Developer Studio建立點陣圖,包括資源腳本中的點陣圖檔案,並在程式使用LoadBitmap時將點陣圖資源載入到記憶體,然後呼叫AppendMenu或InsertMenu將點陣圖附加到功能表上。但是用這種方法會有一些問題:點陣圖不適於所有顯示模式的解析度和縱橫比;有時您需要縮放載入的點陣圖以解決此問題。另一種方法是:在程式內部建立點陣圖,並將它選進記憶體裝置內容,畫出來,然後再附加到功能表中。

GRAFMENU中的GetBitmapFont函式的參數為0、1或2,傳回一個點陣圖代號。此點陣圖包含字串「Courier New」、「Arial」或「Times New Roman」,而且字體是各自對應的字體,大小是正常系統字體的兩倍。讓我們看看GetBitmapFont是怎麼做的。(下面的程式碼與GRAFMENU.C檔案中的有些不同。為了清楚起見,我用「Arial」字體相應的值代替了引用szFaceName陣列。)

第一步是用TEXTMETRIC結構來確定目前系統字體的大小,並建立一個與目前螢幕相容的記憶體裝置內容:

hdc = CreateIC (TEXT ("DISPLAY"), NULL, NULL, NULL) ; GetTextMetrics (hdc, &tm) ; hdcMem = CreateCompatibleDC (hdc) ;

CreateFont函式建立了一種邏輯字體,該字體高是系統字體的兩倍,而且邏輯名稱為「Arial」:

從記憶體裝置內容中選擇該字體,然後儲存內定字體代號:

現在,當我們向記憶體裝置內容寫一些文字時,Windows就會使用選進裝置內容的TrueType Arial字體了。

但這個記憶體裝置內容最初只有一個單圖素單色設備平面。我們必須建立一個足夠大的點陣圖以容納我們所要顯示的文字。通過GetTextExtentPoint32函式,可以取得文字的大小,而用CreateBitmap可以根據這些尺寸來建立點陣圖:

hFont = CreateFont (2 * tm.tmHeight, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, TEXT ("Arial")) ;

hFont = (HFONT) SelectObject (hdcMem, hFont) ;

GetTextExtentPoint32 (hdcMem, TEXT ("Arial"), 5, &size) ; hBitmap = CreateBitmap (size.cx, size.cy, 1, 1, NULL) ; SelectObject (hdcMem, hBitmap) ;

現在這個裝置內容是一個單色的顯示平面,大小也是嚴格的文字尺寸。我們現在要做的就是書寫文字:

除了清除,所有的工作都完成了。要清除,我們可以用SelectObject將系統字體(帶有代號hFont)重新選進裝置內容,然後刪除SelectObject傳回的前一個字體代號,也就是Arial字體代號:

現在可以刪除兩個裝置內容:

這樣,我們就獲得了一個點陣圖,該點陣圖上有Arial字體的字串「Arial」。

當我們需要縮放字體以適應不同顯示解析度或縱橫比時,記憶體裝置內容也能解決問題。在GRAFMENU程式中,我建立了四個點陣圖,這些點陣圖只適用於系統字體高8圖素、寬4圖素的顯示。對於其他尺寸的系統字體,只能縮放點陣圖。GRAFMENU中的StretchBitmap函式完成此功能。

第一步是獲得顯示的裝置內容,然後取得系統字體的文字規格,接下來建立兩個記憶體裝置內容:

TextOut (hdcMem, 0, 0, TEXT ("Arial"), 5) ;

DeleteObject (SelectObject (hdcMem, hFont)) ;

DeleteDC (hdcMem) ; DeleteDC (hdc) ;

hdc = CreateIC (TEXT ("DISPLAY"), NULL, NULL, NULL) ; GetTextMetrics (hdc, &tm) ; hdcMem1 = CreateCompatibleDC (hdc) ; hdcMem2 = CreateCompatibleDC (hdc) ; DeleteDC (hdc) ;

傳遞給函式的點陣圖代號是hBitmap1。程式能用GetObject獲得點陣圖的大小:

此操作將尺寸複製到BITMAP型態的結構bm1中。結構bm2等於結構bm1,然後根據系統字體大小來修改某些欄位:

GetObject (hBitmap1, sizeof (BITMAP), (PSTR) &bm1) ;

bm2 = bm1 ; bm2.bmWidth = (tm.tmAveCharWidth * bm2.bmWidth) / 4 ; bm2.bmHeight = (tm.tmHeight * bm2.bmHeight) / 8 ; bm2.bmWidthBytes = ((bm2.bmWidth + 15) / 16) * 2 ;

下一個點陣圖帶有代號hBitmap2,可以根據動態的尺寸建立:

然後將這兩個點陣圖選進兩個記憶體裝置內容中:

我們想把第一個點陣圖複製給第二個點陣圖,並在此程序中進行拉伸。這包括StretchBlt呼叫:

現在第二幅圖適當地縮放了,我們可將其用到功能表中。剩下的清除工作很簡單:

hBitmap2 = CreateBitmapIndirect (&bm2) ;

SelectObject (hdcMem1, hBitmap1) ; SelectObject (hdcMem2, hBitmap2) ;

StretchBlt ( hdcMem2, 0, 0, bm2.bmWidth, bm2.bmHeight, hdcMem1, 0, 0, bm1.bmWidth, bm1.bmHeight, SRCCOPY) ;

DeleteDC (hdcMem1) ; DeleteDC (hdcMem2) ; DeleteObject (hBitmap1) ;

在建造功能表時,GRAFMENU中的CreateMyMenu函式呼叫了StretchBitmap和GetBitmapFont函式。GRAFMENU在資源檔案中定義了兩個功能表,在選擇「File」和「Edit」選項時會彈出這兩個功能表。函式開始先取得一個空功能表的代號:

從資源檔案載入「File」的突現式功能表(包括四個選項:「New」、「Open」、「Save」和「Save as」):

從資源檔案還載入了包含「FILE」的點陣圖,並用StretchBitmap進行了拉伸:

點陣圖代號和突現式功能表代號都是AppendMenu呼叫的參數:

「Edit」功能表類似程序如下:

hMenu = CreateMenu () ;

hMenuPopup = LoadMenu (hInstance, TEXT ("MenuFile")) ;

hBitmapFile = StretchBitmap (LoadBitmap (hInstance, TEXT ("BitmapFile"))) ;

AppendMenu (hMenu, MF_BITMAP | MF_POPUP, hMenuPopup, (PTSTR) (LONG) hBitmapFile) ;

hMenuPopup = LoadMenu (hInstance, TEXT ("MenuEdit")) ; hBitmapEdit = StretchBitmap (LoadBitmap (hInstance, TEXT ("BitmapEdit"))) ; AppendMenu (hMenu, MF_BITMAP | MF_POPUP, hMenuPopup, (PTSTR)(LONG) hBitmapEdit) ;

呼叫GetBitmapFont函式可以構造這三種不同字體的突現式功能表:

hMenuPopup = CreateMenu () ; for (i = 0 ; i < 3 ; i++) { hBitmapPopFont [i] = GetBitmapFont (i) ; AppendMenu (hMenuPopup, MF_BITMAP, IDM_FONT_COUR + i, (PTSTR) (LONG) hMenuPopupFont [i]) ; }

然後將突現式功能表添加到功能表中:

WndProc通過呼叫SetMenu,完成了視窗功能表的建立工作。

GRAFMENU還改變了AddHelpToSys函式中的系統功能表。此函式首先獲得一個系統功能表代號:

這將載入「HELP」點陣圖,並將其拉伸到適當尺寸:

這將給系統功能表添加一條分隔線和拉伸的點陣圖:

GRAFMENU在退出之前呼叫一個函式來清除並刪除所有點陣圖。

下面是在功能表中使用點陣圖的一些注意事項。

在頂層功能表中,Windows調整功能表列的高度以適應最高的點陣圖。其他點陣圖(或字串)是根據功能表列的頂端對齊的。如果在頂層功能表中使用了點陣圖,那麼從使用常數SM_CYMENU的GetSystemMetrics得到的功能表列大小將不再有效。

執行GRAFMENU期間可以看到:在突現式功能表中,您可使用帶有點陣圖功能表項的勾選標記,但勾選標記是正常尺寸。如果不滿意,您可以建立一個自訂的勾選標記,並使用SetMenuItemBitmaps。

在功能表中使用非文字(或者使用非系統字體的文字)的另一種方法是「擁有者繪製」功能表。

功能表的鍵盤介面是另一個問題。當功能表含有文字時,Windows會自動添加鍵盤介面。要選擇一個功能表項,可以使用Alt與字串中的一個字母的組合鍵。而一旦在功能表中放置了點陣圖,就刪除了鍵盤介面。即使點陣圖表達了一定的含義,但Windows並不知道。

目前我們可以使用WM_MENUCHAR訊息。當您按下Alt和與功能表項不相符的一個字元鍵的組合鍵時,Windows將向您的視窗訊息處理程式發送一個WM_MENUCHAR訊息。GRAFMENU需要截取WM_MENUCHAR訊息並檢查wParam的值(即按鍵的ASCII碼)。如果這個值對應一個功能表項,那麼向Windows傳回雙字組:其中高字組為2,低字組是與該鍵相關的功能表項索引值。然後由Windows處理餘下的事。

非矩形點陣圖圖像

點陣圖都是矩形,但不需要都顯示成矩形。例如,假定您有一個矩形點陣圖圖像,但您卻想將它顯示成橢圓形。

首先,這聽起來很簡單。您只需將圖像載入Visual C++ Developer Studio或者Windows的「畫圖」程式(或者更昂貴的應用程式),然後用白色的畫筆將圖像四周畫上白色。這時將獲得一幅橢圓形的圖像,而橢圓的外面就成了白色。只有當背景色為白色時此點陣圖才能正確顯示,如果在其他背景色上顯示,您就會發現橢圓形的圖像和背景之間有一個白色的矩形。這種效果不好。

有一種非常通用的技術可解決此類問題。這種技術包括「遮罩(mask)」點陣圖和一些位元映射操作。遮罩是一種單色點陣圖,它與您要顯示的矩形點陣圖圖像尺寸相同。每個遮罩的圖素都對應點陣圖圖像的一個圖素。遮罩圖素是1(白色),對應著點陣圖圖素顯示;是0(黑色),則顯示背景色。(或者遮罩點陣圖與此相反,這根據您使用的位元映射操作而有一些相對應的變化。)

讓我們看看BITMASK程式是如何實作這一技術的。如程式14-9所示。

hBitmapFont = StretchBitmap (LoadBitmap (hInstance, "BitmapFont")) ; AppendMenu ( hMenu, MF_BITMAP | MF_POPUP, hMenuPopup, (PTSTR) (LONG) hBitmapFont) ;

hMenu = GetSystemMenu (hwnd, FALSE) ;

hBitmapHelp = StretchBitmap (LoadBitmap (hInstance, TEXT ("BitmapHelp"))) ;

AppendMenu (hMenu, MF_SEPARATOR, 0, NULL) ; AppendMenu (hMenu, MF_BITMAP, IDM_HELP, (PTSTR)(LONG) hBitmapHelp) ;

程式14-9 BITMASK BITMASK.C /*--------------------------------------------------------------------------- BITMASK.C -- Bitmap Masking Demonstration (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 ("BitMask") ; 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 (LTGRAY_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 ("Bitmap Masking Demo"), 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 hBitmapImag, hBitmapMask ; static HINSTANCE hInstance ; static int cxClient, cyClient, cxBitmap, cyBitmap ; BITMAP bitmap ; HDC hdc, hdcMemImag, hdcMemMask ; int x, y ; PAINTSTRUCT ps ; switch (message) { case WM_CREATE: hInstance = ((LPCREATESTRUCT) lParam)->hInstance ; // Load the original image and get its size hBitmapImag = LoadBitmap (hInstance, TEXT ("Matthew")) ; GetObject (hBitmapImag, sizeof (BITMAP), &bitmap) ; cxBitmap = bitmap.bmWidth ; cyBitmap = bitmap.bmHeight ; // Select the original image into a memory DC hdcMemImag = CreateCompatibleDC (NULL) ; SelectObject (hdcMemImag, hBitmapImag) ; // Create the monochrome mask bitmap and memory DC hBitmapMask = CreateBitmap (cxBitmap, cyBitmap, 1, 1, NULL) ; hdcMemMask = CreateCompatibleDC (NULL) ; SelectObject (hdcMemMask, hBitmapMask) ; // Color the mask bitmap black with a white ellipse SelectObject (hdcMemMask, GetStockObject (BLACK_BRUSH)) ; Rectangle (hdcMemMask, 0, 0, cxBitmap, cyBitmap) ; SelectObject (hdcMemMask, GetStockObject (WHITE_BRUSH)) ; Ellipse (hdcMemMask, 0, 0, cxBitmap, cyBitmap) ; // Mask the original image BitBlt (hdcMemImag, 0, 0, cxBitmap, cyBitmap, hdcMemMask, 0, 0, SRCAND) ; DeleteDC (hdcMemImag) ; DeleteDC (hdcMemMask) ; return 0 ; case WM_SIZE: cxClient = LOWORD (lParam) ; cyClient = HIWORD (lParam) ; return 0 ; case WM_PAINT: hdc = BeginPaint (hwnd, &ps) ; // Select bitmaps into memory DCs hdcMemImag = CreateCompatibleDC (hdc) ; SelectObject (hdcMemImag, hBitmapImag) ; hdcMemMask = CreateCompatibleDC (hdc) ; SelectObject (hdcMemMask, hBitmapMask) ; // Center image x = (cxClient - cxBitmap) / 2 ; y = (cyClient - cyBitmap) / 2 ; // Do the bitblts BitBlt (hdc, x, y, cxBitmap, cyBitmap, hdcMemMask, 0, 0, 0x220326) ; BitBlt (hdc, x, y, cxBitmap, cyBitmap, hdcMemImag, 0, 0, SRCPAINT) ; DeleteDC (hdcMemImag) ; DeleteDC (hdcMemMask) ; EndPaint (hwnd, &ps) ; return 0 ; case WM_DESTROY: DeleteObject (hBitmapImag) ; DeleteObject (hBitmapMask) ; PostQuitMessage (0) ; return 0 ; } return DefWindowProc (hwnd, message, wParam, lParam) ; }

BITMASK.RC // Microsoft Developer Studio generated resource script. #include "resource.h" #include "afxres.h" ///////////////////////////////////////////////////////////////////////////// // Bitmap MATTHEW BITMAP DISCARDABLE "matthew.bmp"

資源檔案中的MATTHEW.BMP檔案是我侄子的一幅黑白數位照片,寬200圖素,高320圖素,每圖素8位元。不過,另外製作個BITMASK只是因為此檔案的內容是任何東西都可以。

注意,BITMASK將視窗背景設為亮灰色。這樣就確保我們能正確地遮罩點陣圖,而不只是將其塗成白色。

下面讓我們看一下WM_CREATE的處理程序:BITMASK用LoadBitmap函式獲得hBitmapImag變數中原始圖像的代號。用GetObject函式可取得點陣圖的寬度高度。然後將點陣圖代號選進代號為hdcMemImag的記憶體裝置內容中。

程式建立的下一個單色點陣圖與原來的圖大小相同,其代號儲存在hBitmapMask,並選進代號為hdcMemMask的記憶體裝置內容中。在記憶體裝置內容中,使用GDI函式,遮罩點陣圖就塗成了黑色背景和一個白色的橢圓:

SelectObject (hdcMemMask, GetStockObject (BLACK_BRUSH)) ; Rectangle (hdcMemMask, 0, 0, cxBitmap, cyBitmap) ; SelectObject (hdcMemMask, GetStockObject (WHITE_BRUSH)) ; Ellipse (hdcMemMask, 0, 0, cxBitmap, cyBitmap) ;

因為這是一個單色的點陣圖,所以黑色區域的位元是0,而白色區域的位元是1。

然後BitBlt呼叫就按此遮罩修改了原圖像:

SRCAND位元映射操作在來源位元(遮罩點陣圖)和目的位元(原圖像)之間執行了位元AND操作。只要遮罩點陣圖是白色,就顯示目的;只要遮罩是黑色,則目的就也是黑色。現在原圖像中就形成了一個黑色包圍的橢圓區域。

現在讓我們看一下WM_PAINT處理程序。此程序同時改變了選進記憶體裝置內容中的圖像點陣圖和遮罩點陣圖。兩次BitBlt呼叫完成了這個魔術,第一次在視窗上執行遮罩點陣圖的BitBlt:

這裏使用了一個沒有名稱的位元映射操作。邏輯運算子是D & ~S。回憶來源-即遮罩點陣圖-是黑色(位元值0)包圍的一個白色(位元值1)橢圓。位元映射操作首先將來源反色,也就是改成白色包圍的黑色橢圓。然後位元操作在這個已轉換的來源和目的(即視窗上)之間執行位元AND操作。當目的和位元值1「AND」時保持不變;與位元值0「AND」時,目的將變黑。因此,BitBlt操作將在視窗上畫一個黑色的橢圓。

第二次的BitBlt呼叫則在視窗中繪製圖像點陣圖:

位元映射操作在來源和目的之間執行位元「OR」操作。由於來源點陣圖的外面是黑色,因此保持目的不變;而在橢圓區域內,目的是黑色,因此圖像就原封不動地複製了過來。執行結果如圖14-9所示。

注意事項:

有時您需要一個很複雜的遮罩-例如,抹去原始圖像的整個背景。您將需要在畫圖程式中手工建立然後將其儲存到成檔案。

如果正在為Windows NT編寫類似的應用程式,那麼您可以使用與MASKBIT程式類似的MaskBlt函式,而只需要更少的函式呼叫。Windows NT還包括另一個類似BitBlt的函式,Windows 98不支援該函式。此函式是PlgBlt(「平行四邊形位元塊移動:parallelogram blt」)。這個函式可以對圖像進行旋轉或者傾斜點陣圖圖像。

最後,如果在您的機器上執行BITMASK程式,您就只會看見黑色、白色和兩個灰色的陰影,這是因為您執行的顯示模式是16色或256色。對於16色模式,顯示效果無法改進,但在256色模式下可以改變調色盤以顯示灰階。您將在 第十六章 學會如何設定調色盤。

簡單的動畫

小張的點陣圖顯示起來非常快,因此可以將點陣圖和Windows計時器聯合使用,來完成一些基本的動畫。

現在開始這個彈球程式。

BOUNCE程式,如程式14-10所示,產生了一個在視窗顯示區域彈來彈去的小球。該程式利用計時器來控制小球的行進速度。小球本身是一幅點陣圖,程式首先通過建立點陣圖來建立小球,將其選進記憶體裝置內容,然後呼叫一些簡單的GDI函式。程式用BitBlt從一個記憶體裝置內容將這個點陣圖小球畫到顯示器上。

BitBlt (hdcMemImag, 0, 0, cxBitmap, cyBitmap, hdcMemMask, 0, 0, SRCAND) ;

BitBlt (hdc, x, y, cxBitmap, cyBitmap, hdcMemMask, 0, 0, 0x220326) ;

BitBlt (hdc, x, y, cxBitmap, cyBitmap, hdcMemImag, 0, 0, SRCPAINT) ;

圖14-9 BITMASK的螢幕顯示

程式14-10 BOUNCE BOUNCE.C /*--------------------------------------------------------------------------- BOUNCE.C -- Bouncing Ball Program (c) Charles Petzold, 1998 ----------------------------------------------------------------------------*/ #include <windows.h> #define ID_TIMER 1 LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ; int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { static TCHAR szAppName[] = TEXT ("Bounce") ; 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 ("Bouncing Ball"), 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 iMsg, WPARAM wParam, LPARAM lParam) { static HBITMAP hBitmap ; static int cxClient, cyClient, xCenter, yCenter, cxTotal, cyTotal, cxRadius, cyRadius, cxMove, cyMove, xPixel, yPixel ; HBRUSH hBrush ; HDC hdc, hdcMem ; int iScale ; switch (iMsg) { case WM_CREATE: hdc = GetDC (hwnd) ; xPixel = GetDeviceCaps (hdc, ASPECTX) ; yPixel = GetDeviceCaps (hdc, ASPECTY) ; ReleaseDC (hwnd, hdc) ; SetTimer (hwnd, ID_TIMER, 50, NULL) ; return 0 ; case WM_SIZE: xCenter = (cxClient = LOWORD (lParam)) / 2 ; yCenter = (cyClient = HIWORD (lParam)) / 2 ; iScale = min (cxClient * xPixel, cyClient * yPixel) / 16 ; cxRadius = iScale / xPixel ; cyRadius = iScale / yPixel ; cxMove = max (1, cxRadius / 2) ; cyMove = max (1, cyRadius / 2) ; cxTotal = 2 * (cxRadius + cxMove) ; cyTotal = 2 * (cyRadius + cyMove) ; if (hBitmap) DeleteObject (hBitmap) ; hdc = GetDC (hwnd) ; hdcMem = CreateCompatibleDC (hdc) ; hBitmap = CreateCompatibleBitmap (hdc, cxTotal, cyTotal) ; ReleaseDC (hwnd, hdc) ; SelectObject (hdcMem, hBitmap) ; Rectangle (hdcMem, -1, -1, cxTotal + 1, cyTotal + 1) ; hBrush = CreateHatchBrush (HS_DIAGCROSS, 0L) ; SelectObject (hdcMem, hBrush) ; SetBkColor (hdcMem, RGB (255, 0, 255)) ; Ellipse (hdcMem, cxMove, cyMove, cxTotal - cxMove, cyTotal - cyMove) ; DeleteDC (hdcMem) ; DeleteObject (hBrush) ; return 0 ; case WM_TIMER: if (!hBitmap) break ; hdc = GetDC (hwnd) ; hdcMem = CreateCompatibleDC (hdc) ; SelectObject (hdcMem, hBitmap) ; BitBlt (hdc, xCenter - cxTotal / 2, yCenter - cyTotal / 2, cxTotal, cyTotal, hdcMem, 0, 0, SRCCOPY) ; ReleaseDC (hwnd, hdc) ; DeleteDC (hdcMem) ; xCenter += cxMove ; yCenter += cyMove ; if ((xCenter + cxRadius >= cxClient) || (xCenter - cxRadius <= 0)) cxMove = -cxMove ; if ((yCenter + cyRadius >= cyClient) || (yCenter - cyRadius <= 0)) cyMove = -cyMove ; return 0 ; case WM_DESTROY: if (hBitmap) DeleteObject (hBitmap) ; KillTimer (hwnd, ID_TIMER) ; PostQuitMessage (0) ; return 0 ; } return DefWindowProc (hwnd, iMsg, wParam, lParam) ; }

BOUNCE每次收到一個WM_SIZE訊息時都重畫小球。這就需要與視訊顯示器相容的記憶體裝置內容:

小球的直徑設為視窗顯示區域高度或寬度中較短者的十六分之一。不過,程式構造的點陣圖卻比小球大:從點陣圖中心到點陣圖四個邊的距離是小球半徑的1.5倍:

將點陣圖選進記憶體裝置內容後,整個點陣圖背景設成白色:

那些不固定的座標使矩形邊框在點陣圖之外著色。一個對角線開口的畫刷選進記憶體裝置內容,並將小球畫在點陣圖的中央:

當小球移動時,小球邊界的空白會有效地刪除前一時刻的小球圖像。在另一個位置重畫小球只需在BitBlt呼叫中使用SRCCOPY的ROP代碼:

BOUNCE程式只是展示了在顯示器上移動圖像的最簡單的方法。在一般情況下,這種方法並不能令人滿意。如果您對動畫感興趣,那麼除了在來源和目的之間執行或操作以外,您還應該研究其他的ROP代碼(例如SRCINVERT)。其他動畫技術包括Windows調色盤(以及AnimatePalette函式)和CreateDIBSection函式。對於更高級的動畫您只好放棄GDI而使用DirectX介面了。

視窗外的點陣圖

SCRAMBLE程式,如程式14-11所示,編寫非常粗糙,我本來不應該展示這個程式,但它示範了一些有趣的技術,而且在交換兩個顯示矩形內容的BitBlt操作的程序中,用記憶體裝置內容作為臨時儲存空間。

hdcMem = CreateCompatibleDC (hdc) ;

hBitmap = CreateCompatibleBitmap (hdc, cxTotal, cyTotal) ;

Rectangle (hdcMem, -1, -1, xTotal + 1, yTotal + 1) ;

Ellipse (hdcMem, xMove, yMove, xTotal - xMove, yTotal - yMove) ;

BitBlt (hdc, xCenter - cxTotal / 2, yCenter - cyTotal / 2, cxTotal, cyTotal, hdcMem, 0, 0, SRCCOPY) ;

程式14-11 SCRAMBLE SCRAMBLE.C /*--------------------------------------------------------------------------- SCRAMBLE.C -- Scramble (and Unscramble) Screen (c) Charles Petzold, 1998 -----------------------------------------------------------------------------*/ #include <windows.h> #define NUM 300 LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ; int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { static int iKeep [NUM][4] ; HDC hdcScr, hdcMem ; int cx, cy ; HBITMAP hBitmap ; HWND hwnd ; int i, j, x1, y1, x2, y2 ; if (LockWindowUpdate (hwnd = GetDesktopWindow ())) { hdcScr = GetDCEx (hwnd, NULL, DCX_CACHE | DCX_LOCKWINDOWUPDATE) ; hdcMem = CreateCompatibleDC (hdcScr) ; cx = GetSystemMetrics (SM_CXSCREEN) / 10 ; cy = GetSystemMetrics (SM_CYSCREEN) / 10 ; hBitmap = CreateCompatibleBitmap (hdcScr, cx, cy) ; SelectObject (hdcMem, hBitmap) ; srand ((int) GetCurrentTime ()) ; for (i = 0 ; i < 2 ; i++) for (j = 0 ; j < NUM ; j++) { if (i == 0) { iKeep [j] [0] = x1 = cx * (rand () % 10) ; iKeep [j] [1] = y1 = cy * (rand () % 10) ; iKeep [j] [2] = x2 = cx * (rand () % 10) ; iKeep [j] [3] = y2 = cy * (rand () % 10) ; } else { x1 = iKeep [NUM - 1 - j] [0] ; y1 = iKeep [NUM - 1 - j] [1] ; x2 = iKeep [NUM - 1 - j] [2] ; y2 = iKeep [NUM - 1 - j] [3] ; } BitBlt (hdcMem, 0, 0, cx, cy, hdcScr, x1, y1, SRCCOPY) ; BitBlt (hdcScr, x1, y1, cx, cy, hdcScr, x2, y2, SRCCOPY) ; BitBlt (hdcScr, x2, y2, cx, cy, hdcMem, 0, 0, SRCCOPY) ; Sleep (10) ; } DeleteDC (hdcMem) ; ReleaseDC (hwnd, hdcScr) ; DeleteObject (hBitmap) ; LockWindowUpdate (NULL) ; } return FALSE ; }

SCRAMBLE沒有視窗訊息處理程式。在WinMain中,它首先呼叫帶有桌面視窗代號的LockWindowUpdate。此函式暫時防止其他程式更新螢幕。然後SCRAMBLE通過呼叫帶有參數DCX_LOCKWINDOWUPDATE的GetDCEx來獲得整個螢幕的裝置內容。這樣就只有SCRAMBLE可以更新螢幕了。

然後SCRAMBLE確定全螢幕的尺寸,並將長寬分別除以10。程式用這個尺寸(名稱是cx和cy)來建立一個點陣圖,並將該點陣圖選進記憶體裝置內容。

使用C語言的rand函式,SCRAMBLE計算出四個隨機值(兩個座標點)作為cx和cy的倍數。程式透過三次呼叫BitBlt函式來交換兩個矩形塊中顯示的內容。第一次將從第一個座標點開始的矩形複製到記憶體裝置內容。第二次BitBlt將從第二座標點開始的矩形複製到第一點開始的位置。第三次將記憶體裝置內容中的矩形複製到第二個座標點開始的區域。

此程序將有效地交換顯示器上兩個矩形中的內容。SCRAMBLE執行300次交換,這時的螢幕顯示肯定是一團糟。但不用擔心,因為SCRAMBLE記得是怎麼把顯示弄得這樣一團糟的,接著在退出前它會按相反的次序恢復原來的桌面顯示(鎖定螢幕前的畫面)!

您也可以用記憶體裝置內容將一個點陣圖複製給另一個點陣圖。例如,假定您要建立一個點陣圖,該點陣圖只包含另一個點陣圖左上角的圖形。如果原來的圖像代號為hBitmap,那麼您可以將其尺寸複製到一個BITMAP型態的結構中:

然後建立一個未初始化的新點陣圖,該點陣圖的尺寸是原來圖的1/4:

現在建立兩個記憶體裝置內容,並將原來點陣圖和新點陣圖選分別進這兩個記憶體裝置內容:

GetObject (hBitmap, sizeof (BITMAP), &bm) ;

hBitmap2 = CreateBitmap ( bm.bmWidth / 2, bm.bmHeight / 2, bm.bmPlanes, bm.bmBitsPixel, NULL) ;

hdcMem1 = CreateCompatibleDC (hdc) ; hdcMem2 = CreateCompatibleDC (hdc) ; SelectObject (hdcMem1, hBitmap) ; SelectObject (hdcMem2, hBitmap2) ;

最後,將第一個點陣圖的左上角複製給第二個:

剩下的只是清除工作:

BitBlt ( hdcMem2, 0, 0, bm.bmWidth / 2, bm.bmHeight / 2, hdcMem1, 0, 0, SRCCOPY) ;

DeleteDC (hdcMem1) ; DeleteDC (hdcMem2) ; DeleteObject (hBitmap) ;

BLOWUP.C程式,如圖14-21所示,也用視窗更新鎖定來在程式視窗之外顯示一個捕捉的矩形。此程式允許使用者用滑鼠圈選螢幕上的矩形區域,然後BLOWUP將該區域的內容複製到點陣圖。在WM_PAINT訊息處理期間,點陣圖複製到程式的顯示區域,必要時將拉伸或壓縮。(參見程式14-12。)

程式14-12 BLOWUP BLOWUP.C /*-------------------------------------------------------------------------- BLOWUP.C -- Video Magnifier Program (c) Charles Petzold, 1998 ----------------------------------------------------------------------------*/ #include <windows.h> #include <stdlib.h> // for abs definition #include "resource.h" LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ; int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { static TCHAR szAppName [] = TEXT ("Blowup") ; 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, TEXT ("Blow-Up Mouse Demo"), WS_OVERLAPPEDWINDOW, 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 ; } void InvertBlock (HWND hwndScr, HWND hwnd, POINT ptBeg, POINT ptEnd) { HDC hdc ; hdc = GetDCEx (hwndScr, NULL, DCX_CACHE | DCX_LOCKWINDOWUPDATE) ; ClientToScreen (hwnd, &ptBeg) ; ClientToScreen (hwnd, &ptEnd) ; PatBlt (hdc, ptBeg.x, ptBeg.y, ptEnd.x - ptBeg.x, ptEnd.y - ptBeg.y, DSTINVERT) ; ReleaseDC (hwndScr, hdc) ; } HBITMAP CopyBitmap (HBITMAP hBitmapSrc) { BITMAP bitmap ; HBITMAP hBitmapDst ; HDC hdcSrc, hdcDst ; GetObject (hBitmapSrc, sizeof (BITMAP), &bitmap) ; hBitmapDst = CreateBitmapIndirect (&bitmap) ; hdcSrc = CreateCompatibleDC (NULL) ; hdcDst = CreateCompatibleDC (NULL) ; SelectObject (hdcSrc, hBitmapSrc) ; SelectObject (hdcDst, hBitmapDst) ; BitBlt (hdcDst, 0, 0, bitmap.bmWidth, bitmap.bmHeight, hdcSrc, 0, 0, SRCCOPY) ; DeleteDC (hdcSrc) ; DeleteDC (hdcDst) ; return hBitmapDst ; } LRESULT CALLBACK WndProc ( HWND hwnd, UINT message, WPARAM wParam,LPARAM lParam) { static BOOL bCapturing, bBlocking ; static HBITMAP hBitmap ; static HWND hwndScr ; static POINT ptBeg, ptEnd ; BITMAP bm ; HBITMAP hBitmapClip ; HDC hdc, hdcMem ; int iEnable ; PAINTSTRUCT ps ; RECT rect ; switch (message) { case WM_LBUTTONDOWN: if (!bCapturing) { if (LockWindowUpdate (hwndScr = GetDesktopWindow ())) { bCapturing = TRUE ; SetCapture (hwnd) ; SetCursor (LoadCursor (NULL, IDC_CROSS)) ; } else MessageBeep (0) ; } return 0 ; case WM_RBUTTONDOWN: if (bCapturing) { bBlocking = TRUE ; ptBeg.x = LOWORD (lParam) ; ptBeg.y = HIWORD (lParam) ; ptEnd = ptBeg ; InvertBlock (hwndScr, hwnd, ptBeg, ptEnd) ; } return 0 ; case WM_MOUSEMOVE: if (bBlocking) { InvertBlock (hwndScr, hwnd, ptBeg, ptEnd) ; ptEnd.x = LOWORD (lParam) ; ptEnd.y = HIWORD (lParam) ; InvertBlock (hwndScr, hwnd, ptBeg, ptEnd) ; } return 0 ; case WM_LBUTTONUP: case WM_RBUTTONUP: if (bBlocking) { InvertBlock (hwndScr, hwnd, ptBeg, ptEnd) ; ptEnd.x = LOWORD (lParam) ; ptEnd.y = HIWORD (lParam) ; if (hBitmap) { DeleteObject (hBitmap) ; hBitmap = NULL ; } hdc = GetDC (hwnd) ; hdcMem = CreateCompatibleDC (hdc) ; hBitmap = CreateCompatibleBitmap (hdc, abs (ptEnd.x - ptBeg.x), abs (ptEnd.y - ptBeg.y)) ; SelectObject (hdcMem, hBitmap) ; StretchBlt (hdcMem, 0, 0, abs (ptEnd.x - ptBeg.x), abs (ptEnd.y - ptBeg.y), hdc, ptBeg.x, ptBeg.y, ptEnd.x - ptBeg.x, ptEnd.y - ptBeg.y, SRCCOPY) ; DeleteDC (hdcMem) ; ReleaseDC (hwnd, hdc) ; InvalidateRect (hwnd, NULL, TRUE) ; } if (bBlocking || bCapturing) { bBlocking = bCapturing = FALSE ; SetCursor (LoadCursor (NULL, IDC_ARROW)) ; ReleaseCapture () ; LockWindowUpdate (NULL) ; } return 0 ; case WM_INITMENUPOPUP: iEnable = IsClipboardFormatAvailable (CF_BITMAP) ? MF_ENABLED : MF_GRAYED ; EnableMenuItem ((HMENU) wParam, IDM_EDIT_PASTE, iEnable) ; iEnable = hBitmap ? MF_ENABLED : MF_GRAYED ; EnableMenuItem ((HMENU) wParam, IDM_EDIT_CUT, iEnable) ; EnableMenuItem ((HMENU) wParam, IDM_EDIT_COPY, iEnable) ; EnableMenuItem ((HMENU) wParam, IDM_EDIT_DELETE, iEnable) ; return 0 ; case WM_COMMAND: switch (LOWORD (wParam)) { case IDM_EDIT_CUT: case IDM_EDIT_COPY: if (hBitmap) { hBitmapClip = CopyBitmap (hBitmap) ; OpenClipboard (hwnd) ; EmptyClipboard () ; SetClipboardData (CF_BITMAP, hBitmapClip) ; } if (LOWORD (wParam) == IDM_EDIT_COPY) return 0 ; //fall through for IDM_EDIT_CUT case IDM_EDIT_DELETE: if (hBitmap) { DeleteObject (hBitmap) ; hBitmap = NULL ; } InvalidateRect (hwnd, NULL, TRUE) ; return 0 ; case IDM_EDIT_PASTE: if (hBitmap) { DeleteObject (hBitmap) ; hBitmap = NULL ; } OpenClipboard (hwnd) ; hBitmapClip = GetClipboardData (CF_BITMAP) ; if (hBitmapClip) hBitmap = CopyBitmap (hBitmapClip) ; CloseClipboard () ; InvalidateRect (hwnd, NULL, TRUE) ; return 0 ; } break ; case WM_PAINT: hdc = BeginPaint (hwnd, &ps) ; if (hBitmap) { GetClientRect (hwnd, &rect) ; hdcMem = CreateCompatibleDC (hdc) ; SelectObject (hdcMem, hBitmap) ; GetObject (hBitmap, sizeof (BITMAP), (PSTR) &bm) ; SetStretchBltMode (hdc, COLORONCOLOR) ; StretchBlt (hdc, 0, 0, rect.right, rect.bottom, hdcMem, 0, 0, bm.bmWidth, bm.bmHeight, SRCCOPY) ; DeleteDC (hdcMem) ; } EndPaint (hwnd, &ps) ; return 0 ; case WM_DESTROY: if (hBitmap) DeleteObject (hBitmap) ; PostQuitMessage (0) ; return 0 ; } return DefWindowProc (hwnd, message, wParam, lParam) ; }

BLOWUP.RC (摘錄) //Microsoft Developer Studio generated resource script. #include "resource.h" #include "afxres.h" ///////////////////////////////////////////////////////////////////////////// // Menu BLOWUP MENU DISCARDABLE BEGIN 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 "De&lete\tDelete", IDM_EDIT_DELETE END END ///////////////////////////////////////////////////////////////////////////// // Accelerator BLOWUP ACCELERATORS DISCARDABLE BEGIN "C", IDM_EDIT_COPY, 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 Blowup.rc #define IDM_EDIT_CUT 40001 #define IDM_EDIT_COPY 40002 #define IDM_EDIT_PASTE 40003 #define IDM_EDIT_DELETE 40004

圖14-10 BLOWUP顯示的一個範例

由於滑鼠攔截的限制,所以開始使用BLOWUP時會有些困難,需要逐漸適應。下面是使用本程式的方法:

  1. 在BLOWUP顯示區域按下滑鼠左鍵不放,滑鼠指標會變成「+」字型。
  2. 繼續按住左鍵,將滑鼠移到螢幕上的任何其他位置。滑鼠游標的位置就是您要圈選的矩形區域的左上角。
  3. 繼續按住左鍵,按下滑鼠右鍵,然後拖動滑鼠到您要圈選的矩形區域的右下角。釋放滑鼠左鍵和右鍵。(釋放滑鼠左、右鍵次序無關緊要。)

滑鼠游標恢復成箭頭狀,這時您圈選的矩形區域已複製到了BLOWUP的顯示區域,並作了適當的壓縮或拉伸變化。

如果您從右上角到左下角選取的話,BLOWUP將顯示矩形區域的鏡像。如果從左下到右上角選取,BLOWUP將顯示顛倒的圖像。如果從右上角至左上角選取,程式將綜合兩種效果。

BLOWUP還包含將點陣圖複製到剪貼簿,以及將剪貼簿中的點陣圖複製到程式的處理功能。BLOWUP處理WM_INITMENUPOPUP訊息來啟用或禁用「Edit」功能表中的不同選項,並通過WM_COMMAND訊息來處理這些功能表項。您應該對這些程式碼的結構比較熟悉,因為它們與 第十二章 中的複製和粘貼文字項目的處理方式在本質上是一樣的。

不過,對於點陣圖,剪貼簿物件不是整體代號而是點陣圖代號。當您使用CF_BITMAP時, GetClipboardData函式傳回一個HBITMAP物件,而且SetClipboardData函式接收一個HBITMAP物件。如果您想將點陣圖傳送給剪貼簿又想保留副本以供程式本身使用,那麼您必須複製點陣圖。同樣,如果您從剪貼簿上粘貼了一幅點陣圖,也應該做一個副本。BLOWUP中的CopyBitmap函式是通過取得現存點陣圖的BITMAP結構,並在CreateBitmapIndirect函式中用這個結構建立一個新點陣圖來完成此項操作的。(變數名的尾碼Src和Dst分別代表「來源」和「目的」。) 兩個點陣圖都被選進記憶體裝置內容,而且通過呼叫BitBlt來複製點陣圖內容。(另一種複製位元的方法,可以先按點陣圖大小配置一塊記憶體,然後為來源點陣圖呼叫GetBitmapBits,為目的點陣圖呼叫SetBitmapBits。)

我發現BLOWUP對於檢查Windows及其應用程式中大量分散的小點陣圖和圖片非常有用。