電腦遊戲製作開發設計論壇 首頁 電腦遊戲製作開發設計論壇
任何可以在PC上跑的遊戲都可以討論,主要以遊戲之製作開發為主軸,希望讓台灣的遊戲人有個討論、交流、教學、經驗傳承的園地
 
 常見問題常見問題   搜尋搜尋   會員列表會員列表   會員群組會員群組   會員註冊會員註冊 
 個人資料個人資料   登入檢查您的私人訊息登入檢查您的私人訊息   登入登入 

Google
win bmp

 
發表新主題   回覆主題    電腦遊戲製作開發設計論壇 首頁 -> 遊戲程式初級班:語法及基礎概念
上一篇主題 :: 下一篇主題  
發表人 內容
satanupup
喜歡上這裡的冒險者


註冊時間: 2007-05-29
文章: 80

68.10 果凍幣

發表發表於: 2012-3-23, PM 2:19 星期五    文章主題: win bmp 引言回覆

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
IRGB RGB顏色 顏色名稱
0000 00-00-00 黑
0001 00-00-80 暗藍
0010 00-80-00 暗綠
0011 00-80-80 暗青
0100 80-00-00 暗紅
0101 80-00-80 暗洋紅
0110 80-80-00 暗黃
0111 C0-C0-C0 亮灰
1000 80-80-80 暗灰
1001 00-00-FF 藍
1010 00-FF-00 綠
1011 00-FF-FF 青
1100 FF-00-00 紅
1101 FF-00-FF 洋紅
1110 FF-FF-00 黃
1111 FF-FF-FF 白
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
IRGB RGB顏色 顏色名稱
00000000 00-00-00 黑
00000001 80-00-00 暗紅
00000010 00-80-00 暗綠
00000011 80-80-00 暗黃
00000100 00-00-80 暗藍
00000101 80-00-80 暗洋紅
00000110 00-80-80 暗青
00000111 C0-C0-C0 亮灰
00001000 C0-DC-C0 美元綠
00001001 A6-CA-F0 天藍
11110110 FF-FB-F0 乳白
11110111 A0-A0-A4 中性灰
11111000 80-80-80 暗灰
11111001 FF-00-00 紅
11111010 00-FF-00 綠
11111011 FF-FF-00 黃
11111100 00-00-FF 藍
11111101 FF-00-FF 洋紅
11111110 00-FF-FF 青
11111111 FF-FF-FF 白
最近幾年,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
BITSPIXEL PLANES 顏色數
1 1 2
1 4 16
8 1 256
15或16 1 32,768或65 536
24或32 1 16 777 216
最近,您應該不會再碰到單色顯示器了,但即便碰到了,您的應用程式也應該不會發生問題。
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所示。

 

