位圖[轉載]

一、概述

????? 在Windows中每屏是一個圖形圖像,靈巧的Windows制作系統,面對龐大的圖形編程任務,建立了為繪畫多彩的邊界、按鈕、圖標、字體的函數庫。當 然啦,通過Windows API,這些函數都是可調用的。所謂Windows顯示屏幕以及數量眾多的打印機其實都是屬于“光柵設備”。在光柵設備中,一幅圖象由多條掃描線以及能訪 問的單獨像素構成。Windows也支持非光柵設備,比如繪圖儀等,本人對此一無經驗,無從談起,想來也差不到哪兒去。以下只以顯示器為重點展開討論。

????? 計算機視頻系統的核心是內存。該內存包含代表著顯示圖案的數據,而這些圖案顯示在監視器(顯示器)上。每次鼠標移動時,內存中的少量數據發生變化。然后你 會看到鼠標指針在屏幕上移動。每次以及每一個圖形操作都會影響視頻內存,因為GDI執行計算并以相應方式更改視頻內存。計算機中還有一個存放圖象數據的內 存,叫做位圖內存。位圖內存與視頻內存的重要的一個區別是∶位圖內存看不到,而視頻內存則能夠看到。也就是說放在位圖內存的圖象數據并不反映在屏幕上,而 視頻內存中存放的圖象信息則反映在監視器上。如果把駐留在位圖內存的數據移動到視頻內存中,那么圖象將顯示在監視器上。以一個桌面圖標為例,圖標從磁盤加 載到內存中(位圖內存)中,然后內存被移到視頻內存中的適當地址上,這樣當視頻內存通過視頻硬件被顯示到屏幕上時,圖標成為可見的。

????? 從圖象的種類來講,Windows中存在兩種位圖,一種叫與設備有關位圖(或叫設備相關位圖),另一種叫與設備無關位(或叫設備無關位圖,或DIB)。除 非特別聲明,W indows中的位圖都是與設備有關的位圖。有些朋友一說位圖,就可能想到BMP圖象,但它是屬于與設備無關的位圖。在編程的領域我們常常說位圖,一般并 不是指BMP圖象,而是指對他們來說也是想象中存在的那個叫與設備有關的位圖。另一個區別與設備有關位圖和與設備無關位圖的重要依據是,判斷該位圖是否具 有句柄。具有句柄的位圖便是與設備有關的位圖,因為它是GDI對象之一。常見的BMP圖象則屬于與設備無關的位圖。我們一般不叫它位圖,而是叫做“BMP 圖象”或者叫做“DIB”,它是一種數據的組織方式,并非GDI繪圖對象。
????? 可以把與設備無關位圖理解為對與設備有關位圖數據的一種標準格式的數據保存方式。比如我們平常看到的附加名為BMP的文件。這種位圖文件會在文件頭上放置 文件的組織信息,形式上講和一些數據庫文件的文件頭起著同樣的作用━━描述文件的結構。文件頭后面緊跟著的是圖象的顏色數據。由于這種機制的存在,使得與 設備無關的位圖可以在各種設備之間進行讀寫。

????? 與設備無關的位圖只有一種格式,而與設備有關的位圖則可能...大概有多少種設備就有多少種格式吧?鬼才知道!幸運的是您根本沒有必要了解它的格式,只需簡單理解為保存圖象數據的內存塊就可以了。就好像您跟本不用了解我長得什么模樣,也照樣能和我打交道一樣。

?????? PC的視頻系統顯示一個像素的矩陣(整齊排列的小光點)。在一個基于字符的環境中,視頻系統包含一套"硬件編程"的形狀,代表標準字符的位置及各種畫圖字 符(直線、角線、實線等等)和幾個符號(笑臉、鉆石、鏟等)。通過發送給系統一個ASCII碼或ANSI字符碼來顯示一個字符。而在Windows這樣一 個圖形顯示系統中,計算機及其軟件定義出現在屏幕上的圖形形狀。這些形狀是用位圖來代表的。用這種方法的最主要的優點是圖象和文本能以不同的大小、字體、 方式來顯示。那么,準確地說,什么是位圖呢?它們是數據元素的集合,這些數據元素決定在每個屏幕位置上顯示什么樣的顏色。在單色的(黑和白)圖形圖像中, 每位代表一個屏幕像素,0對應黑(沒有顏色),1對應白(顯示色)。不久您就會看到,位圖能描述彩色圖像。每個像素的位數決定能在單個位圖中出現的不同色 彩的數目。除了每個像素一位的單色視頻,還有其他三種∶每個像素4位產生16種顏色,8位產生256種顏色,24位產生16'777'216種顏色(補 充:我已經親眼看到了32位顯卡)。如下表所示∶

???????????????????????? Windows支持的彩色

每個像素的位數?????? 顏色總數?????????????????????? ?典型設備
1?????????????????????????? ??2???????????? ????????????? ?單色圖形
4??????????????????????????? 16??????????????????????? ?? 標準備VGA
8??????????????????????????? 256???????????????????????? 256色VGA
16????????????????????????? 32'768或65'536????? 32K或64K色SVGA
24????????????????????????? 16'777'216???????????? 24位真彩色設
?????? 計算機顯示系統的性能決定Windows對彩色圖象的處理。現在常看到的顯示器是SVGA(AGP也出來了,我的就是,但資料少無從談起)。理論上講, SVGA系統能顯示驚人的含有16'777'216種顏色數組。把能夠顯示16'000'000種顏色范圍的系統通常稱為真彩色(true color)。那么"真彩色"的真實意思是什么呢?它源于對是否一個彩色顯示器能顯示無限大范圍的顏色的想象,圖象將看起來完全逼“真”。真彩色的顯示用 24位來決定每個像素的顏色。24位被分成三組,每組8位,來表示紅、綠、藍三種顏色。(參考∶自然界的顏色是通過紅、綠、藍(RGB)三種顏色來組合而 成的。叫做三元色。)

二、與設備無關位圖(DIB)

????? 所謂DIB是指與設備無關的位圖。DIB并不是Windows的對象,它沒有自己的句柄,實際上是一種數據文件。和很多數據文件相同,DIB位圖文件的開 頭部分有它內在的數據結構描述部分。不少書做了一些示意圖來說明這個問題,但我覺得還是親眼目睹DIB文件的內部更好一些,百聞不如一睹嘛。
????? 現在我們準備要看的BMP文件是一個256色位圖,文件名叫Hua.bmp ,這個文件曾在《VB前線》的演示程序第25號中使用過。

????? 首先讓我們查看一下文件在磁盤中的大體狀況∶
D:\vbplay\vbplay25\>dir

Volume in drive D is PRO???????
?Volume Serial Number is 268F-AB4B
?Directory of D:\vbplay\vbplay25

.????????????? <DIR>??????? 03-27-99? 20:47 .
..???????????? <DIR>??????? 03-27-99? 20:47 ..
PLAY25?????? VBW?????????? 111? 09-20-99? 23:43 play25.vbw
MSSCCPRJ? SCC?????????? ?193? 04-07-99? 23:52 MSSCCPRJ.SCC
BITMAP?????? CLS????????????12,711? 06-16-99? 21:27 Bitmap.cls
MYMEMORY CLS????????????3,419? 04-08-99?? 2:07 MyMemory.cls??????????? PLAY25?????? VBP?????????? ?636? 06-16-99? 21:42 play25.vbp
HUA??????????? BMP?????????? 96,446? 04-04-99? 12:06 hua.bmp
PL25_1????? ?FRM?????????? 3,865? 06-16-99? 21:25 pl25_1.frm
???????? 7 file(s)??????? 117,381 bytes
???????? 2 dir(s)???? 569,049,088 bytes free


????? 看到了吧?它的大小是96?46個字節。接下來,我們可以用DEBUG的d命令以16進制形式來顯示文件的文件頭信息。操作如下∶

