一.關於GDI的基本概念程序員
什麼是GDI?算法
Windows繪圖的實質就是利用Windows提供的圖形設備接口GDI(Graphics Device Interface)將圖形繪製在顯示器上。編程
在Windows操做系統中,動態連接庫C:/WINDOWS/system32/gdi32.dll(GDI Client DLL)中定義了GDI函數,實現與設備無關的包括屏幕上輸出像素、在打印機上輸出硬拷貝以及繪製Windows用戶界面功能。在Visual C++6.0中的頭文件C:/Program Files/Microsoft Visual Studio/VC98/Include/wingdi.h和Visual Studio 2005中的頭文件C:/Program Files/Microsoft Visual Studio 8/VC/PlatformSDK/Include/WinGDI.h是訪問gdi32.dll庫文件的鑰匙。下面咱們大體瀏覽一下wingdi.h(included in Windows.h)頭文件:數組
/* Bitmap Header Definition */定義了BITMAP位圖結構數據結構
/* Mapping Modes */定義了DC中的座標映射方式,包括如下經常使用函數:app
SetMapMode、SetViewportExtEx、SetViewportOrgEx、 SetWindowExtEx 、SetWindowOrgEx。函數
/* Stock Logical Objects */系統預約義的堆(STOCK)對象,包括BRUSH、PEN和FONT對象工具
/* Brush Styles */定義了畫刷格式,包括SOLID、HOLLOW、HATCHED等格式字體
/* Hatch Styles */定義了畫刷陰影格式,包括:this
HS_VERTICAL /* ||||| */
HS_FDIAGONAL /* ///// */
HS_BDIAGONAL /* ///// */
HS_CROSS /* +++++ */
HS_DIAGCROSS /* xxxxx */
/* Pen Styles */定義了畫筆格式,包括SOLID、DASH、DOT等格式
什麼是DC?
設備環境DC(Device Context),也稱爲設備描述表或設備上下文。
設備環境保存了繪圖操做中一些共同須要設置的信息,如當前的畫筆、畫刷、字體和位圖等圖形對象及屬性,以及座標映射、顏色和背景等影響圖形輸出的繪圖模式。形象的說,一個設備環境提供了一張畫布和一些繪畫的工具,咱們可使用不一樣格式、顏色的繪畫工具在上面塗鴉。這裏,設備環境中的「設備」是指任何類型的顯示器或打印機等輸出設備,繪圖時,咱們沒必要關心所使用設備的編程的原理和方法,全部的繪製操做必須經過設備環境進行間接的處理,Windows會自動將設備環境所描述的結構映射到相應的物理設備上。
從根本上來講,DC它是Windows內部使用的數據結構,它存儲着向設備輸出時說須要的信息,應用程序利用它定義圖形對象及其屬性,並實現應用程序、設備驅動程序和輸出設備之間繪圖命令的轉換。要想調用GDI函數向某個區域輸出文字或繪製圖形,必須先取得或創建設備環境句柄,應用程序每一次繪圖操做均按照設備環境中的設置的繪圖屬性進行。
設備環境不像其餘Windows結構,在程序中不能直接存取設備環境結構,只能經過系統提供的一系列函數或使用設備環境的句柄HDC來間接地獲取或設置設備環境結構中的各項屬性,這些屬性包括顯示器高度和寬度、支持的顏色數和分辨率等。
MFC中與GDI有關的類
爲了支持GDI繪圖,MFC提供了兩種重要的類:設備環境DC(Device Context)類,用於設置繪圖屬性和繪製圖形;繪圖對象類,封裝了各類GDI繪圖對象,包括畫筆、刷子、字體、位圖、調色板和區域。
在MFC中,CDC是設備環境類的基類,除了通常的窗口顯示外,還用於基於桌面的全屏幕繪製和非屏幕顯示的打印機輸出。CDC類封裝了全部圖形輸出函數,包括矢量、光柵和文本輸出。CDC的派生類包括CClientDC、CPaintDC、WindowDC、CMetaFileDC。
(1)CPaintDC類是一個來自CDC的設備環境類。它在構造期間執行CWnd::BeginPaint,在析構期間執行CWnd::EndPaint,EndPaint()除了釋放設備環境外,還負責從消息隊列中清除WM_PAINT消息。一個CPaintDC對象只在響應一個窗口重繪消息(WM_PAINT)的時候被使用,一般是在你的OnPaint消息處理成員函數中。所以,在處理窗口重畫時,必須使用CPaintDC,不然WM_PAINT消息沒法從消息隊列中清除,將引發不斷的窗口重畫。
CPaintDC類成員:
數據成員
m_ps:包含了用於畫客戶區的PAINTSTRUCT
m_hWnd: CPaintDC對象所附着的HWND
構造函數CPaintDC:構造一個鏈接到指定的CWnd上的CPaintDC對象
(2)CClientDC(窗口客戶區設備環境)類用於管理窗口用戶區對應的顯示上下文,它在構造時調用了Windows函數GetDC,在析構時調用了ReleaseDC。這意味着和CClientDC對象相關的設備上下文是窗口的客戶區。通常在響應非窗口重畫消息(如鍵盤輸入時繪製文本、鼠標繪圖)繪圖時要用到它。
CClientDC類的成員:
構造函數CClientDC:構造一個鏈接到CWnd上的CClientDC對象數據成員
數據成員m_hWnd:所在的有效窗口的HWND
(3)CWindowDC(窗口設備環境)類用於管理與整個窗口對應的顯示上下文,包括它的結構和控件。它在構造的時候調用Windows函數GetWindowDC,在銷燬的時候調用ReleaseDC。這意味着CWindowDC對象能夠訪問CWnd的所有屏幕區域(包括客戶區和非客戶區)。它用於窗口(包括窗口邊框、標題欄、控制按鈕等)的繪製,除非要本身繪製窗口邊框和按鈕(如一些CD播放程序等),不然通常不用它。
CWindowDC類成員:
Construction CWindowDC:構造一個CWindowDC對象
Data Members m_hWnd:與這個CWindowDC相關聯的HWND句柄
(4)CMetaFileDC專門用於圖元文件的繪製。圖元文件記錄一組GDI命令,能夠經過這一組GDI命令重建圖形輸出。使用CMetaFileDC時,全部的圖形輸出命令會自動記錄到一個與CMetaFileDC相關的圖元文件中。
(5)此外咱們還能夠利用Windows內存DC進行繪圖,此時涉及到屏幕DC和內存DC。把所要繪製的一切先在內存DC中進行繪製,以後所有搬到屏幕DC中,從而把全部繁瑣的繪製過程都在內存DC中完成了,咱們在屏幕上看到的是一幅完整的圖畫,因此不可能出現閃爍的狀況。
二.MFC中GDI繪圖
GDI繪圖包括如下步驟:獲取設備環境,設置座標映射,建立繪圖工具,調用DC繪圖函數繪圖。
1、獲取設備環境
(1)在SDK編程中,獲取設備環境的方法有兩種:
<1>經過API函數BeginPaint。應用程序響應WM_PAINT消息進行圖形刷新時主要經過BeginPaint函數獲取設備環境,在消息處理函數返回前調用API函數EndPaint釋放設備環境。
函數原型爲:WINUSERAPI HDC WINAPI BeginPaint( HWND hWnd,LPPAINTSTRUCT lpPaint);
//如下爲Win API示例::BeginPaint(HWND hWnd, LPPAINTSTRUCT lpPaint);
case WM_PAINT://窗口客戶區須要重繪
{
char szText[]="Hello World";
PAINTSTRUCT ps;
HDC hdc=::BeginPaint(hWnd,&ps);
::TextOut(hdc,10,10,szText,strlen(szText));
::EndPaint(hWnd,&ps);
return 0;
}
MFC對BeginPaint進行了封裝:
CWnd::BeginPaint,CDC* BeginPaint( LPPAINTSTRUCT lpPaint ); 等價於::BeginPaint(CWnd::m_hWnd, LPPAINTSTRUCT lpPaint);
<2>經過API函數GetDC。在非WM_PAINT消息處理函數中,須要調用GetDC來獲取設備環境,調用API函數ReleaseDC來釋放設備環境。
函數原型爲:WINUSERAPI HDC WINAPI GetDC( HWND hWnd);
(2)在MFC中,MFC提供了不一樣類型的DC類,每個類都封裝了DC句柄,而且它們的構造函數自動調用獲取DC的API函數,析構函數自動調用釋放DC的API函數。所以,在程序中經過聲明一個MFC設備環境類的對象就自動獲取了一個DC,而當該對象被銷燬時就自動釋放了獲取的DC。MFC AppWizard應用程序嚮導建立的OnDraw()函數自動支持所獲取的DC。
<1> CPaintDC構造函數:CPaintDC(CWnd* pWnd); 構造一個CPaintDC對象(pWnd指向一個CPaintDC對象所屬的CWnd對象),準備用於繪畫的應用程序窗口。
// BeginPaint
void CView::OnPaint()
{
CPaintDC dc(this); // device context for painting
// TODO: Add your message handler code here
OnPrepareDC(&dc);
OnDraw(&dc)
}
當咱們改變了窗口尺寸、移動窗口或恢復了先前被覆蓋的部分,應用程序窗口就會收到一個Windows系統發送來的WM_PAINT消息,而後調用基類Cview的OnPaint函數或咱們本身添加的消息處理函數OnPaint。咱們能夠在OnPaint函數中重繪窗口中從新可見的部分(),但簡單的處理辦法是重繪整個窗口。上面的代碼中,因爲基類Cview的OnPaint函數調用了OnDraw函數,所以應用程序常常在OnDraw函數中繪製視圖。
<2>CClientDC構造函數:CClientDC(CWnd* pWnd); 構造一個CClientDC對象,它將存取pWnd指向的CWnd的客戶區。
// 鼠標左鍵事件處理
void CExView::OnLButtonDown(UINT nFlags, CPoint point)
{
// TODO: 在此添加消息處理程序代碼和/或調用默認值
CClientDC dc(this);//定義客戶區設備環境
dc.LineTo(point);//繪製線段
}
CClientDC表明了窗口客戶區對應的顯示上下文,它在構造時調用了API函數GetDC,並將當前窗口的句柄m_hWnd做爲函數參數;在析構時調用了API函數ReleaseDC。當在客戶去繪圖時,須要利用CClientDC類定義一個客戶區設備環境句柄。
有時候須要訪問與一個客戶設備環境相關聯的窗口對象,能夠經過Attach函數把這個CClientDC的成員m_hWnd句柄傳遞給一個窗口對象,該窗口就是與客戶區設備環境相關聯的窗口。
CWnd::Attach,BOOL Attach( HWND hWndNew );
說明:將一個Windows窗口與CWnd對象相鏈接。
返回值:若是成功,則返回非零值;不然返回0。
參數:hWndNew指定了Windows窗口的句柄
<3>CWindowDC構造函數:CWindowDC( CWnd* pWnd );構造一個CWindowDC對象,它能夠訪問pWnd指向的CWnd對象的整個屏幕區域(包括客戶區和非客戶區)。好比咱們在作屏幕保護程序時,通常以整個屏幕區域做爲繪製區域。
2、設置座標映射
(1)Windows座標系統
Windows座標系分爲邏輯座標系和設備座標系兩種,GDI支持這兩種座標系。通常而言,GDI的文本和圖形輸出函數使用邏輯座標,而在客戶區移動或按下鼠標的鼠標位置是採用設備座標。
<1>邏輯座標系是面向DC的座標系,這種座標不考慮具體的設備類型,在繪圖時,Windows會根據當前設置的映射模式將邏輯座標轉換爲設備座標。
<2>設備座標系是面向物理設備的座標系,這種座標以像素或設備所能表示的最小長度單位爲單位,X軸方向向右,Y軸方向向下。設備座標系的原點位置(0, 0)不限定在設備顯示區域的左上角。
設備座標系分爲屏幕座標系、窗口座標系和客戶區座標系三種相互獨立的座標系。
屏幕座標系以屏幕左上角爲原點,一些與整個屏幕有關的函數均採用屏幕座標,如GetCursorPos()、SetCursorPos()、CreateWindow()、MoveWindow()。彈出式菜單使用的也是屏幕座標。
窗口座標系以窗口左上角爲座標原點,它包括窗口標題欄、菜單欄和工具欄等範圍。
客戶區座標系以窗口客戶區左上角爲原點,主要用於客戶區的繪圖輸出和窗口消息的處理。鼠標消息的座標參數使用客戶區座標,CDC類繪圖成員函數使用與客戶區座標對應的邏輯座標。
(2)座標之間的相互轉換
編程時,有時須要根據當前的具體狀況進行三種設備座標之間或與邏輯座標的相互轉換。
MFC提供了兩個函數CDC::DPtoLP()和CDC::LPtoDP()用於設備座標與邏輯座標之間的相互轉換。
MFC提供了兩個函數CWnd::ScreenToClient()和CWnd::ClientToScreen()用於屏幕座標與客戶區座標的相互轉換。
(3)映射模式
映射模式肯定了在繪製圖形時所依據的座標系,它定義了邏輯單位的實際大小、座標增加方向,全部映射模式的座標原點均在設備輸出區域(如客戶區或打印區)的左上角。此外,對於某些映射模式,用戶還能夠自定義窗口的長度和寬度,設置視圖區的物理範圍。
Windows定義了8種映射模式,見下表。
映射模式使得程序員可沒必要考慮輸出設備的具體設備座標系,而在一個統一的邏輯座標系中進行圖形的繪製。
映射方法(Mapping Mode) |
邏輯單位 |
座標軸方向 |
MM_TEXT(默認方式) |
1 pixel |
X軸正方向朝右,Y軸正方向朝下 |
MM_LOMETRIC |
0.1 mm |
X軸正方向朝右,Y軸正方向朝上 |
MM_HIMETRIC |
0.01 mm |
X軸正方向朝右,Y軸正方向朝上 |
MM_LOENGLISH |
0.01 inch |
X軸正方向朝右,Y軸正方向朝上 |
MM_HIENGLISH |
0.001 inch |
X軸正方向朝右,Y軸正方向朝上 |
MM_TWIPS |
1/1440 inch |
X軸正方向朝右,Y軸正方向朝上 |
MM_ISOTROPIC |
自定義(X=Y) |
自定義 |
MM_ANISOTROPIC |
自定義(X!=Y) |
自定義 |
當繪製的圖形須要隨着窗口的大小改變而自動改變的時候,通常選擇MM_ISOTROPIC和MM_ANISOTROPIC映射方式。它們的惟一區別就是前者的X軸和Y軸的邏輯單位的大小是相同的,單詞「isotropic」就是各個方向相等的意思,此映射方式適合繪製圓或正方形。而實際應用中,經常給X軸和Y軸取不一樣的比例,這時候選擇MM_ANISOTROPIC映射方式。單詞「anisotropic」就是各個方向相異的意思。
(4)自定義映射模式
「窗口」和「視口」的概念:
窗口(Window):對應邏輯座標系上程序員設定的區域
視口(Viewport):對應實際輸出設備上程序員設定的區域
窗口原點是指邏輯窗口座標系的原點在視口(設備)座標系中的位置,視口原點是指設備實際輸出區域的原點。
除了映射模式,窗口和視口也是決定一個點的邏輯座標如何轉換爲設備座標的一個因素。一個點的邏輯座標按照以下式子轉換爲設備座標:
設備(視口)座標 = 邏輯座標 –窗口原點座標 + 視口原點座標
//定義座標映射方式
WINGDIAPI int WINAPI SetMapMode(HDC, int);
此API函數在MFC中封裝爲CDC::virtual int SetMapMode(int nMapMode);
//定義邏輯窗口區域,單位爲邏輯單位(Logical)
WINGDIAPI BOOL WINAPI SetWindowExtEx (HDC, int, int, LPSIZE);
此API函數在MFC中封裝爲CDC::virtual CSize SetWindowExt(int cx, int cy);
//設置邏輯窗口的原點座標,缺省原點爲(0,0)。
WINGDIAPI BOOL WINAPI SetWindowOrgEx(HDC, int, int, LPPOINT);
此API函數在MFC中封裝爲CDC::CPoint SetWindowOrg(int x, int y);
注意:SetWindowOrg(Ex) 只有在映射模式爲MM_ANISOTROPIC或MM_ISOTROPIC時纔有意義。
//定義視口的座標軸方向及區域、定義域和值域,單位爲像素(Pixel)
WINGDIAPI BOOL WINAPI SetViewportExtEx(HDC, int, int, LPSIZE);
此API函數在MFC中封裝爲CDC::virtual CSize SetViewportExt(int cx, int cy);
注意:SetViewportExt(Ex) 只有在映射模式爲MM_ANISOTROPIC或MM_ISOTROPIC時纔有意義。
//設置視口的原點座標,缺省原點爲(0,0)。
WINGDIAPI BOOL WINAPI SetViewportOrgEx(HDC, int, int, LPPOINT);
此API函數在MFC中封裝爲CDC:: virtual CPoint SetViewportOrg(int x, int y);
參考:《GDI中的座標映射問題》http://dev.csdn.NET/article/12/12013.shtm
3、建立繪圖工具並選入DC
有了畫布,要繪圖咱們必須有畫筆畫刷。在Windows中有HPEN、HBRUSH等GDI對象,MFC對GDI對象進行了很好的封裝,提供了封裝GDI對象的類,如CPen、CBrush、CFont、CBitmap和CPalette等,這些類都是GDI對象類CGdiObject的派生類。
通常先建立畫筆(刷),而後調用CDC::SelectObject函數將畫筆(刷)選入設備環境最爲當前繪圖工具,繪圖完畢恢復設備環境之前的畫筆(刷)對象,最後調用CGdiObject::DeleteObject函數刪除畫筆(刷)對象。
這裏須要注意的是,CGdiObject::DeleteObject函數完全刪除底層GDI對象(CPen和CBrush類的基類)。在MFC中,當對象銷燬時會調用對象的析構函數自動刪除對象,通常沒必要調用CGdiObject::DeleteObject刪除GDI對象,由於若是設備環境還在使用一個GDI對象時,將引發應用程序崩潰或出現難以理解的運行錯誤。
(1)建立畫筆
BOOL CPen::CreatePen( int nPenStyle, int nWidth, COLORREF cfColor );
nPenStyle 指定畫筆的風格。其可能取值的列表,請參見CPen構造函數中的nPenStyle參數。
nWidth 指定畫筆的寬度。若是這個值爲0,則不論是什麼映射模式,以設備單位表示的寬度老是一個像素。
crColor 包含畫筆的一個RGB顏色,爲COLORREF結構。
此外,可經過CDC::SelectStockObject函數來調用系統預約義的庫存筆對應的CGdiObject對象。
pOldPen = (Cpen*)pDC->SelectStockObject(BLACK_PEN);
(2)建立畫刷
BOOL CBrush::CreateSolidBrush ( COLORREF crColor );
BOOL CBrush::CreateHatchBrush( int nIndex, COLORREF crColor );
參數: nIndex 指定畫刷的陰影線風格。可取的值以下:
HS_HORIZONTAL /* ==== */
HS_VERTICAL /* ||||| */
HS_FDIAGONAL /* ///// */
HS_BDIAGONAL /* ///// */
HS_CROSS /* +++++ */
HS_DIAGCROSS /* xxxxx */
返回值:調用成功時返回非零值,不然爲0。
此外,可經過CDC::SelectStockObject函數來調用系統預約義的庫存畫刷對應的CGdiObject對象。
pOldBrush = (CBrush*)pDC->SelectStockObject(BLACK_BRUSH);
(3)將畫筆(刷)選入設備環境。
如下爲MFC中默認映射方式下的GDI繪圖的模塊:
//先獲取設備環境pDC
CPen *pOldPen,newPen;
CBrush *pOldBrush,newBrush1,newBrush2;
//建立寬度爲pixel的白色實線畫筆
newPen.CreatePen(PS_SOLID,1,RGB(0,0,0));
//建立紅色實線畫刷
newBrush1.CreateSolidBrush(RGB(255,0,0));
//建立紅色實線度的向下(從右到左)影線的陰影畫刷
newBrush2.CreateHatchBrush(HS_BDIAGONAL,RGB(255,0,0));
//將newPen畫筆和newBrush1畫刷對象選入設備環境
pOldPen = pDC->SelectObject(&newPen);
pOldBrush = pDC->SelectObject(&newBrush1);
//調用DC繪圖函數繪圖
//……
//繪圖完畢,恢復原來畫筆、畫刷
pDC->SelectObject(pOldPen);
pDC->SelectObject(pOldBrush);
//刪除建立的畫筆、畫刷
// newPen.DeleteObject();
// newBrush1.DeleteObject();
// newBrush2.DeleteObject();
(4)當繪製文本Text時,通常能夠經過調用CDC::SetBkColor函數來設置背景顏色,調用CDC::SetTextColor函數來設置文字顏色,調用CDC::SetTextAlign函數設置文本對齊標記。
4、調用DC繪圖函數繪圖
GDI爲提供了繪製基本圖形的成員函數,在MFC中這些函數封裝在CDC類中。
注意:繪圖函數使用的座標都是邏輯座標。
經常使用CDC繪圖函數 |
|
函數 |
功能 |
線輸出函數 |
|
GetCurrentPosition |
獲取筆的當前位置(以邏輯座標表示) |
MoveTo |
移動當前位置 |
LineTo |
從當前位置到一點畫直線,但不包括那個點 |
Arc |
畫一段橢圓弧 |
ArcTo |
畫一段橢圓弧。除了更新當前位置之外,這個函數與Arc相似 |
PolyPolyline |
畫多組相連線段。這個函數不使用也不更新當前位置 |
PolylineTo |
畫一條或多條直線,並把當前位置移到最後一條直線的終點 |
PolyBezier |
畫一條或多條Bezier樣條。不使用也不更新當前位置 |
PolyBezierTo |
畫一條或多條Bezier樣條,並把當前位置移到最後一條Bezier樣條的終點 |
|
|
橢圓和多邊形函數 |
|
Chord |
繪製橢圓弧(橢圓和一條線段相交圍成的閉合圖形) |
DrawFocusRect |
繪製用於表示焦點的風格的矩形 |
Ellipse |
繪製橢圓 |
Pie |
繪製餅形圖 |
Polygon |
繪製多邊形,包含由線段鏈接的一個或多個點(頂點) |
PolyPolygon |
建立使用當前多邊形填充模式的兩個或多個多邊形,多邊形能夠相互分開或疊加 |
Polyline |
繪製多邊形,包含鏈接指定點的一組線段 |
Rectangle |
使用當前筆繪製矩形,用當前畫刷填充 |
RoundRect |
使用當前筆繪製圓角矩形,用當前畫刷填充 |
位圖函數 |
|
BitBlt |
從指定設備上下文拷貝位圖 |
StretchBlt |
把位圖由源矩形和設備移動到目標矩形,必要時拉伸或壓縮位圖以適合目標矩形的維數 |
GetPixel |
獲取指定點像素的RGB顏色值 |
SetPixel |
設置指定點像素爲最接近指定色的近似值 |
文本函數 |
|
TextOut |
用當前選取字體在指定位置寫字符串 |
ExtTextOut |
用當前選取字體在矩形區域寫字符串 |
TabbedTextOut |
在指定位置寫字符串,製表符擴展爲製表符中止位置數組中指定值 |
DrawText |
在指定矩形內繪製格式化文本 |
------------------------------詳情參考MSDN、MFC類庫詳解--------------------------- |