15. 與裝置無關的點陣圖

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

15. 與裝置無關的點陣圖

上一章 我們瞭解到Windows GDI點陣圖物件(也稱為與裝置相關的點陣圖,或DDB)有許多程式設計用途。但是我並沒有展示把這些點陣圖儲存到磁片檔案或把它們載入記憶體的方法。這是以前在Windows中使用的方法,現在根本不用了。因為點陣圖的位元格式相當依賴於設備,所以DDB不適用於圖像交換。DDB內沒有色彩對照表來指定點陣圖的位與色彩之間的聯繫。DDB只有在Windows開機到關機的生命期內被建立和清除時才有意義。

在Windows 3.0中發表了與裝置無關的點陣圖(DIB),提供了適用於交換的圖像檔案格式。正如您所知的,像.GIF或.JPEG之類的其他圖像檔案格式在Internet上比DIB檔案更常見。這主要是因為.GIF和.JPEG格式進行了壓縮,明顯地減少了下載的時間。儘管有一個用於DIB的壓縮方案,但極少使用。DIB內的點陣圖幾乎都沒有被壓縮。如果您想在程式中操作點陣圖,這實際上是一個優點。DIB不像.GIF和.JPEG檔案,Windows API直接支援DIB。如果在記憶體中有DIB,您就可以提供指向該DIB的指標作為某些函式的參數,來顯示DIB或把DIB轉化為DDB。

DIB檔案格式

有意思的是,DIB格式並不是源自於Windows。它首先定義在OS/2的1.1版中,該作業系統最初由IBM和Microsoft在八十年代中期開始開發。OS/2 1.1在1988年發佈,並且是第一個包含了類似Windows的圖形使用者介面的OS/2版本,該圖形使用者介面被稱之為「Presentation Manager(PM)」。「Presentation Manager」包含了定義點陣圖格式的「圖形程式介面」(GPI)。

然後在Windows 3.0中(發佈於1990)使用了OS/2點陣圖格式,這時稱之為DIB。Windows 3.0也包含了原始DIB格式的變體,並在Windows下成為標準。在Windows 95(以及Windows NT 4.0)和Windows 98(以及Windows NT 5.0)下也定義了一些其他的增強能力,我會在本章討論它們。

DIB首先作為一種檔案格式,它的副檔名為.BMP,在極少情況下為.DIB。Windows應用程式使用的點陣圖圖像被當做DIB檔案建立,並作為唯讀資源儲存在程式的可執行檔案中。圖示和滑鼠游標也是形式稍有不同的DIB檔案。

程式能將DIB檔案減去前14個位元組載入連續的記憶體塊中。這時就可以稱它為「packed DIB(packed-DIB)格式的點陣圖」。在Windows下執行的應用程式能使用packed DIB格式,通過Windows剪貼簿來交換圖像或建立畫刷。程式也可以完全存取DIB的內容並以任意方式修改DIB。

程式也能在記憶體中建立自己的DIB然後把它們存入檔案。程式使用GDI函式呼叫就能「繪製」這些DIB內的圖像,也能在程序中利用別的記憶體DIB直接設定和操作圖素位元。

在記憶體中載入了DIB後,程式也能通過幾個Windows API函式呼叫來使用DIB資料,我將在本章中討論有關內容。與DIB相關的API呼叫是很少的,並且主要與視訊顯示器或印表機頁面上顯示DIB相關,還與轉換GDI點陣圖物件有關。

除了這些內容以外,還有許多應用程式需要完成的DIB任務,而這些任務Windows作業系統並不支援。例如,程式可能存取了24位元DIB並且想把它轉化為帶有最佳化的256色調色盤的8位元DIB,而Windows不會為您執行這些操作。但是在本章和下一章將向您顯示Windows API之外的操作DIB的方式。

OS/2樣式的DIB

先不要陷入太多的細節,讓我們看一下與首先在OS/2 1.1中出現的點陣圖格式相容的Windows DIB格式。

DIB檔案有四個主要部分:

  • 檔案表頭
  • 資訊表頭
  • RGB色彩對照表(不一定有)
  • 點陣圖圖素位元

您可以把前兩部分看成是C的資料結構,把第三部分看成是資料結構的陣列。在Windows表頭檔案WINGDI.H中說明了這些結構。在記憶體中的packed DIB格式內有三個部分:

  • 資訊表頭
  • RGB色彩對照表(不一定有)
  • 點陣圖圖素位元

除了沒有檔案表頭外,其他部分與儲存在檔案內的DIB相同。

DIB檔案(不是記憶體中的packed DIB)以定義為如下結構的14個位元組的檔案表頭開始:

typedef struct tagBITMAPFILEHEADER // bmfh { WORD bfType ; // signature word "BM" or 0x4D42 DWORD bfSize ; // entire size of file WORD bfReserved1 ; // must be zero WORD bfReserved2 ; // must be zero DWORD bfOffsetBits ; // offset in file of DIB pixel bits } BITMAPFILEHEADER, * PBITMAPFILEHEADER ;

在WINGDI.H內定義的結構可能與這不完全相同,但在功能上是相同的。第一個注釋(就是文字「bmfh」)指出了給這種資料型態的資料變數命名時推薦的縮寫。如果在我的程式內看到了名為pbmfh的變數,這可能是一個指向BITMAPFILEHEADER型態結構的指標或指向PBITMAPFILEHEADER型態變數的指標。

結構的長度為14位元組,它以兩個字母「BM」開頭以指明是點陣圖檔案。這是一個WORD值0x4D42。緊跟在「BM」後的DWORD以位元組為單位指出了包括檔案表頭在內的檔案大小。下兩個WORD欄位設定為0。(在與DIB檔案格式相似的滑鼠游標檔案內,這兩個欄位指出游標的「熱點(hot spot)」)。結構還包含一個DWORD欄位,它指出了檔案中圖素位元開始位置的位元組偏移量。此數值來自DIB資訊表頭中的資訊,為了使用的方便提供在這裏。

在OS/2樣式的DIB內,BITMAPFILEHEADER結構後緊跟了BITMAPCOREHEADER結構,它提供了關於DIB圖像的基本資訊。緊縮的DIB(Packed DIB)開始於BITMAPCOREHEADER:

typedef struct tagBITMAPCOREHEADER // bmch { DWORD bcSize ; // size of the structure = 12 WORD bcWidth ; // width of image in pixels WORD bcHeight ; // height of image in pixels WORD bcPlanes ; // = 1 WORD bcBitCount ; // bits per pixel (1, 4, 8, or 24) } BITMAPCOREHEADER, * PBITMAPCOREHEADER ;

「core(核心)」用在這裡看起來有點奇特,它是指這種格式是其他由它所衍生的點陣圖格式的基礎。

BITMAPCOREHEADER結構中的bcSize欄位指出了資料結構的大小,在這種情況下是12位元組。

bcWidth和bcHeight欄位包含了以圖素為單位的點陣圖大小。儘管這些欄位使用WORD意味著一個DIB可能為65,535圖素高和寬,但是我們幾乎不會用到那麼大的單位。

bcPlanes欄位的值始終是1。這個欄位是我們在上一章中遇到的早期Windows GDI點陣圖物件的殘留物。

bcBitCount欄位指出了每圖素的位元數。對於OS/2樣式的DIB,這可能是1、4、8或24。DIB圖像中的顏色數等於2bmch.bcBitCount,或用C的語法表示為:

這樣,bcBitCount欄位等於:

1 << bmch.bcBitCount

  • 1代表2色DIB
  • 4代表16色DIB
  • 8代表256色DIB
  • 24代表full -Color DIB

當我提到「8位元DIB」時,就是說每圖素占8位元的DIB。

對於前三種情況(也就是位元數為1、4和8時),BITMAPCOREHEADER後緊跟色彩對照表,24位元DIB沒有色彩對照表。色彩對照表是一個3位元組RGBTRIPLE結構的陣列,陣列中的每個元素代表圖像中的每種顏色:

typedef struct tagRGBTRIPLE // rgbt { BYTE rgbtBlue ; // blue level BYTE rgbtGreen ; // green level BYTE rgbtRed ; // red level } RGBTRIPLE ;

這樣排列色彩對照表以便DIB中最重要的顏色首先顯示,我們將在 下一章 說明原因。

WINGDI.H表頭檔案也定義了下面的結構:

typedef struct tagBITMAPCOREINFO // bmci { BITMAPCOREHEADER bmciHeader ; // core-header structure RGBTRIPLE bmciColors[1] ; // color table array } BITMAPCOREINFO, * PBITMAPCOREINFO ;

這個結構把資訊表頭與色彩對照表結合起來。雖然在這個結構中RGBTRIPLE結構的數量等於1,但在DIB檔案內您絕對不會發現只有一個RGBTRIPLE。根據每個圖素的位元數,色彩對照表的大小始終是2、16或256個RGBTRIPLE結構。如果需要為8位元DIB配置PBITMAPCOREINFO結構,您可以這樣做:

然後可以這樣存取RGBTRIPLE結構:

因為RGBTRIPLE結構的長度是3位元組,許多RGBTRIPLE結構可能在DIB中以奇數位址開始。然而,因為在DIB檔案內始終有偶數個的RGBTRIPLE結構,所以緊跟在色彩對照表陣列後的資料塊總是以WORD位址邊界開始。

緊跟在色彩對照表(24位元DIB中是資訊表頭)後的資料是圖素位元本身。

由下而上

像大多數點陣圖格式一樣,DIB中的圖素位元是以水平行組織的,用視訊顯示器硬體的術語稱作「掃描線」。行數等於BITMAPCOREHEADER結構的bcHeight欄位。然而,與大多數點陣圖格式不同的是,DIB從圖像的底行開始,往上表示圖像。

在此應定義一些術語,當我們說「頂行」和「底行」時,指的是當其正確顯示在顯示器或印表機的頁面上時出現在虛擬圖像的頂部和底部。就好像肖像的頂行是頭髮,底行是下巴,在DIB檔案中的「第一行」指的是DIB檔案的色彩對照表後的圖素行,「最後行」指的是檔案最末端的圖素行。

因此,在DIB中,圖像的底行是檔案的第一行,圖像的頂行是檔案的最後一行。這稱之為由下而上的組織。因為這種組織和直覺相反,您可能會問:為什麼要這麼做?

好,現在我們回到OS/2的Presentation Manager。IBM的人認為PM內的座標系統-包括視窗、圖形和點陣圖-應該是一致的。這引起了爭論:大多數人,包括在全畫面文字方式下編程和視窗環境下工作的程式寫作者認為應使用垂直座標在螢幕上向下增加的座標。然而,電腦圖形程式寫作者認為應使用解析幾何的數學方法進行視訊顯示,這是一個垂直座標在空間中向上增加的直角(或笛卡爾)座標系。

簡而言之,數學方法贏了。PM內的所有事物都以左下角為原點(包括視窗座標),因此DIB也就有了那種方式。

DIB圖素位元

DIB檔案的最後部分(在大多數情況下是DIB檔案的主體)由實際的DIB的圖素位元組成。圖素位元是由從圖像的底行開始並沿著圖像向上增長的水平行組織的。

DIB中的行數等於BITMAPCOREHEADER結構的bcHeight欄位。每一行的圖素數等於該結構的bcWidth欄位。每一行從最左邊的圖素開始,直到圖像的右邊。每個圖素的位元數可以從bcBitCount欄位取得,為1、4、8或24。

以位元組為單位的每行長度始終是4的倍數。行的長度可以計算為:

或者在C內用更有效的方法:

如果需要,可通過在右邊補充行(通常是用零)來完成長度。圖素資料的總位元組數等於RowLength和bmch.bcHeight的乘積。

要瞭解圖素編碼的方式,讓我們分別考慮四種情況。在下面的圖表中,每個位元組的位元顯示在框內並且編了號,7表示最高位元,0表示最低位元。圖素也從行的最左端從0開始編號。

對於每圖素1位元的DIB,每位元組對應為8圖素。最左邊的圖素是第一個位元組的最高位元:

每個圖素可以是0或1。0表示該圖素的顏色由色彩對照表中第一個RGBTRIPLE項目給出。1表示圖素的顏色由色彩對照表的第二個項目給出。

對於每圖素4位元的DIB,每個位元組對應兩個圖素。最左邊的圖素是第一個位元組的高4位元,以此類推:

每圖素4位元的值的範圍從0到15。此值是指向色彩對照表中16個項目的索引。

對於每圖素8位元的DIB,每個位元組為1個圖素:

位元組的值從0到255。同樣,這也是指向色彩對照表中256個項目的索引。

對於每圖素24位元的DIB,每個圖素需要3個位元組來代表紅、綠和藍的顏色值。圖素位元的每一行,基本上就是RGBTRIPLE結構的陣列,可能需要在每行的末端補0以便該行為4位元組的倍數:

每圖素24位元的DIB沒有色彩對照表。

擴展的Windows DIB

現在我們掌握了Windows 3.0中介紹的與OS/2相容的DIB,同時也看一看Windows中DIB的擴展版本。

這種DIB形式跟前面的格式一樣,以BITMAPFILEHEADER結構開始,但是接著是BITMAPINFOHEADER結構,而不是BITMAPCOREHEADER結構:

pbmci = malloc (sizeof (BITMAPCOREINFO) + 255 * sizeof (RGBTRIPLE)) ;

pbmci->bmciColors[i]

RowLength = 4 * ((bmch.bcWidth * bmch.bcBitCount + 31) / 32) ;

RowLength = ((bmch.bcWidth * bmch.bcBitCount + 31) & ~31) >> 3 ;

typedef struct tagBITMAPINFOHEADER // bmih { DWORD biSize ; // size of the structure = 40 LONG biWidth ; // width of the image in pixels LONG biHeight ; // height of the image in pixels WORD biPlanes ; // = 1 WORD biBitCount ; // bits per pixel (1, 4, 8, 16, 24, or 32) DWORD biCompression ; // compression code DWORD biSizeImage ; // number of bytes in image LONG biXPelsPerMeter ; // horizontal resolution LONG biYPelsPerMeter ; // vertical resolution DWORD biClrUsed ; // number of colors used DWORD biClrImportant ; // number of important colors } BITMAPINFOHEADER, * PBITMAPINFOHEADER ;

您可以通過檢查結構的第一欄位區分與OS/2相容的DIB和Windows DIB,前者為12,後者為40。

您將注意到,在這個結構內有六個附加的欄位,但是BITMAPINFOHEADER不是簡單地由BITMAPCOREHEADER加上一些新欄位而成。仔細看一下:在BITMAPCOREHEADER結構中,bcWidth和bcHeight欄位是16位元WORD值;而在BITMAPINFOHEADER結構中它們是32位元LONG值。這是一個令人討厭的小變化,當心它會給您帶來麻煩。

另一個變化是:對於使用BITMAPINFOHEADER結構的1位元、4位元和8位元DIB,色彩對照表不是RGBTRIPLE結構的陣列。相反,BITMAPINFOHEADER結構緊跟著一個RGBQUAD結構的陣列:

typedef struct tagRGBQUAD // rgb { BYTE rgbBlue ; // blue level BYTE rgbGreen ; // green level BYTE rgbRed ; // red level BYTE rgbReserved ; // = 0 } RGBQUAD ;

除了包括總是設定為0的第四個欄位外,與RGBTRIPLE結構相同。 WINGDI.H表頭檔案也定義了以下結構:

typedef struct tagBITMAPINFO // bmi { BITMAPINFOHEADER bmiHeader ; // info-header structure RGBQUAD bmiColors[1] ; // color table array } BITMAPINFO, * PBITMAPINFO ;

注意,如果BITMAPINFO結構以32位元的位址邊界開始,因為BITMAPINFOHEADER結構的長度是40位元組,所以RGBQUAD陣列內的每一個項目也以32位邊界開始。這樣就確保通過32位元微處理器能更有效地對色彩對照表資料定址。

儘管BITMAPINFOHEADER最初是在Windows 3.0中定義的,但是許多欄位在Windows 95和Windows NT 4.0中又重新定義了,並且被帶入Windows 98和Windows NT 5.0中。比如現在的文件中說:「如果biHeight是負數,則點陣圖是由上而下的DIB,原點在左上角」。這很好,但是在1990年剛開始定義DIB格式時,如果有人做了這個決定,那會更好。我的建議是避免建立由上而下的DIB。有一些程式在編寫時沒有考慮這種新「特性」,在遇到負的biHeight欄位時會當掉。還有如Microsoft Word 97帶有的Microsoft Photo Editor在遇到由上而下的DIB時會報告「圖像高度不合法」(雖然Word 97本身不會出錯)。

biPlanes欄位始終是1,但biBitCount欄位現在可以是16或32以及1、4、8或24。這也是在Windows 95和Windows NT 4.0中的新特性。一會兒我將介紹這些附加格式工作的方式。

現在讓我們先跳過biCompression和biSizeImage欄位,一會兒再討論它們。