D:\vbplay\vbplay25\>debug hua.bmp
-d 100 1000
0EAE:0100? 42 4D BE 78 01 00 00 00-00 00 36 04 00 00 28 00?? BM.x......6...(.
0EAE:0110? 00 00 69 01 00 00 06 01-00 00 01 00 08 00 00 00?? ..i.............
0EAE:0120? 00 00 88 74 01 00 C4 0E-00 00 C4 0E 00 00 00 00?? ...t............
0EAE:0130? 00 00 00 00 00 00 00 00-00 00 80 80 80 00 00 00?? ................
0EAE:0140? 80 00 00 80 80 00 00 80-00 00 80 80 00 00 80 00?? ................

0EAE:0150? 00 00 80 00 80 00 40 80-80 00 40 40 00 00 FF 80?? ......@...@@....
0EAE:0160? 00 00 80 40 00 00 FF 00-40 00 00 40 80 00 FF FF?? ...@....@..@....
省略了57行∶50 - 16 -1 = 39(16進制) = 57(10進制)
0EAE:0500? CC 00 AD E8 FD 00 78 AE-D3 00 1F 38 65 00 66 89?? ......x....8e.f.
0EAE:0510? F0 00 63 9B C3 00 79 AD-DA 00 08 13 73 00 0A 1C?? ..c...y.....s...
0EAE:0520? A5 00 BB ED FD 00 83 C1-E5 00 2A 50 74 00 7C B3?? ..........*Pt.|.

0EAE:0530? ED 00 56 82 BD 00 B3 66-B3 B3 6C B3 BD 8B C9C9?? ..V....f..l.....
0EAE:0540? BD 50 21 60 6C 6C 44 44-B3 44 6C 86 44 66 44 D9?? .P!`llDD.Dl.DfD.
0EAE:0550? 44 B3 6C C9 87 7B B9 7B-44 BD 87 60 87 C9 6C AB?? D.l..{.{D..`..l.
0EAE:0560? 60 D6 6C AB AB 2A 88 55-88 7B 7B 7B 7B 3D 3D 3D?? `.l..*.U.{{{{===
0EAE:0570? C5 3D C5 24 7B 7B 7B 7B-B4 B9 7B B9 B5 C6 3D7B?? .=.${{{{..{...={
(以下全部省略)
0EAE:1000? B3??????????????????????????????????????????????? .

-q
????? 注∶若使用D:\vbplay\vbplay25\>debug hua.bmp >> Temp.txt則可以把結果保存到Temp.txt文件,并可加以分析研究。但這樣給出命令后什么都看不見。這時,你應當鍵入d 100 1000這個命令后回車,然后再按q就可以返回到DOS命令行提示符下。這段內容是這樣獲取的。詳細內容,可參考有關DOS命令參考手冊。用 pctools5.0也可以。


????? 通過上述方式獲取的是該位圖文件第一個字節開始的若干個字節的16進制形式的文本內容。以下,我們具體分析一下。在分析以前為了在整體上把握DIB文件的結構,有必要先給出其整體情況,如下表格所示∶

DIB文件的組成

三大部分????????????????????????????????????????? 字節????????????????????????? ?說明
BITMAPFILEHEADER??????????????????????? 14????????????????
用紅色表示的部分
BITMAPINFO?BITMAPINFOHEADER?? 40???????????????? 用深綠色表示的部分
RGBQUAD(結構數組)???????????????????????? 最小0,最大4×256。(在16位以上的位圖中,根本不存在此部分)?????????????????????????????????????????? ?用深紅色表示的部分
數據部分?????????????????????????????????????????? 大于0(具體大小信息記錄在BITMAPINFOHEADER結構中)???????????????????????????????? 用下劃線表示的部分
????? 可以看出,DIB的文件頭主要由兩個部分組成,即BITMAPFILEHEADER和BITMAPINFO,
?????? 而BITMAPINFO又分為BITMAPINFOHEADER和RGBQUAD兩個小部分。讓我們一個一個分析。首先是開頭以紅色表示的14個字節,如下

42 4D BE 78 01 00 00 00-00 00 36 04 00 00
這是BITMAPFILEHEADER結構部分。BITMAPFILEHEADER結構如表所示∶


??????????????????? BITMAPFILEHEADER結構

結構內各字段??????????????????? 數據類型???????????????????????????? 說明
bfType????????????????????????? Integer??????????????????? 指定文件類型,必須 BM
bfSize?????????????????????????? Long??????????????????????? 指定位圖文件大小,以字節為單位
bfReserved1???????????????? Integer??????????????????? 保留未用,必須設為0
bfReserved2???????????????? Integer??????????????????? 同上
bfOffBits??????????????????????? Long?????????????????????? 從此結構到位圖數據位的字節偏移量
????? 此結構,主要記錄了DIB文件大小以及結構有關的信息。在很多情況下,可以計算來獲取這些這些信息,所以很多人并不訪問這個結構內的數據。不少人說
BITMAPFILEHEADER結構記錄著無大用處的信息。其中bfOffBits說明的是從此結構到位圖數據位的字節偏移量。通過對此字段的訪問,我們可以知道這個位圖文件的顏色數據是從哪里開始的,或者可以知道DIB文件頭的結構有多長。


m_BMFileHeader.bfType = 42 4D

??????? 這是一個Integer整型數據,對DIB文件來說此數據必須是42 4D。那么為什么定為42 4D呢,其實是在ASCII碼表中42即十進制的66表示大寫B,4D即十進制的77表示M,也就是說字符擝M數囊饈丁?梢遠哉飧穌褪蕕姆夢世磁卸銜 募欠袷荄IB(或BMP)文件。

m_BMFileHeader.bfSize = BE 78 01 00 = &H178BE = 96446個字節

??? 說明文件的總長度。注意,右側的數據是高字節,即右側字節的地址比左側字節的地址值大,所以BE 78 01 00 = &H178BE。在上面,用dir命令觀察文件時,我們已經看到文件的長度正是96446個字節。如∶
??? HUA????? BMP?????? 96,446? 04-04-99? 12:06 hua.bmp


m_BMFileHeader.bfReserved1 = 00 00? (系統保留,沒有設置數據)
m_BMFileHeader.bfReserved2 = 00 00? (系統保留,沒有設置數據)

m_BMFileHeader.bfOffBits = 36 04 00 00 = &H00000436 = 1078 =14 + 40 + 256 * 4

??? (從文件開始處起,帶下劃線的數據位的以字節為單位的長度,總共1078個字節。對真彩色圖象來說,這個數據經常是36 00 00 00,即54。說明文件頭只有BITMAPFILEHEADER(14個字節) + BITMAPINFOHEADER(40個字節)的部分,即沒有RGBQUAD部分。為什么沒有RGBQUAD部分也可以?稍后你就明白怎么一回事。繼續 往下看,繼續)

??? 與此結構緊連在一起的下一個叫BITMAPINFO的結構,其組成如下表所示∶


????????????????? BITMAPINFO結構(表二)

結構內各字段?????????????????? 數據類型???????????????????????????? ?說明
bmiHeader???????????? ?BITMAPINFOHEADER????? 一個 BITMAPINFOHEADER
bmiColors??????????????? RGBQUAD????????????????????? 一個 RGBQUAD結構組成的數組
????? 從此表中可以看出,結構內部還存在兩個結構,即BITMAPINFOHEADER和RGBQUAD。因此,從某種意義上講,位圖文件的文件頭結構可以說為 三個結構來構成的: BITMAPFILEHEADER 、BITMAPINFOHEADER和RGBQUAD。但,必須認清的是BITMAPINFO結構并不是離開BITMAPINFOHEADER和 RGBQUAD這兩個結構而獨立存在的。BITMAPINFOHEADER結構的長度是固定著的,為40個字節。BITMAPINFOHEADER結構的 有關說明,可參考如下表∶


???????????? BITMAPINFOHEADER結構(表三)

結構內各字段????????????????????? 數據類型????????????????? ?說明
biSize????????????????????????????? Long?????????????????? 結構長度(40)
biWidth???????????????????????????Long????????????????? ?指定位圖的寬度,以像素為單位
biHeight????????????????????????? Long?????????????????? 指定位圖的高度,以像素為單位
biPlanes??????????????????????? ?Integer????????????????指定目標設備的級數(必須為 1 )
biBitCount????????????????????? Integer????????????????每一個像素的位(1,4,8,16,24,32)
biCompression????????????? ?Long??????????????????? 指定壓縮類型(BI_RGB 為不壓縮)
biSizeImage?????????????????? Long??????????????????? 指定圖象的大小,以字節為單位
biXPelsPerMeter???????????? Long?????????????????? 指定設備水平分辨率,以每米的像素為單位
biYPelsPerMeter???????????? Long?????????????????? 垂直分辨率,其他同上
biClrUsed?????????????????????? Long?????????????????? 在顏色表中實際使用的色彩索引的個數,用O表示全要使用
biClrImportant?????????????? Long?????????????????? 指定認為重要的顏色索引個數,用 0 表示所有顏色均重要
???? BITMAPINFOHEADER結構主要記載了數據區的大小及顏色信息。必須認清的是,
biSizeImage字段說明的是圖象的大小,也就是數據部分的大小,而不是文件的大小。如果我們只把位圖的數據部分讀入到內存(而把結果部分讀入到數組),就需要按此大小來申請內存大小。為了便于對照,我把上面給出的示范數據在這里重貼一下了∶
0EAE:0100??????????????????????????????????????????????? 28 00?? BM.x......6...(.
0EAE:0110? 00 00 69 01 00 00 06 01-00 00 01 00 08 00 00 00?? ..i.............

0EAE:0120? 00 00 88 74 01 00 C4 0E-00 00 C4 0E 00 00 00 00?? ...t............
0EAE:0130? 00 00 00 00 00 00??????????????????????????????????? ................

??? 分析說明如下∶

biSize = 28 00 00 00 = &H28 = 40
此結構的長度,總共40個字節。總是這個數據。

?biWidth = 69 01 00 00 = &H169 = 361
?圖象的寬度(像素質為單位)。把此圖片放到PictureBox控件,并運行如下命令∶
?Text1.Text = Picture1.ScaleX(Picture1.Picture.Width, vbHimetric, vbPixels)
?結果為∶360.9827。可以看出VB的計算存在一點誤差。但如果你用整形變量來讀取這個數據,那么就一樣了。

biHeigh = 06 01 00 00 = &H106 = 262
圖象的高度,請參照bitWidth .

biPlanes = 01 00 = &H1 = 1???
必須為1。

biBitCount? = 08 00 = &H8 = 8
每 個像素的位。當前我例舉的這個叫hua.bmp的位圖文件是以8個位來表示一個像素的,即一個字節。一個256(2的8次方)色位圖能顯示的顏色總數限定 在256種顏色,但這并不意味著任何256色位圖都只能顯示相同的256種顏色。一個256色位圖所能夠顯示的256種顏色被規定在該位圖的 RGBQUAD結構中。256色位圖所能顯示的顏色范圍,能夠達到設備所允許的范圍(16位增強模式中可能的顏色數為2的16次方,24位真彩模式中可能 的顏色數為2
的24次方),但必須是其中的256個顏色。RGBQUAD結構所描述的顏色值用來產生調色板。在這種情況下數據區內存放的是對這 256種顏色的索引值,而不是實際顏色。當用1位來表示一個像素的時候,RGBQUAD的數組個數為2,存放著黑色和白色兩個顏色,而數據區存放著1和0 兩個索引值;當用4位來表示一個像素的時候,則RGBQUAD的數組個數為16,存放著16種顏色,數據區存放著0至15的索引號;當8位的時候存放 256種顏色,數據區存放著0至255的索引號;而當像素位超過或等于16位的時候,RGBQUAD結構部分不存在,數據區存放著以16位或24位表示的 實際顏色。在這種時候就不存在調色板了。其中的原因一想就知道,難道我們要設上萬種的調色板項?

biCopression = 00 00 00 00 = &H0 = 0
指定壓縮類型。不是說BI_RGB 為不壓縮嗎?讓我查查,BI_RGB究竟多少?
來嘍! Const BI_RGB& = 0& ,呵呵,原來這種文件叫做沒有壓縮。
還有∶Const BI_RLE4& = 2&?? 和 Const BI_RLE8& = 1&?? 估計沒有什么用處,想壓縮就采用JPG吧,何必用BMP ?

biSizeImage = 88 74 01 00 = &H17488 = 95368
圖象數據的長度(大小)。從數據關系上來講,應該是∶圖象數據的長度+文件頭長度=文件長度。讓我們驗證一下,是否是這樣∶

biSizeImage + m_BMFileHeader.bfOffBits
= 95368 + 1078 = 96446 = m_BMFileHeader.bfSize
@@~果然如此!

???

biXPelsPerMeter = C4 0E 00 00 = &HEC4 = 3780

biYPelsPerMeter = C4 0E 00 00 = &HEC4 = 3780

水平分辨率和垂直分辨率。

ibClrUsed = 00 00 00 00 = &H0 = 0

顏色表(調色板)中使用的索引個數。既然是0,說明全要使用了。全就是256個,再多了不可能。你千萬不要用此數據來判斷RGBQUAD數組的個數。

biClrImportant = 00 00 00 00 = &H0 = 0

指定認為重要的顏色索引個數,用 0 表示所有顏色均重要。請大家多多去選用0吧,這樣才能有利于團結。

??? 與BITMAPINFOHEADER 結構不同RGBQUAD是一個數組,其長度(RGBQUAD結構長度×數組元素個數)可以是多種情況,但不能超過256個。RGBQUAD結構如表所示∶

RGBQUAD結構(表四)

結構內各字段?數據類型?說明
rgbBlue?Byte?指定彩色中藍色成分的多少
rgbGreen?Byte?指定彩色中綠色成分的多少
rgbRed?Byte?指定彩色中紅色成分的多少
rgbReserved?Byte?保留,可設為0
0EAE:0130????????????????????? 00 00-00 00 80 80 80 00 00 00?? ................
0EAE:0140? 80 00 00 80 80 00 00 80-00 00 80 80 00 00 80 00?? ................
0EAE:0150? 00 00 80 00 80 00 40 80-80 00 40 40 00 00 FF 80?? ......@...@@....
0EAE:0160? 00 00 80 40 00 00 FF 00-40 00 00 40 80 00 FF FF?? ...@....@..@....
以下省略(還很長)

您大概已經注意到了,我為什么用紅色標示了一些 00 呢?原因是因為它們都是白癡,只占地方,不起作用,是微軟公司保留的RGBQUAD結構中的rgbReserved字段。每個 00
和 00 和之間有三個字節,當然,他們分別表示RGB顏色的了。這樣顏色表中索引號為0的顏色應當是等于RGB(&H0,&H0,&H0),索引號為1的顏色應當是等于RGB(&H80,&H80,
&H80),索引號為2的顏色那就是等于RGB(&H0,&H0,&H80),依次類推,至到索引號為255的那個顏色。

OKay!現在已經把文件頭講得夠細的了,不能再細,再細了就沒戲了。接著看一下數據部分。


0EAE:0530? ED 00 56 82 BD 00 B3 66-B3 B3 6C B3 BD 8B C9 C9?? ..V....f..l.....
0EAE:0540? BD 50 21 60 6C 6C 44 44-B3 44 6C 86 44 66 44 D9?? .P!`llDD.Dl.DfD.

以下省略

我們可以看到第一個像素點的數據是B3,即179,說 明此顏色相當與顏色表中的索引號為237的那個顏色。那么那個顏色究竟什么顏色呢?糟糕,給我省略了,沒有列出來。來找一個沒有省略的吧,看看有沒有。好 象沒有。算了,就到這里吧。其實找也是那么一回事。不過我還是告訴你吧,因為你自己找的話可能找不到。原因是∶行是以逆序存放的。
也就是說,位圖 文件的像素數據部分中的第一行實際上是圖象中的最后一行。Dan說∶除非將BITMAPINFOHEADER 結構的 biHeight 段設為負值,否則起點就位于左下角。但大多情況下biHeight并非負值,所以起點一般也就在左下角了。但這并不是說掃描行的起點等于圖象的高度,起點 還是等于0,終點才等于高度。也就是說掃描行的編號是從最后一行開始順序排列為 0,1,2,3 的。

現在,我們已經完成了對一個位圖文件的全面分析。如果 還有需要講的話,那么有兩點。第一點是關于真彩色位圖。已經在上面提到過,真彩色位圖是沒有顏色表的。這種圖的數據部分存放的是實際的RGB顏色。那么如 何知道沒有顏色表呢?其實很簡單,好象上面也已經講到了,m_BMFileHeader.bfOffBits? 將等于 &H36,即54。還有biBitCount
會是大于等于16位。


第二點,是一個很討厭的規定。我們把位圖中的一行數據信息叫做掃描線。Windows
規定,每一個掃描線必須結束與一個32位的邊界。也就是說,一個掃描線的位長度(按位計算)必須整除于32,或字節長度必須整除于4 。也就是說如果只有8位,那么拿空白的24
位來補充,如果只有48位,那么也就拿空白的16位來補充。我們可以證實一下∶

在剛才的圖片中,我們已經知道圖象的寬度為361個像 素點,高度為262個像素點。那么數據區的總的像素點個數為361×262=94582。本例中一個字節代表一個像素,但字節個數并不等于94582。正 如已經看到的那樣,是為biSizeImage = 95368。還缺少786個字節。這說明一些位給補上了。總共是262行,因此說明每行補充了786÷262=3個字節,即24位。為什么每行補充了24 位呢?現在看來只有一種可能。也就是,32位等于是4個字節,因此一行361個字節除以4以后,必須剩下1,Windows才補充了不足的3 個字節。呵呵,361 MOD 4 = 1 @@~

計算DIB各行中的字節數(掃描線長度)的VB代碼總結如下∶

imageWidth? -->位圖的寬度(像素為單位)
?bmBits????? -->每像素的位個數


Function GetLineWidth(bmBits As Integer, imageWidth As Long) As Long

?? GetLineWidth = imageWidth

?? Select Case bmBits

????? Case 1

????????? GetLineWidth = (GetLineWidth + 7) / 8

????? Case 4

????????? GetLineWidth = (GetLineWidth + 1) / 2

????? Case 8

????? Case 16

????????? GetLineWidth = GetLineWidth * 2

????? Case 24

????????? GetLineWidth = GetLineWidth * 3

????? Case 32

????????? GetLineWidth = GetLineWidth * 4

????? Case Else

????????? GetLineWidth = 0???? 'error

????????? Exit Function

?? End Select

????? If (GetLineWidth And 3&) > 0 Then _

????????? GetLineWidth = (GetLineWidth And &HFFFFFFFC) + 4

End Function

以下給出了與設備無關位圖有關的函數

函??? 數????????????????????????????????????? 說??? 明
CreateDIBitmap??????????? 在一幅DIB的基礎上創建與設備有關位圖
CreateDIBSection???????? 創建一個DIBSection對象(一種新的位圖對象,和普通設備相關位圖相似,但也能在內存中指定一個緩沖區,用以保存DIB格式的位圖數據。具體內容已超出本講座的范圍,讀者自己參考有關資料)
CreateDIBColorTalbe?? 為DIBSection對象取得顏色表信息。
GetDIBits???????????????????? 在與設備無關位圖里載如來自一幅與設備有關位圖的數據。很不錯的函數。
SetDIBColorTalbe??????? 為DIBSection設置顏色表信息。
SetDIBits???????????????????? 在與設備相關位圖里用來自一個DIB的數據設置圖象。
SetDIBitsToDevice?????? 將來自DIB的數據直接設到一個設備。可用它將數據從DIB直接傳給屏幕或打印機。
StretchDIBits?????????????? 將來自DIB的數據設到設備場景,同時根據需要伸縮圖象。有些設備支持用這個函數將數據直接設到輸出設備。可用GetDeviceCaps函數判斷具體是否支持。常用函數之一。
?????? 好了,大家稍微休息以下,下一節我們學習與設備有關的位圖。

三、與設備有關的位圖(DDB)

????? 與設備有關的位圖,相對與與設備無關位圖來講,比較簡單。只所以簡單是因為我們根本沒有必要去學習它的結構。因為,與設備有關的位圖的格式總是與其設備場 景(順便說一下,很多書把設備場景叫做“設備上下文”)有關。但2色位圖除外。如果你正坐在計算機前,不要認為計算機中只存在一種與設備有關的位圖,其實 不然。所謂設備并不是指一臺計算機,內存,顯示緩存,打印機,等都有各自的位圖數據格式。不要對與設備有關的位圖進行對其結構的任何猜測。你見過我嗎?沒 有吧?但你是否覺得與我交往仍然是很有效呢?我們可以通過Email,因特網進行交流。你就把我理解為一個人,一個遠方的朋友就可以了,是不是?對與設備 有關的位圖也就這樣想就可以了。它的確是存在著的,就在計算機的某個設備內部(比如內存,打印機)。當然我們不能通過Email跟它交往,而是要靠函數, 范圍包括創建,獲取信息,設置信息,銷毀等等。用慣了你會發現,這也很得勁兒,其難度也就相當與給我發一個E-Mail── 填寫需要的地址,需要的標題和內容,然后發送。當然,這里要做的是為一個函數的調用為其添寫需要的參數。如果你真的想見它一面,我倒有個方法,是家傳秘方 呦!好好聽,你可以拿錘子把你的計算機用力,請記住一定要用力,用力砸一下,然后打開機箱看看它出來了沒有,一般成功率能達到99%。但由于存在那1%的 失敗的可能,我得先聲明如果見不到后果自負∶)

