第五講GDI/GDI+進階編程進階
在本講中主要解決一下三個方面的內容
1.圖片平鋪
2.DC的位移
3.儲存影像檔到本地磁碟
在繪製背景圖時,發現位元影像並沒有鋪滿整個客戶區,那麼解決這個問題的辦法有兩個,一個位元影像展開,另一個是位元影像平鋪,位元影像展開比較容易,調用StretchBlt函數就可以對位元影像進行展開拷貝,以適應客戶區大小的改變。那麼位元影像平鋪如何??在第一講中剛開始是採用BitBlt來將記憶體DC中的位元影像拷貝到目標DC上,採用的標誌位是SRCCOPY, 因此位元影像以自己預設的大小顯示並不會隨視窗大小的改變而變大或縮小,那麼是否可以同過計算客戶區的大小和預設位元影像的大小,然後用恰當數量的位元影像來填充客戶區呢?
顯然這種方法是可行的,這個操作比第一講中直接載入位元影像填充客戶區來講,相當於進行了多次填充, 因為第一講中之所以沒有鋪滿整個客戶區是因為 位元影像只填充了一次,即不管是否要重繪視窗,都只填充了一張位元影像,那麼現在是要計算客戶區的大小,當大小發生改變時,填充的位元影像數也要跟著改變以保證能夠全部鋪滿整個客戶區。
首先要對客戶區進行繪製,就需要擷取其DC,那麼這裡我們通過GetDC()這個函數來擷取,GetDC()中需要傳入一個視窗控制代碼,以便知道擷取的是哪個視窗的DC。那麼現在擷取了DC之後,就應該對其進行繪圖等操作了, 但是在操作之前一定要記得先儲存當前系統預設的DC的各種屬性,以便操作完了之後恢複回來,從而不影響其它調用系統預設DC屬性的方法。那麼為了簡單起見,這裡採用SaveDC來對DC中的所有屬性一次性儲存。
具體作業碼如下:
//擷取視窗DC
HDC hdc =GetDC(hWnd);
//儲存DC狀態
int isdc =SaveDC(hdc);
我們要做的操作是將位元影像鋪滿整個客戶區, 那麼就要把位元影像載入到記憶體中來, 通過調用LoadBitmap將位元影像載入進來,並儲存返回的位元影像控制代碼。LoadBitmap原型如下:
HBITMAP LoadBitmap(
HINSTANCE hInstance,//handle to application instance
LPCTSTR lpBitmapName//name of bitmap resource
);
具體載入代碼:
HBITMAP hBitMap =::LoadBitmap(hInst, MAKEINTRESOURCE(IDB_BITMAP1));
位元影像載入到記憶體中之後還不能直接繪製到目標DC上去, 就相當於列印東西一樣,比如列印東西的流程如下:
第一步把待列印的文檔拷貝到隨身碟上,
第二步把隨身碟裡的東西拷貝到電腦上,
第三步將電腦上的這個文檔的內容通過列印程式輸出到印表機中的A4上
那麼把位元影像載入到記憶體就相當於第一步把文檔拷貝到隨身碟中
那麼為了列印這個文檔, 則第二步就是要文檔拷貝到電腦中,要拷貝到電腦上,首先就要有一個電腦,那麼位元影像要繪製到目標DC上,就要先拷貝到記憶體DC上,要拷貝到記憶體DC上就要有一個記憶體DC, 所以先建立一個記憶體DC
HDC hMemDC =CreateCompatibleDC(hdc);
之後將東西拷貝到記憶體DC中來,即將位元影像從記憶體中選入到記憶體DC中來
SelectObject(hMemDC,hBitmap);
我們這次是要將位元影像以平鋪的方式鋪滿整個客戶區,那麼就要知道這位元影像和和這個客戶區的大小,以確定是用一張位元影像可以鋪滿還是要N張才能鋪滿,那麼下面就要計算位元影像的大小和客戶區的大小。
首先聲明一個位元影像變數, 通過調用GetObject()來擷取位元影像的資訊
BITMAP bitmap;
::GetObject(hBitmap,sizeof(BITMAP),&bitmap);
擷取了位元影像資訊之後,就要來擷取客戶區大小
RECT rect;
::GetClientRect(hdc,&rect);
但是這裡我們先不去完成整個客戶區的背景繪製,而是指定一個地區去平鋪填充,因為前面已經通過展開的方式實現了這個功能,如果再用平鋪來做這個功能則需要先屏蔽之前的展開代碼。那麼這裡指定一個300長,300寬的矩形地區,讓位元影像來填充。那麼就需要計算這個地區要幾張位元影像才能填充得滿?
先來計算一下,一行需要幾張位元影像才能填得滿
int iNumX =300/bitmap.bmWidth +1; 這裡應該很好理解,300/bitmap.bmWidth 就是一行填入幾張位元影像, 加1個像素 是我們好繪製一個矩形來檢驗它是否剛好填充這個指定的矩形框,同樣計算一列需要填入幾張位元影像
int iNumY =300/bitmap.bmHeight +1;
現在圖片本身的大小已經計算出來, 指定地區需要幾張位元影像也已經計算完畢, 之前把位元影像也是選入了記憶體DC中來了, 那麼現在的記憶體DC就成了源DC, 然後就需要把源DC中的位元影像,分別貼到目標DC上的指定地區裡面去
for(inti=0;i<iNumY; ++i)
for(int j = 0;j<iNumX; ++j)
BitBlt(hdc,j*bitmap.bmWidth,i*bitmap.bmHeight,bitmap.bmWidth,bmp.bmHeight,hMemDC,0,0,SRCCOPY);
好,現在在指定的矩形地區內已經把位元影像貼上去了,那怎麼看出來是貼在指定的矩形中呢? 我們畫一個這麼大小的矩形框 協助檢驗就可以了,畫邊框是用畫筆來繪製, 那麼先建立一個畫筆, 然後選入當前DC ,然後調用Rectangle繪製, 銷毀相應的控制代碼,就可以了
HPEN hPenRed =::CretePen(PS_SOLID , 1, RGB(255,0,0));
::SelectObject(hdc,hPenRed);
繪製矩形框
Rectangle(hdc,0,0,300,300);
::DeleteObject(hPenRed);
::DeleteObject(hBitmap);
::DeleteObject(hMemDC);
用完之後要恢複DC
RestoreDC(hdc,isdc);
注意前面我們通過GetDC獲得了一個DC, 現在不需要了的時候一定記得要用ReleaseDC來釋放掉
ReleaseDC(hWnd,hdc);
F5 運行,結果發現矩形框中被系統預設的白色畫刷填充了,那麼在矩形繪製之前應該選擇一個空畫刷選入到當前DC
SelectObject(hdc,GetStockObject(NULL_BRUSH));
F5 運行, 可以看到矩形中填充的背景位元影像了,但是發現位元影像填充超出了矩形框, 但是我們可能不需要它在矩形框外面還顯示 ,這時候該怎麼解決呢? 這裡就需要引入減區的概念了。
就是對DC進行剪輯
用SelectClipRgn函數來剪輯指定的地區,只有被剪輯的部分,才會被繪製,原型如下:
SelectClipRgn(HDChDC,HRGN hRgn);
參數hDC就是我們要剪輯的DC控制代碼。
參數hRgn就是我們要剪輯的地區。
要剪輯的地區,不一定要是矩形地區,也可以是圓角矩形地區,也可以是圓形地區等。 比如:
CreateRectRgn(intx1, int y1, int x2, int y2);就是建立矩形地區,
CreateRoundRectRgn(intx1, int y1, int x2, int y2,int w ,int h)就是建立圓角矩形地區。
CreateElipticRgn(intx1, int y2, int x2, int y2);就是建立圓形地區。
那麼我們現在怎麼來修改之前的代碼 使位元影像不貼出矩形框外去呢?
其實就是在BitBlt貼圖之前,我們先用HRGN 建立一個矩形地區,然後通過SelectClipRgn來將這個矩形地區選入當DC就可以了, 這樣在當前DC上所的繪製 只有在矩形地區裡內容才會被顯示, 其它地方的內容將不會被顯示, 直達會DC為止。
具體作業碼如下:
HRGN hRgn =CreateRectRgn(0,0,300,300);
SelectClipRgn(hdc,hRgn);
for(inti=0;i<iNumY;++i)
……
這樣即可。那麼上面的平鋪方式固然是一種思路, 那麼還有沒有另外的方法可以實現這個平鋪功能呢?上面是通過計算矩形地區中需要多少張位元影像來填充,然後填入多少張位元影像來解決這個問題, 其實不用這麼麻煩, 可以直接通過建立位元影像畫刷來完成, 因為位元影像畫刷本身支援平鋪,所以這樣做就不需要計算客戶區和載入位元影像的大小了
操作方式同樣也是:
1.通過LoadBitmap()載入位元影像
2.調用CreatepatternBrush()根據載入的位元影像建立紋理畫刷
3.將畫刷選入當前DC
4.為了觀察我們建立一支紅色畫筆
5.然後調用Rectangle來繪製矩形
6.善後清理工作
具體代碼如下;
HBITMAP hBitmap =LoadBitmap(hInst,MAKEINTRESOURCE(IDB_BITMAP1));
HBRUSH hbr=CreatePatternBrush(hBitmap);
SelectObject(hdc,hbr);
HPEN hPenRed =CreateRed(PS_SOLID,1,RGB(255,0,0));
SelectObject(hdc,hPenRed);
Rectangle(hdc,0,0,300,300);
::DeleteObject(hPenRed);
::DeleteObject(hBitmap);
DeleteObject(hbr);
RestoreDC(hdc,isdc);
好完成了位元影像平鋪之後,下面來說說DC位移的概念,比如原來寫了很多代碼,但是它們都是基於0,0那個座標的,可能有時候我們覺得座標不應該從0,0出發,要去修改DC裡面函數裡的值,可能修改不了,一個是可能命令比較多,修改了一個其它的都要修改,二個是有時候DC繪圖這些代碼不是我們本身寫的,無法去修改裡面的代碼,比如別人動態庫裡的繪圖是針對左上方的,但是你現在要位移一下,但是無法修改原始碼,那此時該怎麼辦?此時可以通過修改畫布的視口原點,就可以改變它的值。這裡可以調用SetViewportOrgEx來使得原來的原點位移到想要的位置,比如原來的原點是0,0,現在調用
SetViewportOrgEx(hdc,300,300,NULL); 那麼現在的原點就變成了300,300了。 它的原型如下:
BOOLSetViewportOrgEx(
HDC hdc, //handle todevice context
int x, //newx-coordinate of viewport origin
int y, // newy-coordinate of viewport origin
LPPOINT lpPoint //original viewport origin
);
lpPoint就是返回之前的原點。
也可以通過GetViewportOrgEx(HDChdc, LPPOINT lppoint);來擷取當前繪製的起點的座標
參數hdc 就是要擷取繪製起點座標的DC控制代碼
參數lppoint用來擷取hdc當前繪製的起點座標。
好映像繪製之後通常我們需要把它儲存起來。那怎麼儲存映像呢?我們這裡儲存為BMP檔案,那麼首先來瞭解一下Bitmap的結構資訊:
BMP 檔案總體上由4部分組成,分別是位元影像檔案頭,位元影像資訊頭,調色盤和映像資料。
位元影像檔案頭包含了映像類型,映像大小,映像資料存放地址和兩個保留未使用的欄位。原型如下:
typedef structtagBITMAPFILEHEADER{
WORD bfType; //映像類型
DWORD bfSize; //映像大小
WORD bfResereved1;//保留未使用的欄位
WORD bfReserved2; //保留未使用的欄位
DWORD bfOffBits; //映像資料存放地址
}BITMAPFILEHEADER;
位元影像資訊頭包含了位元影像資訊頭的大小,映像的寬高,映像的色深,壓縮說明,映像資料的大小和其它一些參數。
typedef struct tagBITMAPINFOHEADER{
DWORD biSize; //本結構的大小
LONG biWidth; //BMP映像的寬度
LONG biHeight; BMP映像的高度
WORD biPlanes; //映像的面數
WORD biBitCount; //映像的色深,即一個像素用多少位表示。
DWORD biCompression; //壓縮方式,如0表示不壓縮,1表示RLE8壓縮,2表示RLE4壓縮。
DWORD biSizeImage; //BMP映像資料的大小。
LONG biXPelsperMeter; // 水平解析度
LONG biYPelsperMeter; //垂直解析度
DWORD biClrUsed; //BMP映像使用的顏色,0表示使用全部顏色,對於256位元影像來說,此值為256.
DWORD biClrImportant; //重要的顏色數,此值為0時所有顏色都重要,對於使用調色盤的BMP映像來說,當顯卡不能夠顯示所有顏色時,此值將被輔助驅動程式顯示顏色。
}BITMAPINFOHEADER;
調色盤是單色,16色和256色影像檔所特有的,相對應的調色盤大小是2、16和256,調色盤以4位元組為單位,每4位元組存放一個顏色值,映像的資料是指向調色盤的索引。
調色盤是用來解決色彩不豐滿的時候用的。
調色盤的資料結構定義:
typedef struct tagRGBQUAD{
BYTE rgbBlue; //藍色值
BYTE rgbGreen;
BYTE rgbRed;
BYTE rgbReserved;
}RGBQUAD;
位元影像資料:如果映像是單色、16色和256色,則緊跟著調色盤的是位元影像資料,位元影像資料是指向調色盤的索引序號。
如果映像是16位,24位和32位色,則檔案中不保留調色盤,即不存在調色盤,映像的顏色直接在位元影像資料中給出。
16位元影像像使用2位元組儲存顏色值,常見有兩種格式:5位紅5位綠5位藍和5位紅6位綠5位藍,即555格式和565格式。555格式只使用了15位,最後一位保留,設為0.
24位元影像像使用3位元組儲存顏色值,每一個位元組代表一種顏色,按紅,綠,藍排列。
32位元影像像使用4位元組儲存顏色值,每一個位元組代表一種顏色,除了原來的紅、綠、藍,還有Alpha通道,即透明色。
那麼我們這裡是要儲存一個24位個BMP影像檔,所以我們先設定每個像素所佔位元組數為24的變數
WORD wBitCount = 24;
然後定義位元影像中像素位元組的大小,位元影像檔案的大小,寫入的檔案位元組數這裡都初始化為0
DWORD dwBmBitSize= 0, dwDIBSize=0,dwWritten =0;
接下來聲明位元影像屬性結構
BITMAP Bitmap;
聲明位元影像檔案頭結構
BITMAPFILEHEADER bmfHdr;
聲明位元影像資訊頭結構
BITMAPINFOHEADER bi;
聲明指向位元影像資訊頭結構的指標
LPBITMAPINFOHEADER lpbi;
聲明一個檔案控制代碼,和一個記憶體控制代碼
HANDLE fh,hDib;
然後計算位元影像檔案每個像素所佔的位元組數
hDC = GetDC(NULL);
GetObject(hBitmap,sizeof(Bitmap),(LPSTR)&Bitmap);
bi.biSize =sizeof(BITMAPINFOHEADER);
bi.biWidth =Bitmap.bmWidth;
bi.biHeight =Bitmap.bmHeight;
bi.biPlanes =1;
bi.biBitCount =wBitCount;
bi.biCompression =BI_RGB;
bi.biSizeImage =0;
bi.biXPelsPerMeter =0;
bi.biYPelsPerMeter =0;
bi.biClrImportant=0;
bi.biClrUsed = 0;
dwBmBitSize =((Bitmap.bmWidth*wBitCount+31)/32)*4*Bitmap.bmHeight;
hDib =GlobalAlloc(GHND,dwBmBitSize + sizeof(BITMAPINFOHEADER));
lpbi =(LPBITMAPINFOHEADER)GlobalLock(hDib);
*lpbi = bi;
GetDIBits(hDC,hBitmap,0,(UINT)Bitmap.bmHeight,(LPSTR)lpbi+sizeof(BITMAPINFOHEADER),(BITMAPINFO*)lpbi,DIB_RGB_COLORS);
ReleaseDC(NULL,hDC);
fh = CreateFileA(L"C:\\Image.bmp",GENERIC_WRITE,0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL |FILE_FLAG_SEQUENTIAL_SCAN, NULL);
if(INVALID_HANDLE_VALUE == fh)
{
ShowErrMsg();
return FALSE;
}
//設定位元影像檔案頭
bmfHdr.bfType =0x4D42; //"BM"
dwDIBSize = sizeof(BITMAPFILEHEADER)+sizeof(BITMAPINFOHEADER)+dwBmBitSize;
bmfHdr.bfSize =dwDIBSize;
bmfHdr.bfReserved1=0;
bmfHdr.bfReserved2=0;
bmfHdr.bfOffBits =(DWORD)sizeof(BITMAPFILEHEADER)+
(DWORD)sizeof(BITMAPINFOHEADER);
//寫入位元影像檔案頭 WriteFile(fh,(LPSTR)&bmfHdr,sizeof(BITMAPFILEHEADER),&dwWritten,NULL);
//寫入位元影像檔案其餘內容
WriteFile(fh,(LPSTR)lpbi,dwDIBSize,&dwWritten,NULL);
//清除
GlobalUnlock(hDib);
GlobalFree(hDib);
CloseHandle(fh);
F5運行 到C盤下去, 怎麼沒有儲存的Image.bmp檔案呢? 怎麼回事呢? 也沒報錯,沒有什麼錯誤提示,怎麼會沒有儲存呢? 把C盤改成其他盤就可以看到儲存了檔案了,那這是怎麼回事呢? 沒關係我們可以通過GetLastError()函數和FormatMessage函數來擷取其相應的錯誤資訊。 具體代碼如下:
void ShowErrMsg()
{
TCHAR szBuf[80];
LPVOID lpMsgBuf;
DWORD dw = GetLastError();
FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER |
FORMAT_MESSAGE_FROM_SYSTEM ,
NULL,
dw,
MAKELANGID(LANG_NEUTRAL,
SUBLANG_DEFAULT
),
(LPTSTR) &lpMsgBuf,
0, NULL );
wsprintf(szBuf, _T("%sfailed: GetLastError returned %u\n"),
lpMsgBuf, dw);
MessageBox(NULL, (LPCWSTR)szBuf, _T("Error"), MB_OK);
LocalFree(lpMsgBuf);
}
然後我們調用這個錯誤碼, 發現是 錯誤碼為5 表示拒絕訪問? 哦是因為沒有許可權所以沒法寫入到C盤, 解決方案是 右鍵項目屬性,連結器 選擇 資訊清單檔 選擇 UAC 執行層級 選擇requireAdministrator 然後儲存 然後F5執行, C盤中就看到儲存了的檔案了。
//Start//////////平鋪方式A//////////////*int isdcfa = SaveDC(hdc);HBITMAP hBitmapfa = ::LoadBitmapW(hInst,MAKEINTRESOURCE(IDB_BITMAP1));HDC hMemDCfa = ::CreateCompatibleDC(hdc);SelectObject(hMemDCfa, hBitmapfa);BITMAP bmpfa;::GetObjectA(hBitmapfa,sizeof(BITMAP),&bmpfa);int iNumX = 300/bmpfa.bmWidth+1;int iNumY = 300/bmpfa.bmHeight+1;HRGN hRgn =::CreateRectRgn(0,0,300,300);SelectClipRgn(hdc,hRgn);for(int i=0; i<iNumY;++i)for(int j=0;j<iNumX; ++j)BitBlt(hdc,j*bmpfa.bmWidth,i*bmpfa.bmHeight,bmpfa.bmWidth,bmpfa.bmHeight,hMemDCfa,0,0,SRCCOPY);HPEN hPenRed = ::CreatePen(PS_SOLID,1,RGB(255,0,0));SelectObject(hdc,hPenRed);SelectObject(hdc,GetStockObject(NULL_BRUSH));Rectangle(hdc,0,0,300,300);::DeleteObject(hPenRed);::DeleteObject(hMemDCfa);::DeleteObject(hBitmapfa);RestoreDC(hdc,isdcfa);*///End//////////平鋪方式A///////////////Start//////////位元影像畫刷平鋪方式B/////////////int isdc = SaveDC(hdc);::SetViewportOrgEx(hdc,800,50,NULL);HBITMAP hBitmap = ::LoadBitmapW(hInst, MAKEINTRESOURCE(IDB_BITMAP1));HBRUSH hbrfb = ::CreatePatternBrush(hBitmap);SelectObject(hdc,hbrfb);HPEN hPenRed = CreatePen(PS_SOLID,1,RGB(255,0,0));SelectObject(hdc,hPenRed);Rectangle(hdc,0,0,300,300);::DeleteObject(hBitmap);::DeleteObject(hbrfb);::DeleteObject(hPenRed);RestoreDC(hdc,isdc);//End//////////位元影像畫刷平鋪方式B/////////////