1. 程式人生 > >GDI+ 雙緩衝實現和區域性刷新技術

GDI+ 雙緩衝實現和區域性刷新技術

GDI+雙緩衝技術: 早前曾為此問題在CSDN發帖求助(GDI+ 如何使用雙緩衝繪製圖像),得到了一個GDI+下較可行的方法,雖然繪製效果比直接繪製要好一些,不過還不能跟GDI的雙緩衝方式比肩。 現在,我終於找到了一個理想的實現方式,效果與GDI的實現不相上下,程式碼如下: /*C++ code*/ RECT rc; GetClientRect(g_hwnd,&rc); Bitmap bmp(int(rc.right),int(rc.bottom)); Graphics bmpGraphics(&bmp); bmpGraphics.SetSmoothingMode(SmoothingModeAntiAlias); /*Drawing on bitmap*/
SolidBrush bkBrush(Color(0,0,0)); bmpGraphics.FillRectangle(&bkBrush,0,0,rc.right,rc.bottom); /*Drawing on DC*/ Graphics graphics(hdc); /*Important! Create a CacheBitmap object for quick drawing*/ CachedBitmap cachedBmp(&bmp,&graphics); graphics.DrawCachedBitmap(&cachedBmp,0,0); 以上的繪製程式碼最區別於網路上其他GDI+實現的一處就是,在最後添加了一個CacheBitmap物件用於快速繪製。 CacheBitmap是一個包含了bmp全部象素,並且針對graphics所關聯的DC做過特別優化的點陣圖物件。這點可以從其構造引數上看到。  關於雙緩衝的實現還有一點十分關鍵,雖然它不屬於雙緩衝實現的核心。如果繪製需要經常的重繪背景,則需要自己攔截WM_ERASEBKGND訊息,並在處理函式中什麼也不做,即此訊息發生時不重畫背景,背景的重畫在WM_PAINT中全權控制。 如,WM_ERASEBKGND訊息處理的實現 void OnEraseBkGnd(HDC hdc) { //do nothing } 附:
GDI的雙緩衝實現 RECT rc; GetClientRect(hwnd,&rc); HDC hMemDc = CreateCompatibleDC(hdc); HBITMAP hBmp = CreateCompatibleBitmap(hdc,rc.right,rc.bottom); HBITMAP hOldBmp = (HBITMAP)SelectObject(hMemDc,hBmp); //在此使用hMemDc進行 GDI 繪製 BitBlt(hdc,0,0,rc.right,rc.bottom,hMemDc,0,0,SRCCOPY); SelectObject(hMemDc,hOldBmp); DeleteObject(hBmp); DeleteObject(hMemDc);

前言:GDI+很好用,但要將所有影象混合,必須要用到UpdateLayeredWindow,但UpdateLayeredWindow註定每次重新整理都要重新整理整個窗體,也就是說,哪怕我們只是改變的只是一個畫素,也都要重新繪製整個窗體,就沒有像GDI中那樣有區域裁剪的功能,真無語了,搜遍整個網路也沒找到UpdateLayeredWindow的區域性重新整理方案,最後想到在MFC中是可以使用GDI+的,而MFC中的重新整理方案就是區域性重新整理的,這說明,利用GDI的函式bitblt或alphaBlend是可以整合GDI+的,最後嘗試了一下,成功了,下面分享給大家。


本文摘要:這個工程是在第一篇《WIN32介面開發之一:初試載入背景介面》的基礎上講解的,所有請大家先看看第一篇,這篇內容實現的效果是,建立一個背景圖案,在圖案上模擬一個按鈕,按鈕原是紅色塊,當點選按鈕的時候,變成綠色色塊,滑鼠彈起時,還原為紅色色塊。

正常狀態:

佔擊狀態:

思想:首先,在第一次建立工程時,我們在在相容DC繪圖,並把這個DC儲存起來,當下次重繪時,重新建立一個相容DC,用AlphaBlend將我們儲存的原相容DC上的內容複製到當前的相容DC上,然後再在要更新的區域重新畫圖就是了,這裡我們只需要更新按鈕的區域。

一、幾個全域性變數

  1. HDC hdcBKMemory=NULL;//相容DC
  2. HBITMAP hBKBitmap=NULL;//相容DC上的畫布
  3. HGDIOBJ hBKBitmapOld=NULL;//被選出的原相容DC上的預設畫布
二、背景相容DC的初次建立與釋放

我們先看建立的程式碼:

  1. HDC hDC = ::GetDC(m_hWnd);  
  2. if (hdcBKMemory==NULL)  
  3. {  
  4.     hdcBKMemory = CreateCompatibleDC(hDC);  
  5.     //建立背景畫布
  6.     BITMAPINFOHEADER stBmpInfoHeader = { 0 };     
  7.     int nBytesPerLine = ((sizeWindow.cx * 32 + 31) & (~31)) >> 3;  
  8.     stBmpInfoHeader.biSize = sizeof(BITMAPINFOHEADER);     
  9.     stBmpInfoHeader.biWidth = sizeWindow.cx;     
  10.     stBmpInfoHeader.biHeight = sizeWindow.cy;     
  11.     stBmpInfoHeader.biPlanes = 1;     
  12.     stBmpInfoHeader.biBitCount = 32;     
  13.     stBmpInfoHeader.biCompression = BI_RGB;     
  14.     stBmpInfoHeader.biClrUsed = 0;     
  15.     stBmpInfoHeader.biSizeImage = nBytesPerLine * sizeWindow.cy;     
  16.     PVOID pvBits = NULL;     
  17.     hBKBitmap = ::CreateDIBSection(NULL, (PBITMAPINFO)&stBmpInfoHeader, DIB_RGB_COLORS, &pvBits, NULL, 0);  
  18.     assert(hBKBitmap != NULL);  
  19.     hBKBitmapOld = ::SelectObject( hdcBKMemory, hBKBitmap);  
  20.     //gdi+畫圖
  21.     Gdiplus::Graphics graph(hdcBKMemory);  
  22.     graph.SetSmoothingMode(Gdiplus::SmoothingModeNone);  
  23.     graph.DrawImage(pImage, 0, 0, sizeWindow.cx, sizeWindow.cy);  
  24.     graph.FillRectangle(&SolidBrush(Color::Green),10,10,25,25);  
  25.     graph.FillRectangle(&SolidBrush(Color::Red),100,50,30,30);//模擬按鈕
  26.     graph.ReleaseHDC(hdcBKMemory);  
  27. }  
講解:上面的程式碼比較簡單,首先是用CreateDIBSection建立一個裝置相關位置(DDB),然後將點陣圖選入到背景相容DC(hdcBKMemory)中,後面就是用GDI+在這個DC上畫圖。
再看釋放:
因為我們要一直用它,直到銷燬,所以我們在WM_DESTROY訊息中銷燬這些變數:
  1. case WM_DESTROY:  
  2.     PostQuitMessage(100);  
  3.     delete pImage;  
  4.     ::SelectObject( hdcBKMemory, hBKBitmapOld); //不要把預設的點陣圖選回來,如果選回來的話,我們新建的點陣圖就被替換掉了,當然我們上面畫的東東也就沒有了
  5.     ::DeleteObject(hBKBitmapOld);  
  6.     ::DeleteObject(hBKBitmap);   
  7.     ::DeleteDC(hdcBKMemory);  
  8.     break;  
三、新建一個相容DC與畫布,將背景複製到上面,並且在當前畫布上畫上按鈕正常狀態
先看程式碼:
  1. HDC hdcEnd = CreateCompatibleDC(hDC);//新建相容DC
  2. BITMAPINFOHEADER stBmpInfoHeader2 = { 0 };     
  3. int nBytesPerLine2 = ((sizeWindow.cx * 32 + 31) & (~31)) >> 3;  
  4. stBmpInfoHeader2.biSize = sizeof(BITMAPINFOHEADER);     
  5. stBmpInfoHeader2.biWidth = sizeWindow.cx;     
  6. stBmpInfoHeader2.biHeight = sizeWindow.cy;     
  7. stBmpInfoHeader2.biPlanes = 1;     
  8. stBmpInfoHeader2.biBitCount = 32;     
  9. stBmpInfoHeader2.biCompression = BI_RGB;     
  10. stBmpInfoHeader2.biClrUsed = 0;     
  11. stBmpInfoHeader2.biSizeImage = nBytesPerLine2 * sizeWindow.cy;     
  12. PVOID pvBits2 = NULL;     
  13. HBITMAP hbmpMem2 = ::CreateDIBSection(NULL, (PBITMAPINFO)&stBmpInfoHeader2, DIB_RGB_COLORS, &pvBits2, NULL, 0);//新建畫布
  14. HGDIOBJ hEndBitmapOld=SelectObject(hdcEnd,hbmpMem2);  
  15. POINT ptSrc = { 0, 0};  
  16. BLENDFUNCTION blendFunc;  
  17. blendFunc.BlendOp = 0;  
  18. blendFunc.BlendFlags = 0;  
  19. blendFunc.AlphaFormat = 1;  
  20. blendFunc.SourceConstantAlpha = 255;//AC_SRC_ALPHA
  21. ::AlphaBlend(hdcEnd,0,0,sizeWindow.cx,sizeWindow.cy,hdcBKMemory,0,0,sizeWindow.cx,sizeWindow.cy,blendFunc);//將背景複製到新畫布上
  22. Graphics graph2(hdcEnd);  
  23. if (inBtnRect)//這個變數下面講解
  24. {  
  25.     graph2.FillRectangle(&SolidBrush(Color::Green),100,50,30,30);//按鈕按下狀態
  26. }else{  
  27.     graph2.FillRectangle(&SolidBrush(Color::Red),100,50,30,30);//按鈕正常狀態
  28. }  
  29. POINT ptWinPos = { rcWindow.left, rcWindow.top };  
  30. //UpdateLayeredWindow
  31. HMODULE hFuncInst = LoadLibrary(_T("User32.DLL"));  
  32. typedefBOOL (WINAPI *MYFUNC)(HWNDHDC, POINT*, SIZE*, HDC, POINT*, COLORREF, BLENDFUNCTION*, DWORD);            
  33. MYFUNC UpdateLayeredWindow;  
  34. UpdateLayeredWindow = (MYFUNC)::GetProcAddress(hFuncInst, "UpdateLayeredWindow");  
  35. //不會發送 WM_SIZE和WM_MOVE訊息
  36. if(!UpdateLayeredWindow(m_hWnd, hDC, &ptWinPos, &sizeWindow, hdcEnd, &ptSrc, 0, &blendFunc, ULW_ALPHA))  
  37. {  
  38.     assert(L"UpdateLayeredWindow 呼叫失敗");  
  39.     TCHAR tmp[255] = {_T('\0')};  
  40. }//使用UpdateLayeredWindow更新到當前窗體上
  41. //釋放資源
  42. graph2.ReleaseHDC(hdcEnd);  
  43. SelectObject(hdcEnd,hEndBitmapOld);  
  44. ::DeleteObject(hFuncInst);  
  45. ::DeleteObject(hEndBitmapOld);  
  46. ::DeleteObject(hbmpMem2);  
  47. ::DeleteDC(hdcEnd);  
  48. ::DeleteDC(hDC);  

講解:這段程式碼看起來比較長,但主要是這麼個流程:
1、建立新的相容DC
2、建立畫布並將此畫布選到當前的相容DC中,準備做畫
3、應用AlphaBlend將原背景複製到當前畫布中,這時當前的相容DC就具有了背景圖案
4、在原背景圖案上畫上按鈕
5、最後利用UpdateLayeredWindow將最終的圖案複製到當前的窗體上

執行後的軟體圖案是這樣的:

4、新增按鈕及響應

1、添加當前按鈕狀態的變數(全域性變數)

  1. bool inBtnRect=false;//儲存當前按鈕是否是被按下,FALSE是沒被按下,TRUE是按下
2、在WM_LBUTTONDOWN中新增以下程式碼
  1. case WM_LBUTTONDOWN:  
  2.     {  
  3.         POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) };  
  4.         RectF btnRectf(100,50,30,30);  
  5.         if (btnRectf.Contains(pt.x,pt.y))  
  6.         {  
  7.             inBtnRect=true;  
  8.             SendMessage(hwnd,WM_PAINT,NULL,NULL);  
  9.             SetCapture(hwnd);  
  10.         }  
  11.     }  
  12.     break;  
講解:
1、首先用GET_X_LPARAM和GET_Y_LPARAM獲取當前滑鼠在窗體中的點選位置;
2、如果點選位置在按鈕區域內的話,就重新整理窗體,並且設定焦點

3、在WM_LBUTTONUP新增程式碼
  1. case WM_LBUTTONUP:  
  2.     {  
  3.         if (inBtnRect)  
  4.         {  
  5.             ReleaseCapture();  
  6.             inBtnRect=false;  
  7.             SendMessage(hwnd,WM_PAINT,NULL,NULL);  
  8.         }     
  9.     }  
  10.     break;  
講解:主要是釋放焦點,把inBtnRect設定成FALE,並且重繪窗體;


宣告:軟體所用圖片來於網路,感謝金山影音漂亮的介面圖片