????? 使用與設備無關位圖的過程當中始終不能忘記的一點是,確保位圖與設備的兼容性。你不能把一個內存設備場景中的位圖直接選入到打印機設備場景,會出現異常或 錯誤。這里所說的設備其實就是指設備場景。為了創建一個與指定設備場景兼容的設備相關位圖,可以使用CreateBitmap或者 CreateCompatibleBitmap。但兩個函數的功能是不同的。我的經驗是,在創建一個Mask(掩模位圖)圖象的時候使用 CreateBitmap,而創建一個彩色位圖的時候使用CreateCompatibleBitmap。還有幾個函數,如 CreateDIBitmap,它可以根據一個DIB(設備無關位圖)的基礎上創建一個與設備相關位圖。反正,你應該注意到,幾乎所有的與設備相關位圖有 關的API函數都具有一個hDC參數,有時候看起來沒有必要的,但應當清楚這主要是為了保證設備的兼容性而給出的。說明與設備有關位圖的有些信息是記錄在 設備場景中的。

????? 作為GDI對象,設備相關位圖被創建后,需要把它選入到設備場景,這和向設備場景選入一個畫筆完全一樣,可以用SelectObject函數,只是在先前 要寫畫筆句柄的參數位置上寫下位圖句柄即可。當你不再使用一個被你創建的位圖的時候,應當用DeleteObject函數刪除之,以釋放系統資源。為了刪 除它,你還需要用SelectObject函數把原來的位圖選回到設備場景,這樣你為設備場景選入過的位圖就退了回來,處于可以刪除或重新被選入的狀態。 這需要你在為設備場景選入一個你自己創建的位圖時保存舊的(原先的)位圖句柄。反正這和操縱畫筆或畫刷之類的GDI對象完全一樣。同樣,不可將一個位圖同 時選入兩個不同的設備場景。