圖14-1 BITBLT的螢幕顯示
BitBlt函式從稱為「來源」的裝置內容中將一個矩形區的圖素傳輸到稱為「目的(destination)」的另一個裝置內容中相同大小的矩形區。此函式的語法如下:
BitBlt (hdcDst, xDst, yDst, cx, cy, hdcSrc, xSrc, ySrc, dwROP) ;
來源和目的裝置內容可以相同。
在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 (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函式的語法如下:
StretchBlt ( hdcDst, xDst, yDst, cxDst, cyDst,
hdcSrc, xSrc, ySrc, cxSrc, cySrc, dwROP) ;
此函式添加了兩個參數。現在的函式就分別包含了目的和來源各自的寬度和高度。STRETCH程式展示了StretchBlt函式,如程式14-2所示。
程式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所示。

 

圖14-2 STRETCH的螢幕顯示
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函式來修改這個屬性。
SetStretchBltMode (hdc, iMode) ;
iMode可取下列值:
· 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
圖案(P):1 1 1 1 0 0 0 0來源(s):1 1 0 0 1 1 0 0目的(D):1 0 1 0 1 0 1 0 布林操作 ROP 代碼 名稱
結果: 0 0 0 0 0 0 0 0 0 0x000042 BLACKNESS
0 0 0 1 0 0 0 1 ~(S ¦ D) 0x1100A6 NOTSRCERASE
0 0 1 1 0 0 1 1 ~S 0x330008 NOTSRCCOPY
0 1 0 0 0 1 0 0 S & ~D 0x440328 SRCERASE
0 1 0 1 0 1 0 1 ~D 0x550009 DSTINVERT
0 1 0 1 1 0 1 0 P ^ D 0x5A0049 PATINVERT
0 1 1 0 0 1 1 0 S ^ D 0x660046 SRCINVERT
1 0 0 0 1 0 0 0 S & D 0x8800C6 SRCAND
1 0 1 1 1 0 1 1 ~S ¦ D 0xBB0226 MERGEPAINT
1 1 0 0 0 0 0 0 P & S 0xC000CA MERGECOPY
1 1 0 0 1 1 0 0 S 0xCC0020 SRCCOPY
1 1 1 0 1 1 1 0 S ¦ D 0xEE0086 SRCPAINT
1 1 1 1 0 0 0 0 P 0xF00021 PATCOPY
1 1 1 1 1 0 1 1 P ¦ ~S ¦ D 0xFB0A09 PATPAINT
1 1 1 1 1 1 1 1 1 0xFF0062 WHITENESS
此表格對於理解和使用位元映射操作非常重要,因此我們應花點時間來研究。
在這個表格中,「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語法是:
PatBlt (hdc, x, y, cx, cy, dwROP) ;
x、y、cx和cy參數位於邏輯單位。邏輯點(x,y)指定了矩形的左上角。矩形寬為cx單位,高為cy單位。這是PatBlt修改的矩形區域。PatBlt在畫刷與目的裝置內容上執行的邏輯操作由dwROP參數決定,此參數是ROP代碼的子集-也就是說,您可以只使用那些不包括來源目的裝置內容的ROP代碼。下表列出了PatBlt支援的16個位元映射操作:
表14-5
圖案(P):1 1 0 0目的(D):1 0 1 0 布林操作 ROP 代碼 名稱
結果: 0 0 0 0 0 0x000042 BLACKNESS
0 0 0 1 ~(P | D) 0x0500A9
0 0 1 0 ~P & D 0x0A0329
0 0 1 1 ~P 0x0F0001
0 1 0 0 P & ~D 0x500325
0 1 0 1 ~D 0x550009 DSTINVERT
0 1 1 0 P ^ D 0x5A0049 PATINVERT
0 1 1 1 ~(P & D) 0x5F00E9
1 0 0 0 P & D 0xA000C9
1 0 0 1 ~(P ^ D) 0xA50065
1 0 1 0 D 0xAA0029
1 0 1 1 ~P | D 0xAF0229
1 1 0 0 P 0xF00021 PATCOPY
1 1 0 1 P | ~D 0xF50225
1 1 1 0 P | D 0xFA0089
1 1 1 1 1 0xFF0062 WHITENESS
下面列出了PatBlt一些更常見用途。如果想畫一個黑色矩形,您可呼叫
PatBlt (hdc, x, y, cx, cy, BLACKNESS) ;
要畫一個白色矩形,請用
PatBlt (hdc, x, y, cx, cy, WHITENESS) ;
函式
PatBlt (hdc, x, y, cx, cy, DSTINVERT) ;
用於改變矩形的顏色。如果目前裝置內容中選擇了WHITE_BRUSH,那麼函式
PatBlt (hdc, x, y, cx, cy, PATINVERT) ;
也改變矩形。
您可以再次呼叫FillRect函式來用畫筆充滿一個矩形區域:
FillRect (hdc, &rect, hBrush) ;
FillRect函式相同於下列代碼:
hBrush = SelectObject (hdc, hBrush) ;
PatBlt (hdc, rect.left, rect.top,
rect.right - rect.left,
rect.bottom - rect.top, PATCOPY) ;
SelectObject (hdc, hBrush) ;
實際上,此程式碼是Windows用於執行FillRect函式的動作。如果您呼叫
InvertRect (hdc, &rect) ;
Windows將其轉換成函式:
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 (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) ;
給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:點陣圖代號」)型態的變數中,例如:
HBITMAP hBitmap ;
然後通過呼叫DDB建立的一個函式來獲得代號,例如:CreateBitmap。這些函式配置並初始化GDI記憶體中的一些記憶體來儲存關於點陣圖的資訊,以及實際點陣圖位元的資訊。應用程式不能直接存取這段記憶體。點陣圖與裝置內容無關。當程式使用完點陣圖以後,就要清除這段記憶體:
DeleteObject (hBitmap) ;
如果程式執行時您使用了DDB,那麼程式終止時,您可以完成上面的操作。
CreateBitmap函式用法如下:
hBitmap = CreateBitmap (cx, cy, cPlanes, cBitsPixel, bits) ;
前兩個參數是點陣圖的寬度和高度(以圖素為單位),第三個參數是顏色面的數目,第四個參數是每圖素的位元數,第五個參數是指向一個以特定顏色格式存放的位元陣列的指標,陣列內存放有用來初始化該DDB的圖像。如果您不想用一張現有的圖像來初始化DDB,可以將最後一個參數設為NULL。以後您還是可以設定該DDB內圖素的內容。
使用此函式時,Windows也允許建立您喜歡的特定型態GDI點陣圖物件。例如,假設您希望點陣圖寬7個圖素、高9個圖素、5個?色位元面,並且每個圖素占3位元,您只需要執行下面的操作:
hBitmap = CreateBitmap (7, 9, 5, 3, NULL) ;
這時Windows會好好給您一個有效的點陣圖代號。
在此函式呼叫期間,Windows將儲存您傳遞給函式的資訊,並為圖素位元配置記憶體。粗略的計算是此點陣圖需要7×9×5×3,即945位元,這需要比118個位元組還多幾個位元。
然而,Windows為點陣圖配置好記憶體以後,每行圖素都佔用許多連貫的位元組,這樣
iWidthBytes = 2 * ((cx * cBitsPixel + 15) / 16) ;
或者C程式寫作者更傾向於寫成:
iWidthBytes = (cx * cBitsPixel + 15) & ~15) >> 3 ;
因此,為DDB配置的記憶體就是:
iBitmapBytes = cy * cPlanes * iWidthBytes ;
本例中,iWidthBytes占4位元組,iBitmapBytes占180位元組。
現在,知道一張點陣圖有5個顏色位元面,每圖素占3個顏色位有什麼意義嗎?真是見鬼了,這甚至不能把它稱作一個習題作業。雖然您讓GDI內部配置了些記憶體,並且讓這些記憶體有一定結構的內容,但是您這張點陣圖完全作不出任何有用的事情來。
實際上,您將用兩種型態的參數來呼叫CreateBitmap。
· cPlanes和cBitsPixel都等於1(表示單色點陣圖);或者
 
