Win32 GDI 非矩形地區剪裁,雙緩衝技術

來源:互聯網
上載者:User

標籤:des   tar   ext   com   get   使用   

傳統的Win32通過GDI提供圖形顯示的功能,包括了基本的繪圖功能,如畫線、方塊、橢圓等等,進階功能包括了多邊形和Bezier的繪製。這樣app就不用關心那些圖形學的細節了,有點類似於UNIX上的X-window協議。你信或者不信,那些看上去很花哨的控制項,其實就是一筆一划畫上去的而已。GDI提供了畫筆(用於線條)、畫刷(用於填充)、調色盤(用於支援256色顯示)、字型(用於文字)。如果簡單的圖形不足以表達,你可以使用位元影像和畫布(DC,裝置上下文)直接將映像繪製到螢幕上去。此外,GDI還支援一些簡單的座標變換,以方便app縮放、平移繪製的內容,特別是支援印表機操作。在列印裝置上,程式使用英寸而不是像素來繪畫。不得不說,GDI的變換和位元影像部分不是很好用,DIB和DDB簡直是折磨人,以至於經典的《Windows程式設計》大部分篇幅都在教你如何寫字和畫畫。

 

GDI+內部調用了GDI,提供了一些更進階的新功能,如半透明(Alpha channel)、路徑合并(Path Combination)、消除鋸齒(Anti-alias)和漸層色(Gradient)。另外還包括了比GDI更全面的顏色/座標變換方法,以及方便的映像顯示。除了支援BMP以外,添加了GIF、PNG、JPG等編碼解碼器,方便app匯入匯出影像檔。從Windows Vista開始,圖形顯示領域有弱化、代替GDI的趨勢。XP的普通視窗運行於GDI視窗管理器中,而從Vista開始所有視窗都運行於D3D的視窗管理器中。也就是說以前你往顯示器上點顏色,而你現在只不過在往一個3D遊戲中帖平面圖而已。新的WPF程式的圖形呈現完全依賴於D3D而跳過了GDI、GDI+。Win 7新出現的Direct 2D就致力於用顯卡的加速功能代替GDI進行二維圖形和文字的顯示。

 

不知道Win8的Metro App是不是也是基於D3D的呢~~~

充分利用硬體加速好像是一個趨勢

 

 

例子1 

====

如何畫出如“唐老鴨”這樣一個造型的視窗。

 

視窗風格為 WS_POPUP,所以建立的視窗沒有標題列。但是當視窗沒有標題列後,我們就無法用拖動標題列的辦法來移動視窗,如果讓視窗一動不動呆在螢幕中間顯然是不行的,這裡有一個替代辦法,我們可以響應按下滑鼠左鍵的訊息,在 WM_LBUTTONDOWN 訊息中想視窗發送 WM_NCLBUTTONDOWN (非客戶區滑鼠按下訊息) 位置在 HTCAPTION 來類比滑鼠按在標題列中來實現移動的功能。

Windows 裡有專門的 API 來實現特殊形狀的視窗,步驟是首先建立地區(Region),Region 可以合并,這樣一來就可以用幾個簡單的地區合并出一個複雜的地區,建立、合并地區和設定視窗的 API 主要有以下幾條:

 

CreateRectRgn(Left,Top,Right,Bottom) - 建立矩型地區 

CreateEllipticRgn(Left,Top,Right,Bottom) - 建立橢圓地區 

CreatePolygonRgn(lpPoints,NumberOfPoints,Mode) - 建立多邊形地區,這些API返回地區控制代碼 

CombineRgn(hDest,hSource1,hSource2,CombineMode) - 合并地區 

SetWindowRgn(hWnd,hRgn,bRedraw) - 根據地區設定視窗形狀 

本程式的方法是掃描位元影像的點,按行設定地區,然後合并到總的地區中。

 

例子2 

====

雙緩衝技術 

 

用VC做的畫圖程式,當所畫的圖形大於螢幕時,在拖動捲軸時螢幕就會出現嚴重的閃爍,為瞭解決這一問題,就得使用雙緩衝來解決。程式產生嚴重的閃爍問題是因為畫圖過程中前後兩次的畫面反差很大造成的人的視覺的閃爍。因為在VC中每次在調用OnDraw時系統都是先用背景畫刷將畫布清除再執行畫圖命令,這樣在你每次移動捲軸時每執行一次OnDraw就會有一個空白頁,這樣和你的最終結果圖象之間有一個很大的反差,因而看起來閃爍,而且捲軸滾動越快閃爍越嚴重。當然,你可以將背景畫刷設為NULL,這樣可以解決閃爍問題,但是不能將先前的圖象擦除,這樣整個螢幕就顯得很亂。