????? 創建和使用位圖的常用技巧是,創建一個DC或多個DC以及相應的位圖,然后在后臺(用戶看不到)進行各種光柵運算和加工處理后,最后把形成的圖象一次性發 送到關聯設備場景中(關聯設備場景中的圖象會自動影射到視頻內存,如PictureBox和hDC所代表的內存。詳細情況請參考前期教程設備場景部分)中 (常用BitBlt),以獲取速度和最大限度地避免屏幕閃爍(后面給出了例子)。這主要是因為向屏幕輸出圖象的函數執行速度比較慢,而且圖象的加工過程對 用戶來說沒有必要看到的。為了創建若干個設備場景,你需要多次用CreateCompatibleDc函數(這也是最簡單的方法),它可以使你獲得一個與 指定設備場景兼容的設備場景。反正你應該保證創建位圖中所使用的hDC參數和創建設備場景中所使用的hDC參數保持一致。別忘了最后用DeleteDC來 刪除你自己創建的設備場景。在刪除設備場景前,應當從該設備場景中抽出為其選入的位圖,并將其銷毀。以上這種技巧在一本VC書上叫做“雙緩沖”,意識是把 操作分成前臺和后臺兩部分同時進行。

????? 接下來給出與設備有關位圖的函數吧,請您不要客氣隨便看。

?????????????????????????????? 與設備有關位圖函數

函??? 數????????????????????????????????????????? ?說??? 明
CreateBitmap?????????????????????? 創建一幅位圖,并選擇性地初始化位圖數據
CreateBitmapIndirect?????????? 在一個BITMAP數據結構的基礎上創建一幅位圖
CreateCompatibleBitmap???? 創建與指定設備場景兼容的一幅位圖
GetBitmapBits????????????????????? 獲取位圖的像素位數據
GetBitmapDimensionEx??????? 取得一幅位圖的大小
LoadBitmap???????????????????????? 從資源文件中載入一幅固有的系統位圖
LoadImage????????????????????????? 一個常規用途的函數,用于裝載圖象、圖標及指針
SetBitmapBits????????????????????? 根據一個數據緩沖區設置位圖圖象。使用GetDIBits吧,更好用。
SetBitmapDimensionEx??????? 設置位圖的大小。??????????????????

????????????????????????? 位圖傳輸函數

函??? 數??????????????????????????????? 說??? 明
BitBlt????????????????????? ?位塊傳輸。將一個圖象區域傳到另一個圖象區域。必須掌握到熟背。
PatBlt????????????????????? 圖案塊傳輸。根據指定圖案(就象由一個刷子表示的那樣)填充一個圖象區域。
GetStretchBltMode? 進行伸縮處理時,用于判斷Windows刪除線段或像素方式。
MaskBlt?????????????????? 執行復雜的圖象傳輸,同時進行掩模(MASK)處理
PlgBlt????????????????????? 平行四邊形塊傳輸。允許我們伸縮、扭曲及放置一幅圖象。這個函數我在Windows95中用過,可始終沒有成功。功能看起來很不錯。
SetStretchBltMode? 進行伸縮處理時,用于決定Windows刪除線段或像素的方式。
StretchBlt??????????????? 將一幅位圖從一個設備場景復制到另一個。源和目標DC相互間必須兼容。這個函數會在設備場景中定義一個目標矩形,并在位圖中定義一個源圖象。源矩形會根據需要進行伸縮,以便與目標矩形的大小相符。
???? DIB并不存在于設備場景中(device context)。在它被選入設備場景或在它被畫入設備場景前,DIB必須被翻譯為DDB。你可以用StretchDIBits函數完成這一工作。本教程 提供了一個源代碼例程。通過它你能夠學習DDB到DIB,再從DIB到DDB的轉化過程。程序名為vbplay46.vbp。實際上是源碼解析中的演示程 序第46號。