biXPelsPerMeter和biYPelsPerMeter欄位以每公尺多少圖素這種笨拙的單位指出圖像的實際尺寸。(「pel」--picture element(圖像元素)--是IBM對圖素的稱呼。)Windows在內部不使用此類資訊。然而,應用程式能夠利用它以準確的大小顯示DIB。如果DIB來源於沒有方圖素的設備,這些欄位是很有用的。在大多數DIB內,這些欄位設定為0,這表示沒有建議的實際大小。每英寸72點的解析度(有時用於視訊顯示器,儘管實際解析度依賴於顯示器的大小)大約相當於每公尺2835個圖素,300 DPI的普通印表機的解析度是每公尺11,811個圖素。

biClrUsed是非常重要的欄位,因為它影響色彩對照表中項目的數量。對於4位元和8位元DIB,它能分別指出色彩對照表中包含了小於16或256個項目。雖然並不常用,但這是一種縮小DIB大小的方法。例如,假設DIB圖像僅包括64個灰階,biClrUsed欄位設定為64,並且色彩對照表為256個位元組大小的色彩對照表包含了64個RGBQUAD結構。圖素值的範圍從0x00到0x3F。DIB仍然每圖素需要1位元組,但每個圖素位元組的高2位元為零。如果biClrUsed欄位設定為0,意味著色彩對照表包含了由biBitCount欄位表示的全部項目數。

從Windows 95開始,biClrUsed欄位對於16位元、24位元或32位元DIB可以為非零。在這些情況下,Windows不使用色彩對照表解釋圖素位元。相反地,它指出DIB中色彩對照表的大小,程式使用該資訊來設定調色盤在256色視訊顯示器上顯示DIB。您可能想起在OS/2相容格式中,24位元DIB沒有色彩對照表。在Windows 3.0中的擴展格式中,也與這一樣。而在Windows 95中,24位元DIB有色彩對照表,biClrUsed欄位指出了它的大小。

總結如下:

  • 對於1位元DIB,biClrUsed始終是0或2。色彩對照表始終有兩個項目。
  • 對於4位元DIB,如果biClrUsed欄位是0或16,則色彩對照表有16個項目。如果是從2到15的數,則指的是色彩對照表中的項目數。每個圖素的最大值是小於該數的1。
  • 對於8位元DIB,如果biClrUsed欄位是0或256,則色彩對照表有256個項目。如果是從2到225的數,則指的是色彩對照表中的項目數。每個圖素的最大值是小於該數的1。
  • 對於16位元、24位元或32位元DIB,biClrUsed欄位通常為0。如果它不為0,則指的是色彩對照表中的項目數。執行於256色顯示卡的應用程式能使用這些項目來為DIB設定調色盤。

另一個警告:原先使用早期DIB文件編寫的程式不支援24位元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。

真實檢查

當遇到一個由其他程式或別人建立的DIB時,您希望從中發現什麼內容呢?

儘管在Windows3.0首次推出時,OS/2樣式的DIB已經很普遍了,但最近這種格式卻已經很少出現了。許多程式寫作者在實際編寫快速DIB常式時忽略了它們。您遇到的任何4位元DIB可能是Windows的「小畫家」程式使用16色視訊顯示器建立的,在這些顯示器上色彩對照表具有標準的16種顏色。

最普遍的DIB可能是每圖素8位元。8位元DIB分為兩類:灰階DIB和混色DIB。不幸的是,表頭資訊中並沒有指出8位元DIB的型態。

許多灰階DIB有一個等於64的biClrUsed欄位,指出色彩對照表中的64個項目。這些項目通常以上升的灰階層排列,也就是說色彩對照表以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值。此類色彩對照表可用下列公式計算:

在這裏rgb是RGBQUAD結構的陣列,i的範圍從0到63。灰階色彩對照表可用下列公式計算:

因而此表以FF-FF-FF結尾。

實際上使用哪個計算公式並沒有什麼區別。許多視訊顯示卡和顯示器沒有比6位元更大的色彩精確度。第一個公式承認了這個事實。然而當產生小於64的灰階時-可能是16或32(在此情況下公式的除數分別是15和31)-使用第二個公式更適合,因為它確保了色彩對照表的最後一個項目是FF-FF-FF,也就是白色。

當某些8位元灰階DIB在色彩對照表內有64個項目時,其他灰階的DIB會有256個項目。biClrUsed欄位實際上可以為0(指出色彩對照表中有256個項目)或者從2到256的數。當然,biClrUsed值是2的話就沒有任何意義(因為這樣的8位元DIB能當作1位元DIB被重新編碼)或者小於或等於16的值也沒意義(因為它能當作4位元DIB被重新編碼)。任何情況下,色彩對照表中的項目數必須與biClrUsed欄位相同(如果biClrUsed是0,則是256),並且圖素值不能超過色彩對照表項目數減1的值。這是因為圖素值是指向色彩對照表陣列的索引。對於biClrUsed值為64的8位元DIB,圖素值的範圍從0x00到0x3F。

在這裏應記住一件重要的事情:當8位元DIB具有由整個灰階組成的色彩對照表(也就是說,當紅色、綠色和藍色程度相等時),或當這些灰階層在色彩對照表中遞增(像上面描述的那樣)時,圖素值自身就代表了灰色的程度。也就是說,如果biClrUsed是64,那麼0x00圖素值為黑色,0x20的圖素值是50%的灰階,0x3F的圖素值為白色。

這對於一些圖像處理作業是很重要的,因為您可以完全忽略色彩對照表,僅需處理圖素值。這是很有用的,如果讓我回溯時光去對BITMAPINFOHEADER結構做一個簡單的更改,我會添加一個旗標指出DIB映射是不是灰階的,如果是,DIB就沒有色彩對照表,並且圖素值直接代表灰階。

混色的8位元DIB一般使用整個色彩對照表,它的biClrUsed欄位為0或256。然而您也可能遇到較小的顏色數,如236。我們應承認一個事實:程式通常只能在Windows顏色面內更改236個項目以正確顯示這些DIB,我將在 下章 討論這些內容。

biXPelsPerMeter和biYPelsPerMeter很少為非零值,biClrImportant欄位不為0或biClrUsed值的情況也很少。

DIB壓縮

前面我沒有討論BITMAPINFOHEADER中的biCompression和biSizeImage欄位,現在我們討論一下這些值。

biCompression欄位可以為四個常數之一,它們是:BI_RGB、BI_RLE8、BI_RLE4或BI_BITFIELDS。它們定義在WINGDI.H表頭檔案中,值分別為0到3。此欄位有兩個用途:對於4位元和8位元DIB,它指出圖素位元被用一種運行長度(run-length)編碼方式壓縮了。對於16位元和32位元DIB,它指出了顏色遮罩(color masking)是否用於對圖素位元進行編碼。這兩個特性都是在Windows 95中發表的。

首先讓我們看一下RLE壓縮:

rgb[i].rgbRed = rgb[i].rgbGreen = rgb[i].rgbBlue = i * 256 / 64 ;

rgb[i].rgbRed = rgb[i].rgbGreen = rgb[i].rgbBlue = i * 255 / 63 ;

  • 對於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映射在一列內經常有相同的圖素字串這個事實進行的。RLE通過對重複圖素的值及重複的次數編碼來節省空間,而用於DIB的RLE方案只定義了很少的矩形DIB圖像,也就是說,矩形的某些區域是未定義的,這能被用於表示非矩形圖像。

8位元DIB的運行長度編碼在概念上更簡單一些,因此讓我們從這裏入手。表15-1會幫助您理解當biCompression欄位等於BI_RGB8時,圖素位元的編碼方式。

表15-1

當對壓縮的DIB解碼時,可成對查看DIB資料位元組,例如此表內的「位元組1」和「位元組2」。表格以這些位元組值的遞增方式排列,但由下而上討論這個表格會更有意義。

如果第一個位元組非零(表格最後一行的情況),那麼它就是運行長度的重複因數。下面的圖素值被重複多次,例如,位元組對

解碼後的圖素值為:

當然DIB會有許多資料不是圖素到圖素的重複,表格倒數第二行處理這種情況,它指出緊跟著的圖素數應逐個使用。例如:考慮序列

解碼後的圖素值為:

這些序列總是以2位元組的界限排列。如果第二個位元組是奇數,那麼序列內就有一個未使用的多餘位元組。例如,序列

解碼後的圖素值為:

這就是運行長度編碼的工作方式。很明顯地,如果在DIB圖像內沒有重複的圖素,使用此壓縮技術實際上會增加了DIB檔案的大小。

上面表格的前三行指出了矩形DIB圖像的某些部分可以不被定義的方法。想像一下,您寫的程式對已壓縮的DIB進行解壓縮,在這個解壓縮的常式中,您將保持一對數字(x,y),開始為(0,0)。每對一個圖素解碼,x的值就增加1,每完成一行就將x重新設為0並且增加y的值。

當遇到跟著0x02的位元組0x00時,您讀取下兩個位元組並把它們作為無正負號的增量添加到目前的x和y值中,然後繼續解碼。當遇到跟著0x00的0x00時,您就解完了一行,應將x設0並增加y值。當遇到跟著0x01的0x00時,您就完成解碼了。這些代碼准許DIB包含那些未定義的區域,它們用於對非矩形圖像編碼或在製作數位動畫和電影時非常有用(因為幾乎每一格影像都有來自前一格的資訊而不需重新編碼)。

對於4位元DIB,編碼一般是相同的,但更複雜,因為位元組和圖素之間不是一對一的關係。

如果讀取的第一個位元組非零,那就是一個重複因數n。第二個位元組(被重複的)包含2個圖素,在n個圖素的被解碼的序列中交替出現。例如,位元組對

被解碼為:

其中的問號指出圖素還未知,如果是上面顯示的0x07 0x35對緊跟著下面的位元組對:

則整個解碼的序列為:

如果位元組對中的第一位元組是0x00 ,第二個位元組是0x03或更大,則使用第二位元組指出的圖素數。例如,序列

解碼為:

注意必須填補解碼的序列使其成為偶數位元組。

無論biCompression欄位是BI_RLE4或BI_RLE8,biSizeImage欄位都指出了位元組內DIB圖素資料的大小。如果biCompression欄位是BI_RGB,則biSizeImage通常為0,但是它能被設定為行內位元組長度的biHeight倍,就像在本章前面計算的那樣。

目前文件說「由上而下的DIB不能被壓縮」。由上而下的DIB是在biHeight欄位為負數的情況下出現的。

顏色遮罩(color masking)

biCompression欄位也用於連結Windows 95中新出現的16位元和32位元DIB。對於這些DIB,biCompression欄位可以是BI_RGB或BI_BITFIELDS(均定義為值3)。

讓我們看一下24位元DIB的圖素格式,它始終有一個等於BI_RGB的biCompression欄位:

也就是說,每一行基本上都是RGBTRIPLE結構的陣列,在每行末端有可能補充值以使行內的位元組是4的倍數。

對於具有biCompression欄位等於BI_RGB的16位元DIB,每個圖素需要兩個位元組。顏色是這樣來編碼的:

每種顏色使用5位元。對於行內的第一個圖素,藍色值是第一個位元組的最低五位元。綠色值在第一和第二個位元組中都有位元:綠色值的兩個最高位元是第二個位元組中的兩個最低位元,綠色值的三個最低位元是第一個位元組中的三個最高位元。紅色值是第二個位元組中的2到6位元。第二個位元組的最高位元是0。

當以16位元字組存取圖素值時,這會更加有意義。因為多個位元組值的最低位元首先被儲存,圖素字組如下:

假設在wPixel內儲存了16位元圖素,您能用下列公式計算紅色、綠色和藍色值:

0x05 0x27

0x27 0x27 0x27 0x27 0x27

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

0x07 0x35

0x35 0x35 0x35 0x3?

0x05 0x24

0x35 0x35 0x35 0x32 0x42 0x42

0x00 0x05 0x23 0x57 0x10 0x00

0x23 0x57 0x1?

Red = ((0x7C00 & wPixel) >> 10) << 3 ; Green = ((0x03E0 & wPixel) >> 5) << 3 ; Blue = ((0x001F & wPixel) >> 0) << 3 ;

首先,使用遮罩值與圖素進行了位元AND運算。此結果是:紅色向右移動10位元,綠色向右移動5位元,藍色向右移動0位元。(這些移動值我稱之為「右移值」)。這就產生了從0x00和0x1F的顏色值,這些值必須向左移動3位元以合成從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 ;

左移值全為零,因為顏色值在0xFF已是最大。注意這個雙字組與Windows GDI函式呼叫中用於指定RGB顏色的32位元COLORREF值不一致。在COLORREF值中,紅色佔最低位元的位元組。

到目前為止,我們討論了當biCompression欄位為BI_RGB時,16位元和32位元DIB的內定情況。如果biCompression欄位為BI_BITFIELDS,則緊跟著DIB的BITMAPINFOHEADER結構的是三個32位元顏色遮罩,第一個用於紅色,第二個用於綠色,第三個用於藍色。可以使用C的位元AND運算子(&)把這些遮罩應用於16位元或32位元的圖素值上。然後通過右移值向右移動結果,這些值只有檢查完遮罩後才能知道。顏色遮罩的規則應該很明確:每個顏色遮罩位元串內的1必須是連續的,並且1不能在三個遮罩位元串中重疊。

讓我們來舉個例子,如果您有一個16位元DIB,並且biCompression欄位為BI_BITFIELDS。您應該檢查BITMAPINFOHEADER結構之後的前三個雙字組:

0x0000F800 0x000007E0 0x0000001F

注意,因為這是16位元DIB,所以只有位於底部16位元的位元值才能被設定為1。您可以把變數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函式三次來獲得右移值:

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] ;

除了顏色標記能大於0x0000FFFF(這是16位元DIB的最大遮罩值)之外,程序與32位元DIB一樣。

注意:

對於16位元或32位元DIB,紅色、綠色和藍色值能大於255。實際上,在32位元DIB中,如果遮罩中有兩個為0,第三個應為32位元顏色值0xFFFFFFFF。當然,這有點荒唐,但不用擔心這個問題。

不像Windows NT,Windows 95和Windows 98在使用顏色遮罩時有許多的限制。可用的值顯示在表15-2中。

表15-2

換句話說,就是當biCompression是BI_RGB時,您能使用內定的兩組遮罩,包括前面例子中顯示的遮罩組。表格底行顯示了一個速記符號來指出每圖素紅色、綠色和藍色的位元數。

第4版本的Header

我說過,Windows 95更改了一些原始BITMAPINFOHEADER欄位的定義。Windows 95也包括了一個稱為BITMAPV4HEADER的新擴展的資訊表頭。如果您知道Windows 95曾經稱作Windows 4.0,則就會明白此結構的名稱了,Windows NT 4.0也支援此結構。