裝置描述符抽象了不同的硬體環境為標準環境,使用者編寫時使用的是這個虛擬標準環境,而不是真實的硬體,與真實硬體打交道的工作一般交給系統和驅動程式去完成(這同樣解釋了為什麼我們需要經常更新驅動程式的問題)。使用在windows圖形系統(gdi,而不包括direct x)上面,就體現在一系列的圖形DC上面,我們如果要在gdi上面繪圖,就必須先得到圖形DC的控制代碼(handle),然後在指定控制代碼的基礎上進行圖形操作。

但是WM_PAINT訊息響應的頻度太高了,比如最小化最大化,移動表單,覆蓋等等都引起重繪,經常的這樣畫圖,很是消耗效能;在有些場合,比如隨機作圖的場合,每一次就改變,還導致了程式的無法實現。怎麼解決後一種問題呢。

 

ms在msdn的例子裡面交給我們document/view的經典解決辦法,將圖形的資料存放區在document類裡面,view類只是根據這些資料繪圖。比如你要畫個圓,只是將圓心和半徑存在document裡面,view類根據這個裡面的資料在螢幕上面重新繪製。那麼,我們只需要隨機產生一次資料就可以了。

 

這樣還是存在效能的問題,於是我們開始考慮另外的解決方案。我們知道,將記憶體中的圖片原樣輸出到螢幕是很快的,這也是我們在dos時代經常做的事情,能不能在windows也重新利用呢?答案就是記憶體緩衝繪圖。

 

CRect rc; // 定義一個矩形地區變數

GetClientRect(rc); 

int nWidth = rc.Width(); 

int nHeight = rc.Height(); 

 

CDC *pDC = GetDC(); // 定義裝置上下文

CDC MemDC; // 定義一個記憶體顯示裝置對象 

CBitmap MemBitmap; // 定義一個位元影像對象

 

//建立與螢幕顯示相容的記憶體顯示裝置 

MemDC.CreateCompatibleDC(pDC); 

//建立一個與螢幕顯示相容的位元影像,位元影像的大小可選用視窗客戶區的大小 

MemBitmap.CreateCompatibleBitmap(pDC,nWidth,nHeight); 

//將位元影像選入到記憶體顯示裝置中,只有選入了位元影像的記憶體顯示裝置才有地方繪圖,畫到指定的位元影像上 

CBitmap *pOldBit = MemDC.SelectObject(&MemBitmap); 

//先用背景色將位元影像清除乾淨,否則是黑色。這裡用的是白色作為背景 

MemDC.FillSolidRect(0,0,nWidth,nHeight,RGB(255,255,255)); 

 

//繪圖操作等在這裡實現 

MemDC.MoveTo(……); 

MemDC.LineTo(……); 

MemDC.Ellipse(……); 

 

//將記憶體中的圖拷貝到螢幕上進行顯示 

pDC->BitBlt(0,0,nWidth,nHeight,&MemDC,0,0,SRCCOPY); 

 

//繪圖完成後的清理 

MemDC.SelectObject(pOldbitmap); 

MemBitmap.DeleteObject();

 

 

在緩衝區還可以實現很多進階的圖形操作,比如透明,合成等等,取決於具體的演算法,需要對記憶體直接操作(其實就是當年dos怎麼做,現在還怎麼做)。

 

為什麼不能用全域變數儲存DC?

 

DC需要佔用一定的記憶體,那麼在頻繁的頁面調度中,位置難免改變,於是用來標誌指標的控制代碼也就不同了。

 

為什麼動畫的重畫頻率高,而看起來卻不閃?

閃爍是什嗎?閃爍就是反差,反差越大,閃爍越厲害。因為動畫的連續兩個幀之間的差異很小所以看起來不閃。如果不信,可以在動畫的每一幀中間加一張純白的幀,不閃才怪呢。 

 

電力系統的網狀圖形的CAD軟體,在一個視窗中往往要顯示成千上萬個電力元件,而每個元件又是由點、線、圓等基本圖形構成。如果真要在一次重繪過程重畫這麼多元件,可想而知這個過程是非常漫長的。如果加上了圖形的瀏覽功能,滑鼠拖動圖形滾動時需要進行大量的重繪,速度會慢得讓使用者將無法忍受。怎麼辦?只有再研究研究MFC的繪圖過程了。

實際上,在OnDraw(CDC *pDC)中繪製的圖並不是所有都顯示了的,例如:你在OnDraw中畫了兩個矩形,在一次重繪中雖然兩個矩形的繪製函數都有執行,但是很有可能只有一個顯示了,這是因為MFC本身為了提高重繪的效率設定了裁剪區。裁剪區的作用就是:只有在這個區內的繪圖過程才會真正有效,在區外的是無效的,即使在區外執行了繪圖函數也是不會顯示的。因為多數情況下視窗重繪的產生大多是因為視窗部分被遮擋或者視窗有滾動發生,改變的地區並不是整個圖形而只有一小部分,這一部分需要改變的就是pDC中的裁剪區了。因為顯示(往記憶體或者顯存都叫顯示)比繪圖過程的計算要費時得多,有了裁剪區後顯示的就只是應該顯示的部分,大大提高了顯示效率。但是這個裁剪區是MFC設定的,它已經為我們提高了顯示效率,在進行複雜圖形的繪製時如何進一步提高效率呢?那就只有去掉在裁剪區外的繪圖過程了。可以先用pDC->GetClipBox()得到裁剪區,然後在繪圖時判斷你的圖形是否在這個區內,如果在就畫,不在就不畫。但如果你的繪圖過程不複雜,這樣做可能對你的繪圖效率不會有提高。

 

 

 