????? 為了使用好位塊傳輸,你需要掌握圖形光柵方面的知識。其內容和上一期給出的位操作運算在其原理上相同。只是需要進行的并不是一個、兩個或四個直接等的數 據,而是一個位圖數據。位圖數據也是以字節來構成的,只是長度要長得多。為了使用光柵運算,你需要有一個自己的表格,以便從中查找并計劃光柵方案。以下是 Dan的書中的,也是我最喜歡的一個表(詳細的中文說明格式的表不如它好用)∶

SRCCOPY?????? Destination = Source

SRCPAINT????? Destination = Source OR Destination

SRCAND??????? Destination = Source AND Destination

SRCINVERT???? Destination = Source XOR Destination

SRCERASE????? Destination = Source AND (NOT Destination)

NOTSRCCOPY??? Destination = NOT Source

NOTSRCERASE?? Destination = (NOT Source) AND (NOT Destination)

MERGECOPY???? Destination = Source AND Pattern

MERGEPAINT??? Destination = (NOT Source) OR Destination

PATCOPY?????? Destination = Pattern

PATPAINT????? Destination = (NOT Source) OR Pattern OR Destination

PATINVERT???? Destination = Pattern XOR Destination

DSTINVERT???? Destination = NOT Destination

BLACKNESS???? Destination = 0

WHITENESS???? Destination = All bits set to 1

?????? 可惜,少一個很重要的。可別擔心,本老師有辦法∶

&H220326???? Destination=(NOT Source) AND Detination

注∶以上,Destination指的是目標位圖(BitBlt中前一個DC中的),Source是來源位圖(BitBlt中的后一個DC中的)。

???????以下給出一個例子,是我常用的一個函數。它用來實現透明復制位圖。你可以在你的程序中直接粘貼使用。它總結了本節給出的內容。

作者(xing)? 1999年10月17日 整理備用

功能∶

? 透明復制位圖

參數表∶

? hDestDC --------? Long,目標設備場景

? x,y ------------? Long,對目標DC中目標矩形左上角位置進行描述的那個點。用目標DC的邏輯坐標表示

? nWidth,nHeight -? Long,欲傳輸圖象的寬度和高度

? hSrcDC ---------? Long,源設備場景。如光柵運算未指定源,則應設為0

? xSrc,ySrc ------? Long,對源DC中源矩形左上角位置進行描述的那個點。用源DC的邏輯坐標表示

? TransColor -----? OLE_COLOR,被透明處理的顏色

Sub TransBlt(ByVal hDestDC As Long, ByVal x As Long, ByVal y As Long, ByVal nWidth As Long,???????????????? ByVal nHeight As Long, ByVal hSrcDC As Long, ByVal xSrc As Long, ByVal ySrc As Long, ByVal TransColor As OLE_COLOR)

?????? Dim dl????????????? As Long

??????

?????? Dim OrigColor?????? As Long

?????? Dim OrigMode??????? As Long

??????

?????? Dim saveDC????????? As Long

?????? Dim maskDC????????? As Long

?????? Dim invDC?????????? As Long

?????? Dim resultDC??????? As Long

??????

?????? Dim hSaveBmp??????? As Long

?????? Dim hMaskBmp??????? As Long

?????? Dim hInvBmp???????? As Long

?????? Dim hResultBmp????? As Long

??????

?????? Dim hSavePrevBmp??? As Long

?????? Dim hMaskPrevBmp??? As Long

?????? Dim hInvPrevBmp???? As Long

?????? Dim hDestPrevBmp??? As Long

??????

?????? saveDC = CreateCompatibleDC(hDestDC)

?????? maskDC = CreateCompatibleDC(hDestDC)

?????? invDC = CreateCompatibleDC(hDestDC)

?????? resultDC = CreateCompatibleDC(hDestDC)

??????

?????? '按照規定的格式創建一幅與設備有關位圖

?????? hMaskBmp = CreateBitmap(nWidth, nHeight, 1, 1, ByVal 0&)

?????? hInvBmp = CreateBitmap(nWidth, nHeight, 1, 1, ByVal 0&)

???

?????? '創建一幅與設備有關位圖

?????? hResultBmp = CreateCompatibleBitmap(hDestDC, nWidth, nHeight)

?????? hSaveBmp = CreateCompatibleBitmap(hDestDC, nWidth, nHeight)

??????

?????? hSavePrevBmp = SelectObject(saveDC, hSaveBmp)

?????? hMaskPrevBmp = SelectObject(maskDC, hMaskBmp)

?????? hInvPrevBmp = SelectObject(invDC, hInvBmp)

?????? hDestPrevBmp = SelectObject(resultDC, hResultBmp)

??????

?????? '產生Mask圖象

?????? OrigColor = SetBkColor(hSrcDC, TransColor)

?????? dl& = BitBlt(maskDC, 0, 0, nWidth, nHeight, hSrcDC, xSrc, ySrc, vbSrcCopy)??????

?????? TransColor = SetBkColor(hSrcDC, OrigColor)

?????? 'invDC的圖象將與maskDC圖象相反

?????? dl& = BitBlt(invDC, 0, 0, nWidth, nHeight, maskDC, 0, 0, vbNotSrcCopy)

?????? 'resultDC的圖象將成為被寫位置的圖象

?????? dl& = BitBlt(resultDC, 0, 0, nWidth, nHeight, hDestDC, x, y, vbSrcCopy)

?????? 'resultDC中,需要新寫的位置將變為黑色

?????? dl& = BitBlt(resultDC, 0, 0, nWidth, nHeight, maskDC, 0, 0, vbSrcAnd)

?????? dl& = BitBlt(saveDC, 0, 0, nWidth, nHeight, hSrcDC, xSrc, ySrc, vbSrcCopy)

?????? 'resultDC中不被寫入的顏色成為黑色

?????? dl& = BitBlt(saveDC, 0, 0, nWidth, nHeight, invDC, 0, 0, vbSrcAnd)

?????? '將兩幅圖合并起來

?????? dl& = BitBlt(resultDC, 0, 0, nWidth, nHeight, saveDC, 0, 0, vbSrcInvert)

?????? '完工后輸出

?????? dl& = BitBlt(hDestDC, x, y, nWidth, nHeight, resultDC, 0, 0, vbSrcCopy)

??????

?????? SelectObject saveDC, hSavePrevBmp

?????? SelectObject resultDC, hDestPrevBmp

?????? SelectObject maskDC, hMaskPrevBmp

?????? SelectObject invDC, hInvPrevBmp

??????

?????? DeleteObject hSaveBmp

?????? DeleteObject hMaskBmp

?????? DeleteObject hInvBmp

?????? DeleteObject hResultBmp

??????

?????? DeleteDC saveDC

?????? DeleteDC maskDC

?????? DeleteDC invDC

?????? DeleteDC resultDC

End Sub

函數需要的API

Private Declare Function BitBlt& Lib "gdi32" (ByVal hDestDC As Long, ByVal x As Long,??????? ByVal y As Long, ByVal nWidth As Long, ByVal nHeight As Long, ByVal hSrcDC As Long,???????? ByVal xSrc As Long, ByVal ySrc As Long, ByVal dwRop As Long)

Private Declare Function CreateCompatibleBitmap& Lib "gdi32" (ByVal hdc As Long,??????? ByVal nWidth As Long, ByVal nHeight As Long)

Private Declare Function CreateCompatibleDC& Lib "gdi32" (ByVal hdc As Long)

Private Declare Function DeleteObject& Lib "gdi32" (ByVal hObject As Long)

Private Declare Function SelectObject& Lib "gdi32" (ByVal hdc As Long, ByVal hObject As Long)

Private Declare Function SetBkColor& Lib "gdi32" (ByVal hdc As Long, ByVal crColor As Long)

Private Declare Function CreateBitmap& Lib "gdi32" ( ByVal nWidth As Long, ByVal nHeight As Long, ByVal nPlanes As Long, ByVal nBitCount As Long, lpBits As Any)??

?四、關于ImageIo.dll