· cPlanes和cBitsPixel都等於某個特定裝置內容的值,您可以使用PLANES和BITSPIXEL索引來從GetDeviceCaps函式獲得。
 
更現實的情況下,您只會在第一種情況下呼叫CreateBitmap。對於第二種情況,您可以用CreateCompatibleBitmap來簡化問題:
hBitmap = CreateCompatibleBitmap (hdc, cx, cy) ;
此函式建立了一個與設備相容的點陣圖,此設備的裝置內容代號由第一個參數給出。CreateCompatibleBitmap用裝置內容代號來獲得GetDeviceCaps資訊,然後將此資訊傳遞給CreateBitmap。除了與實際的裝置內容有相同的記憶體組織之外,DDB與裝置內容沒有其他聯繫。
CreateDiscardableBitmap函式與CreateCompatibleBitmap的參數相同,並且功能上相同。在早期的Windows版本中,CreateDiscardableBitmap建立的點陣圖可以在記憶體減少時由Windows將其從記憶體中清除,然後程式再重建點陣圖資料。
第三個點陣圖建立函式是CreateBitmapIndirect:
hBitmap CreateBitmapIndirect (&bitmap) ;
其中bitmap是BITMAP型態的結構。BITMAP結構定義如下:
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型態的結構。
BITMAP bitmap ;
並呼叫函式如下:
GetObject (hBitmap, sizeof (BITMAP), &bitmap) ;
Windows將用點陣圖資訊填充BITMAP結構的欄位,不過,bmBits欄位等於NULL。
您最後應呼叫DeleteObject來清除程式內建立的所有點陣圖。
點陣圖位元
 
用CreateBitmap或CreateBitmapIndirect來建立設備相關GDI點陣圖物件時,您可以給點陣圖圖素位元指定一個指標。或者您也可以讓點陣圖維持未初始化的狀態。在建立點陣圖以後,Windows還提供兩個函式來獲得並設定圖素位元。
要設定圖素位元,請呼叫:
SetBitmapBits (hBitmap, cBytes, &bits) ;
GetBitmapBits函式有相同的語法:
GetBitmapBits (hBitmap, cBytes, &bits) ;
在這兩個函式中,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,那麼您可以像下面那樣建立記憶體裝置內容:
hdcMem = CreateCompatibleDC (hdc) ;
通常,函式的呼叫比這更簡單。如果您將參數設為NULL,那麼Windows將建立一個與視訊顯示器相相容的記憶體裝置內容。應用程式建立的任何記憶體裝置內容最終都通過呼叫DeleteDC來清除。
記憶體裝置內容有一個與實際位元映射設備相同的顯示平面。不過,最初此顯示平面非常小-單色、1圖素寬、1圖素高。顯示平面就是單獨1位元。
當然,用1位元的顯示平面,您不能做更多的工作,因此下一步就是擴大顯示平面。您可以通過將一個GDI點陣圖物件選進記憶體裝置內容來完成這項工作,例如:
SelectObject (hdcMem, hBitmap) ;
此函式與您將畫筆、畫刷、字體、區域和調色盤選進裝置內容的函式相同。然而,記憶體裝置內容是您可以選進點陣圖的唯一一種裝置內容型態。(如果需要,您也可以將其他GDI物件選進記憶體裝置內容。)
只有選進記憶體裝置內容的點陣圖是單色的,或者與記憶體裝置內容相容設備有相同的色彩組織時,SelectObject才會起作用。這也是建立特殊的DDB(例如有5個位元面,且每圖素3位元)沒有用的原因。
現在情況是這樣:SelectObject呼叫以後,DDB就是記憶體裝置內容的顯示平面。處理實際裝置內容的每項操作,您幾乎都可以用於記憶體裝置內容。例如,如果用GDI畫圖函式在記憶體裝置內容中畫圖,那麼圖像將畫在點陣圖上。這是非常有用的。還可以將記憶體裝置內容作為來源,把視訊裝置內容作為目的來呼叫BitBlt。這就是在顯示器上繪製點陣圖的方法。如果把視訊裝置內容作為來源,把記憶體裝置內容作為目的,那麼呼叫BitBlt可將螢幕上的一些內容複製給點陣圖。我們將看到這些都是可能的。
載入點陣圖資源
 