例子3 

====

在視窗中顯示出按正弦曲線起伏排列的“龍騰虎躍”五個楷體大字。視窗背景為灰色,文字前景則為一幅256色位元影像,就好象是把彩圖剪成文字粘貼在視窗上一樣。

 

 

調用BeginPath()函數開始路徑定義

定義路徑的GDI繪圖函數包括: 

AngleArc Arc ArcTo Chord *CloseFigure 

Ellipse *ExtTextOut *LineTo *MoveToEx Pie 

*PolyBezier *PolyBezierTo PolyDraw *Polygon *Polyline 

*PolyLineTo *PolyPolygon *PolyPolylin Rectangl RoundRect 

*TextOut 

調用EndPath()函數結束路徑定義。

 

繪製路徑輪廓StrokePath(),填充路徑FillPath(),繪製輪廓並填充StrokeAndFillPath(),把路徑轉換成地區PathToRegion(),把路徑直線化FlattenPath(),提取路徑資料GetPath(),加寬路徑WidenPath()和設定裁剪路徑SelectClipPath()等。

 

 

m_fontKaiTi.CreateFont(200 , 0 , 0 , 0 , FW_BLACK , 

FALSE , FALSE , FALSE , 

GB2312_CHARSET , 

OUT_DEFAULT_PRECIS , 

CLIP_DEFAULT_PRECIS , 

DEFAULT_QUALITY , 

FIXED_PITCH | FF_MODERN, 

"楷體_GB2312"); 

 

RECT rect; 

GetClientRect(&rect); 

CFont* pOldFont=(CFont*)pDC->SelectObject(&m_fontKaiTi); 

pDC->SetBkMode(TRANSPARENT); 

//定義路徑 

pDC->BeginPath();{ 

pDC->TextOut(0,10,"龍",2); 

pDC->TextOut(200,10,"騰",2); 

pDC->TextOut(400,10,"虎",2); 

pDC->TextOut(600,10,"躍",2); } 

pDC->EndPath(); 

pDC->SelectObject(pOldFont); 

//檢取路徑資料 

int nCount=pDC->GetPath(NULL,NULL,0); 

CPoint* points=new CPoint[nCount]; 

BYTE* bytes=new BYTE[nCount]; 

pDC->GetPath(points,bytes,nCount); 

//對路徑定義點按正弦曲線進行變換 

int i; 

for(i=0;i< nCount;i++)>/p> 

points[i].y=points[i].y+(int)(80*sin(points[i].x 

/300.*3.1415926)+100); 

//重建一個新的路徑 

CPoint ptStart; 

pDC->BeginPath();{ 

for(i=0;i< nCount;i++){>/p> 

switch(bytes[i]){ 

//移動當前點位置 

case PT_MOVETO: 

pDC->MoveTo(points[i]); 

ptStart=points[i]; 

break; 

//畫直線 

case PT_LINETO: 

pDC->LineTo(points[i]); 

break; 

//畫貝茲路徑 

case PT_BEZIERTO: 

pDC->PolyBezierTo(points+i,3); 

i=i+2; 

break; 

//畫貝茲路徑並封閉圖形 

case PT_BEZIERTO|PT_CLOSEFIGURE: 

points[i+2]=ptStart; 

pDC->PolyBezierTo(points+i,3); 

i=i+2; 

break; 

//畫直線並封閉圖形 

case PT_LINETO|PT_CLOSEFIGURE: 

pDC->LineTo(ptStart); 

break; 

pDC->EndPath(); 

//繪製視窗灰色背景 

CBrush* pOldBrush=(CBrush*)(pDC->SelectStockObject(GRAY_BRUSH)); 

pDC->Rectangle(&rect); 

pDC->SelectObject(pOldBrush); 

//設定裁剪路徑 

pDC->SetPolyFillMode(WINDING); 

pDC->SelectClipPath(RGN_COPY); 

//用位元影像填充裁剪地區 

CBitmap bmp; 

CBitmap* pBmpOld; 

bmp.LoadBitmap(IDB_BMP); 

CDC dcMem; 

dcMem.CreateCompatibleDC(pDC); 

pBmpOld=dcMem.SelectObject(&bmp); 

pDC->StretchBlt(0,0,rect.right,rect.bottom, 

&dcMem,0,0,600,100,SRCCOPY); 

dcMem.SelectObject(pBmpOld); 

dcMem.DeleteDC(); 

bmp.DeleteObject(); 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.