????? 在前一段時間,有很多朋友來信問我如何把BMP圖象或圖片框中的圖象保存為JPG或GIF格式文件。我還沒有回答過一次。因為我始終沒有找到稱心的DLL (動態連接庫)。最近3個月,我一直學了VC,也算是勉強入門。我發現VC界的很多人都在使用一個叫ImageLoad.dll的動態連接庫來完成這些工 作。可惜,這個動態連接庫到VB就不好使了。因為其函數使用了指針(而不是句柄)。然而,可但是,只要本老師在,你就可以用這個動態連接庫了。本老師寫了 一個叫ImageIo.dll的動態連接庫,用VC編寫的。此連接庫完全面對VB編寫的,自然參數上完全考慮了VB用戶。該連接庫可以把你在VB中傳送的 句柄適當地改成指針并再調用ImageLoad.dll這個動態連接庫,然后把處理結果以VB能夠接受的方式返回給VB用戶。另外一點是, ImageLoad.dll的函數比較低級的,用起來也不方面。為此,我在ImageIo.dll中寫了一些代碼,使得使用起來更加方便,更加可靠。

????? 然而,也出現了一個接著一個的讓我傷心的事情。第一個是,函數不能讀取部分GIF格式的文件(有些可以讀出,但不是動畫,只有一張靜態畫面);另一個是無 法存成GIF格式,最后一點就古怪了,當像素位不等于24的時候位圖文件不能存成JPG格式。該連接庫可以操縱6中格式文件,有BMP,GIF,JPG, PCX,TGA,TIF。我對TGA和TIF沒有怎么考慮過,因為在GIF中出現一些問題后感到這個ImageIo.dll不能使其功能齊全了,而且 TGA和TIF也并不是常用文件,也就不大測試了。那么,是因為我還不懂VC嗎?不,并不是我寫錯了代碼,而是該動態連接庫提供的演示程序同樣不能解決我 遇到的問題。據圖象處理方面的書上說,一個JPG庫代碼,需要一個人用半年的時間才能寫完。我總不能花半年去寫一個這種庫文件吧。其實我手里有一個,而我 卻懶得去用它。

????? 我考慮了,你能拿ImageIo.dll能做什么?想編寫一個圖象處理軟件?如果你這樣想的話,那么我建議你不要用它,如果你真的想編一個圖象處理軟件, 最好還是自己從頭編寫一個完整的DLL文件。我想,算是出了著名的圖象處理軟件的公司都是如此。而且我建議你不要用VB來編寫這種軟件,不適合。我想,對 絕大部分VB屆的朋友來講,對JPG的需求無疑是將一些圖象文件壓縮起來,以節約磁盤空間。比如,你需要編寫一個人事檔案管理系統,把人員照片掃描后保存 成JGP文件。那么我想,你用這個ImageIo.dll文件,算是很不錯的。我曾經在網絡上下載過一個DLL文件,但其壓縮質量實在差,而 ImageLoad.dll卻做得很出色。

?? 你在本教程的打包文件中可以找到一個Test.vbp文件,正是對這個ImageIo.dll寫的。有關ImageIo.dll的技術文檔,現給出如下,歡迎提一些寶貴意見∶


ImageIo.dll

???? ?本動態連接庫是為了解決VB用戶不能使用ImageLoad.dll動態連接庫文件而編寫的。它可以作為一個橋梁,使VB用戶能夠使用 ImageLoad.dll。但它并不是對ImageLoad.dll的簡單轉化,而是添加了諸多代碼,使其用起來更加簡單、方便。它可以幫助用戶完成 Bmp,Gif,Jpg 等多種圖象文件的數據的讀取和存盤工作。

制作者∶Xing

編程環境∶VC6.0

日期∶1999,12,20

常量∶

1∶文件格式

Const IMAGETYPE_NONE = 0???? '未知格式

Const IMAGETYPE_BMP = 1????? 'bmp 文件格式

Const IMAGETYPE_GIF = 2????? 'gif 文件格式

Const IMAGETYPE_PCX = 3????? 'pcx 文件格式

Const IMAGETYPE_TGA = 4????? 'tga 文件格式?

Const IMAGETYPE_JPG = 5????? 'jpg 文件格式

Const IMAGETYPE_TIF = 6????? 'tif 文件格式

Const IMAGETYPE_FIRSTTYPE = IMAGETYPE_BMP???? '第一個文件格式?? (bmp) = 1

Const IMAGETYPE_LASTTYPE = IMAGETYPE_TIF????? '最后一個文件格式 (tif) = 6

函數∶

GetImageType

VB∶

?? Private Declare Function GetImageType Lib "ImageIo.dll" (lpsFilename As String) As Long

VC∶

?? __API int GetImageType(LPTSTR & pszFilename);

說 明∶

??? 獲取圖形文件類型

返回值∶

??? 文件類型 0--6,0 表示出錯或未知文件類型

參數∶

??? lpsFilename? String 文件名

ImageLoad

VB∶

??? Declare Function ImageLoad Lib "ImageIo.dll" (lpsFilename As String, ByVal hDC As Long, ByVal nX As Long, ByVal nY As Long) As Long

VC∶

?? _API HGLOBAL ImageLoad( LPTSTR & pszFilename,HDC hDC, int nX, int nY);

說明∶

??? 裝載一個圖象文件,并必要時輸出到指定的DC

返回∶

??? 返回指向裝有圖象文件數據的一個緩沖區的指針。不管你打開任何文件,裝載到緩沖區的數據都已經是轉換為DIB格式,也就是說和BMP文件一樣。在不在使用該緩沖區的時候,應該用API函數GlobalFree或者本動態連接庫提供的KillImage函數釋放掉。

參數∶

KillImage:

VB∶

??? Declare Function KillImage Lib "ImageIo.dll" (ByVal hDib As Long) As Long

VC∶

??? __API HGLOBAL KillImage(HGLOBAL hDib);

說明∶

??? 本函數與API函數GlobalFree在其功能上完全一樣,當然可以改用GlobalFree 。VC代碼如下∶

HGLOBAL CImageIoApp::KillImage(HGLOBAL hDib)

{
?return(GlobalFree(hDib));
}
注∶如果成功返回0,否則返回hDib。

ImageDraw:
VB∶
??? Declare Function ImageDraw Lib "ImageIo.dll" (ByVal hDib As Long, ByVal hDC As Long, ByVal nX As Long, ByVal nY As Long) As Long
VC∶
??? __API BOOL ImageDraw(HGLOBAL hDib,HDC hDC, int nX, int nY );
說明∶
?? 將圖象輸出到指定DC。
返回值∶
?? 成功返回1,否則返回0。
參數∶
?? hDib 一個指向緩沖區的指針。該緩沖區中裝有DIB位圖數據。可以用LoadImage函數獲取它。也可以自己創建。

?? hDC 需要繪制圖象的設備常場景句柄
?? nX,nY 設備場景中被繪制圖象的左上角的坐標。

GetInfo:
VB:
??? Declare Function GetInfo Lib "ImageIo.dll" (ByVal hDib As Long, BIH As BITMAPINFOHEADER) As Long
VC:
??? __API BOOL GetInfo(HGLOBAL hDib,BITMAPINFOHEADER & BIH);
說明∶
?? 本來是想用GetImageInfo作為函數名,但VC中用此名發生一些沖突,改用了GetInfo。此函數用于獲取DIB文件中的BITMAPINFOHEADER結構信息。
返回值∶
?? 成功返回1,失敗返回0。

參數∶
?? hDib 一個指向緩沖區的指針。該緩沖區中裝有DIB位圖數據。
?? BIH 一個準備裝載信息的BITMAPINFOHEADER結構。傳遞前沒有必要設置其biSize(結構長度)成員變量。


__API BOOL ImageSave(LPTSTR & pszFilename,HGLOBAL hDib,int nType,int nQuality);

只適用于BMP,JPG,TGA

五、圖表與指針

?????? 圖表是一幅小位圖,圖表文件的常用附加名是ICO。它的一項特殊能力就是不僅允許任何像素使用某種位圖顏色,也允許它們使用屏幕或反轉的屏幕顏色。 Windows3.x中的圖表通常是32×32像素大小,但在Windows95(98)和NT4.0中,對16×16和64×64像素圖標的支持已非常 普遍了。圖標實際包含兩幅獨立位圖。第一幅位圖可能是單色的或彩色的,其中包含了要與顯示屏幕圖象合并到一起的一幅圖象。這種合并是通過異或(XOR)操 作實現的。我們將其稱為XOR位圖,其中包含了一個掩模。在顯示屏幕圖象與XOR位圖合并到一起之前,這個掩模會先與屏幕圖象進行AND操作,然后才是 XOR位圖和屏幕進行XOR。