typedef struct { DWORD bV4Size ; // size of the structure = 120 LONG bV4Width ; // width of the image in pixels LONG bV4Height ; // height of the image in pixels WORD bV4Planes ; // = 1 WORD bV4BitCount ; // bits per pixel (1, 4, 8, 16, 24, or 32) DWORD bV4Compression ; // compression code DWORD bV4SizeImage ; // number of bytes in image LONG bV4XPelsPerMeter ; // horizontal resolution LONG bV4YPelsPerMeter ; // vertical resolution DWORD bV4ClrUsed ; // number of colors used DWORD bV4ClrImportant ; // number of important colors DWORD bV4RedMask ; // Red color mask DWORD bV4GreenMask ; // Green color mask DWORD bV4BlueMask ; // Blue color mask DWORD bV4AlphaMask ; // Alpha mask DWORD bV4CSType ; // color space type CIEXYZTRIPLE bV4Endpoints ; // XYZ values DWORD bV4GammaRed ; // Red gamma value DWORD bV4GammaGreen ; // Green gamma value DWORD bV4GammaBlue ; // Blue gamma value } BITMAPV4HEADER, * PBITMAPV4HEADER ;

注意前11個欄位與BITMAPINFOHEADER結構中的相同,後5個欄位支援Windows 95和Windows NT 4.0的圖像顏色調配技術。除非使用BITMAPV4HEADER結構的後四個欄位,否則您應該使用BITMAPINFOHEADER(或BITMAPV5HEADER)。

當bV4Compression欄位等於BI_BITFIELDS時,bV4RedMask、bV4GreenMask和bV4BlueMask可以用於16位元和32位元DIB。它們作為定義在BITMAPINFOHEADER結構中的顏色遮罩用於相同的函式,並且當使用除了明確的結構欄位之外的原始結構時,它們實際上出現在DIB檔案的相同位置。就我所知,bV4AlphaMask欄位不被使用。

BITMAPV5HEADER結構剩餘的欄位包括「Windows顏色管理(Image Color Management)」,它的內容超越了本書的範圍,但是瞭解一些背景會對您有益。

為色彩使用RGB方案的問題在於,它依賴於視訊顯示器、彩色照相機和彩色掃描器的顯示技術。如果顏色指定為RGB值(255,0,0),意味著最大的電壓應該加到陰極射線管內的紅色電子槍上,RGB值(128,0,0)表示使用一半電壓。不同顯示器會產生不同的效果。而且,印表機使用了不同的顏色表示方法,以青色、洋紅色、黃色和黑色的組合表示顏色。這些方法稱之為CMY(cyan-magenta-yellow:青色-洋紅色-黃色)和CMYK( cyan-magenta-yellow-black:青色-洋紅色-黃色-黑色 )。數學公式能把RGB值轉化為CMY和CMYK,但不能保證印表機顏色與顯示器顏色相符合。「色彩調配技術」是把顏色與對裝置無關的標準聯繫起來的一種嘗試。

顏色的現象與可見光的波長有關,波長的範圍從380nm(藍)到780nm(紅)之間。一切我們能察覺的光線是可見光譜內不同波長的組合。1931年,Commission Internationale de L'Eclairage (International Commission on Illumination)或CIE開發了一種科學度量顏色的方法。這包括使用三個顏色調配函數(名稱為x、y和z),它們以其省略的形式(帶有每5nm的值)發表在CIE Publication 15.2-1986,「Colorimetry,Second Edition」的表2.1中。

顏色的光譜(S)是一組指出每個波長強度的值。如果知道光譜,就能夠將與顏色相關的函數應用到光譜來計算X、Y和Z:

這些值稱為 大X、大Y和大Z 。y顏色匹配函式等於肉眼對範圍在可見光譜內光線的反應。(他看上去像一條由380nm和780nm到0的時鐘形曲線)。Y稱之為CIE亮度,因為它指出了光線的總體強度。

如果使用BITMAPV5HEADER結構,bV4CSType欄位就必須設定為LCS_CALIBRATED_RGB,其值為0。後四個位元組必須設定為有效值。

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值,意味著它們是帶有2位元整數部分和30位元小數部分的定點值。這樣,0x40000000是1.0,0x48000000是1.5。最大值0xFFFFFFFF僅比4.0小一點點。

bV4Endpoints欄位提供了三個與RGB顏色(255,0,0)、(0,255,0)和(0,0,255)相關的X、Y和Z值。這些值應該由建立DIB的應用程式插入以指明這些RGB顏色的裝置無關的意義。

BITMAPV4HEADER剩餘的三個欄位指「伽馬值」(希臘的小寫字母γ),它指出顏色等級規格內的非線性。在DIB內,紅、綠、藍的範圍從0到225。在顯示卡上,這三個數值被轉化為顯示器使用的三個類比電壓,電壓決定了每個圖素的強度。然而,由於陰極射線管中電子槍的電子特性,圖素的強度(I)並不與電壓(V)線性相關,它們的關係為:

ε是由顯示器的「亮度」控制設定的黑色等級(理想值為0)。指數 γ由顯示器的「圖像」或「對比度」控制設定的。對於大多數顯示器,γ大約在2.5左右。

為了對此非線性作出補償,攝影機在線路內包含了「伽馬修正」。指數0.45修正了進入攝影機的光線,這意味著視訊顯示器的伽馬為2.2。(視訊顯示器的高伽馬值增加了對比度,這通常是不需要的,因為周圍的光線更適合於低對比度。)

視訊顯示器的這個非線性反應實際上是很適當的,這是因為人類對光線的反應也是非線性的。我曾提過,Y被稱為CIE亮度,這是線性的光線度量。CIE也定義了一個接近於人類感覺的亮度值。亮度是L* (發音為 "ell star") ,通過使用如下公式從Y計算得到的:

在此Yn是白色等級。公式的第一部分是一個小的線性部分。一般,人類的亮度感覺是與線性亮度的立方根相關的,這由第二個公式指出。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亮度。

第5版的Header

為Windows 98和Windows NT 5.0(即Windows 2000)編寫的程式能使用擁有新的BITMAPV5HEADER資訊結構的DIB:

typedef struct { DWORD bV5Size ; // size of the structure = 120 LONG bV5Width ; // width of the image in pixels LONG bV5Height ; // height of the image in pixels WORD bV5Planes ; // = 1 WORD bV5BitCount ; // bits per pixel (1,4,8,16,24,or32) DWORD bV5Compression ; // compression code DWORD bV5SizeImage ; // number of bytes in image LONG bV5XPelsPerMeter ; // horizontal resolution LONG bV5YPelsPerMeter ; // vertical resolution DWORD bV5ClrUsed ; // number of colors used DWORD bV5ClrImportant ; // number of important colors DWORD bV5RedMask ; // Red color mask DWORD bV5GreenMask ; // Green color mask DWORD bV5BlueMask ; // Blue color mask DWORD bV5AlphaMask ; // Alpha mask DWORD bV5CSType ; // color space type CIEXYZTRIPLE bV5Endpoints ; // XYZ values DWORD bV5GammaRed ; // Red gamma value DWORD bV5GammaGreen ; // Green gamma value DWORD bV5GammaBlue ; // Blue gamma value DWORD bV5Intent ; // rendering intent DWORD bV5ProfileData ; // profile data or filename DWORD bV5ProfileSize ; // size of embedded data or filename DWORD bV5Reserved ; } BITMAPV5HEADER, * PBITMAPV5HEADER ;

這裏有四個新欄位,只有其中三個有用。這些欄位支援ICC Profile Format Specification,這是由「國際色彩協會(International Color Consortium)」(由Adobe、Agfa、Apple、Kodak、Microsoft、Silicon Graphics、Sun Microsystems及其他公司組成)建立的。您能在 http://www.icc.org 上取得這個標準的副本。基本上,每個輸入(掃描器和攝影機)、輸出(印表機和膠片記錄器)以及顯示(顯示器)設備與將原始裝置相關顏色(一般為RGB或CMYK)聯繫到裝置無關顏色規格的設定檔案有關,最終依據CIE XYZ值來修正顏色。這些設定檔案的副檔名是.ICM(指「圖像顏色管理:image color management」)。設定檔案能嵌入DIB檔案中或從DIB檔案連結以指出建立DIB的方式。您能在/Platform SDK/Graphics and Multimedia Services/Color Management中取得有關Windows「圖像顏色管理」的詳細資訊。

BITMAPV5HEADER的bV5CSType欄位能擁有幾個不同的值。如果是LCS_CALIBRATED_RGB,那麼它就與BITMAPV4HEADER結構相容。bV5Endpoints欄位和伽馬欄位必須有效。

如果bV5CSType欄位是LCS_sRGB,就不用設定剩餘的欄位。預設的顏色空間是「標準」的RGB顏色空間,這是由Microsoft和Hewlett-Packard主要為Internet設計的,它包含裝置無關的內容而不需要大量的設定檔案。此文件位於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和伽馬欄位。

顯示DIB資訊

現在讓我們來看一些程式碼。實際上我們並不未充分瞭解顯示DIB的知識,但至少能表從頭結構上顯示有關DIB的資訊。如程式15-1 DIBHEADS所示。

程式15-1 DIBHEADS DIBHEADS.C /*--------------------------------------------------------------------------- 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 name Printf (hwnd, TEXT ("File: %s\r\n\r\n"), szFileName) ; // Open the file hFile = 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 file dwFileSize = GetFileSize (hFile, &dwHighSize) ; if (dwHighSize) { Printf (hwnd, TEXT ("Cannot deal with >4G files.\r\n\r\n")) ; CloseHandle (hFile) ; return ; } // Allocate memory for the file pFile = malloc (dwFileSize) ; if (!pFile) { Printf (hwnd, TEXT ("Cannot allocate memory.\r\n\r\n")) ; CloseHandle (hFile) ; return ; } // Read the file SetCursor (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 file CloseHandle (hFile) ; // Display file size Printf (hwnd, TEXT ("File size = %u bytes\r\n\r\n"), dwFileSize) ; // Display BITMAPFILEHEADER structure pbmfh = (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 have pbmih = (BITMAPV5HEADER *) (pFile + sizeof (BITMAPFILEHEADER)) ; switch (pbmih->bV5Size) { case sizeof (BITMAPCOREHEADER):i= 0 ; 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 fields if (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 fields Printf(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 fields Printf (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 fields Printf (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 (摘錄) //Microsoft Developer Studio generated resource script. #include "resource.h" #include "afxres.h" /////////////////////////////////////////////////////////////////////////// // Accelerator DIBHEADS ACCELERATORS DISCARDABLE BEGIN "O", IDM_FILE_OPEN, VIRTKEY, CONTROL, NOINVERT END /////////////////////////////////////////////////////////////////////////// // Menu DIBHEADS MENU DISCARDABLE BEGIN POPUP "&File" BEGIN MENUITEM "&Open\tCtrl+O", IDM_FILE_OPEN END END

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

此程式有一個簡短的WndProc函式,它建立了一個唯讀的編輯視窗來填滿它的顯示區域,它也處理功能表上的「File Open」命令。它通過呼叫GetOpenFileName函式使用標準的「File Open」對話方塊,然後呼叫DisplayDibHeaders函式。此函式把整個DIB檔案讀入記憶體並逐欄地顯示所有的表頭資訊。

顯示和列印

點陣圖是用來看的。在這一節中,我們看一看Windows在視訊顯示器上或列印頁面上支援顯示DIB的兩個函式。要得到更好的性能,您可以使用一種兜圈子的方法來顯示點陣圖,我會在本章的後面討論該方法,但先研究這兩個函式會好一些。

這兩個函式稱為SetDIBitsToDevice(發音為「set dee eye bits to device」)和StretchDIBits (發音為「stretch dee eye bits」)。每個函式都使用儲存在記憶體中的DIB並能顯示整個DIB或它的矩形部分。當使用SetDIBitsToDevice時,以圖素為單位所顯示映射的大小與DIB的圖素大小相同。例如,一個640×480的DIB會佔據整個標準的VGA螢幕,但在300dpi的雷射印表機上它只有約2.1×1.6英寸。StretchDIBits能延伸和縮小DIB尺寸的行和列從而在輸出設備上顯示一個特定的大小。

瞭解DIB

當呼叫兩個函式之一來顯示DIB時,您需要幾個關於圖像的資訊。正如我前面說過的,DIB檔案包含下列部分:

DIB檔案能被載入記憶體。如果除了檔案表頭外,整個檔案被儲存在記憶體的連續區塊中,指向該記憶體塊開始處(也就是資訊表頭的開頭)的指標被稱為指向packed DIB的指標(見下圖)。

這是通過剪貼簿傳輸DIB時所用的格式,並且也是您從DIB建立畫刷時所用的格式。因為整個DIB由單個指標(如pPackedDib)引用,所以packed DIB是在記憶體中儲存DIB的方便方法,您可以把指標定義為指向BYTE的指標。使用本章前面所示的結構定義,能得到所有儲存在DIB內的資訊,包括色彩對照表和個別圖素位元。

然而,要想得到這麼多資訊,還需要一些程式碼。例如,您不能通過以下敘述簡單地取得DIB的圖素寬度:

DIB有可能是OS/2相容格式的。在那種格式中,packed DIB以BITMAPCOREHEADER結構開始,並且DIB的圖素寬度和高度以16位元WORD,而不是32位元LONG儲存。因此,首先必須檢查DIB是否為舊的格式,然後進行相對應的操作:

iWidth = ((PBITMAPINFOHEADER) pPackedDib)->biWidth ;

if (((PBITMAPCOREHEADER) pPackedDib)->bcSize == sizeof (BITMAPCOREHEADER)) iWidth = ((PBITMAPCOREHEADER) pPackedDib)->bcWidth ; else iWidth = ((PBITMAPINFOHEADER) pPackedDib)->biWidth ;

當然,這不很糟,但它不如我們所喜好的清晰。

現在有一個很有趣的實驗:給定一個指向packed DIB的指標,我們要找出位於座標(5,27)的圖素值。即使假定DIB不是OS/2相容的格式,您也需要瞭解DIB的寬度、高度和位元數。您需要計算每一列圖素的位元組長度,確定色彩對照表內的項目數,以及色彩對照表是否包括三個32位元的顏色遮罩。您還需檢查DIB是否被壓縮,在這種情況下圖素是不能直接由位址得到的。

如果您需要直接存取所有的DIB圖素(就像許多圖形處理工作一樣),這可能會增加一點處理時間。由於這個原因,儲存一個指向packed DIB的指標就很方便了,不過這並不是一種有效率的解決方式。另一個漂亮的解決方法是為DIB定義一個包含足夠成員資料的C++類別,從而允許快速隨機地存取DIB圖素。然而,我曾經答應讀者在本書內無需瞭解C++,我將在下一章說明一個C的解決方法。

對於SetDIBitsToDevice和StretchDIBits函式,需要的資訊包括一個指向DIB的BITMAPINFO結構的指標。您應回想起,BITMAPINFO結構由BITMAPINFOHEADER結構和色彩對照表組成。因此這僅是一個指向packed DIB的指標。

函式也需要一個指向圖素位元的指標。儘管程式碼寫得很不漂亮,但這個指標還是可以從資訊表頭內的資訊推出。注意,當您存取BITMAPFILEHEADER結構的bfOffBits欄位時,這個指標能很容易地計算出。bfOffBits欄位指出了從DIB檔案的開頭到圖素位元的偏移量。您可以簡單地把此偏移量加到BITMAPINFO指標中,然後減去BITMAPFILEHEADER結構的大小。然而,當您從剪貼簿上得到指向packed DIB的指標時,這並不起作用,因為沒有BITMAPFILEHEADER結構。

此圖表顯示了兩個所需的指標:

SetDIBitsToDevice和StretchDIBits函式需要兩個指向DIB的指標,因為這兩個部分不在連續的記憶體塊內。您可能有如下所示的兩塊記憶體:

確實,把DIB分成兩個記憶體塊是很有用的,只是我們更喜歡與整個DIB儲存在單個記憶體塊的packed DIB打交道。

除了這兩個指標,SetDIBitsToDevice和StretchDIBits函式通常也需要DIB的圖素寬度和高度。如只想顯示DIB的一部分,就不必明確地知道這些值,但它們會定義您在DIB圖素位元陣列內定義的矩形的上限。

點對點圖素顯示

SetDIBitsToDevice函式顯示沒有延伸和縮小的DIB。DIB的每個圖素對應到輸出設備的一個圖素上,而且DIB中的圖像一定會被正確顯示出來-也就是說,圖像的頂列在上方。任何會影響裝置內容的座標轉換都影響了顯示DIB的開始位置,但不影響顯示出來的圖片大小和方向。該函式如下:

iLines = SetDIBitsToDevice ( hdc, // device context handle xDst, // x destination coordinate yDst, // y destination coordinate cxSrc, // source rectangle width cySrc, // source rectangle height xSrc, // x source coordinate ySrc, // y source coordinate yScan, // first scan line to draw cyScans, // number of scan lines to draw pBits, // pointer to DIB pixel bits pInfo, // pointer to DIB information fClrUse) ; // color use flag

不要對參數的數量感到厭煩,在多數情況下,函式用起來比看起來要簡單。不過在其他用途上來說,它的用法真的是亂七八糟,不過我們將學會怎麼用它。

和GDI顯示函式一樣,SetDIBitsToDevice的第一個參數是裝置內容代號,它指出顯示DIB的設備。下面兩個參數xDst和yDst,是輸出設備的邏輯座標,並指出了顯示DIB圖像左上角的座標(「上端」指的是視覺上的上方,並不是DIB圖素的第一行)。注意,這些都是邏輯座標,因此它們附屬於實際上起作用的任何座標轉換方式或-在Windows NT的情況下-設定的任何空間轉換。在內定的MM_TEXT映射方式下,可以把這些參數設為0,從顯示平面上向左向上顯示DIB圖像。

您可以顯示整個DIB圖像或僅顯示其中的一部分,這就是後四個參數的作用。但是DIB圖素資料的由上而下的方向產生了許多誤解,待會兒會談到這些。現在應該清楚當顯示整個DIB時,應把xSrc和ySrc設定為0,並且cxSrc和cySrc應分別等於DIB的圖素寬度和高度。注意,因為BITMAPINFOHEADER結構的biHeight欄位對於由上而下的DIB來說是負的,cySrc應設定為biHeight欄位的絕對值。

此函式的文件 (/Platform SDK/Graphics and Multimedia Services/GDI/Bitmaps/Bitmap Reference/Bitmap Functions/SetDIBitsToDevice)中說xSrc、ySrc、cxSrc和cySrc參數是邏輯單位。這是不正確的,它們是圖素的座標和尺寸。對於DIB內的圖素,擁有邏輯座標和單位是沒有什麼意義的。而且,不管是什麼映射方式,在輸出設備上顯示的DIB始終是cxSrc圖素寬和cySrc圖素高。

現在先不詳細討論這兩個參數yScan和cyScan。這些參數在您從磁片檔案或通過數據機讀取資料時,透過每次顯示DIB的一小部分減少對記憶體的需求。通常,yScan設定為0,cyScan設定為DIB的高度。

pBits參數是指向DIB圖素位元的指標。pInfo參數是指向DIB的BITMAPINFO結構的指標。雖然BITMAPINFO結構的位址與BITMAPINFOHEADER結構的位址相同,但是SetDIBitsToDevice結構被定義為使用BITMAPINFO結構,暗示著:對於1位元、4位元和8位元DIB,點陣圖資訊表頭後必須跟著色彩對照表。儘管pInfo參數被定義為指向BITMAPINFO結構的指標,它也是指向BITMAPCOREINFO、BITMAPV4HEADER或BITMAPV5HEADER結構的指標。

最後一個參數是DIB_RGB_COLORS或DIB_PAL_COLORS,在WINGDI.H內分別定義為0和1。如果您使用DIB_RGB_COLORS,這意味著DIB包含了色彩對照表。DIB_PAL_COLORS旗標指出,DIB內的色彩對照表已經被指向在裝置內容內選定並識別的調色盤的16位元索引代替。在下一章我們將學習這個選項。現在先使用DIB_RGB_COLORS,或者是0。

SetDIBitsToDevice函式傳回所顯示的掃描行的數目。

因此,要呼叫SetDIBitsToDevice來顯示整個DIB圖像,您需要下列資訊:

  • hdc 目的表面的裝置內容代號
  • xDst和yDst 圖像左上角的目的座標
  • cxDib和cyDib DIB的圖素寬度和高度,在這裏,cyDib是BITMAPINFOHEADER結構內biHeight欄位的絕對值。
  • pInfo和pBits 指向點陣圖資訊部分和圖素位元的指標

然後用下列方法呼叫SetDIBitsToDevice:

SetDIBitsToDevice ( hdc, // device context handle xDst, // x destination coordinate yDst, // y destination coordinate cxDib, // source rectangle width cyDib, // source rectangle height 0, // x source coordinate 0, // y source coordinate 0, // first scan line to draw cyDib, // number of scan lines to draw pBits, // pointer to DIB pixel bits pInfo, // pointer to DIB information 0) ; // color use flag

因此,在DIB的12個參數中,四個設定為0,一個是重複的。

程式15-2 SHOWDIB1通過使用SetDIBitsToDevice函式顯示DIB。

程式15-2 SHOWDIB1 SHOWDIB1.C /*--------------------------------------------------------------------- SHOWDIB1.C -- Shows a DIB in the client area (c) Charles Petzold, 1998 ---------------------------------------------------------------------*/ #include <windows.h> #include "dibfile.h" #include "resource.h" LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ; TCHAR szAppName[] = TEXT ("ShowDib1") ; 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 ("Show DIB #1"), 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 ; } LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam,LPARAM lParam) { static BITMAPFILEHEADER * pbmfh ; static BITMAPINFO * pbmi ; static BYTE * pBits ; static int cxClient, cyClient, cxDib, cyDib ; static TCHAR szFileName [MAX_PATH], szTitleName [MAX_PATH] ; BOOL bSuccess ; HDC hdc ; PAINTSTRUCT ps ; switch (message) { case WM_CREATE: DibFileInitialize (hwnd) ; return 0 ; case WM_SIZE: cxClient = LOWORD (lParam) ; cyClient = HIWORD (lParam) ; return 0 ; case WM_INITMENUPOPUP: EnableMenuItem ((HMENU) wParam, IDM_FILE_SAVE, pbmfh ? MF_ENABLED : MF_GRAYED) ; return 0 ; case WM_COMMAND: switch (LOWORD (wParam)) { case IDM_FILE_OPEN: // Show the File Open dialog box if (!DibFileOpenDlg (hwnd, szFileName, szTitleName)) return 0 ; // If there's an existing DIB, free the memory if (pbmfh) { free (pbmfh) ; pbmfh = NULL ; } // Load the entire DIB into memory SetCursor (LoadCursor (NULL, IDC_WAIT)) ; ShowCursor (TRUE) ; pbmfh = DibLoadImage (szFileName) ; ShowCursor (FALSE) ; SetCursor (LoadCursor (NULL, IDC_ARROW)) ; // Invalidate the client area for later update InvalidateRect (hwnd, NULL, TRUE) ; if (pbmfh == NULL) { MessageBox ( hwnd, TEXT ("Cannot load DIB file"), szAppName, 0) ; return 0 ; } // Get pointers to the info structure & the bits pbmi = (BITMAPINFO *) (pbmfh + 1) ; pBits = (BYTE *) pbmfh + pbmfh->bfOffBits ; // Get the DIB width and height if (pbmi->bmiHeader.biSize == sizeof (BITMAPCOREHEADER)) { cxDib = ((BITMAPCOREHEADER *) pbmi)->bcWidth ; cyDib = ((BITMAPCOREHEADER *) pbmi)->bcHeight ; } else { cxDib = pbmi->bmiHeader.biWidth ; cyDib = abs( pbmi->bmiHeader.biHeight) ; } return 0 ; case IDM_FILE_SAVE: // Show the File Save dialog box if (!DibFileSaveDlg (hwnd, szFileName, szTitleName)) return 0 ; // Save the DIB to memory SetCursor (LoadCursor (NULL, IDC_WAIT)) ; ShowCursor (TRUE) ; bSuccess = DibSaveImage (szFileName, pbmfh) ; ShowCursor (FALSE) ; SetCursor (LoadCursor (NULL, IDC_ARROW)) ; if (!bSuccess) MessageBox ( hwnd, TEXT ("Cannot save DIB file"), szAppName, 0) ; return 0 ; } break ; case WM_PAINT: hdc = BeginPaint (hwnd, &ps) ; if (pbmfh) SetDIBitsToDevice (hdc, 0, // xDst 0, // yDst cxDib, // cxSrc cyDib, // cySrc 0, // xSrc 0, // ySrc 0, // first scan line cyDib, // number of scan lines pBits, pbmi, DIB_RGB_COLORS) ; EndPaint (hwnd, &ps) ; return 0 ; case WM_DESTROY: if (pbmfh) free (pbmfh) ; PostQuitMessage (0) ; return 0 ; } return DefWindowProc (hwnd, message, wParam, lParam) ; }

DIBFILE.H /*------------------------------------------------------------------------- DIBFILE.H -- Header File for DIBFILE.C -----------------------------------------------------------------------*/ void DibFileInitialize (HWND hwnd) ; BOOL DibFileOpenDlg (HWND hwnd, PTSTR pstrFileName, PTSTR pstrTitleName) ; BOOL DibFileSaveDlg (HWND hwnd, PTSTR pstrFileName, PTSTR pstrTitleName) ; BITMAPFILEHEADER * DibLoadImage (PTSTR pstrFileName) ; BOOL DibSaveImage(PTSTR pstrFileName, BITMAPFILEHEADER *) ;

DIBFILE.C /*--------------------------------------------------------------------------- DIBFILE.C -- DIB File Functions ----------------------------------------------------------------------------*/ #include <windows.h> #include <commdlg.h> #include "dibfile.h" static OPENFILENAME ofn ; void DibFileInitialize (HWND hwnd) { static TCHAR szFilter[] = TEXT ("Bitmap Files (*.BMP)\0*.bmp\0") \ TEXT ("All Files (*.*)\0*.*\0\0") ; ofn.lStructSize = sizeof (OPENFILENAME) ; ofn.hwndOwner = hwnd ; ofn.hInstance = NULL ; ofn.lpstrFilter = szFilter ; ofn.lpstrCustomFilter = NULL ; ofn.nMaxCustFilter = 0 ; ofn.nFilterIndex = 0 ; ofn.lpstrFile = NULL ; // Set in Open and Close functions ofn.nMaxFile = MAX_PATH ; ofn.lpstrFileTitle = NULL ; // Set in Open and Close functions ofn.nMaxFileTitle = MAX_PATH ; ofn.lpstrInitialDir = NULL ; ofn.lpstrTitle = NULL ; ofn.Flags = 0 ; // Set in Open and Close functions ofn.nFileOffset = 0 ; ofn.nFileExtension = 0 ; ofn.lpstrDefExt = TEXT ("bmp") ; ofn.lCustData = 0 ; ofn.lpfnHook = NULL ; ofn.lpTemplateName = NULL ; } BOOL DibFileOpenDlg (HWND hwnd, PTSTR pstrFileName, PTSTR pstrTitleName) { ofn.hwndOwner = hwnd ; ofn.lpstrFile = pstrFileName ; ofn.lpstrFileTitle = pstrTitleName ; ofn.Flags = 0 ; return GetOpenFileName (&ofn) ; } BOOL DibFileSaveDlg (HWND hwnd, PTSTR pstrFileName, PTSTR pstrTitleName) { ofn.hwndOwner = hwnd ; ofn.lpstrFile = pstrFileName ; ofn.lpstrFileTitle = pstrTitleName ; ofn.Flags = OFN_OVERWRITEPROMPT ; return GetSaveFileName (&ofn) ; } BITMAPFILEHEADER * DibLoadImage (PTSTR pstrFileName) { BOOL bSuccess ; DWORD dwFileSize, dwHighSize, dwBytesRead ; HANDLE hFile ; BITMAPFILEHEADER * pbmfh ; hFile = CreateFile ( pstrFileName, GENERIC_READ, FILE_SHARE_READ,NULL, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN,NULL) ; if ( hFile == INVALID_HANDLE_VALUE) return NULL ; dwFileSize = GetFileSize (hFile, &dwHighSize) ; if (dwHighSize) { CloseHandle (hFile) ; return NULL ; } pbmfh = malloc (dwFileSize) ; if (!pbmfh) { CloseHandle (hFile) ; return NULL ; } bSuccess = ReadFile (hFile, pbmfh, dwFileSize, &dwBytesRead, NULL) ; CloseHandle (hFile) ; if (!bSuccess || (dwBytesRead != dwFileSize) || (pbmfh->bfType != * (WORD *) "BM") || (pbmfh->bfSize != dwFileSize)) { free (pbmfh) ; return NULL ; } return pbmfh ; } BOOL DibSaveImage (PTSTR pstrFileName, BITMAPFILEHEADER * pbmfh) { BOOL bSuccess ; DWORD dwBytesWritten ; HANDLE hFile ; hFile = CreateFile ( pstrFileName, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL) ; if (hFile == INVALID_HANDLE_VALUE) return FALSE ; bSuccess = WriteFile (hFile, pbmfh, pbmfh->bfSize, &dwBytesWritten, NULL) ; CloseHandle (hFile) ; if (!bSuccess || (dwBytesWritten != pbmfh->bfSize)) { DeleteFile (pstrFileName) ; return FALSE ; } return TRUE ; }

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

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

DIBFILE.C檔案包含了顯示「File Open」和「File Save」對話方塊的常式,以及把整個DIB檔案(擁有BITMAPFILEHEADER結構)載入單個記憶體塊的常式。程式也會將這樣一個記憶體區寫出到檔案。

當在SHOWDIB1.C內執行「File Open」命令載入DIB檔案後,程式計算記憶體塊中BITMAPINFOHEADER結構和圖素位元的偏移量,程式也獲得DIB的圖素寬度和高度。所有資訊都儲存在靜態變數中。在處理WM_PAINT訊息處理期間,程式通過呼叫SetDIBitsToDevice顯示DIB。

當然,SHOWDIB1還缺少一些功能。例如,如果DIB對顯示區域來說太大,則沒有捲動列可用來移動查看。在 下一章 的末尾將修改這些缺陷。

DIB的顛倒世界

我們將得到一個重要的教訓,它不僅在生活中重要,而且在作業系統的應用程式介面的設計中也重要。這個教訓是:覆水難收。

回到OS/2 Presentation Manager那由下而上的DIB圖素位元的定義處,這樣的定義是有點道理的,因為PM內的任何座標系都有一個內定的左下角原點。例如:在PM視窗內,內定的(0,0)原點是視窗的左下角。(如果您覺得這很古怪,很多人和您的感覺一樣。如果您不覺得古怪,那您可能是位數學家。)點陣圖的繪製函式也根據左下角指定目的地。

因此,在OS/2內如果給點陣圖指定了目的座標(0,0),則圖像將從視窗的左下角向上向右顯示,如圖15-1所示。

在夠慢的機器上,您能實際看到電腦由下而上繪製點陣圖。

儘管OS/2座標系統顯得很古怪,但它的優點是高度的一致。點陣圖的 (0,0)原點是點陣圖檔案中第一行的第一個圖素,並且此圖素被映射到在點陣圖繪製函式中指定的目的座標上。

Windows存在的問題是不能保持內部的一致性。當您只要顯示整個DIB圖像中的一小塊矩形時,就要使用參數xSrc、ySrc、cxSrc和cySrc。這些來源座標和大小與DIB資料的第一行(圖像的最後一行)相關。這方面與OS/2相似,與OS/2不同的是,Windows在目的座標上顯示圖像的頂列。因此,如果顯示整個DIB圖像,顯示在(xDst,yDst)的圖素是位於座標(0,cyDib - 1)處的圖素。DIB資料的最後一列就是圖形的頂列。如果僅顯示圖像的一部分,則在(xDst,yDst)顯示的圖素是位於座標(xSrc, ySrc + cySrc - 1)處的DIB圖素。

圖15-2顯示的圖表將幫助您理解這方面的內容。您可以把下面顯示的DIB當成是儲存在記憶體中的-就是說,上下顛倒。座標的原點與DIB圖素資料的第一個位元是一致的。SetDIBitsToDevice的xSrc參數是以DIB的左邊為基準,並且cxSrc是xSrc右邊的圖像寬度,這很直觀。ySrc參數以DIB資料的首列(也就是圖像的底部)為基準,並且cySrc是從ySrc到資料的末列(圖像的頂端)的圖像高度。

如果目的裝置內容具有使用MM_TEXT映射方式的內定圖素座標,來源矩形和目的矩形角落座標之間的關係顯示在表15-3中。

圖15-1 在OS/2中以(0,0)為目的點顯示的點陣圖

圖15-2 正常DIB(由下而上)的座標

表15-3

(xSrc,ySrc)不映射到(xDst,yDst),使得表格顯得很混亂。在其他映射方式中,點(xSrc,ySrc + cySrc - 1)總是映射到邏輯點(xDst,yDst),圖像也與MM_TEXT所顯示的一樣。

到目前為止,我們討論了當BITMAPINFOHEADER結構的biHeight欄位是正值時的正常情況。如果biHeight欄位是負值,則DIB資料會以合理的由上而下的方式排列。您可能會認為這樣將解決所有問題,如果您真地這樣認為,那您就錯了。

很明顯地,有人會認為如果把DIB上下倒置,旋轉每一行,然後給biHeight設定一個正值,它將像正常的由下而上的DIB一樣操作,所有與DIB矩形相關的現存程式碼就不必修改。我認為這是一個合理的目的,但它忘記了一個事實,程式需要修改以處理由上而下的DIB,這樣就不會使用一個負高度。

而且,此決定的結果意味著由上而下的DIB的來源座標在DIB資料的最後一列有一個原點,它也是圖像的底列。這與我們遇到的情況完全不同。位於(0,0)原點的DIB圖素不再是pBits指標引用的第一個圖素,也不是DIB檔案的最後一個圖素,它位於兩者之間。

圖15-3顯示的圖表說明了在由上而下的DIB中指定矩形的方法,也是它儲存在檔案或記憶體中的樣子。

無論如何,這個方案的實際優點是SetDIBitsToDevice函式的參數與DIB資料的方向無關。如果有顯示了同一圖像的兩個DIB(一個由下而上,另一個由上而下。表示在兩個DIB檔案內的列順序相反),您可以使用相同的參數呼叫SetDIBitsToDevice來選擇顯示圖像的相同部分。

如程式15-3 APOLLO11中所示。

圖15-3 指定由上而下的DIB的座標

程式15-3 APOLLO11 APOLLO11.C /*------------------------------------------------------------------------- APOLLO11.C -- Program for screen captures (c) Charles Petzold, 1998 ----------------------------------------------------------------------*/ #include <windows.h> #include "dibfile.h" LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ; TCHAR szAppName[] = TEXT ("Apollo11") ; int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { HWND hwnd ; MSG msg ; WNDCLAS 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 ("Apollo 11"), 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 BITMAPFILEHEADER * pbmfh [2] ; static BITMAPINFO * pbmi [2] ; static BYTE * pBits [2] ; static int cxClient, cyClient, cxDib[2], cyDib[2] ; HDC hdc ; PAINTSTRUCT ps ; switch (message) { case WM_CREATE: pbmfh[0] = DibLoadImage (TEXT ("Apollo11.bmp")) ; pbmfh[1] = DibLoadImage (TEXT ("ApolloTD.bmp")) ; if (pbmfh[0] == NULL || pbmfh[1] == NULL) { MessageBox (hwnd, TEXT ("Cannot load DIB file"), szAppName, 0) ; return 0 ; } // Get pointers to the info structure & the bits pbmi [0] = (BITMAPINFO *) (pbmfh[0] + 1) ; pbmi [1] = (BITMAPINFO *) (pbmfh[1] + 1) ; pBits [0] = (BYTE *) pbmfh[0] + pbmfh[0]->bfOffBits ; pBits [1] = (BYTE *) pbmfh[1] + pbmfh[1]->bfOffBits ; // Get the DIB width and height (assume BITMAPINFOHEADER) // Note that cyDib is the absolute value of the header value!!! cxDib [0] = pbmi[0]->bmiHeader.biWidth ; cxDib [1] = pbmi[1]->bmiHeader.biWidth ; cyDib [0] = abs (pbmi[0]->bmiHeader.biHeight) ; cyDib [1] = abs (pbmi[1]->bmiHeader.biHeight) ; return 0 ; case WM_SIZE: cxClient = LOWORD (lParam) ; cyClient = HIWORD (lParam) ; return 0 ; case WM_PAINT: hdc = BeginPaint (hwnd, &ps) ; // Bottom-up DIB full size SetDIBitsToDevice (hdc, 0, // xDst cyClient / 4, // yDst cxDib[0], // cxSrc cyDib[0], // cySrc 0, // xSrc 0, // ySrc 0, // first scan line cyDib[0], // number of scan lines pBits[0], pbmi[0], DIB_RGB_COLORS) ; // Bottom-up DIB partial SetDIBitsToDevice (hdc, 240, // xDst cyClient / 4, // yDst 80, // cxSrc 166, // cySrc 80, // xSrc 60, // ySrc 0, // first scan line cyDib[0], // number of scan lines pBits[0], pbmi[0], DIB_RGB_COLORS) ; // Top-down DIB full size SetDIBitsToDevice (hdc, 340, // xDst cyClient / 4, // yDst cxDib[0], // cxSrc cyDib[0], // cySrc 0, // xSrc 0, // ySrc 0, // first scan line cyDib[0], // number of scan lines pBits[0], pbmi[0], DIB_RGB_COLORS) ; // Top-down DIB partial SetDIBitsToDevice (hdc, 580, // xDst cyClient / 4, // yDst 80, // cxSrc 166, // cySrc 80, // xSrc 60, // ySrc 0, // first scan line cyDib[1], // number of scan lines pBits[1], pbmi[1], DIB_RGB_COLORS) ; EndPaint (hwnd, &ps) ; return 0 ; case WM_DESTROY: if (pbmfh[0]) free (pbmfh[0]) ; if (pbmfh[1]) free (pbmfh[1]) ; PostQuitMessage (0) ; return 0 ; } return DefWindowProc (hwnd, message, wParam, lParam) ; }

程式載入了名為APOLLO11.BMP(由下而上版本)和APOLLOTD.BMP(由上而下版本)的兩個DIB。它們都是220圖素寬和240圖素高。注意,在程式從表頭資訊結構中確定DIB的寬度和高度時,它使用abs函式得到biHeight欄位的絕對值。當以全部大小或範圍顯示DIB時,不管顯示點陣圖的種類,xSrc、ySrc、cxSrc和cySrc座標都是相同的。結果如圖15-4所示。

注意,「第一條掃描線」和「掃描線數目」參數保持不變,我將在以後簡短說明。pBits參數也不變,不要只為了使它指向您需要顯示的區域而試圖更改pBits。

我在這個問題上花了這麼多時間,並不是因為要讓那些試圖跟API定義中有問題的部分妥協的Windows程式寫作者難堪,而是想讓您不至於因為這個令人混淆的問題而緊張起來。這個問題之所以令人困惑,是因為它本身早就被搞混了。

我也想讓您留意Windows文件中的某些敘述,例如對SetDIBitsToDevice,文件說:「由下而上DIB的原點是點陣圖的左下角;由上而下DIB的原點是左上角」。這不僅模糊,而且是錯誤的。我可以用更好的方式來講述:由下而上DIB的原點是點陣圖圖像的左下角,它是點陣圖資料的第一列的第一個圖素。由上而下DIB的原點也是點陣圖圖像的左下角,但在這種情況下,左下角是點陣圖資料的最後一列的第一個圖素。

如果要撰寫存取DIB個別位元的函式,問題會變的更糟。這應該與您為顯示部分DIB映射而指定的座標一致,我的解決方法是(我將在第十六章的DIB程式庫中使用)以統一的手法參考DIB圖素和座標,就像在圖像被正確顯示時(0,0)原點所指的是DIB圖像頂行的最左邊的圖素一樣。

循序顯示

擁有大量記憶體能確保程式更容易地執行。要顯示磁片檔案內的DIB,可以分為兩個獨立的工作:將DIB載入記憶體,然後顯示它。

然而,您也可能在不把整個檔案載入記憶體的情況下顯示DIB。即使有足夠的實體記憶體提供給DIB,把DIB移入記憶體也會迫使Windows的虛擬記憶體系統把記憶體中別的資料和程式碼移到磁片上。如果DIB僅用於顯示並立即從記憶體中消除,這就非常討厭。

還有另一個問題:假設DIB位於例如軟碟的慢速儲存媒體上,或由數據機傳輸過來,或者來自掃描器或視頻截取程式取得圖素資料的轉換常式。您是否得等到整個DIB被載入記憶體後才顯示它?還是從磁片或電話線或掃描器上得到DIB時,就開始顯示它?

解決這些問題是SetDIBitsToDevice函式中yScan和cyScans參數的目的。要使用這個功能,需要多次呼叫SetDIBitsToDevice,大多數情況下使用同樣的參數。然而對於每次呼叫,pBits參數指向點陣圖圖素總體排列的不同部分。yScans參數指出了pBits指向圖素資料的行,cyScans參數是被pBits引用的行數。這大量地減少了記憶體需求。您僅需要為儲存DIB的資訊部分(BITMAPINFOHEADER結構和色彩對照表)和至少一行圖素資料配置足夠的記憶體。

例如,假設DIB有23行圖素,您希望每次最多5行的分段顯示這個DIB。您可能想配置一個由變數pInfo引用的記憶體塊來儲存DIB的BITMAPINFO部分,然後從檔案中讀取該DIB。在檢查完此結構的欄位後,能夠計算出一行的位元組長度。乘以5並配置該大小的另一個記憶體塊(pBits)。現在讀取前5行,呼叫您正常使用的函式,把yScan設定為0,把cyScans設定為5。現在從檔案中讀取下5行,這一次將yScan設定為5,繼續將yScan設定為10,然後為15。最後,將最後3行讀入pBits指向的記憶體塊,並將yScan設定為20,將cyScans設定為3,以呼叫SetDIBitsToDevice。

現在有一個不好的訊息。首先,使用SetDIBitsToDevice的這個功能要求程式的資料取得和資料顯示元素之間結合得相當緊密。這通常是不理想的,因為您必須在獲得資料和顯示資料之間切換。首先,您將延緩整個程序;第二,SetDIBitsToDevice是唯一具有這個功能的點陣圖顯示函式。StretchDIBits函式不包括這個功能,因此您不能使用它以不同圖素大小顯示發表的DIB。您必須呼叫StretchDIBits多次,每次更改BITMAPINFOHEADER結構中的資訊,並在螢幕的不同區域顯示結果。

程式15-4 SEQDISP展示了這個功能的使用方法。

圖15-4 APOLLO11的螢幕顯示

程式15-4 SEQDISP SEQDISP.C /*---------------------------------------------------------------------------- SEQDISP.C -- Sequential Display of DIBs (c) Charles Petzold, 1998 -----------------------------------------------------------------------------*/ #include <windows.h> #include "resource.h" LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ; TCHAR szAppName[] = TEXT ("SeqDisp") ; 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 Sequential Display"), 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 ; } LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { static BITMAPINFO * pbmi ; static BYTE * pBits ; static int cxDib, cyDib, cBits ; 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") ; BITMAPFILEHEADER bmfh ; BOOL bSuccess, bTopDown ; DWORD dwBytesRead ; HANDLE hFile ; HDC hdc ; HMENU hMenu ; int iInfoSize, iBitsSize, iRowLength, y ; PAINTSTRUCT ps ; switch (message) { case WM_CREATE: ofn.lStructSize = sizeof (OPENFILENAME) ; ofn.hwndOwner = hwnd ; ofn.hInstance = NULL ; ofn.lpstrFilter = szFilter ; ofn.lpstrCustomFilter = NULL ; ofn.nMaxCustFilter = 0 ; ofn.nFilterIndex = 0 ; ofn.lpstrFile = szFileName ; ofn.nMaxFile = MAX_PATH ; ofn.lpstrFileTitle = szTitleName ; ofn.nMaxFileTitle = MAX_PATH ; ofn.lpstrInitialDir = NULL ; ofn.lpstrTitle = NULL ; ofn.Flags = 0 ; ofn.nFileOffset = 0 ; ofn.nFileExtension = 0 ; ofn.lpstrDefExt = TEXT ("bmp") ; ofn.lCustData = 0 ; ofn.lpfnHook = NULL ; ofn.lpTemplateName = NULL ; return 0 ; case WM_COMMAND: hMenu = GetMenu (hwnd) ; switch (LOWORD (wParam)) { case IDM_FILE_OPEN: // Display File Open dialog if (!GetOpenFileName (&ofn)) return 0 ; // Get rid of old DIB if (pbmi) { free (pbmi) ; pbmi = NULL ; } if (pBits) { free (pBits) ; pBits = NULL ; } // Generate WM_PAINT message to erase background InvalidateRect (hwnd, NULL, TRUE) ; UpdateWindow (hwnd) ; // Open the file hFile = CreateFile (szFileName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL) ; if (hFile == INVALID_HANDLE_VALUE) { MessageBox ( hwnd, TEXT ("Cannot open file."), szAppName, MB_ICONWARNING | MB_OK) ; return 0 ; } // Read in the BITMAPFILEHEADER bSuccess = ReadFile (hFile,&bmfh, sizeof (BITMAPFILEHEADER), &dwBytesRead, NULL) ; if (!bSuccess || dwBytesRead != sizeof (BITMAPFILEHEADER)) { MessageBox (hwnd, TEXT ("Cannot read file."), szAppName, MB_ICONWARNING | MB_OK) ; CloseHandle (hFile) ; return 0 ; } // Check that it's a bitmap if (bmfh.bfType != * (WORD *) "BM") { MessageBox (hwnd, TEXT ("File is not a bitmap."), szAppName, MB_ICONWARNING | MB_OK) ; CloseHandle (hFile) ; return 0 ; } // Allocate memory for header and bits iInfoSize = bmfh.bfOffBits - sizeof (BITMAPFILEHEADER) ; iBitsSize = bmfh.bfSize - bmfh.bfOffBits ; pbmi = malloc (iInfoSize) ; pBits = malloc (iBitsSize) ; if (pbmi == NULL || pBits == NULL) { MessageBox (hwnd, TEXT ("Cannot allocate memory."), szAppName, MB_ICONWARNING | MB_OK) ; if (pbmi) free (pbmi) ; if (pBits) free (pBits) ; CloseHandle (hFile) ; return 0 ; } // Read in the Information Header bSuccess = ReadFile (hFile, pbmi, iInfoSize, &dwBytesRead, NULL) ; if (!bSuccess || (int) dwBytesRead != iInfoSize) { MessageBox (hwnd, TEXT ("Cannot read file."), szAppName, MB_ICONWARNING | MB_OK) ; if (pbmi) free (pbmi) ; if (pBits) free (pBits) ; CloseHandle (hFile) ; return 0 ; } // Get the DIB width and height bTopDown = FALSE ; if (pbmi->bmiHeader.biSize == sizeof (BITMAPCOREHEADER)) { cxDib = ((BITMAPCOREHEADER *) pbmi)->bcWidth ; cyDib = ((BITMAPCOREHEADER *) pbmi)->bcHeight ; cBits = ((BITMAPCOREHEADER *) pbmi)->bcBitCount ; } else { if (pbmi->bmiHeader.biHeight < 0) bTopDown = TRUE ; cxDib = pbmi->bmiHeader.biWidth ; cyDib = abs (pbmi->bmiHeader.biHeight) ; cBits = pbmi->bmiHeader.biBitCount ; if (pbmi->bmiHeader.biCompression != BI_RGB && pbmi->bmiHeader.biCompression != BI_BITFIELDS) { MessageBox (hwnd, TEXT ("File is compressed."), szAppName, MB_ICONWARNING | MB_OK) ; if (pbmi) free (pbmi) ; if (pBits) free (pBits) ; CloseHandle (hFile) ; return 0 ; } } // Get the row length iRowLength = ((cxDib * cBits + 31) & ~31) >> 3 ; // Read and display SetCursor (LoadCursor (NULL, IDC_WAIT)) ; ShowCursor (TRUE) ; hdc = GetDC (hwnd) ; for (y = 0 ; y < cyDib ; y++) { ReadFile (hFile, pBits + y * iRowLength,iRowLength,&dwBytesRead, NULL) ; SetDIBitsToDevice (hdc, 0, // xDst 0, // yDst cxDib, // cxSrc cyDib, // cySrc 0, // xSrc 0, // ySrc bTopDown ? cyDib - y - 1 : y, // first scan line 1, // number of scan lines pBits + y * iRowLength, pbmi, DIB_RGB_COLORS) ; } ReleaseDC (hwnd, hdc) ; CloseHandle (hFile) ; ShowCursor (FALSE) ; SetCursor (LoadCursor (NULL, IDC_ARROW)) ; return 0 ; } break ; case WM_PAINT: hdc = BeginPaint (hwnd, &ps) ; if (pbmi && pBits) SetDIBitsToDevice (hdc, 0, // xDst 0, // yDst cxDib, // cxSrc cyDib, // cySrc 0, // xSrc 0, // ySrc 0, // first scan line cyDib, // number of scan lines pBits, DIB_RGB_COLORS) ; EndPaint (hwnd, &ps) ; return 0 ; case WM_DESTROY: if (pbmi) free (pbmi) ; if (pBits) free (pBits) ; PostQuitMessage (0) ; return 0 ; } return DefWindowProc (hwnd, message, wParam, lParam) ; }

SEQDISP.RC (摘錄) //Microsoft Developer Studio generated resource script. #include "resource.h" #include "afxres.h" ///////////////////////////////////////////////////////////////////////////// // Accelerator SEQDISP ACCELERATORS DISCARDABLE BEGIN "O", IDM_FILE_OPEN, VIRTKEY, CONTROL, NOINVERT END ///////////////////////////////////////////////////////////////////////////// // Menu SEQDISP MENU DISCARDABLE BEGIN POPUP "&File" BEGIN MENUITEM "&Open...\tCtrl+O", IDM_FILE_OPEN END END

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

在處理「File Open」功能表命令期間,在SEQDISP.C內的所有檔案I/O都會發生。在處理WM_COMMAND的最後,程式進入讀取單行圖素並用SetDIBitsToDevice顯示該行圖素的迴圈。整個DIB儲存在記憶體中以便在處理WM_PAINT期間也能顯示它。

縮放到合適尺寸

SetDIBitsToDevice完成了將DIB的圖素對點送入輸出設備的顯示程序。這對於列印DIB用處不大。印表機的解析度越高,得到的圖像就越小,您最終會得到如郵票大小的圖像。

要通過縮小或放大DIB,在輸出設備上以特定的大小顯示它,可以使用StretchDIBits:

iLines = StretchDIBits ( hdc, // device context handle xDst, // x destination coordinate yDst, // y destination coordinate cxDst, // destination rectangle width cyDst, // destination rectangle height xSrc, // x source coordinate ySrc, // y source coordinate cxSrc, // source rectangle width cySrc, // source rectangle height pBits, // pointer to DIB pixel bits pInfo, // pointer to DIB information fClrUse, // color use flag dwRop) ; // raster operation

函式參數除了下列三個方面,均與SetDIBitsToDevice相同。

  • 目的座標包括邏輯寬度(cxDst)和高度(cyDst),以及開始點。
  • 不能通過持續顯示DIB來減少記憶體需求。
  • 最後一個參數是位元映射操作方式,它指出了DIB圖素與輸出設備圖素結合的方式,在最後一章將學到這些內容。現在我們為此參數設定為SRCCOPY。

還有另一個更細微的差別。如果查看SetDIBitsToDevice的宣告,您會發現cxSrc和cySrc是DWORD,這是32位元無正負號長整數型態。在StretchDIBits中,cxSrc和cySrc(以及cxDst和cyDst)定義為帶正負號的整數型態,這意味著它們可以為負數,實際上等一下就會看到,它們確實能為負數。如果您已經開始檢查是否別的參數也可以為負數,就讓我聲明一下:在兩個函式中,xSrc和ySrc均定義為int,但這是錯的,這些值始終是非負數。

DIB內的來源矩形被映射到目的矩形的座標顯示如表15-4所示。

表15-4

右列中的-1項是不精確的,因為放大的程度(以及映射方式和其他變換)能產生略微不同的結果。

例如,考慮一個2×2的DIB,這裏StretchDIBits的xSrc和ySrc參數均為0,cxSrc和cySrc均為2。假定我們顯示到的裝置內容具有MM_TEXT映射方式並且不進行變換。如果xDst和yDst均為0,cxDst和cyDst均為4,那麼我們將以倍數2放大DIB。每個來源圖素(x,y)將映射到下面所示的四個目的圖素上:

(0,0) --> (0,2) and (1,2) and (0,3) and (1,3) (1,0) --> (2,2) and (3,2) and (2,3) and (3,3) (0,1) --> (0,0) and (1,0) and (0,1) and (1,1) (1,1) --> (2,0) and (3,0) and (2,1) and (3,1)

上表正確地指出了目的的角,(0,3)、(3,3)、(0,0)和(3,0)。在其他情況下,座標可能是個大概值。

目的裝置內容的映射方式對SetDIBitsToDevice的影響僅是由於xDst和yDst是邏輯座標。StretchDIBits完全受映射方式的影響。例如,如果您設定了y值向上遞增的一種度量映射方式,DIB就會顛倒顯示。

您可以通過把cyDst設定為負數來彌補這種情況。實際上,您可以將任何參數的寬度和高度變為負值來水平或垂直翻轉DIB。在MM_TEXT映射方式下,如果cySrc和cyDst符號相反,DIB會沿著水平軸翻轉並顛倒顯示。如果cxSrc和cxDst符號相反,DIB會沿著垂直軸翻轉並顯示它的鏡面圖像。

下面是總結這些內容的運算式,xMM和yMM指出映射方式的方向,如果x值向右增長,則xMM值為1;如果x值向左增長,則值為-1。同樣,如果y值向下增長,則yMM值為1;如果y值向上增長,則值為-1。Sign函式對於正值傳回TURE,對於負值傳回FALSE。

if (!Sign (xMM × cxSrc × cxDst)) DIB is flipped on its vertical axis (mirror image) if (!Sign (yMM × cySrc × cyDst)) DIB is flipped on its horizontal axis (upside down)

若有疑問,請查閱 表15-4

程式15-5 SHOWDIB以實際尺寸顯示DIB、放大至顯示區域視窗的大小、列印DIB以及把DIB傳輸到剪貼簿。

程式15-5 SHOWDIB SHOWDIB2.C /*-------------------------------------------------------------------------- SHOWDIB2.C -- Shows a DIB in the client area (c) Charles Petzold, 1998 ---------------------------------------------------------------------------*/ #include <windows.h> #include "dibfile.h" #include "resource.h" LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ; TCHAR szAppName[] = TEXT ("ShowDib2") ; 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 ("Show DIB #2"), 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 ; } int ShowDib (HDC hdc, BITMAPINFO * pbmi, BYTE * pBits, int cxDib, int cyDib, int cxClient, int cyClient, WORD wShow) { switch (wShow) { case IDM_SHOW_NORMAL: return SetDIBitsToDevice (hdc, 0, 0, cxDib, cyDib, 0, 0, 0, cyDib, pBits, pbmi, DIB_RGB_COLORS) ; case IDM_SHOW_CENTER: return SetDIBitsToDevice (hdc, (cxClient - cxDib) / 2, (cyClient - cyDib) / 2, cxDib, cyDib, 0, 0, 0, cyDib, pBits, pbmi, DIB_RGB_COLORS) ; case IDM_SHOW_STRETCH: SetStretchBltMode (hdc, COLORONCOLOR) ; return StretchDIBits(hdc,0, 0, cxClient, cyClient, 0, 0, cxDib, cyDib, pBits, pbmi, DIB_RGB_COLORS, SRCCOPY) ; case IDM_SHOW_ISOSTRETCH: SetStretchBltMode (hdc, COLORONCOLOR) ; SetMapMode (hdc, MM_ISOTROPIC) ; SetWindowExtEx (hdc, cxDib, cyDib, NULL) ; SetViewportExtEx (hdc, cxClient, cyClient, NULL) ; SetWindowOrgEx (hdc, cxDib / 2, cyDib / 2, NULL) ; SetViewportOrgEx (hdc, cxClient / 2, cyClient / 2, NULL) ; return StretchDIBits(hdc,0, 0, cxDib, cyDib, 0, 0, cxDib, cyDib, pBits, pbmi, DIB_RGB_COLORS, SRCCOPY) ; } } LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam,LPARAM lParam) { static BITMAPFILEHEADER * pbmfh ; static BITMAPINFO * pbmi ; static BYTE * pBits ; static DOCINFO di = {sizeof (DOCINFO), TEXT ("ShowDib2: Printing") } ; static int cxClient, cyClient, cxDib, cyDib ; static PRINTDLG printdlg = { sizeof (PRINTDLG) } ; static TCHAR szFileName [MAX_PATH], szTitleName [MAX_PATH] ; static WORD wShow = IDM_SHOW_NORMAL ; BOOL bSuccess ; HDC hdc, hdcPrn ; HGLOBAL hGlobal ; HMENU hMenu ; int cxPage, cyPage, iEnable ; PAINTSTRUCT ps ; BYTE * pGlobal ; switch (message) { case WM_CREATE: DibFileInitialize (hwnd) ; return 0 ; case WM_SIZE: cxClient = LOWORD (lParam) ; cyClient = HIWORD (lParam) ; return 0 ; case WM_INITMENUPOPUP: hMenu = GetMenu (hwnd) ; if (pbmfh) iEnable = MF_ENABLED ; else iEnable = MF_GRAYED ; EnableMenuItem (hMenu, IDM_FILE_SAVE, iEnable) ; EnableMenuItem (hMenu, IDM_FILE_PRINT, iEnable) ; EnableMenuItem (hMenu, IDM_EDIT_CUT, iEnable) ; EnableMenuItem (hMenu, IDM_EDIT_COPY, iEnable) ; EnableMenuItem (hMenu, IDM_EDIT_DELETE, iEnable) ; return 0 ; case WM_COMMAND: hMenu = GetMenu (hwnd) ; switch (LOWORD (wParam)) { case IDM_FILE_OPEN: // Show the File Open dialog box if (!DibFileOpenDlg (hwnd, szFileName, szTitleName)) return 0 ; // If there's an existing DIB, free the memory if (pbmfh) { free (pbmfh) ; pbmfh = NULL ; } // Load the entire DIB into memory SetCursor (LoadCursor (NULL, IDC_WAIT)) ; ShowCursor (TRUE) ; pbmfh = DibLoadImage (szFileName) ; ShowCursor (FALSE) ; SetCursor (LoadCursor (NULL, IDC_ARROW)) ; // Invalidate the client area for later update InvalidateRect (hwnd, NULL, TRUE) ; if (pbmfh == NULL) { MessageBox ( hwnd, TEXT ("Cannot load DIB file"), szAppName, MB_ICONEXCLAMATION | MB_OK) ; return 0 ; } // Get pointers to the info structure & the bits pbmi = (BITMAPINFO *) (pbmfh + 1) ; pBits = (BYTE *) pbmfh + pbmfh->bfOffBits ; // Get the DIB width and height if (pbmi->bmiHeader.biSize == sizeof (BITMAPCOREHEADER)) { cxDib = ((BITMAPCOREHEADER *) pbmi)->bcWidth ; cyDib = ((BITMAPCOREHEADER *) pbmi)->bcHeight ; } else { cxDib = pbmi->bmiHeader.biWidth ; cyDib = abs (pbmi->bmiHeader.biHeight) ; } return 0 ; case IDM_FILE_SAVE: // Show the File Save dialog box if (!DibFileSaveDlg (hwnd, szFileName, szTitleName)) return 0 ; // Save the DIB to a disk file SetCursor (LoadCursor (NULL, IDC_WAIT)) ; ShowCursor (TRUE) ; bSuccess = DibSaveImage (szFileName, pbmfh) ; ShowCursor (FALSE) ; SetCursor (LoadCursor (NULL, IDC_ARROW)) ; if (!bSuccess) MessageBox ( hwnd, TEXT ("Cannot save DIB file"), szAppName, MB_ICONEXCLAMATION | MB_OK) ; return 0 ; case IDM_FILE_PRINT: if (!pbmfh) return 0 ; // Get printer DC printdlg.Flags = PD_RETURNDC | PD_NOPAGENUMS | PD_NOSELECTION ; if (!PrintDlg (&printdlg)) return 0 ; if (NULL == (hdcPrn = printdlg.hDC)) { MessageBox ( hwnd, TEXT ("Cannot obtain Printer DC"), szAppName, MB_ICONEXCLAMATION | MB_OK) ; return 0 ; } // Check whether the printer can print bitmaps if (!(RC_BITBLT & GetDeviceCaps (hdcPrn, RASTERCAPS))) { DeleteDC (hdcPrn) ; MessageBox ( hwnd, TEXT ("Printer cannot print bitmaps"), szAppName, MB_ICONEXCLAMATION | MB_OK) ; return 0 ; } // Get size of printable area of page cxPage = GetDeviceCaps (hdcPrn, HORZRES) ; cyPage = GetDeviceCaps (hdcPrn, VERTRES) ; bSuccess = FALSE ; // Send the DIB to the printer SetCursor (LoadCursor (NULL, IDC_WAIT)) ; ShowCursor (TRUE) ; if ((StartDoc (hdcPrn, &di) > 0) && (StartPage (hdcPrn) > 0)) { ShowDib ( hdcPrn, pbmi, pBits, cxDib, cyDib, cxPage, cyPage, wShow) ; if (EndPage (hdcPrn) > 0) { bSuccess = TRUE ; EndDoc (hdcPrn) ; } } ShowCursor (FALSE) ; SetCursor (LoadCursor (NULL, IDC_ARROW)) ; DeleteDC (hdcPrn) ; if (!bSuccess) MessageBox (hwnd, TEXT ("Could not print bitmap"), szAppName, MB_ICONEXCLAMATION | MB_OK) ; return 0 ; case IDM_EDIT_COPY: case IDM_EDIT_CUT: if (!pbmfh) return 0 ; // Make a copy of the packed DIB hGlobal = GlobalAlloc (GHND | GMEM_SHARE, pbmfh->bfSize - sizeof (BITMAPFILEHEADER)) ; pGlobal = GlobalLock (hGlobal) ; CopyMemory ( pGlobal, (BYTE *) pbmfh + sizeof (BITMAPFILEHEADER), pbmfh->bfSize - sizeof (BITMAPFILEHEADER)) ; GlobalUnlock (hGlobal) ; // Transfer it to the clipboard OpenClipboard (hwnd) ; EmptyClipboard () ; SetClipboardData (CF_DIB, hGlobal) ; CloseClipboard () ; if (LOWORD (wParam) == IDM_EDIT_COPY) return 0 ; // fall through if IDM_EDIT_CUT case IDM_EDIT_DELETE: if (pbmfh) { free (pbmfh) ; pbmfh = NULL ; InvalidateRect (hwnd, NULL, TRUE) ; } return 0 ; case IDM_SHOW_NORMAL: case IDM_SHOW_CENTER: case IDM_SHOW_STRETCH: case IDM_SHOW_ISOSTRETCH: CheckMenuItem (hMenu, wShow, MF_UNCHECKED) ; wShow = LOWORD (wParam) ; CheckMenuItem (hMenu, wShow, MF_CHECKED) ; InvalidateRect (hwnd, NULL, TRUE) ; return 0 ; } break ; case WM_PAINT: hdc = BeginPaint (hwnd, &ps) ; if (pbmfh) ShowDib ( hdc, pbmi, pBits, cxDib, cyDib, cxClient, cyClient, wShow) ; EndPaint (hwnd, &ps) ; return 0 ; case WM_DESTROY: if (pbmfh) free (pbmfh) ; PostQuitMessage (0) ; return 0 ; } return DefWindowProc (hwnd, message, wParam, lParam) ; }

SHOWDIB2.RC (摘錄) //Microsoft Developer Studio generated resource script. #include "resource.h" #include "afxres.h" ///////////////////////////////////////////////////////////////////////////// // Menu SHOWDIB2 MENU DISCARDABLE BEGIN POPUP "&File" BEGIN MENUITEM "&Open...\tCtrl+O",IDM_FILE_OPEN MENUITEM "&Save...\tCtrl+S", IDM_FILE_SAVE MENUITEM SEPARATOR MENUITEM "&Print\tCtrl+P", IDM_FILE_PRINT END POPUP "&Edit" BEGIN MENUITEM "Cu&t\tCtrl+X", IDM_EDIT_CUT MENUITEM "&Copy\tCtrl+C", IDM_EDIT_COPY MENUITEM "&Delete\tDelete", IDM_EDIT_DELETE END POPUP "&Show" BEGIN MENUITEM "&Actual Size", IDM_SHOW_NORMAL, CHECKED MENUITEM "&Center", IDM_SHOW_CENTER MENUITEM "&Stretch to Window", IDM_SHOW_STRETCH MENUITEM "Stretch &Isotropically", IDM_SHOW_ISOSTRETCH END END ///////////////////////////////////////////////////////////////////////////// // Accelerator SHOWDIB2 ACCELERATORS DISCARDABLE BEGIN "C", IDM_EDIT_COPY, VIRTKEY, CONTROL, NOINVERT "O", IDM_FILE_OPEN, VIRTKEY, CONTROL, NOINVERT "P", IDM_FILE_PRINT, VIRTKEY, CONTROL, NOINVERT "S", IDM_FILE_SAVE, VIRTKEY, CONTROL, NOINVERT VK_DELETE, IDM_EDIT_DELETE, VIRTKEY, NOINVERT "X", IDM_EDIT_CUT, VIRTKEY, CONTROL, NOINVERT END

RESOURCE.H (摘錄) // Microsoft Developer Studio generated include file. // Used by ShowDib2.rc #define IDM_FILE_OPEN 40001 #define IDM_SHOW_NORMAL 40002 #define IDM_SHOW_CENTER 40003 #define IDM_SHOW_STRETCH 40004 #define IDM_SHOW_ISOSTRETCH 40005 #define IDM_FILE_PRINT 40006 #define IDM_EDIT_COPY 40007 #define IDM_EDIT_CUT 40008 #define IDM_EDIT_DELETE 40009 #define IDM_FILE_SAVE 40010

有意思的是ShowDib函式,它依賴於功能表選擇以四種不同的方式之一在程式的顯示區域顯示DIB。可以使用SetDIBitsToDevice從顯示區域的左上角或在顯示區域的中心顯示DIB。程式也有兩個使用StretchDIBits的選項,DIB能放大填充整個顯示區域。在此情況下它可能會變形,或它能等比例顯示,也就是說不會變形。

把DIB複製到剪貼簿包括:在整體共用記憶體中製作packed DIB記憶體塊的副本。剪貼簿資料型態為CF_DIB。程式沒有列出從剪貼簿複製DIB的方法,因為在僅有指向packed DIB的指標的情況下這樣做需要更多步驟來確定圖素位元的偏移量。我將在 下一章 的末尾示範如何做到這點的方法。

您可能注意到了SHOWDIB2中的一些不足之處。如果您以256色顯示模式執行Windows,就會看到顯示除了單色或4位元DIB以外的其他圖形出現的問題,您看不到真正的顏色。存取那些顏色需要使用調色盤,在 下一章 會做這些工作。您也可能注意到速度問題,尤其在Windows NT下執行SHOWDIB2時。在 下一章 packed DIB和點陣圖時,我會展示處理的方法。我也給DIB顯示添加捲動列,這樣也能以實際尺寸查看大於螢幕的DIB。

色彩轉換、調色盤和顯示效能

記得在虎豹小霸王編劇William Goldman的另一齣電影劇本《All the President's Men》中,Deep Throat告訴Bob Woodward揭開水門秘密的關鍵是「跟著錢走」。那麼在點陣圖顯示中獲得高級性能的關鍵就是「跟著圖素位元走」以及理解色彩轉換發生的時機。DIB是裝置無關的格式,視訊顯示器記憶體幾乎總是與圖素格式不同。在SetDIBitsToDevice或StretchDIBits函式呼叫期間,每個圖素(可能有幾百萬個)必須從裝置無關的格式轉換成設備相關格式。

在許多情況下,這種轉換是很繁瑣的。例如,在24位元視訊顯示器上顯示24位元DIB,顯示驅動程式最多是切換紅、綠、藍的位元組順序而已。在24位元設備上顯示16位元DIB就需要位元的搬移和修剪了。在24位元設備上顯示4位元或8位元DIB要求在DIB色彩對照表內查找DIB圖素位元,然後對位元組重新排列。

但是要在4位元或8位元視訊顯示器上顯示16位元、24位元或32位元DIB時,會發生什麼事情呢?一種完全不一樣的顏色轉換發生了。對於DIB內的每個圖素,裝置驅動程式必須在圖素和顯示器上可用的顏色之間「找尋最接近的色彩」,這包括迴圈和計算。(GDI函式GetNearestColor進行「最接近色彩搜尋」。)

整個RGB色彩的三維陣列可用立方體表示。曲線內任意兩點之間的距離是:

在這裏兩個顏色是R1G1B1和R2G2B2。執行最接近色彩搜尋包括從一種顏色到其他顏色集合中找尋最短距離。幸運的是,在RGB顏色立方體中「比較」距離時,並不需要計算平方根部分。但是需轉換的每個圖素必須與設備的所有顏色相比較以發現最接近的顏色。這是個工作量相當大的工作。(儘管在8位元設備上顯示8位元DIB也得進行最接近色彩搜尋,但它不必對每個圖素都進行,它僅需對DIB色彩對照表中的每種顏色進行尋找。)

正是由於以上原因,應該避免使用SetDIBitsToDevice或StretchDIBits在8位元視訊顯示卡上顯示16位元、24位元或32位元DIB。DIB應轉換為8位元DIB,或者8位元DDB,以求得更好的顯示效能。實際上,您可以經由將DIB轉換為DDB並使用BitBlt和StretchBlt顯示圖像,來加快顯示任何DIB的速度。

如果在8位元視訊顯示器上執行Windows(或僅僅切換到8位元模式來觀察在顯示True-ColorDIB時的效能變化),您可能會注意到另一個問題:DIB不會使用所有顏色來顯示。任何在8位元視訊顯示器上的DIB剛好限制在以20種顏色顯示。如何獲得多於20種顏色是「調色盤管理器」的任務,這將在下一章提到。

最後,如果在同一台機器上執行Windows 98和Windows NT,您可能會注意到:對於同樣的顯示模式,Windows NT顯示大型DIB花費的時間較長。這是Windows NT的客戶/伺服器體系結構的結果,它使大量資料在傳輸給API函式時耗費更多時間。解決方法是將DIB轉換為DDB。而我等一下將談到的CreateDIBSection函式對這種情況特別有用。

DIB和DDB的結合

您可以做許多事情去發掘DIB的格式,並呼叫兩個DIB繪圖函式:SetDIBitsToDevice和StretchDIBits。您可以直接存取DIB中的各個位元、位元組和圖素,且一旦您有了一堆能讓您以結構化的方式檢查和更改資料的函式,您要怎麼處理DIB就沒人管了。

實際上,我們發現還是有一些限制。在上一章,我們瞭解了使用GDI函式在DDB上繪製圖像的方法。到目前為止,還沒有在DIB上繪圖的方法。另一個問題是SetDIBitsToDevice和StretchDIBits沒有BitBlt和StretchBlt速度快,尤其在Windows NT環境下以及執行許多最接近顏色搜尋(例如,在8位元視頻卡上顯示24位元DIB)時。

因此,在DIB和DDB之間進行轉換是有好處的。例如,如果我們有一個需要在螢幕上顯示許多次的DIB,那麼把DIB轉換為DDB就很有意義,這樣我們就能夠使用快速的BitBlt和StretchBlt函式來顯示它了。

從DIB建立DDB

從DIB中建立GDI點陣圖物件可能嗎?基本上我們已經知道了方法:如果有DIB,您就能夠使用CreateCompatibleBitmap來建立與DIB大小相同並與視訊顯示器相容的GDI點陣圖物件。然後將該點陣圖物件選入記憶體裝置內容並呼叫SetDIBitsToDevice在那個記憶體DC上繪圖。結果就是DDB具有與DIB相同的圖像,但具有與視訊顯示器相容的顏色組織。

您也可以通過呼叫CreateDIBitmap用幾個步驟完成上述工作。函式的語法為:

hBitmap = CreateDIBitmap ( hdc, // device context handle pInfoHdr, // pointer to DIB information header fInit, // 0 or CBM_INIT pBits, // pointer to DIB pixel bits pInfo, // pointer to DIB information fClrUse) ; // color use flag

請注意pInfoHdr和pInfo這兩個參數,它們分別定義為指向BITMAPINFOHEADER結構和 BITMAPINFO結構的指標。正如我們所知,BITMAPINFO結構是後面緊跟色彩對照表的BITMAPINFOHEADER結構。我們一會兒會看到這種區別所起的作用。最後一個參數是DIB_RGB_ COLORS(等於0)或DIB_PAL_COLORS,它們在SetDIBitsToDevice函式中使用。下一章我將討論更多這方面的內容。

理解Windows中點陣圖函式的作用是很重要的。不要考慮CreateDIBitmap函式的名稱,它不建立與「裝置無關的點陣圖」,它從裝置無關的規格中建立「設備相關的點陣圖」。注意該函式傳回GDI點陣圖物件的代號,CreateBitmap、CreateBitmapIndirect和CreateCompatibleBitmap也與它一樣。

呼叫CreateDIBitmap函式最簡單的方法是:

唯一的參數是指向BITMAPINFOHEADER結構(不帶色彩對照表)的指標。在這個形式中,函式建立單色GDI點陣圖物件。第二種簡單的方法是:

在這個形式中,函式建立了與裝置內容相容的DDB,該裝置內容由hdc參數指出。到目前為止,我們都是透過CreateBitmap(建立單色點陣圖)或CreateCompatibleBitmap(建立與視訊顯示器相容的點陣圖)來完成一些工作。

在CreateDIBitmap的這兩個簡化模式中,圖素還未被初始化。如果CreateDIBitmap的第三個參數是CBM_INIT,Windows就會建立DDB並使用最後三個參數初始化點陣圖位元。pInfo參數是指向包括色彩對照表的BITMAPINFO結構的指標。pBits參數是指向由BITMAPINFO結構指出的色彩格式中的位元陣列的指標,根據色彩對照表這些位元被轉換為設備的顏色格式,這與SetDIBitsToDevice的情況相同。實際上,整個CreateDIBitmap函式可以用下列程式碼來實作:

hBitmap = CreateDIBitmap (NULL, pbmih, 0, NULL, NULL, 0) ;

hBitmap = CreateDIBitmap (hdc, pbmih, 0, NULL, NULL, 0) ;

HBITMAP CreateDIBitmap ( HDC hdc, CONST BITMAPINFOHEADER * pbmih, DWORD fInit, CONST VOID * pBits, CONST BITMAPINFO * pbmi, UINT fUsage) { HBITMAP hBitmap ; HDC hdc ; int cx, cy, iBitCount ; if (pbmih->biSize == sizeof (BITMAPCOREHEADER)) { cx = ((PBITMAPCOREHEADER) pbmih)->bcWidth ; cy = ((PBITMAPCOREHEADER) pbmih)->bcHeight ; iBitCount = ((PBITMAPCOREHEADER) pbmih)->bcBitCount ; } else { cx = pbmih->biWidth ; cy = pbmih->biHeight ; iBitCount = pbmih->biBitCount ; } if (hdc) hBitmap = CreateCompatibleBitmap (hdc, cx, cy) ; else hBitmap = CreateBitmap (cx, cy, 1, 1, NULL) ; if (fInit == CBM_INIT) { hdcMem = CreateCompatibleDC (hdc) ; SelectObject (hdcMem, hBitmap) ; SetDIBitsToDevice ( hdcMem, 0, 0, cx, cy, 0, 0, 0 cy, pBits, pbmi, fUsage) ; DeleteDC (hdcMem) ; } return hBitmap ; }

如果僅需顯示DIB一次,並擔心SetDIBitsToDevice顯示太慢,則呼叫CreateDIBitmap並使用BitBlt或StretchBlt來顯示DDB就沒有什麼意義。因為SetDIBitsToDevice和CreateDIBitmap都執行顏色轉換,這兩個工作會佔用同樣長的時間。只有在多次顯示DIB時(例如在處理WM_PAINT訊息時)進行這種轉換才有意義。

程式15-6 DIBCONV展示了利用SetDIBitsToDevice把DIB檔案轉換為DDB的方法。

程式15-6 DIBCONV DIBCONV.C /*-------------------------------------------------------------------------- DIBCONV.C -- Converts a DIB to a DDB (c) Charles Petzold, 1998 ----------------------------------------------------------------------------*/ #include <windows.h> #include <commdlg.h> #include "resource.h" LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ; TCHAR szAppName[] = TEXT ("DibConv") ; int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { HWND hwnd ; MSG msg ; WNDCLASS wndclass ; wndclass.style = CS_HREDRAW | CS_VREDRAW ; wndclass.lpfnWndProc = WndProc ; wndclass.cbClsExtra = 0 ; wndclass.cbWndExtra = 0 ; wndclass.hInstance = hInstance ; wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ; wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ; wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ; wndclass.lpszMenuName = szAppName ; wndclass.lpszClassName = szAppName ; if (!RegisterClass (&wndclass)) { MessageBox (NULL, TEXT ("This program requires Windows NT!"), szAppName, MB_ICONERROR) ; return 0 ; } hwnd = CreateWindow (szAppName, TEXT ("DIB to DDB Conversion"), 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 ; } HBITMAP CreateBitmapObjectFromDibFile (HDC hdc, PTSTR szFileName) { BITMAPFILEHEADER * pbmfh ; BOOL bSuccess ; DWORD dwFileSize, dwHighSize, dwBytesRead ; HANDLE hFile ; HBITMAP hBitmap ; // Open the file: read access, prohibit write access hFile = CreateFile (szFileName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL) ; if (hFile == INVALID_HANDLE_VALUE) return NULL ; // Read in the whole file dwFileSize = GetFileSize (hFile, &dwHighSize) ; if (dwHighSize) { CloseHandle (hFile) ; return NULL ; } pbmfh = malloc (dwFileSize) ; if (!pbmfh) { CloseHandle (hFile) ; return NULL ; } bSuccess = ReadFile (hFile, pbmfh, dwFileSize, &dwBytesRead, NULL) ; CloseHandle (hFile) ; // Verify the file if (!bSuccess || (dwBytesRead != dwFileSize) || (pbmfh->bfType != * (WORD *) "BM") || (pbmfh->bfSize != dwFileSize)) { free (pbmfh) ; return NULL ; } // Create the DDB hBitmap = CreateDIBitmap (hdc, (BITMAPINFOHEADER *) (pbmfh + 1), CBM_INIT, (BYTE *) pbmfh + pbmfh->bfOffBits, (BITMAPINFO *) (pbmfh + 1), DIB_RGB_COLORS) ; free (pbmfh) ; return hBitmap ; } LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam,LPARAM lParam) { static HBITMAP hBitmap ; static int cxClient, cyClient ; static OPENFILENAME ofn ; static TCHAR szFileName [MAX_PATH], szTitleName [MAX_PATH] ; static TCHAR szFilter[]=TEXT("Bitmap Files(*.BMP)\0*.bmp\0") TEXT ("All Files (*.*)\0*.*\0\0") ; BITMAP bitmap ; HDC hdc, hdcMem ; PAINTSTRUCT ps ; switch (message) { case WM_CREATE: ofn.lStructSize = sizeof (OPENFILENAME) ; ofn.hwndOwner = hwnd ; ofn.hInstance = NULL ; ofn.lpstrFilter = szFilter ; ofn.lpstrCustomFilter = NULL ; ofn.nMaxCustFilter = 0 ; ofn.nFilterIndex = 0 ; ofn.lpstrFile = szFileName ; ofn.nMaxFile = MAX_PATH ; ofn.lpstrFileTitle = szTitleName ; ofn.nMaxFileTitle = MAX_PATH ; ofn.lpstrInitialDir = NULL ; ofn.lpstrTitle = NULL ; ofn.Flags = 0 ; ofn.nFileOffset = 0 ; ofn.nFileExtension = 0 ; ofn.lpstrDefExt = TEXT ("bmp") ; ofn.lCustData = 0 ; ofn.lpfnHook = NULL ; ofn.lpTemplateName = NULL ; return 0 ; case WM_SIZE: cxClient = LOWORD (lParam) ; cyClient = HIWORD (lParam) ; return 0 ; case WM_COMMAND: switch (LOWORD (wParam)) { case IDM_FILE_OPEN: // Show the File Open dialog box if (!GetOpenFileName (&ofn)) return 0 ; // If there's an existing DIB, delete it if (hBitmap) { DeleteObject (hBitmap) ; hBitmap = NULL ; } // Create the DDB from the DIB SetCursor (LoadCursor (NULL, IDC_WAIT)) ; ShowCursor (TRUE) ; hdc = GetDC (hwnd) ; hBitmap = CreateBitmapObjectFromDibFile (hdc, szFileName) ; ReleaseDC (hwnd, hdc) ; ShowCursor (FALSE) ; SetCursor (LoadCursor (NULL, IDC_ARROW)) ; // Invalidate the client area for later update InvalidateRect (hwnd, NULL, TRUE) ; if (hBitmap == NULL) { MessageBox (hwnd, TEXT ("Cannot load DIB file"), szAppName, MB_OK | MB_ICONEXCLAMATION) ; } return 0 ; } break ; case WM_PAINT: hdc = BeginPaint (hwnd, &ps) ; if (hBitmap) { GetObject (hBitmap, sizeof (BITMAP), &bitmap) ; hdcMem = CreateCompatibleDC (hdc) ; SelectObject (hdcMem, hBitmap) ; BitBlt (hdc,0, 0, bitmap.bmWidth, bitmap.bmHeight, hdcMem, 0, 0, SRCCOPY) ; DeleteDC (hdcMem) ; } EndPaint (hwnd, &ps) ; return 0 ; case WM_DESTROY: if (hBitmap) DeleteObject (hBitmap) ; PostQuitMessage (0) ; return 0 ; } return DefWindowProc (hwnd, message, wParam, lParam) ; }

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

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

DIBCONV.C本身就是完整的,並不需要前面的檔案。在對它僅有的功能表命令(「File Open」)的回應中,WndProc呼叫程式的CreateBitmapObjectFromDibFile函式。此函式將整個檔案讀入記憶體並將指向記憶體塊的指標傳遞給CreateDIBitmap函式,函式傳回點陣圖的代號,然後包含DIB的記憶體塊被釋放。在WM_PAINT訊息處理期間,WndProc將點陣圖選入相容的記憶體裝置內容並使用BitBlt(不是SetDIBitsToDevice)在顯示區域顯示點陣圖。它通過使用點陣圖代號呼叫帶有BITMAP結構的GetObject函式來取得點陣圖的寬度和高度。

在從CreateDIBitmap建立點陣圖時不必初始化DDB圖素位元,之後您可以呼叫SetDIBits初始化圖素位元。該函式的語法如下:

iLines = SetDIBits ( hdc, // device context handle hBitmap, // bitmap handle yScan, // first scan line to convert cyScans, // number of scan lines to convert pBits, // pointer to pixel bits pInfo, // pointer to DIB information fClrUse) ; // color use flag

函式使用了BITMAPINFO結構中的色彩對照表把位元轉換為設備相關的格式。只有在最後一個參數設定為DIB_PAL_COLORS時,才需要裝置內容代號。

從DDB到DIB

與SetDIBits函式相似的函式是GetDIBits,您可以使用此函式把DDB轉化為DIB:

int WINAPI GetDIBits ( hdc, // device context handle hBitmap, // bitmap handle yScan, // first scan line to convert cyScans, // number of scan lines to convert pBits, // pointer to pixel bits (out) pInfo, // pointer to DIB information (out) fClrUse) ; // color use flag

然而,此函式產生的恐怕不是SetDIBits的反運算結果。在一般情況下,如果使用CreateDIBitmap和SetDIBits將DIB轉換為DDB,然後使用GetDIBits把DDB轉換回DIB,您就不會得到原來的圖像。這是因為在DIB被轉換為設備相關的格式時,有一些資訊遺失了。遺失的資訊數量取決於進行轉換時Windows所執行的顯示模式。

您可能會發現沒有使用GetDIBits的必要性。考慮一下:在什麼環境下您的程式發現自身帶有點陣圖代號,但沒有用於在起始的位置建立點陣圖的資料?剪貼簿?但是剪貼簿為DIB提供了自動的轉換。GetDIBits函式的一個例子是在捕捉螢幕顯示內容的情況下,例如第十四章中BLOWUP程式所做的。我不示範這個函式,但在Microsoft網站的Knowledge Base文章Q80080中有一些資訊。

DIB區塊

我希望您已經對設備相關和裝置無關點陣圖的區別有了清晰的概念。DIB能擁有幾種色彩組織中的一種,DDB必須是單色的或是與真實輸出設備相同的格式。DIB是一個檔案或記憶體塊;DDB是GDI點陣圖物件並由點陣圖代號表示。DIB能被顯示或轉換為DDB並轉換回DIB,但是這裏包含了裝置無關位元和設備相關位元之間的轉換程序。

現在您將遇到一個函式,它打破了這些規則。該函式在32位元Windows版本中發表,稱為CreateDIBSection,語法為:

hBitmap = CreateDIBSection ( hdc, // device context handle pInfo, // pointer to DIB information fClrUse, // color use flag ppBits, // pointer to pointer variable hSection, // file-mapping object handle dwOffset) ; // offset to bits in file-mapping object

CreateDIBSection是Windows API中最重要的函式之一(至少在使用點陣圖時),然而您會發現它很深奧並難以理解。

讓我們從它的名稱開始,我們知道DIB是什麼,但「DIB section」到底是什麼呢?當您第一次檢查CreateDIBSection時,可能會尋找該函式與DIB區塊工作的方式。這是正確的,CreateDIBSection所做的就是建立了DIB的一部分(點陣圖圖素位元的記憶體塊)。

現在我們看一下傳回值,它是GDI點陣圖物件的代號,這個傳回值可能是該函式呼叫最會拐人的部分。傳回值似乎暗示著CreateDIBSection在功能上與CreateDIBitmap相同。事實上,它只是相似但完全不同。實際上,從CreateDIBSection傳回的點陣圖代號與我們在本章和 上一章 遇到的所有點陣圖建立函式傳回的點陣圖代號在本質上不同。

一旦理解了CreateDIBSection的真實特性,您可能覺得奇怪為什麼不把傳回值定義得有所區別。您也可能得出結論:CreateDIBSection應該稱之為CreateDIBitmap,並且如同我前面所指出的CreateDIBitmap應該稱之為CreateDDBitmap。

首先讓我們檢查一下如何簡化CreateDIBSection,並正確地使用它。首先,把最後兩個參數hSection和dwOffset,分別設定為NULL和0,我將在本章最後討論這些參數的用法。第二,僅在fColorUse參數設定為DIB_ PAL_COLORS時,才使用hdc參數,如果fColorUse為DIB_RGB_COLORS(或0),hdc將被忽略(這與CreateDIBitmap不同,hdc參數用於取得與DDB相容的設備的色彩格式)。

因此,CreateDIBSection最簡單的形式僅需要第二和第四個參數。第二個參數是指向BITMAPINFO結構的指標,我們以前曾使用過。我希望指向第四個參數的指標定義的指標不會使您困惑,它實際上很簡單。

假設要建立每圖素24位元的384×256位元DIB,24位元格式不需要色彩對照表,因此它是最簡單的,所以我們可以為BITMAPINFO參數使用BITMAPINFOHEADER結構。

您需要定義三個變數:BITMAPINFOHEADER結構、BYTE指標和點陣圖代號:

BITMAPINFOHEADER bmih ; BYTE * pBits ; HBITMAP hBitmap ;

現在初始化BITMAPINFOHEADER結構的欄位

bmih->biSize = sizeof (BITMAPINFOHEADER) ; bmih->biWidth = 384 ; bmih->biHeight = 256 ; bmih->biPlanes = 1 ; bmih->biBitCount = 24 ; bmih->biCompression = BI_RGB ; bmih->biSizeImage = 0 ; bmih->biXPelsPerMeter = 0 ; bmih->biYPelsPerMeter = 0 ; bmih->biClrUsed = 0 ; bmih->biClrImportant = 0 ;

在基本準備後,我們呼叫該函式:

注意,我們為第二個參數賦予BITMAPINFOHEADER結構的位址。這是常見的,但一個BYIE指標pBits的位址,就不常見了。這樣,第四個參數是函式需要的指向指標的指標。

這是函式呼叫所做的:CreateDIBSection檢查BITMAPINFOHEADER結構並配置足夠的記憶體塊來載入DIB圖素位元。(在這個例子裏,記憶體塊的大小為384×256×3位元組。)它在您提供的pBits參數中儲存了指向此記憶體塊的指標。函式傳回點陣圖代號,正如我說的,它與CreateDIBitmap和其他點陣圖建立函式傳回的代號不一樣。

然而,我們還沒有做完,點陣圖圖素是未初始化的。如果正在讀取DIB檔案,可以簡單地把pBits參數傳遞給ReadFile函式並讀取它們。或者可以使用一些程式碼「人工」設定。

程式15-7 DIBSECT除了呼叫CreateDIBSection而不是CreateDIBitmap之外,與DIBCONV程式相似。

hBitmap = CreateDIBSection (NULL, (BITMAPINFO *) &bmih, 0, &pBits, NULL, 0) ;

程式15-7 DIBSECT DIBSECT.C /*---------------------------------------------------------------------------- DIBSECT.C -- Displays a DIB Section in the client area (c) Charles Petzold, 1998 -----------------------------------------------------------------------------*/ #include <windows.h> #include <commdlg.h> #include "resource.h" LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ; TCHAR szAppName[] = TEXT ("DIBsect") ; int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { HWND hwnd ; MSG msg ; WNDCLASS wndclass ; wndclass.style = CS_HREDRAW | CS_VREDRAW ; wndclass.lpfnWndProc = WndProc ; wndclass.cbClsExtra = 0 ; wndclass.cbWndExtra = 0 ; wndclass.hInstance = hInstance ; wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ; wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ; wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ; wndclass.lpszMenuName = szAppName ; wndclass.lpszClassName = szAppName ; if (!RegisterClass (&wndclass)) { MessageBox (NULL, TEXT ("This program requires Windows NT!"), szAppName, MB_ICONERROR) ; return 0 ; } hwnd = CreateWindow (szAppName, TEXT ("DIB Section Display"), 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 ; } HBITMAP CreateDIBsectionFromDibFile (PTSTR szFileName) { BITMAPFILEHEADER bmfh ; BITMAPINFO * pbmi ; BYTE * pBits ; BOOL bSuccess ; DWORD dwInfoSize, dwBytesRead ; HANDLE hFile ; HBITMAP hBitmap ; // Open the file: read access, prohibit write access hFile = CreateFile (szFileName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL) ; if (hFile == INVALID_HANDLE_VALUE) return NULL ; // Read in the BITMAPFILEHEADER bSuccess = ReadFile (hFile, &bmfh, sizeof (BITMAPFILEHEADER), &dwBytesRead, NULL) ; if (!bSuccess || (dwBytesRead != sizeof (BITMAPFILEHEADER)) || (bmfh.bfType != * (WORD *) "BM")) { CloseHandle (hFile) ; return NULL ; } // Allocate memory for the BITMAPINFO structure & read it in dwInfoSize = bmfh.bfOffBits - sizeof (BITMAPFILEHEADER) ; pbmi = malloc (dwInfoSize) ; bSuccess = ReadFile (hFile, pbmi, dwInfoSize, &dwBytesRead, NULL) ; if (!bSuccess || (dwBytesRead != dwInfoSize)) { free (pbmi) ; CloseHandle (hFile) ; return NULL ; } // Create the DIB Section hBitmap = CreateDIBSection (NULL, pbmi, DIB_RGB_COLORS, &pBits, NULL, 0) ; if (hBitmap == NULL) { free (pbmi) ; CloseHandle (hFile) ; return NULL ; } // Read in the bitmap bits ReadFile (hFile, pBits, bmfh.bfSize - bmfh.bfOffBits, &dwBytesRead, NULL) ; free (pbmi) ; CloseHandle (hFile) ; return hBitmap ; } LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam,LPARAM lParam) { static HBITMAP hBitmap ; static int cxClient, cyClient ; static OPENFILENAME ofn ; static TCHAR szFileName [MAX_PATH], szTitleName [MAX_PATH] ; static TCHAR szFilter[] = TEXT ("Bitmap Files (*.BMP)\0*.bmp\0") TEXT ("All Files (*.*)\0*.*\0\0") ; BITMAP bitmap ; HDC hdc, hdcMem ; PAINTSTRUCT ps ; switch (message) { case WM_CREATE: ofn.lStructSize = sizeof (OPENFILENAME) ; ofn.hwndOwner = hwnd ; ofn.hInstance = NULL ; ofn.lpstrFilter = szFilter ; ofn.lpstrCustomFilter = NULL ; ofn.nMaxCustFilter = 0 ; ofn.nFilterIndex = 0 ; ofn.lpstrFile = szFileName ; ofn.nMaxFile = MAX_PATH ; ofn.lpstrFileTitle = szTitleName ; ofn.nMaxFileTitle = MAX_PATH ; ofn.lpstrInitialDir = NULL ; ofn.lpstrTitle = NULL ; ofn.Flags = 0 ; ofn.nFileOffset = 0 ; ofn.nFileExtension = 0 ; ofn.lpstrDefExt = TEXT ("bmp") ; ofn.lCustData = 0 ; ofn.lpfnHook = NULL ; ofn.lpTemplateName = NULL ; return 0 ; case WM_SIZE: cxClient = LOWORD (lParam) ; cyClient = HIWORD (lParam) ; return 0 ; case WM_COMMAND: switch (LOWORD (wParam)) { case IDM_FILE_OPEN: // Show the File Open dialog box if (!GetOpenFileName (&ofn)) return 0 ; // If there's an existing bitmap, delete it if (hBitmap) { DeleteObject (hBitmap) ; hBitmap = NULL ; } // Create the DIB Section from the DIB file SetCursor (LoadCursor (NULL, IDC_WAIT)) ; ShowCursor (TRUE) ; hBitmap = CreateDIBsectionFromDibFile (szFileName) ; ShowCursor (FALSE) ; SetCursor (LoadCursor (NULL, IDC_ARROW)) ; // Invalidate the client area for later update InvalidateRect (hwnd, NULL, TRUE) ; if (hBitmap == NULL) { MessageBox ( hwnd, TEXT ("Cannot load DIB file"), szAppName, MB_OK | MB_ICONEXCLAMATION) ; } return 0 ; } break ; case WM_PAINT: hdc = BeginPaint (hwnd, &ps) ; if (hBitmap) { GetObject (hBitmap, sizeof (BITMAP), &bitmap) ; hdcMem = CreateCompatibleDC (hdc) ; SelectObject (hdcMem, hBitmap) ; BitBlt ( hdc, 0, 0, bitmap.bmWidth, bitmap.bmHeight, hdcMem, 0, 0, SRCCOPY) ; DeleteDC (hdcMem) ; } EndPaint (hwnd, &ps) ; return 0 ; case WM_DESTROY: if (hBitmap) DeleteObject (hBitmap) ; PostQuitMessage (0) ; return 0 ; } return DefWindowProc (hwnd, message, wParam, lParam) ; }

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

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

注意DIBCONV中的CreateBitmapObjectFromDibFile函式和DIBSECT中的CreateDIbsectionFromDibFile函式之間的區別。DIBCONV讀入整個檔案,然後把指向DIB記憶體塊的指標傳遞給CreateDIBitmap函式。DIBSECT首先讀取BITMAPFILEHEADER結構中的資訊,然後確定BITMAPINFO結構的大小,為此配置記憶體,並在第二個ReadFile呼叫中將它讀入記憶體。然後,函式把指向BITMAPINFO結構和指標變數pBits的指標傳遞給CreateDIBSection。函式傳回點陣圖代號並設定pBits指向函式將要讀取DIB圖素位元的記憶體塊。

pBits指向的記憶體塊歸系統所有。當通過呼叫DeleteObject刪除點陣圖時,記憶體會被自動釋放。然而,程式能利用該指標直接改變DIB位元。當應用程式透過API傳遞大量記憶體塊時,只要系統擁有這些記憶體塊,在WINDOWS NT下就不會影響速度。

我之前曾說過,當在視訊顯示器上顯示DIB時,某些時候必須進行從裝置無關圖素到設備相關圖素的轉換,有時這些格式轉換可能相當費時。來看一看三種用於顯示DIB的方法:

  • 當使用SetDIBitsToDevice或StretchDIBits來把DIB直接顯示在螢幕上,格式轉換在SetDIBitsToDevice或StretchDIBits呼叫期間發生。
  • 當使用CreateDIBitmap和(可能是)SetDIBits把DIB轉換為DDB,然後使用BitBlt或StretchBlt來顯示它時,如果設定了CBM_INIT旗標,格式轉換在CreateDIBitmap或SetDIBits期間發生。
  • 當使用CreateDIBSection建立DIB區塊,然後使用BitBlt或StretchBlt顯示它時,格式轉換在BitBlt對StretchBlt的呼叫期間發生。

再讀一下上面這些敘述,確定您不會誤解它的意思。這是從CreateDIBSection傳回的點陣圖代號不同於我們所遇到的其他點陣圖代號的一個地方。此點陣圖代號實際上指向儲存在記憶體中由系統維護但應用程式能存取的DIB。在需要的時候,DIB會轉化為特定的色彩格式,通常是在用BitBlt或StretchBlt顯示點陣圖時。

您也可以將點陣圖代號選入記憶體裝置內容並使用GDI函式來繪製。在 pBits 變數指向的DIB圖素內將反映出結果。因為Windows NT下的GDI函式分批呼叫,在記憶體設備背景上繪製之後和「人為」的存取位元之前會呼叫GdiFlush。

在DIBSECT,我們清除pBits變數,因為程式不再需要這個變數了。您會使用CreateDIBSection的主要原因在於您有需要直接更改位元值。在CreateDIBSection呼叫之後似乎就沒有別的方法來取得位元指標了。

DIB區塊的其他區別

從CreateDIBitmap傳回的點陣圖代號與函式的hdc參數引用的設備有相同的平面和圖素位元組織。您能通過具有BITMAP結構的GetObject呼叫來檢驗這一點。

CreateDIBSection就不同了。如果以該函式傳回的點陣圖代號的BITMAP結構呼叫GetObject,您會發現點陣圖具有的色彩組織與BITMAPINFOHEADER結構的欄位指出的色彩組織相同。您能將這個代號選入與視訊顯示器相容的記憶體裝置內容。這與 上一章 關於DDB的內容相矛盾,但這也就是我說此DIB區塊點陣圖代號不同的原因。

另一個奇妙之處是:你可能還記得,DIB中圖素資料行的位元組長度始終是4的倍數。GDI點陣圖物件中行的位元組長度,就是使用GetObject從BITMAP結構的bmWidthBytes欄位中得到的長度,始終是2的倍數。如果用每圖素24位元和寬度2圖素設定BITMAPINFOHEADER結構並隨後呼叫GetObject,您就會發現bmWidthBytes欄位是8而不是6。

使用從CreateDIBSection傳回的點陣圖代號,也可以使用DIBSECTION結構呼叫GetObject:

此函式不能處理其他點陣圖建立函式傳回的點陣圖代號。DIBSECTION結構定義如下:

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

typedef struct tagDIBSECTION // ds { BITMAP dsBm ; // BITMAP structure BITMAPINFOHEADER dsBmih ; // DIB information header DWORD dsBitfields [3] ; // color masks HANDLE dshSection ; // file-mapping object handle DWORD dsOffset ; // offset to bitmap bits } DIBSECTION, * PDIBSECTION ;

此結構包含BITMAP結構和BITMAPINFOHEADER結構。最後兩個欄位是傳遞給CreateDIBSection的最後兩個參數,等一下將會討論它們。

DIBSECTION結構中包含除了色彩對照表以外有關點陣圖的許多內容。當把DIB區塊點陣圖代號選入記憶體裝置內容時,可以通過呼叫GetDIBColorTable來得到色彩對照表:

hdcMem = CreateCompatibleDC (NULL) ; SelectObject (hdcMem, hBitmap) ; GetDIBColorTable (hdcMem, uFirstIndex, uNumEntries, &rgb) ; DeleteDC (hdcMem) ;

同樣,您可以通過呼叫SetDIBColorTable來設定色彩對照表中的項目。

檔案映射選項

我們還沒有討論CreateDIBSection的最後兩個參數,它們是檔案映射物件的代號和檔案中點陣圖位元開始的偏移量。檔案映射物件使您能夠像檔案位於記憶體中一樣處理檔案。也就是說,可以通過使用記憶體指標來存取檔案,但檔案不需要整個載入記憶體中。

在大型DIB的情況下,此技術對於減少記憶體需求是很有幫助的。DIB圖素位元能夠儲存在磁片上,但仍然可以當作位於記憶體中一樣進行存取,雖然會影響程式執行效能。問題是,當圖素位元實際上儲存在磁片上時,它們不可能是實際DIB檔案的一部分。它們必須位於其他的檔案內。

為了展示這個程序,下面顯示的函式除了不把圖素位元讀入記憶體以外,與DIBSECT中建立DIB區塊的函式很相似。然而,它提供了檔案映射物件和傳遞給CreateDIBSection函式的偏移量:

HBITMAP CreateDIBsectionMappingFromFile (PTSTR szFileName) { BITMAPFILEHEADER bmfh ; BITMAPINFO * pbmi ; BYTE * pBits ; BOOL bSuccess ; DWORD dwInfoSize, dwBytesRead ; HANDLE hFile, hFileMap ; HBITMAP hBitmap ; hFile = CreateFile (szFileName, GENERIC_READ | GENERIC_WRITE, 0, // No sharing! NULL, OPEN_EXISTING, 0, NULL) ; if (hFile == INVALID_HANDLE_VALUE) return NULL ; bSuccess = ReadFile ( hFile, &bmfh, sizeof (BITMAPFILEHEADER), &dwBytesRead, NULL) ; if (!bSuccess || (dwBytesRead != sizeof (BITMAPFILEHEADER)) || (bmfh.bfType != * (WORD *) "BM")) { CloseHandle (hFile) ; return NULL ; } dwInfoSize = bmfh.bfOffBits - sizeof (BITMAPFILEHEADER) ; pbmi = malloc (dwInfoSize) ; bSuccess = ReadFile (hFile, pbmi, dwInfoSize, &dwBytesRead, NULL) ; if (!bSuccess || (dwBytesRead != dwInfoSize)) { free (pbmi) ; CloseHandle (hFile) ; return NULL ; } hFileMap = CreateFileMapping (hFile, NULL, PAGE_READWRITE, 0, 0, NULL) ; hBitmap = CreateDIBSection ( NULL, pbmi, DIB_RGB_COLORS, &pBits,hFileMap, bmfh.bfOffBits) ; free (pbmi) ; return hBitmap ; }

啊哈!這個程式不會動。CreateDIBSection的文件指出「dwOffset [函式的最後一個參數]必須是DWORD大小的倍數」。儘管資訊表頭的大小始終是4的倍數並且色彩對照表的大小也始終是4的倍數,但點陣圖檔案表頭卻不是,它是14位元組。因此bmfh.bfOffBits永遠不會是4的倍數。

總結

如果您有小型的DIB並且需要頻繁地操作圖素位元,您可以使用SetDIBitsToDevice和StretchDIBits來顯示它們。然而,對於大型的DIB,此技術會遇到顯示效能的問題,尤其在8位元視訊顯示器上和Windows NT環境下。

您可以使用CreateDIBitmap和SetDIBits把DIB轉化為DDB。現在,顯示點陣圖可以使用快速的BitBlt和StretchBlt函式來進行了。然而,您不能直接存取這些與裝置無關的圖素位元。

CreateDIBSection是一個很好的折衷方案。在Windows NT下通過BitBlt和StretchBlt使用點陣圖代號比使用SetDIBitsToDevice和StretchDIBits(但沒有DDB的缺陷)會得到更好的效能。您仍然可以存取DIB圖素位元。

下一章 ,在討論「Windows調色盤管理器」之後會進入點陣圖的探索。