HBITMAP是常用的GDI對象,而GetDIBits可以從一個HBITMAP對象中獲得其對應的位數據。
其原型如下:
int GetDIBits( HDC hdc, // handle to DC
hdc, // handle to DC
HBITMAP hbmp, // handle to bitmap
UINT hbmp, // handle to bitmap
UINT uStartScan, // first scan line to set
UINT cScanLines, // number of scan lines to copy
LPVOID cScanLines, // number of scan lines to copy
LPVOID lpvBits, // array for bitmap bits
LPBITMAPINFO lpbi, // bitmap data buffer
UINT lpbi, // bitmap data buffer
UINT uUsage // RGB or palette index );
標準的GetDIBits調用方式是兩次調用:
第一次傳入空的 lpvBits,此時的lpbi作爲傳出參數,從中可以獲得lpvBits所需的內存區域大小。
BYTE *lpvBits = NULL;
BITMAPINFO bmpInfo = {0};
bmpInfo.bmiHeader.biSize = sizeof(bmpInfo.bmiHeader);
/* 第一次調用GetDIBits獲得bmpInfo */
nRet = ::GetDIBits(hDC, hBitmap, 0, pBmpData->bmHeight, NULL, &bmpInfo, DIB_RGB_COLORS);
if (nRet == 0) {
nRet = GetLastError();
TRACE( _T("GetDIBits for bmpInfo failed %d/n"), nRet);
goto err;
}
以上調用如果成功,就會在bmpInfo.bmiHeader.biSizeImage記錄位數據所佔的內存區大小,
此時就可以動態分配內存,然後再次調用GetDIBits獲得實際的位數據:
lpvBits= new BYTE[bmpInfo.bmiHeader.biSizeImage];
if (NULL == pBitsBuffer) {
nRet = -1;
TRACE( _T("Allocate memory for lpvBits failed/n"));
goto err;
}
/* 第二次調用GetDIBits獲得位圖數據 */
nRet = ::GetDIBits(hDC, hBitmap, 0, pBmpData->bmHeight, lpvBits, &bmpInfo, DIB_RGB_COLORS);
if (nRet == 0) {
nRet = GetLastError();
TRACE( _T("GetDIBits for lpvBits failed %d/n"), nRet);
goto err;
}
很多時候,因爲位圖的長寬和顏色深度都是已知的,因此位數據所佔的內存區大小可以由公式
width * heigth * pixelBits / 8 計算獲得
那是否可以省略第一步調用呢?
答案是:最好不要
原因是 bmpInfo.bmiHeader.biSizeImage 並不一定等於 width * heigth * pixelBits / 8
經過多次測試發現,對於16位顏色深度的位圖,如果其寬度爲奇數,則第一次GetDIBits獲得
的位數據大小爲 (width+1) * heigth * pixelBits / 8
也就是說對於16位顏色深度的位圖,HBITMAP對象對其進行存儲時,自動將寬度調整爲了偶
數,也即將每行數據對齊到4字節(一個DWORD的長度)。
暫時沒有查到相關的解釋,我的猜想是這是爲了在進行像素操作時能直接以DWORD爲單位,
這樣比以WORD爲單位能減少一半的操作次數。
////
16位操作系統下如果使用這種方法獲得的將是16位圖
biBitCount=16
表示位圖最多有2^16種顏色。每個像素用16位(2個字節)表示。這種格式叫作高彩色,或叫增強型16位色,或64K色。它的情況比較複雜,當biCompression成員的值是BI_RGB時,它沒有調色板。16位中,最低的5位表示藍色分量,中間的5位表示綠色分量,高的5位表示紅色分量,一共佔用了15位,最高的一位保留,設爲0。//->備註1
這種格式也被稱作555 16位位圖。
內存分佈如下
如果biCompression成員的值是BI_BITFIELDS,(const DWORD BI_BITFIELDS = 3;)那麼情況就複雜了,首先是原來調色板的位置被三個DWORD變量佔據,稱爲紅、綠、藍掩碼。分別用於描述紅、綠、藍分量在16位中所佔的位置。在Windows 95(或98)中,系統可接受兩種格式的位域:555和565,在555格式下,紅、綠、藍的掩碼分別是:0x7C00、0x03E0、0x001F,而在565格式下,它們則分別爲:0xF800、0x07E0、0x001F。你在讀取一個像素之後,可以分別用掩碼“與”上像素值,
從而提取出想要的顏色分量(當然還要再經過適當的左右移操作)。在NT系統中,則沒有格式限制,只不過要求掩碼之間不能有重疊。(注:這種格式的圖像使用起來是比較麻煩的,不過因爲它的顯示效果接近於真彩,而圖像數據又比真彩圖像小的多,所以,它更多的被用於遊戲軟件)。我們只需要讀取其中的R或者G的掩碼,來判斷是那種格式。以紅色掩碼爲例 0111110000000000的時候就是555格式 1111100000000000就是565格式。
555 格式 xrrrrrgggggbbbbb
565 格式 rrrrrggggggbbbbb
解析555格式的代碼:
BYTE b= bmpData[i*storeWidth+j];
BYTE g=((bmpData[i*storeWidth+j +1]<<6)>>3)+(buffer[i*storeWidth+j]>>5);
BYTE r=(bmpData[i*storeWidth+j +1]<<1)>>3;
有一點值得提醒的是由於有較多的位操作,所以在處理的時候在前一次操作的上面加上一對括號。
現在我們得到了55RGB各自的分量,但是還有一個新的問題,那就是由於兩字節表示了3個顏色 555下每個顏色最多到0x1F。所以我們需要一個轉換,這就是掩碼的用武之地,將得到的各顏色分量和相應的掩碼做與運算,或者乘8就可以了,推薦用與運算。
以下是565格式時的數據分離:
BYTE b= bmpData[i*storeWidth+j];
BYTE g=((bmpData[i*storeWidth+j+1]<<5)>>2)+(buffer[i*storeWidth+j]>>5);
BYTE r=bmpData[i*storeWidth+j +1]>>3;
現在我們得到了565RGB各自的分量,但是仍然還有一個新的問題,565格式下最大的綠色分量也就0x3F。所以我們需要一個轉換,這就是掩碼的用武之地,將各顏色分量和相應的掩碼做與運算,
備註一:
所以這種方法需要通過位移來操作 在寫抓屏程序的時候顯示不如後邊的32位圖來的方便, 但是在顏色質量爲16位的操作系統下如何抓屏到32位的圖像呢
通過上邊的兩個GetDIBits的方法是不可行的 第一次LPVOID lpvBits, 爲空 獲得了BITMAPINFO bi信息bi.bmiHeader.biBitCount = 16;
如果想通過修改bi.bmiHeader.biBitCount = 32; 後來第二次調用GetDIBits就會失敗 ;
那如何在顏色質量爲16位的操作系統下如何獲得32位的圖像呢?
方法如下:
首先使用 GetObject(HBITMAP bmpScreen) 獲得信息
然後通過修改BITMAPINFO 中的biBitCount 爲32就可以獲得32位結構的LPVOID 了 再進行操作就方便的多了
biBitCount=32
表示位圖最多有2^32種顏色。這種位圖的結構與16位位圖結構非常類似,當biCompression成員的值是BI_RGB時,它也沒有調色板,32位中有24位用於存放RGB值,順序是:最前一字節保留,紅8位、綠8位、藍8位。這種格式也被成爲888 32位圖。如果 biCompression成員的值是BI_BITFIELDS時,原來調色板的位置將被三個DWORD變量佔據,成爲紅、綠、藍掩碼,分別用於描述紅、綠、藍分量在32位中所佔的位置。在Windows 95(or98)中,系統只接受888格式,也就是說三個掩碼的值將只能是:0xFF0000、0xFF00、0xFF。而在NT系統中,你只要注意使掩碼之間不產生重疊就行。