????? 鼠標指針在其內部結構上和圖表完全一致,只是它多出一個斎鵲銛,即該指針位圖中代表指針位置的準確坐標點。該文件的附加名一般是CUR。能對圖標進行操作的大部分函數也能對(鼠標)指針。

????? 圖標被裝入到內存之后將產生一個句柄。但需要注意的是,圖表并不屬于GDI對象,而是屬于USER對象。所以有可能在應用程序間共享圖表資源。


??? 以下給出了有關圖標和指針的API函數

函??? 數????????????????????????????? 說??? 明
CopyCursor???????????????? 復制指針,使用CopyIcon
CopyIcon???????????????????? 復制圖表。
CreateCursor????????????? 創建指針
CreateIcon???????????????? 創建圖表
CreateIconIndirect????? 在一個ICONINFO結構的基礎上創建一個圖標
DestroyCursor??????????? 清除指針
DestroyIcon?????????????? 清除圖表
DrawIcon??????????????????? 向指定設備場景繪制圖標
DrawIconEx??????????????? 用附加的選項描繪圖標
ExtractIcon???????????????? 從一個可執行文件或DLL中載入圖標
ExtractAssociatedIcon?載入指定文件內部或與它相關的一個圖標
GetIconInfo???????????????? 取得關于圖標的信息
LoadCursor???????????????? 從一個資源文件中載入指針,或者裝載一個固有系統指針。
LoadCursorFromFile??? 通過讀取標準指針文件(.cur)或動畫指針(.ani)創建指針
LoadIcon??????????????????? 從一個資源文件中載入圖表,或載入一個固有系統圖標。

六、顏色區間??

???? RGB
???? 我們已經知道IBM兼容視頻系統上的顏色由RGB三色體現。RGB三色中的每一種顏色都包含顏色中的紅色、綠色和藍色成分的值。這三部分的值組合在一起確 定屏幕上所顯示的顏色。RGB是常見顏色空間,紅色、綠色和藍色被認為是基本色,不能夠被進一步的分解。而色彩系統可以分為兩類,加法色彩系統和減法色彩 系統。加法色彩系統,例如RGB系統中的顏色可通過將顏色添加到黑色中創建新顏色。添加的顏色越多,結果顏色也就越趨向于白色。足夠的主要顏色可以創建出 純白色,而缺少所有主要顏色只能得出純黑色。

???
???? ?一個RGB是用四個字節來構成的。好象該字節中除了表示紅、綠、藍三個字節以外的剩余一個字節是多余的,其實不然。從一個API繪圖函數引用一種顏色, 都不只是用容納紅、綠、藍顏色值的三個字節,而且還用第四個字節(最高字節)。這個字節包含一個標志值。順序排列的最低字節包含紅色值,其次兩個字節分別 是綠色和藍色,而最后位字節則包含一個標志。此標志用于指示是否引用一個高頻脈動色,或者一個調色板匹配色,或者一個明顯的調色板索引。這個字節的值決定 順序排列的三個低位字節如何選擇一種顏色。當你想指定一個建立這些對象的API函數中的筆或刷子的顏色時,可以把高位字節設置為如下表中的三個值之一。

高字節值?????????? 結果
&H00????????????? 在對象繪出后,Windows高頻脈動20個保留色,這叫RGB顏色引用。(普通情況)
&H01????????????? 最低字節不是紅色的值,而是指一個調色板項的數目或索引值,因此Windows使用在那個調色板項中的顏色,中間的兩個字節(字節1和2,原綠色和藍色字節)應保持值&H00,這叫調色板索引引用。
&H02????????????? Windows定位與由紅、綠、藍決定的在三個順序排列的低位字節指定的顏色最相匹配的調色板項。這叫作調色板RGB引用。
因此,嚴密地講,一個RGB值是通過以下方式構成的。

Dim RGBColor As Long

RGBColor=RedValue + (GreenValue * 256) + (BlueValue? * 65536) + _

???????? (FlagValue * 16777216)

??? 這種技術的使用請參考本教程附帶的程序Palette.vbp。??

????? CMY
????? 在計算機領域還存在幾種RGB顏色區間的變種。一個是叫做CMY的顏色區間。指的是藍綠色、紫紅色以及黃色。打印機和照相機通過加墨乳劑渲染顏色時,采用 的是減法色彩系統。它由將顏色色素沉淀到白色紙上的大部分硬拷貝設備使用,例如激光打印機和噴墨打印機。當被現實時,這三種顏色都吸收與自身互補的淺顏 色。藍綠色吸收紅色,紫紅色吸收綠色,黃色吸收藍色。例如,可增加黃色墨的量,圖象中的藍色量減少。RGB模式和
????? CMY模式之間的轉換是非常簡單的。為了計算藍綠色,請從255中減去RGB中的紅色值;對于紫紅色,則從255中減去RGB中的綠色值;至于黃色嘛, 喔!你已經猜測到了,即從255中減去RGB中的藍色值。例如,RGB(240,12,135)的CMY值分別是15,243和120。然而,這種顏色區 間大概并不是我們所感興趣的,當然也有人感興趣的。但下面給出的顏色區間更吸引我們,起碼是我。

????? HSV(顏色,濃度,亮度)
????? HSV是眾多色彩系統中的一個。如果你打開了通用對話框的顏色對話框家會發現,除了RGB調解器,還有一個類似的但其值很古怪的調解器。很多3D動畫軟件 和圖象處理軟件中,這種HSV顏色空間也被廣泛采用。它的基本原理是更改顏色屬性值以創建新顏色,而不是使用顏色本身的混合色。色調般指顏色,例如紅色、 桔黃色、藍色等等。飽和度(也被稱為濃度)指示指示色調中的白色量∶全飽和和色調不包含白色,顯示純色。而部分包含色調根據所混合的白色的情況,顯示的顏 色淺。例如,50%的飽和度的紅色色調顯示為粉紅色。值(也被稱為亮度)是顏色自身的發光度,也就是它所發出的光的多少。高飽和度色調非常亮,而低飽和度 色調非常暗。

????? HSV與畫家以及其他通過將白色、黑色和灰色增加到純色素中以創建色彩、底紋、以及色調的藝術家們使用的色彩系統非常相似。色彩是純色,是組合了白色的全 飽和度顏色,而底紋是組合了黑色的全飽和度顏色。如果使用這兩種顏色混合,那么HSV的飽和度是白色量,值是黑色量,色調是增加了黑白兩色后的顏色。
?更詳細的情況不再這里說了,說起來也費勁,也不常用,以后做個程序放到站點里。

七、關于調色板的基礎知識

????? 關于調色板,很少書談得深入,Dan的書上講得也就那么一點點,少得實在可憐,連一個有關調色板的API都沒有給出。為了弄懂它,我也付出了不少時間。當 然,現在就給你講解講解。把各個角落中學來的知識總結在這里,想來也是滿不錯的主意感興趣的朋友可以看,不看也無訪。。

????? 用戶顯示于簡報的圖片很可能需要大量不同的顏色。可以想象∶艷綠色可滾動的愛爾蘭小山的照片會比火星的圖片需要更多的套不同的顏色、若用戶想同時顯示這兩 幅圖象,會碰到一個很現實的問題。即使不限制逼真度,甚至用256種顏色,用戶都不可能有足夠的濃淡級別同時提供給兩個圖象。值得慶幸的是,若一次只有一 幅圖片顯示,用戶可以要求Windows交換顏色,使每個圖象能激活自己的256種顏色選擇--它自己的顏色調色板。

??????? 那么,什么是調色板?它是一種Windows對象,但適當地,如果你把它想象成一種特定長度的結構數組,對理解會有幫助的。還記得RECT結構吧,如果聲明
???
??? Dim MyRect(0 to 255) as RECT
???
????? 以上,我們定義了一個RECT結構數組。當然,所有的笨蛋都知道調色板并不是RECT的。調色板結構比RECT要復雜一點。首先調色板是一種叫LOGPALETTE 的結構來構成的。如下∶

??? Type LOGPALETTE
???????? palVersion????????? As Integer????
???????? palNumEntries?????? As Integer????
???????? palPalEntry ( 1 )?? As PALETTEENTRY

??? End Type

??????? palVersion 指Windows的版本號,數值300代表Windows3.0或3.1,不過請記住,在Windows9X中仍然使用該數值,即300。第二個參數值調 色板登錄項的數目。也就是說,它指的是第三個參數palPalEntry的元素個數。這個例子中顯然是1嘍!那么在以下的情況呢?