除了各種各樣的點陣圖建立函式以外,獲得GDI點陣圖物件代號的另一個方法就是呼叫LoadBitmap函式。使用此函式,您不必擔心點陣圖格式。在程式中,您只需簡單地按資源來建立點陣圖,這與建立圖示或者滑鼠游標的方法類似。LoadBitmap函式的語法與LoadIcon和LoadCursor相同:
hBitmap = LoadBitmap (hInstance, szBitmapName) ;
如果想載入系統點陣圖,那麼將第一個參數設為NULL。這些不同的點陣圖是Windows視覺介面(例如關閉方塊和勾選標記)的一小部分,它們的識別字以字母OBM開始。如果點陣圖與整數識別字而不是與名稱有聯繫,那麼第二個參數就可以使用MAKEINTRESOURCE巨集。由LoadBitmap載入的所有點陣圖最終應用DeleteObject清除。
如果點陣圖資源是單色的,那麼從LoadBitmap傳回的代號將指向一個單色的點陣圖物件。如果點陣圖資源不是單色,那麼從LoadBitmap傳回的代號將指向一個GDI點陣圖物件,該物件與執行程式的視訊顯示器有相同的色彩組織。因此,點陣圖始終與視訊顯示器相容,並且總是選進與視訊顯示器相容的記憶體裝置內容中。採用LoadBitmap呼叫後,就不用擔心任何色彩轉換的問題了。在下一章中,我們就知道LoadBitmap的具體運作方式了。
程式14-3所示的BRICKS1程式示範了載入一小張單色點陣圖資源的方法。此點陣圖本身不像磚塊,但當它水平和垂直重複時,就與磚牆相似了。
程式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點陣圖物件。我們將在下一章看到這是如何操作的。

 

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

 

您能寫下一系列的位元(0代表黑色,1代表白色),這些位元直接對應於網格。從左到右讀這些位元,您能給每8位元組配置一個十六進位元的位元組值。如果點陣圖的寬度不是16的倍數,在位元組的右邊用零填充,以得到偶數個位元組:
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結構:
static BITMAP bitmap = { 0, 20, 5, 4, 1, 1 } ;
並且可以將位元儲存在BYTE陣列中:
static BYTE bits [] = { 0x51, 0x77, 0x10, 0x00,
0x57, 0x77, 0x50, 0x00,
0x13, 0x77, 0x50, 0x00,
0x57, 0x77, 0x50, 0x00,
0x51, 0x11, 0x10, 0x00 } ;
用CreateBitmapIndirect來建立點陣圖需要下面兩條敘述:
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程式利用此技術直接建立了磚塊點陣圖,而沒有使用資源。
程式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
回頂端
檢視會員個人資料 發送私人訊息
從之前的文章開始顯示:   
發表新主題   回覆主題    電腦遊戲製作開發設計論壇 首頁 -> 遊戲程式初級班:語法及基礎概念 所有的時間均為 台灣時間 (GMT + 8 小時)
1頁(共1頁)

 
前往:  
無法 在這個版面發表文章
無法 在這個版面回覆文章
無法 在這個版面編輯文章
無法 在這個版面刪除文章
無法 在這個版面進行投票
可以 在這個版面附加檔案
可以 在這個版面下載檔案


Powered by phpBB © 2001, 2005 phpBB Group
正體中文語系由 phpbb-tw 維護製作