1.基本概念
GDI在Windows中定義為Graphics Device Interface,即圖形設(shè)備接口,是Windows API(Application Programming Interface)的一個(gè)重要組成部分。它是Windows圖形顯示程序與實(shí)際物理設(shè)備之間的橋梁,GDI使得用戶無需關(guān)心具體設(shè)備的細(xì)節(jié),而只需在一 個(gè)虛擬的環(huán)境(即邏輯設(shè)備)中進(jìn)行操作。它的橋梁作用體現(xiàn)在:
(1)用戶通過調(diào)用GDI函數(shù)將邏輯空間的操作轉(zhuǎn)化為具體針對設(shè)備驅(qū)動(dòng)程序的調(diào)用。
為實(shí)現(xiàn)圖形設(shè)備無關(guān)性,Windows的繪圖操作在一個(gè)設(shè)備描述表上進(jìn)行。用戶擁有自己的"邏輯坐標(biāo)"系統(tǒng),它獨(dú)立于實(shí)際的物理設(shè)備,與"設(shè)備坐標(biāo)"相對應(yīng)。開發(fā)Windows應(yīng)用程序時(shí),程序員關(guān)心的是邏輯坐標(biāo),我們在邏輯坐標(biāo)系上繪圖,利用GDI將邏輯窗口映射到物理設(shè)備上。
(2)GDI能檢測具體設(shè)備的能力,并依據(jù)具體的設(shè)備以最優(yōu)方式驅(qū)動(dòng)這些設(shè)備,完成真實(shí)的顯示。
GDI函數(shù)大致可分類為:設(shè)備上下文函數(shù)(如GetDC、CreateDC、DeleteDC)、 畫線函數(shù)(如LineTo、Polyline、Arc)、填充畫圖函數(shù)(如Ellipse、FillRect、Pie)、畫圖屬性函數(shù)(如 SetBkColor、SetBkMode、SetTextColor)、文本、字體函數(shù)(如TextOut、GetFontData)、位圖函數(shù)(如 SetPixel、BitBlt、StretchBlt)、坐標(biāo)函數(shù)(如DPtoLP、LPtoDP、ScreenToClient、 ClientToScreen)、映射函數(shù)(如SetMapMode、SetWindowExtEx、SetViewportExtEx)、元文件函數(shù) (如PlayMetaFile、SetWinMetaFileBits)、區(qū)域函數(shù)(如FillRgn、FrameRgn、InvertRgn)、路徑函 數(shù)(如BeginPath、EndPath、StrokeAndFillPath)、裁剪函數(shù)(如SelectClipRgn、 SelectClipPath)等。
GDI雖然使程序員得到了一定程度的解脫,但是其編程方式仍很麻煩。譬如,顯示一張位圖,程序員需要進(jìn)行"裝入位圖―讀取位圖文件頭信息―啟用設(shè)備場景―調(diào)色板變換"等一連串操作。而有了GDI+,這些問題便迎刃而解了。
顧名思義,GDI+是GDI的增強(qiáng)版。它是微軟在Windows 2000以后操作系統(tǒng)中提供的新接口,其通過一套部署為托管代碼的類來展現(xiàn),這套類被稱為GDI+的"托管類接口"。GDI+主要提供了以下三類服務(wù):
(1) 二維矢量圖形:GDI+提供了存儲圖形基元自身信息的類(或結(jié)構(gòu)體)、存儲圖形基元繪制方式信息的類以及實(shí)際進(jìn)行繪制的類;
(2) 圖像處理:大多數(shù)圖片都難以劃定為直線和曲線的集合,無法使用二維矢量圖形方式進(jìn)行處理。因此,GDI+為我們提供了Bitmap、Image等類,它們可用于顯示、操作和保存BMP、JPG、GIF等圖像格式。
(3) 文字顯示:GDI+支持使用各種字體、字號和樣式來顯示文本。
GDI接口是基于函數(shù)的,而GDI+是基于C++類的對象化的應(yīng)用程序編程接口,因此使用起來比GDI要方便。
2.例程簡述
單擊此處下載本文例程源代碼。
本文后續(xù)的講解都基于這樣的一個(gè)例子工程(例程的開發(fā)環(huán)境為Visual C++6.0,操作系統(tǒng)為Windows XP),它是一個(gè)基于對話框的MFC應(yīng)用程序,包括2個(gè)父菜單:
(1) GDI
GDI父菜單下包括一個(gè)子菜單:
ID:IDM_GDI_DRAW_LINE caption:畫線
單擊事件:在窗口繪制正旋曲線
(2) GDI+
DIB位圖父菜單下包括兩個(gè)子菜單:
a. ID:IDM_GDIP_DRAW_LINE caption:畫線
單擊事件:在窗口繪制正旋曲線
b. caption:新增功能,其下又包括下列子菜單:
(ⅰ)ID:IDM_Gradient_Brush caption:漸變畫刷
單擊事件:在窗口演示GDI+的漸變畫刷功能
(ⅱ)ID:IDM_Cardinal_Spline caption:基數(shù)樣條
單擊事件:在窗口演示GDI+的基數(shù)樣條函數(shù)功能
(ⅲ)ID:IDM_Transformation_Matrix caption:變形和矩陣對象
單擊事件:在窗口演示GDI+的變形和矩陣對象功能
(ⅳ)ID:IDM_Scalable_Region caption:可伸縮區(qū)域
單擊事件:在窗口演示GDI+的可伸縮區(qū)域功能
(ⅴ)ID:IDM_IMAGE caption:圖像
單擊事件:在窗口演示GDI+的多種圖像格式支持功能
(ⅵ)ID:IDM_Alpha_Blend caption:Alpha混合
單擊事件:在窗口演示GDI+的Alpha混合功能
(ⅶ)ID:IDM_TEXT caption:文本
單擊事件:在窗口演示GDI+的強(qiáng)大文本輸出能力
后續(xù)篇章將集中在對上述菜單單擊事件消息處理函數(shù)的講解,下面的代碼是整個(gè)對話框類CGdiexampleDlg的消息映射:
BEGIN_MESSAGE_MAP(CGdiexampleDlg, CDialog) |
3.GDI編程
頭文件:#include "math.h "
#define PI 3.1415926
"GDI"菜單下的"畫線"子菜單單擊事件消息處理函數(shù)的代碼如下:
void CGdiexampleDlg::OnGdiDrawLine() |
單擊這個(gè)按鈕,會出現(xiàn)如圖1所示的效果,我們來對此進(jìn)行解讀。
|
前文提到,GDI編程需進(jìn)行設(shè)備坐標(biāo)和邏輯坐標(biāo)的轉(zhuǎn)化。而屏幕上的設(shè)備坐標(biāo)通常會按客戶坐標(biāo)給出,客戶坐標(biāo)依賴于窗口的客戶區(qū)域,其起始位置位于客戶區(qū)域的左上角。為示區(qū)別,圖2給出了設(shè)備坐標(biāo)和用戶邏輯坐標(biāo)的示例。
|
設(shè)備坐標(biāo)與邏輯坐標(biāo)的轉(zhuǎn)換關(guān)系如下:
公式中的<Xvorg, Yvorg>是設(shè)備空間中視口的原點(diǎn),而< Xworg, Yworg >是邏輯空間中窗口的原點(diǎn)。 Xwext/Xvext和Ywext/Yvext分別是窗口與視口水平和垂直范圍的比例。
因此,經(jīng)過程序中的dc.SetWindowOrg (0,0) 和dc.SetViewportOrg (0,rect.bottom/2)語句我們設(shè)置了視口和窗口的原點(diǎn);而經(jīng)過程序中的dc.SetWindowExt (rect.right,rect.bottom) 和dc.SetViewportExt (rect.right,-rect.bottom) 語句我們設(shè)置了視口和窗口的范圍。由于視口和窗口的縱坐標(biāo)方向相反,設(shè)置視口的垂直范圍為負(fù)值。這樣我們得到了一個(gè)邏輯坐標(biāo)原點(diǎn)為客戶區(qū)水平方向最左邊和垂直方向居中的坐標(biāo)系,我們在這個(gè)坐標(biāo)系上直接繪制正旋曲線,不需要再理睬Windows對話框客戶區(qū)坐標(biāo)了。
void CGdiexampleDlg::OnGdiDrawLine()函數(shù)中未指定邏輯設(shè)備和物理設(shè)備的映射模式,則為缺省的MM_TEXT。在這種模式下,一個(gè)邏輯單位對應(yīng)于一個(gè)像素點(diǎn)。映射模式是GDI中的一個(gè)重要概念,其它的映射模式還有MM_LOENGLlSH、MM_HIENGLISH、 MM_LOMETRIC和MM_HIMETRIC等。我們可以通過如下語句指定映射模式為MM_TEXT:
dc.SetMapMode(MM_TEXT); |
值得一提的是,從上述代碼可以看出:在GDI編程中,幾乎所有的操作都圍繞設(shè)備上下文dc展開。的確,這正是GDI編程的特點(diǎn)!設(shè)備上下文是Windows 使用的一種結(jié)構(gòu),所有GDI操作前都需取得特定設(shè)備的上下文,函數(shù)中的CClientDC dc (this) 語句完成這一功能。
歸納可得,利用GDI進(jìn)行圖形、圖像處理的一般操作步驟為:
1. 取得指定窗口的DC;
2. 確定使用的坐標(biāo)系及映射方式;
3. 進(jìn)行圖形、圖像或文字處理;
4. 釋放所使用的DC。
5. GDI+編程
"GDI+"菜單下的"畫線"子菜單單擊事件消息處理函數(shù)的代碼如下:
void CGdiexampleDlg::OnGdipDrawLine() |
由于我們使用的是Visual C++6.0而非VS.Net,我們需要下載微軟的GDIPLUS支持包。在微軟官方網(wǎng)站下載時(shí)需認(rèn)證Windows為正版,我們可從這個(gè)地址下載: http://www.codeguru.com/code/legacy/gdi/GDIPlus.zip。一個(gè)完整的GDI+支持包至少包括如下文 件:
(1)頭文件:gdiplus.h
(2)動(dòng)態(tài)鏈接庫的.lib文件:gdiplus.lib
(3)動(dòng)態(tài)鏈接庫的.dll文件:gdiplus.dll
少了(1)、(2)程序不能編譯,少了(3)程序能以共享DLL的方式編譯但是不能運(yùn)行,運(yùn)行時(shí)找不到.dll文件。
為使得Visual C++6.0支持GDI+,我們需要在使用GDI+對象的文件的開頭添加如下代碼:
#define UNICODE |
在Visual C++中使用GDI+必須先進(jìn)行GDI+的初始化,我們在CWinApp派生類的InitInstance函數(shù)中進(jìn)行此項(xiàng)工作是最好的:
///////////////////////////////////////////////////////////////////////////// |
單擊"GDI+"菜單下的"畫線"子菜單,也會出現(xiàn)如圖1所示的效果。觀察void CGdiexampleDlg::OnGdipDrawLine() 函數(shù),我們發(fā)現(xiàn)用GDI+進(jìn)行圖形、圖像操作的步驟為:
(1)創(chuàng)建 Graphics 對象:Graphics 對象表示GDI+繪圖表面,是用于創(chuàng)建圖形圖像的對象;
(2)使用 Graphics 對象繪制線條和形狀、呈現(xiàn)文本或顯示與操作圖像。
Graphics 對象是GDI+的核心,GDI中設(shè)備上下文dc和Graphics 對象的作用相似,但在GDI中使用的是基于句柄的編程模式,而GDI+中使用的則是基于對象的編程模式。Graphics封裝了GDI+ 繪圖面,而且此類無法被繼承,它的所有成員函數(shù)都不是虛函數(shù)。
下面,我們來逐個(gè)用實(shí)際代碼實(shí)現(xiàn)GDI+的新增功能,這些新增功能包括:漸變的畫刷(Gradient Brushes)、基數(shù)樣條函數(shù)(Cardinal Splines)、持久的路徑對象(Persistent Path Objects)、變形和矩陣對象(Transformations &Matrix Object)、可伸縮區(qū)域(Scalable Regions)、Alpha混合(Alpha Blending)和豐富的圖像格式支持等。
漸變的畫刷
GDI+提供了用于填充圖形、路徑和區(qū)域的線性漸變畫刷和路徑漸變畫刷。
線性漸變畫刷使用漸變顏色來填充圖形。
當(dāng)用路徑漸變畫刷填充圖形時(shí),可指定從圖形的一部分移至另一部分時(shí)畫刷顏色的變化方式。例如,我們可以只指定圖形的中心顏色和邊緣顏色,當(dāng)畫刷從圖形中間向外邊緣移動(dòng)時(shí),畫刷會逐漸從中心顏色變化到邊緣顏色。
void CGdiexampleDlg::OnGradientBrush() |
本程序使用線性漸變畫刷,當(dāng)畫刷從客戶區(qū)左上角移向客戶區(qū)右下角的過程中,顏色逐漸由藍(lán)色轉(zhuǎn)變?yōu)榫G色。
|
基數(shù)樣條函數(shù)
GDI+支持基數(shù)樣條,基數(shù)樣條指的是一連串單獨(dú)的曲線,這些曲線連接起來形成一條較大的曲線。樣條由點(diǎn)(Point結(jié)構(gòu)體)的數(shù)組指定,并通過該數(shù)組中的每一個(gè)點(diǎn)。基數(shù)樣條平滑地穿過數(shù)組中的每一個(gè)點(diǎn)(不出現(xiàn)尖角),因此比用直線連接創(chuàng)建的路徑精確。
void CGdiexampleDlg::OnCardinalSpline() { |
圖4演示了直接連線和經(jīng)過基數(shù)樣條平滑擬合后的線條的對比,后者的曲線(Curve)沒有尖角。這個(gè)工作我們在中學(xué)的數(shù)學(xué)課上把離散的點(diǎn)連接成曲線時(shí)做過。
圖4 GDI+基數(shù)樣條
持久的路徑對象
在GDI中,路徑隸屬于一個(gè)設(shè)備上下文,一旦設(shè)備環(huán)境指針超過它的生存期,路徑也會被刪除。利用GDI+,可以創(chuàng)建并維護(hù)與Graphics對象分開的GraphicsPath 對象,它不依賴于Graphics對象的生存期。
變形和矩陣對象
GDI+提供了Matrix對象,它是一種可以使變形(旋轉(zhuǎn)、平移、縮放等) 簡易靈活的強(qiáng)大工具,Matrix對象需與要被變形的對象聯(lián)合使用。對于GraphicsPath類,我們可以使用其成員函數(shù)Transform接收Matrix參數(shù)用于變形。
void CGdiexampleDlg::OnTransformationMatrix() |
圖5演示了正方形經(jīng)過旋轉(zhuǎn)和拉伸之后的效果:黑色的為原始圖形,紅色的為旋轉(zhuǎn)45度之后的圖形,藍(lán)色的為經(jīng)過拉伸為平行四邊形后的圖形。
|
可伸縮區(qū)域
GDI+通過對區(qū)域(Region)的支持極大地?cái)U(kuò)展了GDI。在GDI 中,區(qū)域存儲在設(shè)備坐標(biāo)中,可應(yīng)用于區(qū)域的唯一變形是平移。但是在GDI +中,區(qū)域存儲在全局坐標(biāo)(世界坐標(biāo))中,可對區(qū)域利用變形矩陣進(jìn)行變形(旋轉(zhuǎn)、平移、縮放等)。
void CGdiexampleDlg::OnScalableRegion() |
上述程序中以藍(lán)色填充一個(gè)三角形區(qū)域,接著將此區(qū)域旋轉(zhuǎn)和拉伸,再次顯示,其效果如圖6。
圖6 GDI+區(qū)域變形
豐富的圖像格式支持
GDI +提供了Image、Bitmap 和Metafile 類,方便用戶進(jìn)行圖像格式的加載、操作和保存。GDI+支持的圖像格式有BMP、GIF、JPEG、EXIF、PNG、TIFF、ICON、WMF、EMF等,幾乎涵蓋了所有的常用圖像格式。
void CGdiexampleDlg::OnImage() |
上述程序?qū)?span lang="EN-US">D盤根目錄下文件名為"1.jpg"的jpg圖像以矩陣和平行四邊形兩種方式顯示,效果如圖7。
|
由此我們可以看出,GDI+在圖像顯示和操作方面的確比GDI簡單許多。回憶我們在《Visual C++中DDB與DIB位圖編程全攻略》一文中所介紹的用GDI顯示位圖的方式,其與GDI+圖像處理的難易程度真是有天壤之別。
Alpha混合
Alpha允許將兩個(gè)物體混合起來顯示,在3D氣氛和場景渲染等方面有廣泛應(yīng)用。它能"霧化"圖像,使得一個(gè)圖像著色在另一個(gè)半透明的圖像上,呈現(xiàn)一種朦朧美。我們知道,一個(gè)像素可用 R,G,B三個(gè)維度來表示,我們可以再加上第4個(gè)即:Alpha維度(channel),表征透明程度。
void CGdiexampleDlg::OnAlphaBlend() |
上述程序中將D盤根目錄下文件名為"1.jpg"的圖像以矩陣和平行四邊形兩種方式顯示,然后將文件名為為"2.jpg"的圖像與之進(jìn)行混合,其效果如圖8。
|
為了能進(jìn)行Alpha混合,我們需要使用ImageAttributes類和ColorMatrix矩陣,ImageAttributes可以進(jìn)行顏色、灰度等調(diào)整從而達(dá)到控制圖像著色方式的目的。ColorMatrix是ImageAttributes類大多數(shù)函數(shù)的參數(shù),它包含了Alpha、 Red、Green、Blue維度的值,以及另一維w,順序?yàn)?span lang="EN-US">RGBaw。
CGdiexampleDlg:: OnAlphaBlend()函數(shù)中ColorMatrix的實(shí)例ClrMatrix中元素(4,4)的值為0.5,表示Alpha度的值為0.5(即半 透明)。在ColorMatrix中,元素(5,5)的值恒定為1.0。我們把ClrMatrix的元素(0,0)修改為0.0,就是使得圖像2.jpg的 紅色維度全不顯示,再看效果,為圖9。列位讀者,我們以前在豪杰超級解霸中調(diào)整R,G,B值從而控制圖像輸出顏色的時(shí)候,調(diào)的就是這個(gè)東東!圖9的效果很 像破舊彩色電視機(jī),紅色電子槍"嗝"了。剛大學(xué)畢業(yè)時(shí),俺那個(gè)叫窮啊,就買了這么個(gè)電視機(jī),還看得很爽,真是往事不堪回首!
圖9 GDI+中的ColorMatrix
強(qiáng)大的文字輸出
GDI+擁有極其強(qiáng)大的文字輸出處理能力,輸出文字的顏色、字體、填充方式都可以直接作為Graphics類DrawString成員函數(shù)的參數(shù)進(jìn)行設(shè)置,其功能遠(yuǎn)勝過GDI設(shè)備上下文的TextOut函數(shù)。
void CGdiexampleDlg::OnText() |
上述代碼的執(zhí)行效果如圖10所示,字體、顏色和填充都很豐富!
圖10 GDI+文本輸出
5.GDI與GDI+的比較
GDI+相對GDI而言主要在編程方式上發(fā)生了巨大的改變。
GDI的核心是設(shè)備上下文,GDI函數(shù)都依賴于設(shè)備上下文句柄,其編程方式是基于句柄的;GDI+無需時(shí)刻依賴于句柄或設(shè)備上下文,用戶只需創(chuàng)建一個(gè)Graphics 對象,就可以用面向?qū)ο蟮姆绞秸{(diào)用其成員函數(shù)進(jìn)行圖形操作,編程方式是基于對象的。
GDI在使用設(shè)備上下文繪制線條之前,必須先調(diào)用SelectObject 以使鋼筆對象和設(shè)備上下文關(guān)聯(lián)。其后,在設(shè)備上下文中繪制的所有線條均使用該鋼筆,直到選擇另一支不同的鋼筆為止。CGdiexampleDlg:: OnGdiDrawLine函數(shù)中的下列語句完成的就是這個(gè)功能:
//創(chuàng)建繪制正旋曲線的pen并將其選入設(shè)備上下文 |
但是,在GDI+中,只需將Pen對象直接作為參數(shù)傳遞給Graphics類的DrawLine等方法即可,而不必使Pen對象與Graphics對象關(guān)聯(lián),例如CGdiexampleDlg::OnGdipDrawLine函數(shù)中的下列語句:
Pen myPen(Color::Red); |
GDI中有當(dāng)前位置的概念,所以在使用GDI繪制線條前應(yīng)該先使用MoveTo移動(dòng)當(dāng)前位置,再使用LineTo畫線,例如:
//繪制正旋曲線 |
而GDI+中則沒有當(dāng)前位置的概念,畫線函數(shù)中可以直接指定起點(diǎn)和終點(diǎn),例如:
graphics.DrawLine(&myPen,0,0,rect.right,0); |
6.結(jié)論
鑒于GDI+良好的易用性和其具有的強(qiáng)大功能,我們建議盡快拋棄GDI編程方式,因?yàn)槲覀儧]有必要將時(shí)間浪費(fèi)在無意義的重復(fù)代碼的設(shè)計(jì)上。GDI+對 GDI的增強(qiáng),某種意義上類似于MFC對Windows API的整理和封裝。作為一種良好的"生產(chǎn)工具",它必將大大地促進(jìn)開發(fā)時(shí)的"生產(chǎn)力"。