位圖又可以分為 DDB(Device-Dependent Bitmap) 設備相關位圖 和 DIB(Device-Independent Bitmap) 設備無關位圖
位圖(bitmap) 是一個二維矩形數組
當現實生活中的圖像被存放為位圖時,圖像被分成網絡,像素則是基本采樣單元。
位圖中每個像素的值代表網格的一個單元中的圖像的平均顏色。
位圖有空間尺寸,也就是圖像的寬和高,用像素表示。
位圖除了有空間尺寸外,還有顏色維度,也就是每個像素所需的位的數目,有時候又叫位圖的顏色深度,或位數。
位圖的每個像素都有同樣的顏色位數。每個像素只有一位的位圖稱為單色位圖。
要想有更多顏色,就需要每個像素有更多的位。Windows 引進了 調色板管理器,每個數值對應一個顏色。
通常 0 對應黑色,1 對應於白色。可以看看這個單色位圖:
數據類型是 static BYTE 的二維數組。
從左到右讀這些位時,可以給每八位賦一個十六進制字節。
如果位圖的寬度不是十六的倍數,在右邊添零以得到偶數個字節。
至於如何將這些數值讓計數機顯示成位圖,后面將進行介紹。
可是如果要用更多顏色怎么表示呢? 例如這個圖:
雖然實際上像素點和單元格沒有這么大,但是這個圖便於大家理解。
那么計算機如何用 0 和 1 才能顯示出這個圖呢?為此Windows 制作了相關規定。
由於在 DDB 中沒有顏色表,所以它不能把圖像中的位圖位和顏色對應起來。因此使用 DDB 不太合適。
可以把 DIB 想象為一種文件格式。它的擴展名是 BMP。
Windows 應用程序中用到的位圖一般都是作為 DIB 文件存儲在可執行文件的只讀資源中。
DIB 文件有四個主要部分:
- 文件頭
- 信息頭
- RGB 顏色表(有時可能沒有)(顏色表是根據圖像數據得到的,所以我們用數據來顯示圖像時,不用設置)
- 位圖像素位
文件頭結構體定義如下:
typedef struct tagBITMAPFILEHEADER {
WORD bfType; // 文件簽名,必須是 BM
DWORD bfSize; // 位圖文件的長度(以字節為單位)
WORD bfReserved1; // 必須是0
WORD bfReserved2; // 必須是0
DWORD bfOffBits; // 從 BITMAPFILEHEADER 結構的開頭到位圖位的偏移量 (以字節為單位)
} BITMAPFILEHEADER, *LPBITMAPFILEHEADER, *PBITMAPFILEHEADER;
信息頭結構體定義如下:
typedef struct tagBITMAPCOREHEADER {
DWORD bcSize; // 該結構體大小 = 12
WORD bcWidth; // 位圖的寬度(以像素為單位)
WORD bcHeight; // 位圖的高度(以像素為單位)
WORD bcPlanes; // 目標設備的平面數。必須為 1
WORD bcBitCount; // 每個像素的位數。此值必須為1、4、8或24。
} BITMAPCOREHEADER, *LPBITMAPCOREHEADER, *PBITMAPCOREHEADER;
對於前三種情形(位數為1,4,8), BITMAPCOREHEADER 后面是一個顏色表。而 24 位 DIB 沒有顏色表。
顏色表結構體如下:
typedef struct tagRGBTRIPLE { BYTE rgbtBlue; BYTE rgbtGreen; BYTE rgbtRed; } RGBTRIPLE, *PRGBTRIPLE, *NPRGBTRIPLE, *LPRGBTRIPLE;
例如:如果 bcBitCount = 8; 則 RGB 的范圍是 RGB(0~255, 0~255, 0~255);
信息頭和顏色表結合在一起的結構體:
typedef struct tagBITMAPCOREINFO { BITMAPCOREHEADER bmciHeader; RGBTRIPLE bmciColors[1]; } BITMAPCOREINFO, *LPBITMAPCOREINFO, *PBITMAPCOREINFO;
如果需要為一個 8 位 DIB 的 PBITMAPCOREINFO 結構分配內存,則可以這樣:
PBMI = (BITMAPCOREINFO *)malloc( sizeof(BITMAPCOREINFO) + 255*sizeof(RGBTRIPLE));
在介紹位圖的像素位之前,先了解一下它的存儲方式。
在 DIB 中,圖像的底行是文件的第一行,圖像的頂行是文件的最后一行。
這是一種由下往上的組織方式。可以想象一下笛卡爾(直角)坐標系。
位圖像素位和 DDB 相同,是一組表示像素的數據。
數據類型同樣是 static BYTE 的二維數組。至於如何將該數組和文件頭結合起來,這就涉及到一些函數。
別着急,現在先簡單介紹下 DDB 怎么使用吧!
DDB 是 Windows 圖形設備接口定義的若干圖形對象之一。
這些圖形對象在 GDI 模塊內部存儲,應用程序用數值句柄來引用它們。
創建 DDB:
我們可以用 HBITMAP("指向位圖的句柄") 的變量存儲一個指向 DDB 的句柄。
然后通過調用 DDB 創建函數之一(CreateBitmap) 來取得句柄。創建完之后自然要刪除 DeleteObject(hBitmap);
CreateBitmap() 介紹:
功能:創建一個帶有特定寬度、高度和顏色格式的位圖。
函數原型:HBITMAP CreateBitmap(
int nWidth, // 指定位圖寬度、單位為像素。
int nHeight, // 指定位圖高度、單位為像素。
UINT cPlanes, // 指定該設備使用的顏色位面數目。
UINT nBitCount, // 指定用來區分單個像素點顏色的位數(比特數目)。
CONST VOID *lpBits // 位圖內存指針:指向 static BYTE 類型的數值。可以為 NULL。
);
返回值:如果函數成功,那么返回值是位圖的句柄;如果失敗,那么返回值為NULL。
事實上,調用 CreateBitmap() 時,你只能按照兩種方式設置參數:
cPlanes 和 cBitsCount 值都等於一(指明一個單色位圖)。
cPlanes 和 cBitsCount 值等於某個設備環境中的值,這個值可以通過 PLANES 和 BITSPIXEL 索引調用 GetDeviceCaps() 得到。
在更加現實的情況下,將只是針對第一種情況調用 CreateBitmap()。對於第二種情況,可以用 CreateCompatibleBitmap() 來化繁為簡:
該函數創建一個與設備兼容的位圖,該設備的設備環境句柄由第一個參數給出。
CreateCompatibleBitmap() 使用設備環境句柄獲得 GetDeviceCaps() 返回的信息,然后把這些信息傳給 CreateBitmap()。
除了和真實的設備環境有相同的內存組織方式外,DDB 在其他方面和設備環境無關。
CreateCompatibleBitmap() 介紹:
功能:該函數創建與指定的設備環境相關的設備兼容的位圖。
函數原型:HBITMAP CreateCompatibleBitmap(
HDC hdc, // 設備環境句柄。
int cx, // 指定位圖的寬度,單位為像素。
int cy // 指定位圖的高度,單位為像素。
);
返回值:如果函數成功, 則返回值是兼容位圖 (DDB) 的句柄。如果函數失敗, 返回值為 NULL。
CreateBitmapIndirect() 介紹:
功能:創建一個具有特定寬度、高度和顏色格式的位圖。
函數原型:HBITMAP CreateBitmapIndirect(
CONST BITMAP *lpbm // 指向 BITMAP 結構的指針。該結構包含有關位圖的信息。
);
返回值:如果函數執行成功,那么返回值是指向位圖的句柄;如果函數執行失敗,則返回值為NULL。
BITMAP 結構體:
typedef struct tagBITMAP{ LONG bmType; //位圖類型,必須為0 LONG bmWidth; //位圖寬度 LONG bmHeight; //位圖高度 LONG bmWidthBytes; //每一行像素所在的byte數,必須為偶數 WORD bmPlanes; //顏色平面數 WORD bmBitsPixel; //像素的位數 LPVOID bmBits; //位圖內存指針:指向 static BYTE 類型的數值。可以為 NULL。 }BITMAP,*PBITMAP;
關於第四個參數可以這樣計算:
bmWidthBytes = 2*((bmWidth * bmBitsPixel + 15) / 16);
這個公式可以這樣理解:
由於每個像素行都必須分配偶數個字節:
為了得到個偶數,將分子分母都乘2,但因隱式轉換問題:
當 (bmWidth * bmBitsPixel) 小於16時會直接去 除小數部分,導致結果為0。為了向上取整,在加個15。即得該公式。
而 DIB 每個像素行的字節數總是 4 的倍數,同理:
RowLength = 4*((bmWidth*bmBitsPixel) + 31) / 32) ;
另外我們可以這樣填充 BITMAP 結構體:
GetObject(hBitmap, sizeof(BITMAP), &Bitmap);
不過我們仍需填充一項,那就是 bmBits。因為該函數填充該結構體,這項會為 NULL。
位圖的位:
如果使用 CreateBitmap() 或 CreateBitmapIndirect() 時,沒有給出位圖內存的指針。
我們可以使用下面的函數繼續對該值進行設置:
SetBitmapBits() 介紹:
功能:將位圖的顏色數據位設置為指定的值。
函數原型:LONG SetBitmapBits(
HBITMAP hbm, // 指向要設置的位圖的句柄。
DWORD cb, // 參數 pBits 指向的 static BYTE 類型數組的字節數。
CONST VOID *pvBits // 位圖內存指針.
);
返回值:如果該函數執行成功,則返回值就是在設置位圖位時使用的字節數;如果失敗,則返回值為0。
GetBitmapBits() 介紹:
功能:將指定位圖的位拷貝到緩沖區里。
函數原型:LONG GetBitmapBits(
HBITMAP hbit, // 指向要獲取的位圖的句柄。
LONG cb, // 參數 pBits 指向的 static BYTE 類型數組的字節數。
LPVOID lpvBits // 指向接收位圖位數據的緩沖區指針。
);
返回值:返回值:如果該函數執行成功,那么返回值就是拷貝到緩沖區的字節數;如果該函數執行失敗,那么返回值為0。
總之:對於彩色 DDB,不應該用 CreateBitmap() 或 CreateBitmapIndirect() 或 SetBitmapBits() 來設置像素位。
只有對單色 DDB,才可以放心地設置像素位。
位圖操作相關函數
BitBlt() 介紹:
功能:執行從指定的源設備上下文到目標設備上下文中對應於像素矩形的顏色數據的位塊傳輸。
函數原型:BOOL BitBlt(
HDC hdc, // 指向目標設備環境的句柄。
int x, // 指定目標矩形區域左上角的X軸邏輯坐標。
int y, // 指定目標矩形區域左上角的y軸邏輯坐標。
int cx, // 指定源在目標矩形區域的邏輯寬度。
int cy, // 指定源在目標矩形區域的邏輯高度。
HDC hdcSrc, // 指向源設備環境的句柄。
int x1, // 指定源矩形區域左上角的X軸邏輯坐標。
int y1, // 指定源矩形區域左上角的y軸邏輯坐標。
DWORD rop // 指定光柵操作代碼。
);
參數
rop
這些代碼將定義源矩形區域的顏色數據,如何與目標矩形區域的顏色數據組合以完成最后的顏色。
StretchBlt() 介紹:
功能:該函數從源矩形中復制一個位圖到目標矩形,必要時按目標設備設置的模式進行圖像的拉伸或壓縮。
函數原型:BOOL StretchBlt(
HDC hdcDest, // 指向目標設備環境的句柄。
int xDest, // 指定目標矩形左上角的X軸坐標,按邏輯單位表示坐標。
int yDest, // 指定目標矩形左上角的y軸坐標,按邏輯單位表示坐標。
int wDest, // 指定目標矩形的寬度,按邏輯單位表示寬度。
int hDest, // 指定目標矩形的高度,按邏輯單位表示高度。
HDC hdcSrc, // 指向源設備環境的句柄。
int xSrc, // 指向源矩形區域左上角的X軸坐標,按邏輯單位表示坐標。
int ySrc, // 指向源矩形區域左上角的y軸坐標,按邏輯單位表示坐標。
int wSrc, // 指定源矩形的寬度,按邏輯單位表示寬度。
int hSrc, // 定源矩形的高度,按邏輯單位表示高度。
DWORD rop // 指定要進行的光柵操作。
);
返回值:非零表示成功,零表示失敗。
SetStretchBltMode() 介紹:
功能:設置指定設備環境中的位圖拉伸模式。
函數原型:int SetStretchBltMode(
HDC hdc, // 設備環境句柄。
int mode // 指定拉伸模式。
);
參數
mode
BLACKONWHITE 或 STRETCH_ANDSCANS(默認值) :
如果兩個或多個像素必須被結合成一個像素,StretchBlt 將對像素進行邏輯與操作。
產生的像素只有當所有的初始像素都是白時,才是白色。也就是說黑色像素比白色像素占優勢。
這對以白色為底,圖像主要是黑色的單色位圖來說比較好。
WHITEONBLACK 或 STRETCH_ORSCANS:
如果兩個或多個像素必須被結合成一個像素,StretchBlt 將對像素進行邏輯或操作。
產生的像素只有當所有的初始像素都是黑時,才是黑色。也就是白色像素比黑色像素占優勢。
這對以黑色為底,圖像主要是白色的單色位圖來說比較好。
COLORONCOLOR 或 STRETCH_DELETESCANS:
StretchBlt 只是簡單地去掉像素行或列,而不做任何邏輯操作。這對彩色位圖常常是最佳的方法。
HALFTONE 或 STRETCH_HALFTONE:Windows 根據要結合的源的顏色,計算平均目標顏色。
返回值:如果函數執行成功,那么返回值就是先前的拉伸模式,如果函數執行失敗,那么返回值為0。
GetStretchBltMode() 介紹:
功能:獲取當前的拉伸模式。
函數原型:int GetStretchBltMode(
HDC hdc // 設備環境句柄。
);
返回值:如果函數執行成功,那么返回值是當前伸展模式;如果函數執行失敗,那么返回值為0。
BitBlt() 和 StretchBlt() 不是簡單地進行位塊傳輸。這些函數實際在以下三個圖像之間做位與位的操作:
源:源位圖被拉伸或壓縮成(如果有必要的話)目標矩形同樣的大小。
目標:在BitBlt 或 StertchBlt 調用之前的目標矩形。
圖案:在目標設備上當前所選的畫刷,在橫向和縱向上不斷重復,直到大小和目標矩形一樣。
有名稱的15種光柵操作代碼列表如下:
圖案(P): 源(S): 目標(D): |
11110000 11001100 10101010 |
布爾 操作 |
名稱 |
結果 | 00000000 | 0 | BLACKNESS |
00010001 | ~(S|D) | NOTSRCERASE | |
00110011 | ~S | NOTSRCCOPY | |
01000100 | S&~D | SRCERASE | |
01010101 | ~D | DSTINVERT | |
01011010 | P^D | PATINVERT | |
01100110 | S^D | SRCINVERT | |
10001000 | S&D | SRCAND | |
10111011 | ~S|D | MERGEPAINT | |
11000000 | P&S | MERGECOPY | |
11001100 | S | SRCCOPY | |
11101110 | S|D | SRCPAINT | |
11110000 | D | PATCOPY | |
11111011 | P|~S|D | PATPAINT | |
11111111 | 1 | WHITENESS |
PatBlt() 介紹:
功能:使用當前選入指定設備環境中的刷子繪制給定的矩形區域。通過使用給出的光柵操作來對該刷子的顏色和表面顏色進行組合。
函數原型:BOOL PatBlt(
HDC hdc, // 設備環境句柄。
int nXLeft, // 指定要填充的矩形左上角的X軸坐標,坐標按邏輯單位表示。
int nYLeft, // 指定要填充的矩形左上角的Y軸坐標,坐標按邏輯單位表示。
int nWidth, // 指定矩形的寬度,按邏輯單位表示寬度。
int nHeight, // 指定矩形的高度,按邏輯單位表示高度。
DWORD dwRop // 指定光柵的操作碼。
);
參數
dwRop
返回值:非零表示成功,零表示失敗。
現在我們結合一下相關代碼來熟悉一下:
DEMOCODE(一):
#include <windows.h> LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ; int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow) { static TCHAR szAppName [] = TEXT ("Bricks2") ; HWND hwnd ; MSG msg ; WNDCLASS wndclass ; wndclass.style = CS_HREDRAW | CS_VREDRAW ; wndclass.lpfnWndProc = WndProc ; wndclass.cbClsExtra = 0 ; wndclass.cbWndExtra = 0 ; wndclass.hInstance = hInstance ; wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ; wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ; wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH) ; wndclass.lpszMenuName = NULL ; wndclass.lpszClassName = szAppName ; if (!RegisterClass (&wndclass)) { MessageBox (NULL, TEXT ("This program requires Windows NT!"), szAppName, MB_ICONERROR) ; return 0 ; } hwnd = CreateWindow (szAppName, TEXT ("CreateBitmap Demo"), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL, hInstance, NULL) ; ShowWindow (hwnd, iCmdShow) ; UpdateWindow (hwnd) ; while (GetMessage (&msg, NULL, 0, 0)) { TranslateMessage (&msg) ; DispatchMessage (&msg) ; } return msg.wParam ; } LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { static BITMAP bitmap = { 0, 8, 8, 2, 1, 1 } ; static BYTE bits [8][2] = { 0xFF, 0, 0x0C, 0, 0x0C, 0, 0x0C, 0, 0xFF, 0, 0xC0, 0, 0xC0, 0, 0xC0, 0 } ; static HBITMAP hBitmap ; static int cxClient, cyClient, cxSource, cySource ; HDC hdc, hdcMem ; int x, y ; PAINTSTRUCT ps ; switch (message) { case WM_CREATE: bitmap.bmBits = bits ; hBitmap = CreateBitmapIndirect (&bitmap) ; cxSource = bitmap.bmWidth ; cySource = bitmap.bmHeight ; return 0 ; case WM_SIZE: cxClient = LOWORD (lParam) ; cyClient = HIWORD (lParam) ; return 0 ; case WM_PAINT: hdc = BeginPaint (hwnd, &ps) ; hdcMem = CreateCompatibleDC (hdc) ; SelectObject (hdcMem, hBitmap) ; for (y = 0 ; y < cyClient ; y += cySource) for (x = 0 ; x < cxClient ; x += cxSource) { BitBlt (hdc, x, y, cxSource, cySource, hdcMem, 0, 0, SRCCOPY) ; } DeleteDC (hdcMem) ; EndPaint (hwnd, &ps) ; return 0 ; case WM_DESTROY: DeleteObject (hBitmap) ; PostQuitMessage (0) ; return 0 ; } return DefWindowProc (hwnd, message, wParam, lParam) ; }
顯示位圖可以分下面幾步:
- 加載位圖資源 hBitmap = LoadBitmap(...);
- 獲取位圖信息 GetObject(hBitmap, sizeof(BITMAP), &Bitmap);
- 創建內存設備環境 hdcMem = CreateCompatibleDC(hdc);
- 把 GDI 位圖對象選入內存設備環境 SelectObject(hdcMem, hBitmap);
- 使用 BitBlt 顯示位圖 BitBlt(...);
LoadBitmap() 介紹:
功能:該函數從模塊的可執行文件中加載指定的位圖資源。
函數原型:HBITMAP LoadBitmap(
HINSTANCE hInstance, // 模塊實例的句柄, 其可執行文件包含要加載的位圖。
LPCSTR lpBitmapName // 指向字符串(以NULL結束)的指針。該字符串包含了要加載的位圖資源名稱。
// 也可以用 MAKEINTRESOURCE() 來標識位圖資源名稱。
);
返回值:如果函數成功, 則返回值是指定位圖的句柄。如果函數失敗, 返回值為 NULL。
內存設備環境:
由於使用 GDI 位圖對象時,需要用到內存設備環境。所以先介紹一下它。
通常,設備環境對應於特定的圖形輸出設備和設備驅動。內存設備環境只存在於內存。
要創建一個內存設備環境,必須有一個對應於真實設備的設備句柄。
CreateCompatibleDC() 介紹:
功能:創建一個與指定設備兼容的內存設備上下文環境( DC )。
函數原型:HDC CreateCompatibleDC(
HDC hdc // 現有 DC 的句柄。如果此句柄為 NULL, 該函數將創建與應用程序當前屏幕兼容的內存 DC。
);
返回值:如果函數成功, 則返回值是內存 DC 的句柄。如果函數失敗, 返回值為 NULL。
部分代碼如下:( 需要 #include "resource.h" ,並且加載相應的位圖資源 ,VS2017)
DEMOCODE(二):
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { static HBITMAP hBitmap; static BITMAP Bitmap; static int cxBit, cyBit; HDC hdcMem; switch (message) { case WM_CREATE: hBitmap = LoadBitmap(hInst, MAKEINTRESOURCE(IDB_BITMAP1)); GetObject(hBitmap, sizeof(BITMAP), &Bitmap); cxBit = Bitmap.bmWidth; cyBit = Bitmap.bmHeight; break; case WM_PAINT: PAINTSTRUCT ps; HDC hdc = BeginPaint(hWnd, &ps); // TODO: 在此處添加使用 hdc 的任何繪圖代碼... hdcMem = CreateCompatibleDC(hdc); SelectObject(hdcMem, hBitmap); BitBlt(hdc, 0, 0, cxBit, cyBit, hdcMem, 0, 0, SRCCOPY);
DeleteDC(hdcMem); EndPaint(hWnd, &ps); break; case WM_DESTROY:
DeleteObject(hBitmap); PostQuitMessage(0); break; default: return DefWindowProc(hWnd, message, wParam, lParam); } return 0; }
DDB 的部分就簡單的介紹到這啦。關於 DIB 的部分下次再介紹。