15.1 DIB 文件格式
摘錄于《Windows程序(第5版,珍藏版).CHarles.Petzold 著》P569
??????? 有趣的是,DIB 格式并不是從 Windows 起源的。它最早是在微軟和 IBM 于上世紀 80 年代中期開始合作開發的操作系統 OS/2 1.1 版本中定義的。OS/2 1.1 于 1988 年發布,是第一個擁有像 Windows 一樣的圖形用戶界面(叫 Presentation Manager,PM)的 OS/2 版本。PM 包含一套圖形編程接口,其中定義了位圖的格式。
??????? OS/2 的這種位圖格式后來被用到 Windows 3.0(1990 年發布)中,從此被稱作 DIB。Windows 3.0 還包含了最初 DIB 格式的一個變形,它最后成為了 Windows 環境下的標準。Windows 95(和 Windows NT 4.0)以及后來的 Windows 98(和 Windows NT 5.0)又在此基礎之上做了一些改進,我會在本章的稍后部分討論。
??????? 最好是先把 DIB 想象為一種文件格式。DIB 文件的擴展名是 BMP,在極個別情況下也可以是 DIB。Windows 應用程序中用到的位圖(例如一個按鈕的圖像)一般都是作為 DIB 文件存儲在可執行文件的只讀資源中。圖標和鼠標指針也是 DIB 文件,只不過有一些微小的不同。
??????? 程序可以把 DIB 文件除去開始的 14 個字節外,整個載入到一塊連續的內存區域中。這有時也被稱為“緊湊 DIB 格式的位圖”(packed-DIB format)。Windows 下的應用程序可以用這種格式創建畫刷或者通過 Windows 剪貼板來交換圖像。程序還擁有對該 DIB 內容的完全控制,可以對此 DIB 進行任意修改。
??????? 程序也可以在內存中創建自己的 DIB,然后把它們存到文件中。這些 DIB 中的圖像可以用 GDI 函數繪制。程序也可以直接對像素操作,在此過程中可以使用其他基于內存的 DIB。
??????? 一個 DIB 被載入到內存后,程序可以對該 DIB 數據調用一些 Windows API 函數,這些函數我會在本章中討論。與 DIB 相關的 API 調用并不多,主要都是用來在屏幕或者打印機上顯示圖像,還有一些是用來在 DIB 和 GDI 位圖對象之間進行互相轉換。
??????? 盡管有以上提到的這些功能,但還有很多很多和 DIB 相關的任務 Windows 操作系統并不支持。例如,一個程序可以存取一個 24 位的 DIB,現在它想把這個 DIB 轉換成一個擁有最佳匹配的 256 色調色板的 8 位 DIB。Windows 并不能替你做到這一點。而本章和第 16 章會演示怎樣完成一些 Windows API 并不支持的 DIB 相關任務。
15.1.1? OS/2 風格的 DIB
??????? 在尚未涉及太多細節之前,先來看一下和最早在 OS/2 1.1 版本中引入的位圖文件相兼容的 Windows DIB 文件格式。
??????? DIB 文件有四個主要部分:
- 文件頭
- 信息頭
- RGB 顏色表(有時可能沒有)
- 位圖的像素位
??????? 可以把前兩部分看做是 C 數據結構,把第三部分看做一個數組結構的數組。這些數據結構定義在 Windows 的頭文件 WINGDI.H 中。在內存中緊湊格式的 DIB 有三個部分:
- 信息頭
- RGB 顏色表(有時可能沒有)
- 位圖的像素位
它和存在與文件中的 DIB 一模一樣,唯一的區別在于沒有文件頭。
??????? DIB 文件——不是內存中緊湊格式 DIB——以一個 14 字節的文件頭開始,該文件頭的定義如下:
typedef struct tagBITMAPFILEHEADER // bmfh {WORD bfType; // 文件簽名,是 "BM" 或者 0x4D42DWORD bfSize; // 整個文件的長度WORD bfReserved1; // 必須是 0WORD bfReserved2; // 必須是 0DWORD bfOffsetBits; // 到位圖像素位的位移 } BITMAPFILEHEADER, * PBITMAPFILEHEADER; 這也許和在 WINGDI.H 中的定義不完全一樣,例如,注釋是我加的,但是二者功能上時一樣的。第一個注釋,"bmfh",是我推薦使用的這個數據結構的縮寫名稱。如果你在我的程序中看到一個變量叫做 pbmfh,那就是一個指向 BITMAPFILEHEADER 類型的結構的指針,或者是一個 PBITMAPFILEHEADER 類型的變量。??????? 這個結構有 14 字節長。它以兩個字母 "BM" 打頭,說明這是一個位圖文件。如果用十六進制的 WORD 值來表示,它的數值就是 0X4D42。“BM”之后是一個 DWORD,記錄了整個文件的大小,以字節為單位,該大小包含了文件頭的長度。在這之后的兩個 WORD 字段必須設置為零。(在鼠標指針文件中,采用非常相似于 DIB 的文件格式,而這兩個字段表示指針的“熱點”位置。)這個結構的最后一個字段是一個 DWORD,它指示了圖像的像素位在文件中的起始位置。這個值可以從 DIB 信息頭中推導出來,這里提供它只是為了方便。
??????? 在 OS/2 風格的 DIB 中,緊接著 BITMAPFILEHEADER 結構之后是一個 BITMAPCOREHEADER 結構,它提供了 DIB 圖像的基本信息。緊湊格式的 DIB 就是這個 BITMAPCOREHEADER 結構開頭,如下所示:
typedef struct tagBITMAPCOREHEADER // bmch {DWORD bcSize; // 結構大小 = 12WORD bcWidth; // 以像素計的圖像的寬度WORD bcHeight; // 以像素計的圖像的高度WORD bcPlanes; // = 1WORD bcBitCount; // 每個像素的位數 (1, 4, 8 or 24) } BITMAPCOREHEADER, * PBITMAPCOREHEADER; 對于這個結構,其名稱中的單詞“core”(核心)聽起來有一些奇怪,事實上也正是如此。它其實表面這是其他位圖格式的基礎(所以叫 “core”),其他的格式都由此衍生而來。??????? 在 BITMAPCOREHEADER 結構中的 bcSize 字段表明了這個結構本身的大小,在這里,它是 12 字節。
??????? bcWidth 和 bcHeight 兩個字段按像素為單位,給出了位圖的大小。盡管這兩個字段是 WORD 類型,所以能表示有 65535 個像素寬或者高的 DIB,但是你可能很少會遇到那樣大的圖像。
??????? bcPlanes 字段總是 1。從它開始被定義一直到現在始終是 1。它是我們在第 14 章遇到的早期 Windows GDI 位圖對象的遺留產物。
??????? bcBitCount 字段指明每一個像素的位數。對 OS/2 風格的 DIB 來說,它可以是 1, 4, 8 或者 24。DIB 中的顏色數等于 2^(bmch.bcBitCount),或者,用 C 語言的語法,就是:
1 << bmch.bcBitCount因此,bcBitCount 字段就等于:- 1 對應于雙色 DIB
- 4 對應于 16 色 DIB
- 8 對應于 256 色 DIB
- 24 對應于全彩 DIB
在提到“一個 8 位 DIB”時,我指的是一個 DIB 的每個像素有 8 位。
??????? 對于前三種情形(位數為 1、4 和 8),BITMAPCOREHEADER 后面是一個顏色表。24 位 DIB 沒有顏色表。顏色表是一個數組,數組中的元素是一個 3 字節長的 RGBTRIPLE 結構,對應于圖像中的每一種顏色:
typedef struct tagRGBTRIPLE // rgbt {BYTE rbgtBlue; // 藍色值BYTE rbgtGreen; // 綠色值BYTE rgbtRed; // 紅色值 } RGBTRIPLE; 一般都推薦把圖像中最重要的顏色排在顏色表的前面。我們在第 16 章會看到原因。??????? WINGDI.H 頭文件中還定義了下面的結構:
typedef struct tagBITMAPCOREINFO // bmci {BITMAPCOREHEADER bmciHeader; // 基本信息頭RGBTRIPLE bmciColors[1]; // 顏色表數組 } BITMAPCOREINFO, * PBITMAPCOREINFO; 這個結構把信息頭和顏色表結合在一起。盡管在這里 RGBTRIPLE 的數目看起來只有 1,但是你實際上在 DIB 文件中總會看到不止一個 RGBTRIPLE 結構。根據每一像素的位數,顏色表的大小總是 2、16 或者 256 個 RGBTRIPLE 結構。如果需要為一個 8 位 DIB 的 PBITMAPCOREINFO 結構分配內存,可以像下面這樣: pbmci = malloc(sizeof(BITMAPCOREINFO) + 255 * sizeof(RGBTRIPLE)); 然后可以像下面這樣對任何一個 RGBTRIPLE 結構進行訪問: pbmci->bmciColors[i]??????? 因為 RGBTRIPLE 結構有 3 字節長,因此有些 RGBTRIPLE 結構可能會從 DIB 內一個奇數地址開始。但是,由于 DIB 文件總是有偶數個 RGBTRIPLE 結構,所以在顏色表后面的數據塊總是從一個 WORD 地址邊界開始。
??????? 顏色表之后的數據(對 24 位 DIB 來說,就是信息頭之后的數據)就是像素位本身了。
15.1.2? 自下而上存儲!
??????? 像大多數位圖格式一樣,DIB 中的像素位是以水平的行來排列的,在視頻顯示硬件的術語中,也叫“掃描線”。圖像中的行數等于 BITMAPCOREHEADER 結構中 bcHeight 字段的值。但是和其他大多數位圖格式不同的是,DIB 是從圖像的最下面一行開始,然后逐漸向上來存儲整個圖像的。
??????? 這里先解釋一些術語。當我說“頂行”和“底行”時,我指的是視覺上圖像的頂部和底部,就像在顯示器或打印頁上正確顯示時的樣子。一幅人頭肖像畫的頂行是頭發;底行是下巴。當我說“第一行”時,我指的是 DIB 文件中直接跟在顏色表中之后的像素行。當我說“最后一行”時,我指的是在文件最后的像素行。
??????? 因此,在 DIB 中,圖像的底行是文件的第一行,圖像的頂行是文件的最后一行。這是一種由下往上的組織方式。由于這種組織方式不直觀,你可能會問為什么要這樣安排。
??????? 一切都要從 OS/2 的 Presentation Manager 說起。在 IBM,有人認為在 PM 中的所有坐標系都應該保持一致,包括窗口、圖形以及位圖。這引發了一場辯論:多數人,包括哪些與整個屏幕的文本或窗口環境打交道的程序員,認為垂直坐標應該隨著屏幕向下增加。然而,那些發燒級的計算機圖形程序員更愿意從解析幾何的角度來看圖形顯示系統。這就牽涉到了一個直角(或笛卡爾)坐標系,其中垂直坐標是隨著空間向上而增加的。
??????? 簡而言之,數學家贏了。在 PM 中,所有坐標系都以左下角為源點,包括窗口坐標。這就是 DIB 為什么這樣安排的原因。
15.1.3? DIB 像素位
??????? DIB 文件的最后部分是真正的像素位組成的,在大多數情況下,這也是 DIB 文件的主體。像素位按水平行排列,從圖像的最下一行開始逐漸向上包括整個圖像。
??????? DIB 中的行數等于 BITMAPCOREHEADER 中的 bcHeight 字段。每一行編碼的像素數等于 bcWidth 字段。每一行的像素是從左向右安排的。每一個像素的位數由 bcBitCount 字段定義,可以是 1、4、8 或者 24。
??????? 每一行所用的字節數總是 4 的倍數,可以用以下方法計算出來。
RowLength = 4 * ((bmch.bcWidth * bmch.bcBitCount + 31) / 32);或者,用更高效一點的 C 語言,可以這樣寫:RowLength = ((bmch.bcWidth * bmch.bcBitCount + 31) & ~31) >> 3;
如果需要的話,每一行的結尾要補充一些字節(一般用 0)來達到這個長度。所有像素數據所占用的字節數就是 RowLength 和 bmch.bcHeight 的乘積。
??????? 要想知道像素是怎么編碼的,讓我們分別看一下這四種情況。在下面的圖中,每一個字節的二進制位顯示在格子中,7 代表最高位,0 代表最低位。像素也從 0 開始編號,從一行中最左邊的像素開始。
??????? 對于每像素 1 位的 DIB 來說,每個字節表示了 8 個像素。最左邊的像素就是第一個字節的最高位
每一個像素數值不是 0 就是 1。0 代表這個像素的顏色是由顏色表第一個 RGBTRIPLE 字段指定。1 代表這個像素的顏色是由顏色表第二個 RGBTRIPLE 字段指定。
??????? 對于每像素 4 位的 DIB 來說,每個字節表示了 2 個像素。最左邊的像素就是第一個字節的高 4 位,以此類推:
每一個 4 位像素的值在 0 到 15 之間。這個數值就是顏色表中 16 個元素的下標。
??????? 對于每像素 8 位的 DIB 來說,每個字節表示了 1 個像素:
每一個像素(字節)的數值在 0 到 255 之間。同樣,這個數值就是顏色表中 256 個元素的下標。
??????? 對于每像素 24 位的 DIB 來說,每個像素需要 3 個字節來代表紅、綠和藍色的數值。每一行像素基本上就是一個 RGBTRIPLE 結構的數組,只是可能在每一行的結尾添加一些 0 字節以保證每行的字節數都是 4 的倍數。
再次說明,每像素 24 位的 DIB 沒有顏色表。
15.1.4? Windows 擴展 DIB
??????? 現在我們已經掌握了在 Windows 3.0 中引入的和 OS/2 兼容的 DIB 格式,讓我們來看一下同時在 Windows 3.0 中引入的擴展版本的 DIB 格式吧。
??????? 這個格式的 DIB 和前面提到的格式一樣,也是以一個 BITMAPFILEHEADER 結構開頭,但是,接在后面的是一個 BITMAPINFOHEADER 結構,而不是 BITMAPCOREHEADER 結構:
typedef struct tagBITMAPINFOHEADER // bmih {DWORD biSize; // 結構大小 == 40LONG biWidth; // 以像素計的圖像的寬度LONG biHeight; // 以像素計的圖像的高度WORD biPlanes; // = 1WORD biBitCount; // 每個像素的位數 (1, 4, 8, 16, 24 或 32)DWORD biCompression; // 壓縮編碼DWORD biSizeImage; // 圖像的字節數LONG biXPelsPerMeter; // 水平分辨率LONG biYPelsPerMeter; // 垂直分辨率DWORD biClrUsed; // 用到的顏色數DWORD biClrImportant; // 重要顏色的數目 } BITMAPINFOHEADER, * PBITMAPINFOHEADER;??????? 你可以從這個結構的第一個字段來區別 OS/2 兼容的 DIB 和 Windows DIB: 前者是 12,后者是 40。
??????? 你可能注意到了,這個結構有六個新字段,但是 BITMAPINFOHEADER 結構并不是簡單的 BITMAPCOREHEADER 結構再加上一些新東西。再仔細看一下:在 BITMAPCOREHEADER 結構中 bcWidth 和 bcHeight 字段是 16 位的 WORD 型數值。而在這個結構中,它們是 32 位的 LONG 型數值。這個討厭的小改動肯定會讓你發瘋。
??????? 還有一個改動:對于 1 位、4 位 和 8 位的 DIB,如果用 BITMAPINFOHEADER 結構的話,顏色表不再是一個 RGBTRIPLE 結構的數組。接在 BITMAPINFOHEADER 結構后面的是一個 RGBQUAD 結構的數組:
typedef struct tagRGBQUAD // rgb {BYTE rgbBlue; // 藍色值BYTE rgbGreen; // 綠色值BYTE rgbRed; // 紅色值BYTE rbgReserved; // = 0 } RGBQUAD; 除了第 4 個字段總是設置為 0 以外,這個結構幾乎和 RGBTRIPLE 結構一樣。在 WINGDI.H 頭文件中,還定義了下面的結構: typedef struct tagBITMAPINFO // bmi {BITMAPINFOHEADER bmiHeader; // 信息頭RGBQUAD bmiColors[1]; // 顏色表數組 } BITMAPINFO, * PBITMAPINFO; 請注意,BITMAPINFO 結構從一個 32 位地址邊界開始,每一個 RGBQUAD 字段也都從 32 位地址邊界開始,這是因為 BITMAPINFOHEADER 結構是 40 個字節長。這保證了 32 位的處理器可以很有效地對顏色表數據進行尋址。??????? 盡管 BITMAPINFOHEADER 最初是在 Windows 3.0 中定義的,但有些字段在 Windows 95 和 Windows NT 4.0 中又被重新定義了,并被帶到了 Windows 98 和 Windows NT 5.0 里。例如,現在的文檔中注明:“如果 biHeight 是負數,那么這個 DIB 就是從上到下的,原點為左上角。”知道這個確實不錯,但如果 1990 年最初定義這個 DIB 格式的時候有人就這么決定就更好了。我的建議是不要從上到下的 DIB。因為那些不知道這個“功能”的程序在遇到一個負的 biHeight 字段時會崩潰或者出錯。例如,微軟 Word 97 中包括的 Microsoft Photo Editor 就會在遇到一個從上到下的 DIB 時報告“非法圖像高度”(Word 97 自己倒是沒有這個問題)。
??????? biPlanes 字段仍然是 1,但是 biBitCount 字段現在除了可以是1、4、8、24 之外,還可以是 16 或者 32。這也是在 Windows 95 和 Windows NT 4.0 中的新功能。我一會兒就會討論到這兩個新格式是怎么工作的。
??????? 讓我先跳過 biCompression 和 biSizeImage 字段。我很快就會對此展開討論。
??????? biXPelsPerMeter 和 biYPelsPerMeter 字段指示圖像在真實世界中的大小,以一個拙劣的每米像素數作為單位。(“Pel”,Picture Element,是 IBM 對像素的稱呼。)Windows 內部并不用這個信息。但是,應用程序可以用它來顯示 DIB 真正的大小。有些 DIB 是從不具備正方形像素的設備中得來的,這兩個字段對這些 DIB 也是有用的。在絕大多數 DIB 里,這兩個字段都是 0,說明圖像沒有一個推薦的真實世界的大小。72 DPI(每英寸點數)的分辨率(一般用在顯示器上,但是真正的分辨率要取決于顯示器的大小)大約等于每米 2835 個像素,而一般打印機的 300 DPI 分辨率大約等于每米 11811 個像素。
??????? biClrUsed 字段是一個非常重要的字段,因為它影響到顏色表中元素的數目。對 4 位和 8 位的 DIB 來說,這說明顏色表中可能會有少于 16 或 256 個元素。這是一種減少 DIB 大小的辦法,盡管并沒有減少多少。例如,假如一個 DIB 圖像中只有 64 級灰度,biClrUsed 字段就會設置為 64,顏色表中就會只有 64 個 RGBQUAD 結構,而不是 256 個。每個像素的數值就會是 0x00 到 0x3F。這個 DIB 仍然需要用 1 個字節來表示一個像素,但是每個字節的最高兩位為 0。如果 biClrUsed 字段為 0,則說明顏色表中包括了由 biBitCount 字段指明的全部數目的元素。
??????? 從 Windows 95 開始,biClrUsed 字段對于 16、24 和 32 位的 DIB 來說也可以不是 0。在這種情況下,顏色表不再被 Windows 用來解釋像素位數據,而是被用作在一個 256 色的顯示器上顯示這個 DIB 時所用的調色板。你可能還記得在 OS/2 兼容的格式中,一個 24 位的 DIB 沒有顏色表。在 Windows 3.0 中,擴展的 DIB 格式也是如此。但是在 Windows 95 中,24 位的 DIB 也可以有一個顏色表,其大小由 biClrUsed 字段指明。
??????? 簡單總結如下:
- 1 位 DIB 中,biClrUsed 總是 0 或 2.顏色表總是有 2 個元素。
- 4 位 DIB 中,如果 biClrUsed 是 0 或 16,那么顏色表有 16 個元素。如果 biClrUsed 的數值是 2~15,那么它指明了顏色表的元素數,每一個像素的最大值為這個數減 1。
- 8 位 DIB 中,如果 biClrUsed 是 0 或 256,那么顏色表有 256 個元素。如果 biClrUsed 的數值是 2 ~255,那么它指明了顏色表的元素數,每一個像素的最大值為這個數減 1。
- 16、24 和 32 位 DIB 中,biClrUsed 一般為 0。如果不是,那么它指明了顏色表中的元素數。應用程序可以用這個顏色表為該 DIB 在 256 色的顯示適配器上設置調色板。
??????? 另一個警告:根據較早的 DIB 文檔開發的程序不會預料到 24 位 DIB 會有顏色表。如果已加入一個,那么自己要對此負責。
??????? 和它的名稱不同,biClrImportant 字段其實遠沒有 biClrUsed 字段重要。它通常是 0,說明顏色表里的所有顏色都很重要。或者,它可以被設成和 biClrUsed 同樣的值,兩者是一回事。如果它是從 0 到 biClrUsed 中間的某個數,那么它意味著這個 DIB 可以用顏色表中的前 biClrImportant 種顏色差不多地顯示出來。如果需要在一個 256 色的顯示適配器上并列顯示兩個 8 位的 DIB,這可以是很有用的。
??????? 對 1、4、8 和 24 位的 DIB 來說,像素位的組織和 OS/2 兼容的 DIB 是一樣的。我會很快討論到 16 位和 32 位的 DIB。
15.1.5? 現實情況
??????? 如果遇到一個由別人或程序創建的 DIB,你可以期待發現什么呢?
??????? 盡管 OS/2 風格的 DIB 在 Windows 3.0? 發布時是很常見的,但最近幾年它們已經變得非常罕見了。有些程序在要快速編寫一些 DIB 程序時干脆就把它們忽略不計了。你能遇到的 4 位 DIB 很可能是由 Windows 的畫圖程序在 16 色的顯示適配器上創建的,其中的顏色表就是那些顯示器上的標準 16 色。
??????? 你能找到的最常見的 DIB 可能就是 8 位的了(譯注:這是本書英文版成書時的情況。現在,隨著數碼硬件的普及,最常見的 DIB 已經是 24 位的“真彩”的圖像了)。8 位 DIB 圖像可以分為兩類:灰度 DIB 和調色板化的彩色 DIB。可惜,文件頭中沒有任何信息可以告訴你具體是哪一種。
??????? 有些灰度 DIB 的 biClrUsed 字段等于 64,說明顏色表中有 64 個元素。這些元素一般都以灰度遞增的順序排列。也就是說,顏色表開始的 RGB 值是 00-00-00, 04-04-04, 08-08-08, 0C-0C-0C,最后的 RGB 值是 F0-F0-F0,F4-F4-F4,F8-F8-F8,FC-FC-FC。這種顏色表示用下面的公式計算出來的:
rgb[i].rgbRed = rgb[i].rgbGreen = rgb[i].rgbBlue = i * 256 / 64;其中 rgb 是一個 RGBQUAD 結構的數組,i 的范圍是 0~63。或者,灰度顏色表中的數值是用下面的公式計算出來的: rgb[i].rgbRed = rgb[i].rgbGreen = rgb[i].rgbBlue = i * 255 / 63;這樣顏色表就會以 FF-FF-FF 結尾。??????? 其實用哪一些公式并不重要。很多顯示適配器和顯示器本來也沒有超過 6 位的顏色精度。第一個公式認識到了這一點,而第二個公式在產生少于 64 級灰度時要更合適,例如要產生 16 或者 32 級灰度(在第二個公式末尾的分母就分別是 15 或 31)的時候。這是因為第二個公式保證了顏色表的最后一個元素時 FF-FF-FF,也就是白色。
??????? 有些 8 位的灰度 DIB 的顏色表有 64 個元素,另外一些灰度 DIB 的顏色表也可能有 256 個元素。biClrUsed 字段可以是 0(說明顏色表有 256 個元素),也可以是 2 到 256 的任何一個值。當然了,如果它是 2,就沒有什么必要了(因為這樣的一個 8 位 DIB 可以重新編碼為 1 位的 DIB)。同樣,如果它小于 16,這個 DIB 就可以重新編碼為 4 位 DIB。不管是哪種情況,顏色表中的元素個數一定要等于 biClrUsed(或者在 biClrUsed 為 0 時等于 256),而且每個像素的數值不能大于顏色表元素個數減一,因為像素的數值就是顏色表數組的下標。對于 biClrUsed 為 64 的 8 位 DIB 來說,像素的數值是 0x00 到 0x3F。
??????? 有一件很重要的事情值得記住,當一個 8 位 DIB 的顏色表中的元素全部是灰度(也就是說,紅、綠、藍的級別一樣),而且灰度的等級是均勻的升高時(像我剛才描述的情況),那么像素的值就代表了灰度的比例。例如,biClrUsed 是 64,那么一個值為 0 的像素是黑色,一個值為 0x20 的像素為 50% 灰色,值為 3F 的像素值為白色。
??????? 這一點對某些圖像處理的任務很重要,因為你可以完全忽略顏色表而只和像素值打交道。這太有用了,以至于如果給我一個機會回到過去對 BITMAPINFOHEADER 結構作一個修改的話,我就會在其中加上一個標志位,說明這個 DIB 是灰度的,沒有顏色表,每個像素的值直接代表灰度等級。
??????? 調色板化的 8 位彩色 DIB 一般都會用到整個顏色表,于是其 biClrUsed 字段值為 0 或者 256。然而,你也可能遇到更少顏色的情況——例如,236 色。這和如下事實有關:程序一般只能改變 Windows 調色板中 236 個元素的值以準確顯示這個 DIB。我會在第 16 章討論到這個問題。
??????? biXPelsPerMeter 和 biYPelsPerMeter 值不為 0 的情況很少見。另一種極少見的情況是 biClrImportant 不等于 0 或者不等于 biClrUsed。
15.1. 6? DIB 壓縮
??????? 前面我推遲了對 BITMAPINFOHEADER 結構中 biCompression 和 biSizeImage 字段的討論。現在是時候來檢視它們了。
??????? biCompression 字段可以是 4 個常數值之一:BI_RGB,BI_RLE8,BI_RLE4 或 BI_BITFIELDS,它們在 WINGDI.H 頭文件中定義為 0 到 3。這個字段有兩個意義:對 4 位和 8 位的 DIB 來說,它說明像素的值以一種行程長度編碼(run-length encoding, RLE)壓縮;對 16 位或 32 位 DIB 來說,它說明是否使用顏色遮罩(color mask)來對像素位進行編碼。第二個功能是在 Windows 95 中被引入的。
??????? 讓我們來先看一下行程長度編碼(RLE)壓縮。
- 對 1 位 DIB 來說,biCompression 字段總是 BI_RGB。
- 對 4 位 DIB 來說,biCompression 字段可以是 BI_RGB 或 BI_RLE4。
- 對 8 位 DIB 來說,biCompression 字段可以是 BI_RGB 或 BI_RLE8。
- 對 24 位 DIB 來說,biCompression 字段總是 BI_RGB。
??????? 如果這個值是 BI_RGB,那么像素位就和 OS/2 兼容的 DIB 一樣存儲。否則的話,像素位就是用行程長度編碼來壓縮的。
??????? 行程長度編碼(RLE)是最簡單的數據壓縮方法之一。它基于這樣一個知識:DIB 圖像中經常在一行中會有一連串相同的像素。行程長度編碼通過記錄下重復像素的值和它重復的次數來節省空間。DIB 使用的行程長度編碼算法有時還超出這個范圍,它可以用來定義一個稀疏的矩形 DIB 圖像。也就是說,矩形中有些區域是沒有定義的。這可以用來繪制非矩形的圖像。
??????? 對 8 位 DIB 進行行程長度編碼從概念上來說是最簡單的,那就讓我們從這里開始吧。下表有助于理解在 biCompression 字段等于 BI_RLE8 時像素是怎么編碼的。
| ?00 | ?00 | ? | ? | ?一行的結尾 |
| ?00 | ?01 | ? | ? | ?圖像的結尾 |
| ?00 | ?02 | ?dx | ?dy | ?移動到(x+dx, y+dy) |
| ?00 | ?n = 03 到 FF | ? | ? | ?使用接下來的 n 個像素 |
| ?n = 01 到 FF | ?像素 | ? | ? | ?重復像素 n 次 |
??????? 如果第一個字節不為 0(如表中最后一行所示),那么它就是行程長度編碼的重復因子。它表示后面的像素數據就重復這么多次。例如,以下這對字節:
0x05 0x27就被解碼為如下像素值:0x27 0x27 0x27 0x27 0x27
??????? DIB 中也會有很多不重復的像素值。這種情況由表格的倒數第二行來處理。它表明其后跟隨的若干像素就應該直接使用。例如,以下序列:
0x00 0x06 0x45 0x32 0x77 0x34 0x59 0x90就被解碼為如下像素值: 0x45 0x32 0x77 0x34 0x59 0x90??????? 這樣的序列總是與兩個字節的邊界對齊的。如果第二個字節是奇數,那么在這個序列的結尾就會有一個不同的多余字節。例如,以下序列:
0x00 0x05 0x45 0x32 0x77 0x34 0x59 0x00就被解碼為如下像素值: 0x45 0x32 0x77 0x34 0x59??????? 行程長度編碼就是這么工作的。很明顯,如果 DIB 圖像中沒有重復像素,用這種壓縮技術反而會增加 DIB 文件的大小。
??????? 表格的前三行指出了一個矩形 DIB 圖像中有些部分是可以沒有定義的。現在,假設要寫一個 DIB 解壓縮程序。在解壓縮過程中,會維護一對從(0, 0)開始的數值(y, x)。每次解碼出一個像素,便使 x 加 1。每結束一行的解碼,你會把 y 加 1 并且把 x 重置為 0。
??????? 在遇到一個 0x00 字節,后跟一個 0x02 字節時,應該讀入后面緊跟的兩個字節然后把它們作為無符號整數加到現在的 x 和 y 值上。在遇到一個 0x00 字節后面跟著一個 0x00 字節時,這一行的解碼就結束了。應該把 x 設置為 0 并把 y 加 1。在遇到一個 0x00 字節后面跟著一個 0x01 字節時,整個圖像的解碼就算完成了。這樣的代碼可以讓 DIB 中包括未定義的區域。有時候這會很有用,例如說定義一個非矩形的圖像,或者制作數字動畫或電影的時候(因為每一幀中會包含很多在上一幀中已有的信息,這些信息沒有必要再重新編碼)。
??????? 對 4 位 DIB 來說,編碼的過程基本上是一樣的,但是會復雜一些,因為像素和字節不是一一對應的。
??????? 如果第一個字節不是 0,那么它就是重復因子 n。第二個字節包括兩個像素,它們會在解碼后的序列中交替重復至 n 個像素。例如,以下這對數據:
0x07 0x35就被解碼為如下像素值: 0x35 0x35 0x35 0x3?其中的問號表面這個像素還是未知的。如果前面 0x07 0x35 數據對之后是以下這對數據: 0x05 0x24 那么整個解碼后的序列將如下所示: 0x35 0x35 0x35 0x32 0x42 0x42??????? 如果一對數據的第一個字節是 0x00 而第二個字節為 0x03 或更大時,解碼就要使用由第二個字節指定的數目的像素。例如,下面的序列:
0x00 0x05 0x23 0x57 0x10 0x00 就被解碼為: 0x23 0x57 0x1? 注意,編碼序列必須填補成偶數個字節。??????? 只要 biCompression 字段是 BI_RLE4 或者 BI_RLE8,biSizeImage 字段就會存儲以字節為單位的 DIB 像素數據大小。如果 biCompression 字段是 BI_RGB,biSizeImage 通常為 0,但是它也可以設置成 biHeight 和每一行字節數的乘積,就像我們在本章前面計算過的一樣。
??????? 現在的文檔中注明:“從上到下的 DIB 不能被壓縮”。所謂從上到下的 DIB 指的是那些 biHeight 字段為負值的圖像。
15.1.7? 顏色遮罩
??????? biCompression 字段還可以用在 Windows 95 新加入的 16 位和 32 位的 DIB 中。在這樣的 DIB 中,biCompression 字段可以是 BI_RGB 或者 BI_BITFIELDS(定義值為 3)。
??????? 讓我們先來回顧一下 24 位 DIB 的像素格式,其 biCompression 字段總是 BI_RGB:
每一行基本上就是一個 RGBTRIPLE 結構的數組, 只是在每一行結束時有可能會有一些填充的數據以使每行的字節數都是 4 的倍數。??????? 對一個 biCompression 為 BI_RGB 的 16 位 DIB 來說,每個像素需要兩個字節。顏色編碼如下:
每個顏色用 5 位來表示。對每行的第一個像素來說,藍色值是第一個字節的最低 5 位。綠色值需要用到兩個字節中的二進制位:它的最高兩位是第二個字節的最低兩位,它的低三位是第一個字節的最高三位。紅色值是第二個字節的 2 到 6 位。第二個字節的最高位為 0。
??????? 在用一個 16 位的 WORD 型整數來存取像素值時,這就非常清楚了。因為一個多字節的數值存儲總是把最低位的字節存在前面,這個像素的數值就會是下面這樣:
??????? 假如有一個 16 位的像素儲存在 wPixel 變量中,可以按照下面的公式來計算紅、綠、藍色的值:
Red = ((0x7C00 & wPixel) >> 10) << 3; Greeen = ((0x03E0 & wPixel) >> 5) << 3; Blue = ((0x001F & wPixel) >> 0) << 3;??????? 首先,這個像素要和一個遮罩作一個按位與操作,結果再向右位移。對紅色來說,右移 10 位,綠色右移 5 位,藍色右移 0 位。我會把這些位移值叫做“右移”值。這樣產生的顏色值在 0x00~0x1F 之間。這個值還需要左移三位以使其顏色值在 0x00 到 0xF8 之間。我會把這些位移值稱為“左移”值。
??????? 還有一點要記住:如果一個 16 位 DIB 的像素寬度是奇數,那么每一行就會有 2 個字節填充在最后以使每一行的字節數是 4 的倍數。
??????? 對 32 位的 DIB 來說,如果 biCompression 等于 BI_RGB,那么每一個像素就需要 4 字節。藍色值是第一個字節,綠色是第二個,紅色是第三個,第四個字節為 0。換言之,像素就是一個 RGBQUAD 結構的數組。因為每個像素都是 4 字節,所以每一行的結尾永遠不需要填充。
??????? 如果把每個像素視為一個 32 位的雙字,則會是下面這樣:
??????? 或者,如果 dwPixel 是一個 32 位的雙字,則像下面這樣:
Red = ((0x00FF0000 & dwPixel) >> 16) << 0; Green = ((0x0000FF00 & dwPixel) >> 8) << 0; Blue = ((0x000000ff & dwPixel) >> 0) << 0; 左移位全部是 0,這是因為顏色值已經可以到最大的 0xFF 了。注意,這個 32 位值和 GDI 函數中用于指定 RGB 顏色的 32 位 COLORREF 并不一樣。 在 COLORREF 值中,紅色在最低字節。??????? 到現在為止,我們已經介紹了 16 位和 32 位 DIB 當 biCompression 字段為 BI_RGB 時的默認情況。如果 biCompression 字段是 BI_BITFIELDS,那么在 BITMAPINFOHEADER 結構之后緊跟著的是 3 個 32 位的顏色遮罩,第一個用于紅色,第二個用于綠色,第三個用于藍色。可以用 C 語言中的按位與操作符(&)來把這些遮罩加到 16 位或 32 位的像素值上。然后可以右移來得到最終結果。可惜,右移的位數必須從遮罩中得到。仔細想一下,顏色遮罩的規則應該是很明顯的:每一個顏色遮罩中 1 的位必須是連續的,而且 3 個遮罩中為 1 的位不能互相重疊。
??????? 讓我們來看一個例子吧。假設有一個 16 位的 DIB,而且 biCompression 字段為 BI_BITFIELDS。檢查一下 BITMAPINFOHEADER 結構之后的 3 個 32 位數:
0x0000F800 0x000007E0 0X0000001F??????? 注意,只有在最低的 16 位中才可以設置為 1,因為這時一個 16 位的 DIB。把這三個值保存到變量 dwMask[0]、dwMask[1]和 dwMask[2]中。現在,需要寫兩個函數來從這些遮罩中計算出右移和左移的位數:
int MaskToRShift (DWORD dwMask) {int iShift ;if ( dwMask == 0)return 0 ;for ( iShift = 0 ; !(dwMask & 1) ; iShift++)dwMask >>= 1 ;return iShift ; }int MaskToLShift (DWORD dwMask) {int iShift ;if ( dwMask == 0)return 0 ;while (!(dwMask & 1))dwMask >>= 1 ;for (iShift = 0 ; dwMask & 1 ; iShift++)dwMask >>= 1 ;return 8 - iShift ; } 然后調用 MaskToRShift 函數 3 次來得到右移的位數: iRShift[0] = MaskToRShift (dwMask[0]) ; iRShift[1] = MaskToRShift (dwMask[1]) ; iRShift[2] = MaskToRShift (dwMask[2]) ;分別得到 11、5 和 0。然后可以類似地調用 MaskToLShift: iLShift[0] = MaskToLShift (dwMask[0]) ; iLShift[1] = MaskToLShift (dwMask[1]) ; iLShift[2] = MaskToLShift (dwMask[2]) ; 分別得到 3、2 和 3。現在便可以從像素值從得到顏色值了: Red = ((dwMask[0] & wPixel) >> iRShift[0]) << iLShift[0] ; Green = ((dwMask[1] & wPixel) >> iRShift[1]) << iLShift[1] ; Blue = ((dwMask[2] & wPixel) >> iRShift[2]) << iLShift[2] ; 對 32 位 DIB 來說,過程一樣,只是顏色遮罩可以大于 16 位 DIB 所能允許的最大值 0x0000FFFF。??????? 注意,不管是 16 位還是 32 位的 DIB,紅、綠、藍的顏色值都可以大于 255。事實上,在 32 位 DIB 中,如果兩個顏色的遮罩都是 0,那么第三個可以是 0xFFFFFFFF,也就是一個 32 位的顏色值!當然,這在某種意義上說有些可笑,所以我不會太為此而擔心。
??????? 與 Windows NT 不同,Windows 95 和 Windows 98 對顏色遮罩有更嚴格的規定。允許的數值如下表所示。
| ?紅色遮罩 | ?0x00007C00 | ?0x0000F800 | ?0x00FF0000 |
| ?綠色遮罩 | ?0x000003E0 | ?0x000007E0 | ?0x0000FF00 |
| ?藍色遮罩 | ?0x0000001F | ?0x0000001F | ?0x000000FF |
| ?簡寫 | ?5-5-5 | ?5-6-5 | ?8-8-8 |
15.1.8? 版本 4 的文件頭
??????? 我們還遠未結束呢。正如我提到的,Windows 95 改變了最早的 BITMAPINFOHEADER 結構中有些字段的含義,而且還包括了一個擴展的信息頭,叫做 BITMAPV4HEADER。當你意識到 Windows 95 可能會被命名為 Windows 4.0,而且 Windows NT 4.0 也支持這個結構時,你就會明白為什么這個結構會有這樣的名稱。
typedef struct {DWORD bV4Size ; ?? ??? ? // 結構的大小 = 120LONG bV4Width ; ?? ??? ?// 以像素為單位的圖像寬度LONG bV4Height ; ?? ??? // 以像素為單位的圖像高度WORD bV4Planes ; ?? ??? // = 1WORD bV4BitCount ; ?? ? // 每像素位數 (1, 4, 8, 16, 24, or 32)DWORD bV4Compression ;? // 壓縮編碼DWORD bV4SizeImage ; ?? // 圖像字節數LONG bV4XPelsPerMeter ; // 水平分辨率LONG bV4YPelsPerMeter ; // 垂直分辨率DWORD bV4ClrUsed ; ?? ? // 用到的顏色數DWORD bV4ClrImportant ; // 重要的顏色數DWORD bV4RedMask ; ?? ? // 紅色遮罩DWORD bV4GreenMask ; ?? // 綠色遮罩DWORD bV4BlueMask ; ?? ? // 藍色遮罩DWORD bV4AlphaMask ; ?? // 阿爾法遮罩DWORD bV4CSType ; ?? ?? // 色彩空間類型CIEXYZTRIPLE bV4Endpoints ; // XYZ 值DWORD bV4GammaRed ; ?? ? // 紅色伽瑪值DWORD bV4GammaGreen ; ? // 綠色伽瑪值DWORD bV4GammaBlue ; ?? // 藍色伽瑪值 } BITMAPV4HEADER, * PBITMAPV4HEADER ;??????? 請注意,前面的 11 個字段和 BITMAPINFOHEADER 結構一樣。最后 5 個字段用于支持 Windows 95 和 Windows NT 4.0 的顏色匹配(color-matching)技術。如果用不到 BITMAPV4HEADER 結構的最后 4 個字段,便應該用 BITMAPINFOHEADER(或 BITMAPV5HEADER)。
??????? bV4RedMask、bV4GreenMask 和 bV4BlueMask 只適用于 bV4Compression 字段為 BI_BITFIELDS 的 16 位或 32 位 DIB。它們和 BITMAPINFOHEADER 結構之后的顏色遮罩的功能一樣,而且事實上也出現在 DIB 文件的同樣位置,只不過這里它們是明確命名的字段而已。而 bV4AlphaMask 字段就我所知沒有被用到。
??????? BITMAPV4HEADER 結構的其余字段牽涉到 Windows 的圖像顏色管理系統(Windows Image Color Management),這恐怕超出了本書的范圍。但是,一些背景知識也許可以幫你入門。
??????? 用 RGB 來表示顏色有一個缺陷,就是它和顯示器、彩色照相機和彩色掃描儀的技術密切相關。如果一個顏色的 RGB 值為(255, 0, 0),它僅僅意味著在一個陰極射線管中的紅色電子槍應該施加最大的電壓。一個為(128, 0, 0)的 RGB 值意味著應該施加一半的電壓。但是不同的顯示器反應卻是可以不一樣的。更重要的是,打印機用的顏色模式是不一樣的,它是由青色(Cyan)、品紅色(Magenta)、黃色(Yellow)和黑色(Black)混合成的。這個顏色模式也稱為CMY(cyan-magenta-yellow)和CMYK(cyan-magenta-yellow-black)。有數學公式可以把 RGB 值轉換成 CMY 和 CMYK 值,但是沒法保證打印出來的顏色會和顯示出來的顏色一樣。圖像顏色管理系統就是要把顏色和一個設備無關的標準聯系起來。
??????? 顏色現象和可見光的波長有關,其范圍在 380nm(藍色)到 780nm(紅色)之間。我們能夠看見的光都是由在可見光光譜內的不同數量、不同波長的光組成的。1931 年,國際照明委員會(Commission Internationale de l′Eclairage,CIE)發布了一個科學的定量顏色的方法。這個方法使用了三個顏色匹配函數(稱為x, y, z)來描述一般人對不同光的波長的反應,其精簡形式(每隔 5nm 一個值)出版在 CIE 刊物 15.2-1986,《比色法,第 2 版》,表 2.1 中。
??????? 一個顏色的頻譜(Spectrum, S)是一組值,表明了每個波長的強度。如果知道了頻譜,那么前面提到的三個顏色匹配函數就可以用來計算出 X, Y 和 Z:
這三個值稱為大 X、大 Y 和大 Z。y 顏色匹配函數相當于人眼對可見光譜內的光線強度的反應。(它看起來像一個鈴鐺狀的曲線,在 380nm 和 780nm 時為 0。)因為它代表了光線的總體強度,所以 Y 被稱為 CIE 亮度(CIE Luminance)。
??????? 如果要用 BITMAPV5HEADER 結構,bV4CSType 字段就必須設置為 LCS_CALIBRATED_RGB,其值定義為 0。接下來的 4 個字段必須設置為合理的值。
??????? CIEXYZTRIPLE 結構定義如下:
typedef struct tagCIEXYZTRIPLE {CIEXYZ ciexyzRed ;CIEXYZ ciexyzGreen ;CIEXYZ ciexyzBlue ; } CIEXYZTRIPLE, * LPCIEXYZTRIPLE ;其中的 CIEXYZ 結構如下: typedef struct tagCIEXYZ {FXPT2DOT30 ciexyzX ;FXPT2DOT30 ciexyzY ;FXPT2DOT30 ciexyzZ ; } CIEXYZ, * LPCIEXYZ ;它的三個字段定義為 FXPT2DOT30 類型, 也就是說它們是定點(fixed-point)數值,整數部分是 2 位而小數部分為 30 位。所以,0x40000000 就表示 1.0, 0x48000000 是 1.125。最大值是 0xFFFFFFFF,比 4.0 小一點點。??????? bV4Endpoints 字段提供了三個 X、Y 和 Z 的值,對應于 RGB 顏色的 (255, 0, 0),(0, 255, 0)和(0, 0, 255)。這三個值應該在創建 DIB 時,由創建它的應用程序加入進來,用以指明獨立于設備的 RGB 顏色的含義。
??????? BITMAPV4HEADER 剩下的三個字段是“伽瑪”值(Gamma)。伽瑪(小寫希臘字母 γ)指的是顏色值的非線性屬性。在 DIB 中,紅、綠、藍色的范圍是 0 到 255。在顯示適配器上,這三個數值被轉換成三個模擬電壓輸出到顯示器上。這個電壓決定了每個像素的強度。但是,由于陰極射線管中電子槍發射出的電子的特點,像素的強度(intensity)I 和電壓(voltage)V 的關系并不是線性的,而是
其中,ε 是顯示器的黑電平(Black Level),由顯示器的亮度控制器決定(最好為 0)。指數 γ 由顯示器的對比度或圖像控制器決定。對絕大多數顯示器來說,γ 大約是 2.5。
??????? 為了補償這種非線性關系,攝像機傳統上都要包括一個“伽瑪校正”的功能。進入攝像機的光線要被一個 0.45 的指數修改。這意味著顯示器的伽瑪值大約是 2.2(1/0.45)。(更高的伽瑪值會增加對比度,但通常這不是我們想要的,因為背景光會降低對比度。)
??????? 實際上,顯示器的這個非線性特征比它看起來要恰當得多。這是因為人對光的反應也是非線性的。我前面提到 Y 被稱為 CIE 亮度。這是對光的一個線性度量。CIE 也定義了一個接近人感知的光強數值 L*(英語發音為 “ell star”),它可以用下面的公式從 Y 計算出來:
其中,Yn 是白電平(White Level)。公式的第一部分是一個線性關系。總體來說,人對光的感知與光亮度的立方根是線性關系,這是由第二個公式表示的。L* 的范圍從 0 到 100。L* 的每個整數級的變化,一般被認為是人能察覺的最小光亮變化。
??????? 在程序中,我們最好是根據人對光的感應而不是線性光亮來調節光的強度。這樣我們可以把像素位數降至一個合理的水平并且可以減少模擬電路中的噪音。
??????? 讓我們來看一看整個過程吧。像素值(P)的范圍是從 0 到 255。這個數值被線性地轉換成電壓,我們可以假設它被規范化到 0.0 至 1.0 之間。假設顯示器的黑電平被設置為 0,則該像素點的強度如下:
其中,γ 大約為 2.5。人的感應光亮(L*)基于這個強度的立方根,而且范圍從 0 至 100,那么近似地:
那個指數約為 0.85 左右。如果指數是 1,那么 CIE 亮度就和像素值完美匹配了,盡管事實并非如此,但它還是比用像素值代表線性光亮更接近一些。
??????? BITMAPV4HEADER 的最后三個字段為創建 DIB 的程序提供了一個方式來指明像素值應該具有的伽瑪值。這個值有 16 位整數部分和 16 位小數部分。例如,0x10000 是 1.0。如果 DIB 是一個由真實世界拍攝出的圖像,這個伽瑪值很可能由拍攝硬件隱含指定,并且大概是 2.2(編碼為 0x23333)。如果 DIB 是由程序算法生成的,這個程序一般會用一個指數函數把所有線性光亮轉換成 CIE 光亮。該指數的倒數就是 DIB 中的伽瑪值。
15.1.9? 版本 5 的頭文件
??????? 為 Windows 98 和 Windows NT 5.0 寫的程序可以使用一個新的 BITMAPV5HEADER 結構,如下所示:
typedef struct {DWORD bV5Size; // 結構的大小= 120LONG bV5Width; // 以像素為單位的圖像寬度LONG bV5Height; // 以像素為單位的圖像高度WORD bV5Planes; // = 1WORD bV5BitCount; // 每像素位數(1, 4, 8, 16, 24 或 32)DWORD bV5Compression; // 壓縮編碼DWORD bV5SizeImage; // 圖像字節數LONG bV5XPelsPerMeter; // 水平分辨率LONG bV5YPelsPerMeter; // 垂直分辨率DWORD bV5ClrUsed; // 用到的顏色數DWORD bV5ClrImpportant; // 重要顏色數DWORD bV5RedMask; // 紅色遮罩DWORD bV5GreenMask; // 綠色遮罩DWORD bV5BlueMask; // 藍色遮罩DWORD bV5AlphaMask; // 阿爾法遮罩CIEXYZTRIPLE bV5Endpoints; // XYZ 值DWORD bV5GammaRed; // 紅色伽瑪值DWORD bV5GammaGreen; // 綠色伽瑪值DWORD bV5GammaBlue; // 藍色伽瑪值DWORD bV5Intent; // 渲染意圖DWORD bV5ProfileData; // 顏色配置數據或文件名DWORD bV5ProfileSize; // 內嵌數據或文件名的大小DWORD bV5Reserved; } BITMAPV5HEADER, * PBITMAPV5HEADER 它有 4 個新字段,實際上只用到三個。這些字段支持由國際色彩協會(International Color Consortium,ICC,由 Adobe、Agfa、Apple、Kodak、Microsoft、Silicon Graphics 和 Sun Microsystems 等創立)提出的一個叫 ICC 顏色配置文件格式規范(ICC Profile Format Specification)的提案。可以從? http://www.icc.org 得到這個提案的副本。 簡單地講,每個輸入設備(掃描器或照相機)、輸出設備(打印機或膠片記錄器)和顯示設備(顯示器)都和一個顏色配置文件(profile)聯系在一起。這個顏色配置文件把設備相關的顏色值(一般是 RGB 或 CMYK)與一個設備無關的顏色規范(最終基于 CIE 的 XYZ 值)聯系在一起。顏色配置文件的文件擴展名是 .ICM(Image Color Management, 圖像顏色管理)。顏色配置文件可以內嵌在 DIB 文件中或者鏈接到 DIB 文件中以表明 DIB 是怎么被創建出來的。 可以從 MSDN 文檔的“/Platform SDK/Graphics and Multimedia Service/Color Management”(譯注:MSDN Library/Win32 and COM Development/ Graphics and Multimedia/Windows Color System)部分進一步了解 Windows 圖像顏色管理。??????? BITMAPV5HEADER 中的 bV5CSType 字段可以采用幾個不同的數值。如果它是 LCS_CALIBRATED_RGB,則它就是和 BITMAPV4HEADER 結構兼容的。相應的 bV5Endpoints 字段和伽瑪字段必須是合理值。
??????? 如果 bV5CSType 字段是 LCS_sRGB,則所有剩余的字段都不需要設置。這意味著這個 DIB 的色彩空間是微軟和惠普提出的“標準”RGB色彩空間。這個標準色彩空間的目標是要提供一定的設備無關性,不要顏色配置文件,特別適用于因特網。http://www.color.org/contrib/sRGB.html 有它的詳細文檔。
??????? 如果 bV5CSType 字段是 LCS_WINDOWS_COLOR_SPACE,那么所有剩余字段也都不需要設置。Windows 會使用顯示位圖的 API 函數所用到的色彩空間。
??????? 如果 bV5CSType 字段是 PROFILE_EMBEDDED,那么說明 DIB 文件中包含一個 ICC 顏色配置文件。如果該字段是 PROFILE_LINKED,那么說明 DIB 文件
中包含一個 ICC 顏色配置文件的完整文件名。無論是這兩種情況的哪一種,bV5ProfileData 都是從 BITMAPV5HEADER 的開頭到顏色配置文件數據或文件名開始的位移量。bV5ProfileSize 字段給出了顏色配置文件數據或文件名的大小。不需要設置 bV5Endpoints 和伽瑪字段。
15.1.10? 顯示 DIB 信息
??????? 現在是時候來看一下代碼了。我們還沒有足夠的知識來顯示 DIB,但是我們至少可以從文件頭結構中得到 DIB 的信息并把這些信息顯示出來。DIBHEADS 程序就是為此設計的。
/*-------------------------------------------------DIBHEADS.C -- Displays DIB Header Information(c) Charles Petzold, 1998 --------------------------------------------------*/#include <windows.h> #include "resource.h"LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);TCHAR szAppName[] = TEXT("DibHeads");int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevinstance,PSTR szCmdLine, int iCmdShow) {HACCEL hAccel;HWND hwnd;MSG msg;WNDCLASS wndclass;wndclass.style = CS_HREDRAW | CS_VREDRAW;wndclass.lpfnWndProc = WndProc;wndclass.cbClsExtra = 0;wndclass.cbWndExtra = 0;wndclass.hInstance = hInstance;wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION);wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);wndclass.lpszMenuName = szAppName;wndclass.lpszClassName = szAppName;if (!RegisterClass(&wndclass)){MessageBox(NULL, TEXT("This program requires Windows NT!"),szAppName, MB_ICONERROR);return 0;}hwnd = CreateWindow(szAppName, TEXT("DIB Headers"),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 Printf(HWND hwnd, TCHAR * szFormat, ...) {TCHAR szBuffer[1024];va_list pArgList;va_start(pArgList, szFormat);wvsprintf(szBuffer, szFormat, pArgList);va_end(pArgList);SendMessage(hwnd, EM_SETSEL, (WPARAM)-1, (LPARAM)-1);SendMessage(hwnd, EM_REPLACESEL, FALSE, (LPARAM)szBuffer);SendMessage(hwnd, EM_SCROLLCARET, 0, 0); }void DisplayDibHeaders(HWND hwnd, TCHAR * szFileName) {static TCHAR * szInfoName[] = { TEXT("BITMAPCOREHEADER"),TEXT("BITMAPINFOHEADER"),TEXT("BITMAPV4HEADER"),TEXT("BITMAPV5HEADER") };static TCHAR * szCompression[] = { TEXT("BI_RGB"), TEXT("BI_RLE8"),TEXT("BI_RLE4"),TEXT("BI_BITFIELDS"),TEXT("unknown") };BITMAPCOREHEADER * pbmch;BITMAPFILEHEADER * pbmfh;BITMAPV5HEADER * pbmih;BOOL bSuccess;DWORD dwFileSize, dwHighSize, dwBytesRead;HANDLE hFile;int i;PBYTE pFile;TCHAR * szV;// Display the file namePrintf(hwnd, TEXT("File: %s\r\n\r\n"), szFileName);// Open the filehFile = CreateFile(szFileName, GENERIC_READ, FILE_SHARE_READ, NULL,OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL);if (hFile == INVALID_HANDLE_VALUE){Printf(hwnd, TEXT("Cannot open file.\r\n\r\n"));return;}// Get the size of the filedwFileSize = GetFileSize(hFile, &dwHighSize);if (dwHighSize){Printf(hwnd, TEXT("Cannot deal with > 4G files.\r\n\r\n"));CloseHandle(hFile);return;}// Allocate memory for the filepFile = (PBYTE)malloc(dwFileSize);if (!pFile){Printf(hwnd, TEXT("Cannot allocate memory.\r\n\r\n"));CloseHandle(hFile);return;}// Read the fileSetCursor(LoadCursor(NULL, IDC_WAIT));ShowCursor(TRUE);bSuccess = ReadFile(hFile, pFile, dwFileSize, &dwBytesRead, NULL);ShowCursor(FALSE);SetCursor(LoadCursor(NULL, IDC_ARROW));if (!bSuccess || (dwBytesRead != dwFileSize)){Printf(hwnd, TEXT("Could not read file.\r\n\r\n"));CloseHandle(hFile);free(pFile);return;}// Close the fileCloseHandle(hFile);// Display file sizePrintf(hwnd, TEXT("File size = %u bytes\r\n\r\n"), dwFileSize);// Display BITMAPFILEHEADER structurepbmfh = (BITMAPFILEHEADER *)pFile;Printf(hwnd, TEXT("BITMAPFILEHEADER\r\n"));Printf(hwnd, TEXT("\t.bfType = 0x%X\r\n"), pbmfh->bfType);Printf(hwnd, TEXT("\t.bfSize = %u\r\n"), pbmfh->bfSize);Printf(hwnd, TEXT("\t.bfReserved1 = %u\r\n"), pbmfh->bfReserved1);Printf(hwnd, TEXT("\t.bfReserved2 = %u\r\n"), pbmfh->bfReserved2);Printf(hwnd, TEXT("\t.bfOffBits = %u\r\n\r\n"), pbmfh->bfOffBits);// Determine which information structure we havepbmih = (BITMAPV5HEADER *)(pFile + sizeof(BITMAPFILEHEADER));switch (pbmih->bV5Size){case sizeof(BITMAPCOREHEADER) : i = 0; szV = TEXT(""); break;case sizeof(BITMAPINFOHEADER) : i = 1; szV = TEXT("i"); break;case sizeof(BITMAPV4HEADER) : i = 2; szV = TEXT("V4"); break;case sizeof(BITMAPV5HEADER) : i = 3; szV = TEXT("V5"); break;default:Printf(hwnd, TEXT("Unknown header size of %u.\r\n\r\n"), pbmih->bV5Size);free(pFile);return;}Printf(hwnd, TEXT("%s\r\n"), szInfoName[i]);// Display the BITMAPCOREHEADER fieldsif (pbmih->bV5Size == sizeof(BITMAPCOREHEADER)){pbmch = (BITMAPCOREHEADER *)pbmih;Printf(hwnd, TEXT("\t.bcSize = %u\r\n"), pbmch->bcSize);Printf(hwnd, TEXT("\t.bcWidth = %u\r\n"), pbmch->bcWidth);Printf(hwnd, TEXT("\t.bcHeight = %u\r\n"), pbmch->bcHeight);Printf(hwnd, TEXT("\t.bcPlanes = %u\r\n"), pbmch->bcPlanes);Printf(hwnd, TEXT("\t.bcBitCount = %u\r\n\r\n"), pbmch->bcBitCount);free(pFile);return;}// Display the BITMAPINFOHEADER fieldsPrintf(hwnd, TEXT("\t.b%sSize = %u\r\n"), szV, pbmih->bV5Size);Printf(hwnd, TEXT("\t.b%sWidth = %i\r\n"), szV, pbmih->bV5Width);Printf(hwnd, TEXT("\t.b%sHeight = %i\r\n"), szV, pbmih->bV5Height);Printf(hwnd, TEXT("\t.b%sPlanes = %u\r\n"), szV, pbmih->bV5Planes);Printf(hwnd, TEXT("\t.b%sBitCount=%u\r\n"), szV, pbmih->bV5BitCount);Printf(hwnd, TEXT("\t.b%sCompression = %s\r\n"), szV,szCompression[min(4, pbmih->bV5Compression)]);Printf(hwnd, TEXT("\t.b%sSizeImage= %u\r\n"), szV,pbmih->bV5SizeImage);Printf(hwnd, TEXT("\t.b%sXPelsPerMeter = %i\r\n"), szV,pbmih->bV5XPelsPerMeter);Printf(hwnd, TEXT("\t.b%sYPelsPerMeter = %i\r\n"), szV,pbmih->bV5YPelsPerMeter);Printf(hwnd, TEXT("\t.b%sClrUsed = %i\r\n"), szV,pbmih->bV5ClrUsed);Printf(hwnd, TEXT("\t.b%sClrImportant = %i\r\n\r\n"), szV,pbmih->bV5ClrImportant);if (pbmih->bV5Size == sizeof(BITMAPINFOHEADER)){if (pbmih->bV5Compression == BI_BITFIELDS){Printf(hwnd, TEXT("Red Mask = %08X\r\n"),pbmih->bV5RedMask);Printf(hwnd, TEXT("Green Mask = %08X\r\n"),pbmih->bV5GreenMask);Printf(hwnd, TEXT("Blue Mask = %08X\r\n\r\n"),pbmih->bV5BlueMask);}free(pFile);return;}// Display additional BITMAPV4HEADER fieldsPrintf(hwnd, TEXT("\t.b%sRedMask = %08X\r\n"), szV,pbmih->bV5RedMask);Printf(hwnd, TEXT("\t.b%sGreenMask = %08X\r\n"), szV,pbmih->bV5GreenMask);Printf(hwnd, TEXT("\t.b%sBlueMask = %08X\r\n"), szV,pbmih->bV5BlueMask);Printf(hwnd, TEXT("\t.b%sAlphaMask = %08X\r\n"), szV,pbmih->bV5AlphaMask);Printf(hwnd, TEXT("\t.b%sCSType = %u\r\n"), szV,pbmih->bV5CSType);Printf(hwnd, TEXT("\t.b%sEndpoints.ciexyzRed.ciexyzX = %08X\r\n"),szV, pbmih->bV5Endpoints.ciexyzRed.ciexyzX);Printf(hwnd, TEXT("\t.b%sEndpoints.ciexyzRed.ciexyzY = %08X\r\n"),szV, pbmih->bV5Endpoints.ciexyzRed.ciexyzY);Printf(hwnd, TEXT("\t.b%sEndpoints.ciexyzRed.ciexyzZ = %08X\r\n"),szV, pbmih->bV5Endpoints.ciexyzRed.ciexyzZ);Printf(hwnd, TEXT("\t.b%sEndpoints.ciexyzGreen.ciexyzX = %08X\r\n"),szV, pbmih->bV5Endpoints.ciexyzGreen.ciexyzX);Printf(hwnd, TEXT("\t.b%sEndpoints.ciexyzGreen.ciexyzY = %08X\r\n"),szV, pbmih->bV5Endpoints.ciexyzGreen.ciexyzY);Printf(hwnd, TEXT("\t.b%sEndpoints.ciexyzGreen.ciexyzZ = %08X\r\n"),szV, pbmih->bV5Endpoints.ciexyzGreen.ciexyzZ);Printf(hwnd, TEXT("\t.b%sEndpoints.ciexyzBlue.ciexyzX = %08X\r\n"),szV, pbmih->bV5Endpoints.ciexyzBlue.ciexyzX);Printf(hwnd, TEXT("\t.b%sEndpoints.ciexyzBlue.ciexyzY = %08X\r\n"),szV, pbmih->bV5Endpoints.ciexyzBlue.ciexyzY);Printf(hwnd, TEXT("\t.b%sEndpoints.ciexyzBlue.ciexyzZ = %08X\r\n"),szV, pbmih->bV5Endpoints.ciexyzBlue.ciexyzZ);Printf(hwnd, TEXT("\t.b%sGammaRed = %08X\r\n"), szV,pbmih->bV5GammaRed);Printf(hwnd, TEXT("\t.b%sGammaGreen = %08X\r\n"), szV,pbmih->bV5GammaGreen);Printf(hwnd, TEXT("\t.b%sGammaBlue = %08X\r\n\r\n"), szV,pbmih->bV5GammaBlue);if (pbmih->bV5Size == sizeof(BITMAPV4HEADER)){free(pFile);return;}// Display additional BITMAPV5HEADER fieldsPrintf(hwnd, TEXT("\t.b%sIntent = %u\r\n"), szV,pbmih->bV5Intent);Printf(hwnd, TEXT("\t.b%sProfileData = %u\r\n"), szV,pbmih->bV5ProfileData);Printf(hwnd, TEXT("\t.b%sProfileSize = %u\r\n"), szV,pbmih->bV5ProfileSize);Printf(hwnd, TEXT("\t.b%sReserved = %u\r\n\r\n"), szV,pbmih->bV5Reserved);free(pFile);return; }LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {static HWND hwndEdit;static OPENFILENAME ofn;static TCHAR szFileName[MAX_PATH], szTitleName[MAX_PATH];static TCHAR szFilter[] = TEXT("Bitmap Files (*.BMP)\0*.bmp\0")TEXT("All Files (*.*)\0*.*\0\0");switch (message){case WM_CREATE:hwndEdit = CreateWindow(TEXT("edit"), NULL,WS_CHILD | WS_VISIBLE | WS_BORDER |WS_VSCROLL | WS_HSCROLL |ES_MULTILINE | ES_AUTOVSCROLL | ES_READONLY,0, 0, 0, 0, hwnd, (HMENU)1,((LPCREATESTRUCT)lParam)->hInstance, NULL);ofn.lStructSize = sizeof(OPENFILENAME);ofn.hwndOwner = hwnd;ofn.hInstance = NULL;ofn.lpstrFilter = szFilter;ofn.lpstrCustomFilter = NULL;ofn.nMaxCustFilter = 0;ofn.nFilterIndex = 0;ofn.lpstrFile = szFileName;ofn.nMaxFile = MAX_PATH;ofn.lpstrFileTitle = szTitleName;ofn.nMaxFileTitle = MAX_PATH;ofn.lpstrInitialDir = NULL;ofn.lpstrTitle = NULL;ofn.Flags = 0;ofn.nFileOffset = 0;ofn.nFileExtension = 0;ofn.lpstrDefExt = TEXT("bmp");ofn.lCustData = 0;ofn.lpfnHook = NULL;ofn.lpTemplateName = NULL;return 0;case WM_SIZE:MoveWindow(hwndEdit, 0, 0, LOWORD(lParam), HIWORD(lParam), TRUE);return 0;case WM_COMMAND:switch (LOWORD(wParam)){case IDM_FILE_OPEN:if (GetOpenFileName(&ofn))DisplayDibHeaders(hwndEdit, szFileName);return 0;}break;case WM_DESTROY:PostQuitMessage(0);return 0;}return DefWindowProc(hwnd, message, wParam, lParam); } DIBHEADS.RC (excerpts)// Microsoft Visual C++ generated resource script. // #include "resource.h"/ // // Menu //DIBHEADS MENU BEGINPOPUP "&File"BEGINMENUITEM "&Open\tCtrl+O",?? ??? ??? ??? ?IDM_FILE_OPENEND END/ // // Accelerator //DIBHEADS ACCELERATORS BEGIN"O",??????????? IDM_FILE_OPEN,????????? VIRTKEY, CONTROL, NOINVERT END RESOURCE.H (excerpts)// Microsoft Visual C++ 生成的包含文件。 // 供 DIBHeads.rc 使用 // #define IDM_FILE_OPEN?????????????????? 40001??????? 這個程序有一個很短的 WndProc 函數,它創建了一個只讀的編輯窗口,這個只讀窗口填滿了它的客戶區,該函數還處理菜單的 File Open(打開文件)命令。它使用 GetOpenFileName 函數調用標準的 File Open 對話框,然后調用 DisplayDibHeaders 這個函數。這個函數把整個 DIB 文件讀入內存,然后一個字段一個字段地顯示所有文件頭信息。
總結
以上是生活随笔為你收集整理的15.1 DIB 文件格式的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java 代码佛像_论面向组合子程序设计
- 下一篇: linux的 vi的各种命令(超级好用)