??? Type LOGPALETTE
???????? palVersion????????? As Integer????
???????? palNumEntries?????? As Integer????
???????? palPalEntry ( 255 )?? As PALETTEENTRY
??? End Type
???
??????? 當然是256。但這也不一定,比255小也無妨。比如在4位模式中,應當是16,就算你聲明為palPalEntry ( 255 ),創建調色板時,函數只使用其前面的0到15的數組元素。

??????? 接下來,正如你已經看到的那樣,第三個參數是一種結構數組。當然不是RECT嘍,而是 PALETTEENTRY。它的內容如下∶

??? Type PALETTEENTRY
???????? peRed?????????????? As Byte??????
???????? peGreen???????????? As Byte??????
???????? peBlue????????????? As Byte??????
???????? peFlags???????????? As Byte??????
??? End Type

?????? Red,Green,Blue,不用我說了吧?最后一個peFlags是什么呢? 它是包含用來描述調色板項類型的一個或多個顏色標志的值。如下表顯示了個些標志∶

標志???????????????????????? ? 值??????????????????????? 說???? 明
PC_EXPLICIT?????????? &H2????? 創建一個調色板條目,該調色板條目在系統調色板中指定一個索引而不是顏色。有顯示系統調色板內容的程序使用。
PC_NOCOLLAPSE??? &H4???? ?創建一個被影射到系統調色板中未用條目上的調色板條目,即使該顏色條目已經存在。用于當兩個條目影射到同一顏色上時確保調色板顏色的唯一性。
PC_RESERVED???????? &H1????? 創建一個某應用程序專有的調色板條目。當PC_RESERVED條目被添加到系統調色板時,它不被影射到其他邏輯調色板中的顏色上,即使這些顏色匹配。由執行調色板動畫的程序使用。
???

????? 在8位顯示器上,一個像素的顏色是通過查看一個色彩表中自己的8位像素值來決定的。一個調色板包含一套(數組)24位RGB色彩值(peRed, peGreen,peBlue)。一個調色板中的顏色數目最多256個(從0到255)。每個顯示內存的像素選項包含一個從0到255的值,這個像素指定 哪一個調色板項被用來為這個像素著色。要改變一個像素的顏色,用戶有兩個選擇∶其一,可改變這個像素的顏色索引值;其二,改變調色板登錄項上的顏色值 (RGB值,24位)。在后一種情況下,這種改變使得所有引用這個調色板登錄項的像素均同時引起顏色變化。


????? 然而你應當清楚,初始化一個LOGPALETTE變量,并不能說它是一個調色板。因為調色板是GDI對象的一員,就像畫筆、畫刷之類的GDI對象一樣,必須通過特定的函數創建它。創建后,我們可以獲得一個調色板句柄,那么這個句柄所代表的就是真正的調色板了。

????? 每個用戶顯示的圖象能帶自身的色彩調色板。另外,每個活動窗口能根據自身目的來操作調色板。但是,記住,應用于全屏幕,具有256種顏色的限制,這并不是 對于每個窗口或應用程序而言。對于這些,還得用Windows調色板管理器來進行管理。Windows調色板管理器決定在給定時間哪個窗口擁有調色板控 制。在前臺活動的窗口常常擁有優先權。若此窗口不使用調色板,優先權則傳給Z向(Z-order,聽說過吧?它垂直于屏幕。因為我們常把屏幕坐標用X和Y 來表示。而三維坐標是用X,Y,Z來表示的。這里指的Z并不是說真的三維坐標中的那個Z軸,而只是一個形象的比喻中被命名的,是指桌面窗口垂直方向)的下 一個窗口。一旦帶最高優先權的窗口認清自己的調色板作為前臺調色板,其他窗口將被依次通知為背景調色板,這是由調色板管理器來通知。在一般情況下調色板管 理器是自動工作的。

?????? 剛才我說了一句“認清”這么一個單詞。那么,什么叫“認清一個調色板”呢?每個用戶顯示的圖象能帶自己的調色板(幾個調色板能被同時放在內存中)。存放在 內存的調色板叫做邏輯調色板。在用戶顯示系統的調色板決定著哪一種顏色實際出現在屏幕上,這個調色板被叫做系統調色板或者硬件調色板(集中精力!系統調色 板是硬件層的)只有一個硬件調色板并且調色板管理器保留一個它的復制調色板。當一個應用程序激活自己的顏色時,它必須在設備場景(device context)中選入邏輯調色板并認清它。這意味著,應用程序必須要求調色板管理器裝載它的邏輯調色板給系統(硬件)調色板。所以,所謂“認清”實際上 是一種“拜托”,或者就象日本人常說的“多多關照”∶請多多關照,使用我的調色板吧。因為只有這樣,應用程序才能顯示出符合自己口味的顏色。

????? 由于調色板的大小會發生改變,調色板管理器不會傻呵呵地硬著從邏輯調色板復制一個固定大小的256種顏色元素的塊給硬件調色板。而是,這個調色板管理器只 裝載它在每個邏輯調色板中找到的那么多的顏色。系統調色板可以容納多個邏輯調色板,只要顏色總數不超過256即可。Windows為它靜態顏色(用于畫按 鈕、邊框、文本、圖表等等,叫做系統保留色。可以用GetSystemColor函數獲取的那幾種)保留了20種調色板登錄項。因此,只為我們剩下236 個可變顏色位置。但這并不意味著一個調色板將只包括它需要的236種顏色來支持它的位圖。把8位圖象調色板轉化為Windows本體調色板是明智的。 Windows本體調色板包括20種保留色,特別是用戶打算用調色板繪圖時,否則不能用保留的顏色。本教程附帶的Palette.vbp程序如果在16位 或24位模式下運行,將只顯示這20種顏色。若用戶愿意,可以用系統靜態顏色把可定義的顏色范圍擴展到256。GDI就是為這個目的提供了特殊函數。但這 將干擾其他活動程序的基本命令,因為它將動態改變活動程序的外觀。因此除非你的程序獨占整個屏幕(比如一些游戲就是這樣的,不與其他窗口程序同時顯示), 否則這種做法是不應采取的。

????? 一個邏輯調色板中的顏色在系統調色板中一般不占據與在邏輯調色板中相同的位置。這是因為在認清一個調色板的時候,調色板管理器建立一個交叉引用表,也叫做 調色板映像(palette mapping),正如它把一個邏輯調色板加載給系統調色板。這個表用于GDI繪圖函數把像素從邏輯調色板翻譯到系統調色板索引。很好理解的。可以把它想 象為一種兩列的表,左列中填寫有邏輯調色板的顏色索引值,對應的右列中填寫有系統調色板的索引值。就像英語詞典里一個英語單詞對應著一個中文單詞。記住, 計算機中所謂映像都是指這種情況。比如消息映像,它為各種消息值調用相應的消息處理函數。接著來。正如前面所解釋的,一個像素的顏色是通過在顏色表中查看 它的值來決定的。在設備相關位圖和DIB(設備無關位圖)的情況下,這是256種顏色位圖文件的最通用窗體,組成位圖像素數據的字節包含在文件的顏色表中 的登錄項的值。當GDI把圖象從文件轉移到屏幕時,也就是說,從一個設備相關位圖轉移到另一個依賴于設備的位圖或DDB(設備相關位圖)時,它用調色板映 像來改變像素值,使它能引用系統調色板中當前正確的顏色。為一個邏輯調色板建立的調色板映像叫做前臺映像。

?????? 若從活動窗口沒有獲取所有調色板登錄項,則剩下的位置被填入從非活動窗口拿來的顏色,直到或者所有的位置占據,或者沒有其他的窗口要求識別自己的調色板位 置。若前臺窗口需要所有的236個自由顏色位置,則所有的非活動窗口必須受前臺活動調色板的支配。調色板管理器還能自動地執行這個服務,這是通過把非活動 窗口中的顏色映像給當前被辨認出的調色板中的最匹配的顏色來實現的。這就是所謂的后臺影像。這有時會產生有趣的但對大多數人來講是糟糕的結果。比如,最匹 配色=很不匹配,其他色=更不匹配的情況下。另外,你需要記住的一點是,當焦點每次從一個基于調色板的應用程序更換到另一個基于調色板的應用程序時,整個 辨認過程重復執行一次。

???? DIB中的像素包含與位圖一起的邏輯調色板中的顏色索引,通常一個顏色表存在于DIB文件中;DDB中的像素包含系統調色板中的顏色索引。當用戶是根據索 引值來訪問一種顏色時,用戶是在邏輯調色板中根據它的位置來訪問顏色的,而不是在系統調色板中的它的位置。也就是說,你只能用邏輯調色板的索引值。因此, 假如邏輯調色板中不存在20中系統顏色,那么我們就無法訪問它,這并不是說它不存在。因此,如果你需要系統顏色,就應當把這些顏色值裝載的邏輯調色板