1. 程式人生 > >WIN32 API:繪圖函式

WIN32 API:繪圖函式

二、建立GDI繪圖物件

今天我們要討論的是Win32 API中最有有趣的部分───用繪圖函式完成圖形輸出。可以說,所有前面講的內容都是本課程的前期準備。當時,我們在一些試例程式中偶爾用了一些繪圖函式,可能當時您有些不太好理解。沒有關係,只要您已經來到了這裡,並且對前期的各內容有一點點的蒙朧記憶,那已經是足夠了。因為,前期的各內容,必須與本課堂中內容相結合才能形成一個完整的理解。看完了本期教程以後,再回頭看過去的幾個教程,對您來說問題會變得更加清晰,透明。

我們已經講過,Windows中的繪圖是在裝置場景中進行的。裝置場景有兩種,一是關聯裝置場景,之所以說它是關聯裝置場景,只是因為裝置場景是同某一視窗關聯在一起的。關聯的意識就是,只要你在這個裝置場景中繪圖,那麼繪圖內容自動反映(顯現)在窗體中,使得您能夠看到。那麼另一種裝置場景是不關聯的裝置場景啦。當然,正如你已經猜到的那樣,這種裝置場景,就算你在其中繪了圖,它也不會顯示在視窗中的。
那麼,這兩種裝置場景在其內部結構上有什麼區別呢?其實沒有區別,大概同已婚和未婚的關係一樣,一個有老婆,一個還沒有老婆,只不過就是這樣。

既然,與視窗不關聯的裝置場景中的繪圖內容是看不到的,那麼它又有什麼用途呢?嗨,別小看它,很多圖象是需要在這種裝置場景中加工的,目的是為了讓您看不到圖形的加工過程。等到圖形加工完了以後,可以一次性地把完成的圖象傳送到其他已經與視窗關聯的裝置場景,讓它顯示出來。當然效果是更好的。
那麼,在真正的繪圖以前,我們必須學會如何進行選筆,配色。是不是?繪圖總得去選擇一個筆或者畫刷吧,而且得考慮用什麼顏色來繪圖。
畫筆和畫刷是最常用的GDI繪圖物件。其中,畫筆是定義如何畫線的GDI物件。WIN32的標準畫筆具有三個屬性,分別是顏色、寬度和線型。畫筆顏色用來定義線條的RGB顏色,實際使用的顏色與裝置有關。而GDI能自動選擇與裝置最接近的顏色。寬度屬性的單位是邏輯單位。標準畫筆可畫出的線型有∶實線、不可見線和幾種虛線、點線。注意,只有實線與不可見線的寬度能大於1。WIN32還提供了擴充套件畫筆,準備以後接觸的時候再講。

刷子的用途是填充區域。它定義一塊小區域(一般是8×8畫素),然後和WINDOWS95的桌面背景圖案平埔操作一樣,把這個小塊中定義的圖案複製到整個填充區域中。
刷子主要有三種類型。其一是,實體刷。塊圖為單一色的固定顏色,可用RGB來確定顏色。其二是,圖樣刷。這時塊圖就是一個使用者指定的小點陣圖,當然不能大於8×8畫素點。最後一種是陰影刷。說是陰影刷,實際上是由一些各種型別的交叉的網格線來構成。這些究竟採用哪種網格線,就得由一些BS_為字首的引數來指定。
那麼如何去獲得這些畫筆或花刷呢?可以採用以下列出的API函式。(有關其函式說明均可在APIBROW中找到)

函 數 說 明
CreateBrushIndirect 在一個LOGBRUSH資料結構的基礎上建立一個刷子
CreateDIBPatternBrush 用一幅與裝置無關的點陣圖建立一個刷子,以便指定刷子樣式(圖案)
CreateDIBPatternBrushPt 用一幅與裝置無關的點陣圖建立一個刷子,以便指定刷子樣式(圖案)
CreateHatchBrush 建立帶有陰影圖案的一個刷子(陰影圖案見註解)
CreatePatternBrush 用指定了刷子圖案的一幅點陣圖建立一個刷子
CreatePen 用指定的樣式、寬度和顏色建立一個畫筆
CreatePenIndirect 根據指定的LOGPEN結構建立一個畫筆
CreateSolidBrush 用純色建立一個刷子
ExtCreatePen 建立一個擴充套件畫筆(裝飾或幾何)
GetStockObject 取得一個固有物件(Stock)。這是可由任何應用程式使用的windows標準物件之一


例1∶建立一個紅色實線畫筆,畫筆寬度為3個畫素點
Dim NewPen As Long
Private Const PS_SOLID = 0
NewPen&=CreatePen (PS_SOLID,3,RGB(255,0,0))
注∶其中PS_SOLID常數代表實線
例2∶建立陰影刷子
LOGBRUSH結構的定義如下∶
Private Type LOGBRUSH
lbStyle As Long
lbColor As Long
lbHatch As Long
End Type
以下程式碼饜了建立一個 刷子樣式為 陰影(BS_HATCHED) ,陰影型別為十字交叉(HS_CROSS)的紅色畫筆。
Dim BrushInfo As LOGBRUSH
BrushInfo.lbStyle = BS_HATCHED

BrushInfo.lbColor = RGB(255,0,0)
BrushInfo.lbHatch = HS_CROSS
NewBrush = CreateBrushIndirect(BrushInfo)

例3∶用純色建立刷子(這個例子中是紅色)
NewBrush =CreateSolidBrush(vbRed)


同樣,用其他幾個函式,按照其用法可以建立相應的GDI繪圖物件。現在,您大概瞭解有以上這些函式和,理解給出的幾個例子就可以了。稍後,我們結合實際例子,更深入地探討這些函式的用法。



三、拿起和放下畫筆(GDI物件)

現在我感覺好象向一群繪畫系的學生講課,儘管自己不怎麼會繪畫。首先是練基本功,怎樣拿起畫筆和放下畫筆,這可能是繪畫專業學生首先要學習的吧?當然,更廣義地講應當是怎樣選擇和刪除GDI繪圖物件。
在通過前述方法來建立一個GDI物件控制代碼(上例中的NewBrush,NewPen等)以後,為了使用它們,我們必須用SelectObjecth API函式把它們選入相應的裝置場景。一個裝置場景在某一時刻、在每一種型別中只能擁有一個物件,如一個畫筆和一個刷子一個位圖等。
SelectObjecth函式的用法非常簡單,需要記住的是,此被呼叫後,如果成功將返回舊的物件控制代碼。你需要把它儲存起來。當然,這一過程只需要把返回值附值於某一Long型變數就可以了。如∶

OldPen&=SelectObject(Picture1.hDC , NewPen&)

接下來該做什麼呢?對對,這位同學說的對∶繪圖。該怎麼繪圖呢?不,不,不要著急,這個問題,我們留在下一節中討論,現在你只需記住,這裡可以畫些圓呀、矩形呀、添充多邊型呀的操作。那麼,繪圖操作結束以後該怎麼辦呢?答案是∶應當把舊的繪圖物件回設到裝置場景中去。如下∶

SelectObject Picture1.hDC,OldPen&

這樣,裝置場景將恢復到我們為其選入繪圖物件以前的狀態。因為,我們不能斷定其他繪圖函式會使用什麼樣的繪圖物件。因此把原來的繪圖物件放回去乃是一個上策。但,如果你接著要為裝置場景選擇另一個物件,這個步驟可以留在後面進行。那麼在這次的選入過程中就沒有必要儲存舊的物件控制代碼了,這是因為SelectObject函式返回的舊物件的控制代碼就是剛才我們為其選擇的控制代碼。
繪圖也完了,裝置場景也恢復了原始狀況,那麼就操作告一段落了嗎?不,還有一點。您最好把您自己建立的GDI物件刪除掉,釋放掉剛才使用過的資源。操作如下∶

DeleteObject NewPen
又如∶
DeleteObject NewBrush
其實,您不刪除這些物件資源,應用程式退出時會自動釋放的。這是因為在Win32中,資源為每個應用程式私有的。由於這種原因,應用程式之間也不能共享一個GDI物件。但是,刪除你所建立的GDI物件仍是一個好的程式設計習慣。既然不用,留著它做什麼呢,何必佔用資源空間呢?
外,您千萬千萬切記,切記,千萬∶不要刪除已經選入裝置場景的系統GDI物件。
還有一點,GetStockObject函式返回的物件是系統物件,請不要用DeleteObject函式刪除它,否則會出現非常非常可怕的事情───你的硬碟將被永遠用不了啦。@@~ 呵呵,嚇唬你一把,其實沒那麼嚴重。不過,我想您大概不是明知整了壞,偏向壞裡整的人吧?
如果是的話,隨便整好了。

OK,以下展示了使用GDI物件的API函式。

函 數 說 明
DeleteObject 用這個函式刪除GDI物件,比如畫筆、刷子、字型、點陣圖、區域以及調色盤等等。物件使用的所有系統資源都會被釋放
EnumObjects 列舉可隨同指定裝置場景使用的畫筆和刷子
GetCurrentObject 用於獲得指定型別的當前選定物件
GetObjectAPI 取得對指定物件進行說明的一個結構。windows手冊建議用GetObject 這個名字來引用該函式。GetObjectAPI在vb中用於避免與GetObject關鍵字混淆
GetObjectType 判斷由指定控制代碼引用的GDI物件的型別
SelectObject 每個裝置場景都可能有選入其中的圖形物件。其中包括點陣圖、刷子、字型、畫筆以及區域等等。一次選入裝置場景的只能有一個物件。選定的物件會在裝置場景的繪圖操作中使用。例如,當前選定的畫筆決定了在裝置場景中描繪的線段顏色及樣式
除了DeleteObject和SelectObject以外的其他函式用於從系統或指定裝置場景中獲取有關GDI物件的資訊,一般不十分常用。
這樣,假如我們要用畫筆和刷子來做一些繪圖朝著的話,編寫程式碼的大概步驟是這樣的。
Dim NewPen As Long
Dim NewBrush As Long
Dim OldPen As Long
Dim OldBrush As Long

NewPen& = CreatePen(PS_SOLID, 3, RGB(255, 0, 0)) * 建立畫筆
NewBrush& = CreateSolidBrush(vbRed) * 建立畫刷
OldPen& = SelectObject(Picture1.hDC,NewPen&)
'新增繪圖操作程式碼

OldBrush& = SelectObject(Picture1.hDC,NewBrush)
'新增填充操作程式碼
SelectObject Picture1.hDC,OldPen&
SelectObject Picture1.hDC,OldBrush&
DeleteObject NewPen&
DeleteObject NewBrush&
注意一點,要用API繪圖函式,並非一定要建立畫筆和刷子。完全可以使用現有的GDI物件,直接呼叫函式來繪圖。記住,裝置場景中總有個預設的畫筆和刷子,問題是它符不符合您的要求了。但我覺得,在繪圖之前選擇畫筆是個好習慣。有些VB功能也可以結合使用,比如你想用紅色畫筆,你可以設定Forcolor屬性為紅色、想加寬畫筆的寬度,可以設定DrawWidth屬性等。



四、繪圖屬性與繪圖函式

到目前為止,我們已經學會了繪圖所需要的一切準備工作。上一節中最後給出的程式碼就說明這一點。程式碼中,現在只缺少具體的繪圖程式碼。本節就討論關於如何繪圖的問題。
在接觸繪圖函式之前,首先需要了解繪圖屬性。裝置場景定義了一系列繪圖屬性。這些繪圖屬性定義了刷子和畫筆與視窗或裝置表面當前內容相互作用的方法。比如,當前畫筆的位置、當前背景顏色、圓弧和矩形的繪製方向、光柵操作模式等等。雖然後面給出了很多屬性控制函式,但用VB自身的函式和方法屬性,更容易實現。比如,設定背景模式,只要設定控制元件的BackColor屬性就可以很輕鬆、帶愉快地完成。但是,如果是要在一個不與視窗關聯的自建裝置場景中繪圖的話,想必依靠這些函式是不可逃避的。

線光柵操作∶我們已經知道,光柵操作是一種位操作。通常你想用畫筆進行繪圖時,都假定畫筆色彩只是簡單的繪製到顯示器或裝置上。實際上,WINDOWS支援16種不同的線繪圖模式,它們定義了一條線如何與顯示器上已有的資訊組合。這些模式就叫做線光柵操作(有時叫ROP2模式)。並且它們被作為繪圖模式引入到了VisualBasic。ROP2光柵操作相當於設定VB的DrawMode屬性。
背景模式∶陰影刷子、虛線畫筆和文字都有一個背景。對於陰影刷,它是指陰影線之間的區域,對於虛線畫筆,則指點和虛線之間的區域。而對於文字,它是指每個字元單元的背景。背景模式決定了WINDOWS如何處理這些背景區。它可以是不透明的,也可以是透明的。若是不透明的,則背景區設定為背景色;否則如果是透明的,則背景區域保持原狀。

當前位置∶在VB中,要畫一條直線其實非常簡單,採用Line方法就可以,而且能夠在一個語句中表達完成。如Line (5,5)-(10,10)
但在API中並不這樣簡單了(但也不是太麻煩)。要畫直線,需要首先設定直線的起點。一般用MoveToEx函式來完成。然後在下一行程式碼中繪製直線,如LineTo 10,10。MoveToEx函式是經常使用的函式之一,用來確定繪圖前的起始位置。。

繪圖屬性控制函式

函 數 說 明
GetArcDirection 畫圓弧的時候,判斷當前採用的繪圖方向
GetBkColor 取得指定裝置場景當前的背景顏色
GetBkMode 針對指定的裝置場景,取得當前的背景填充模式
GetCurrentPositionEx 在指定的裝置場景中取得當前的畫筆位置
GetMiterLimit 取得裝置場景的斜率限制(Miter)設定——斜率限制是指斜角長度與線寬間的比率
GetNearestColor 根據裝置的顯示能力,取得與指定顏色最接近的一種純色
GetPolyFillMode 針對指定的裝置場景,獲得多邊形填充模式。
GetROP2 針對指定的裝置場景,取得當前的繪圖模式。這樣可定義繪圖操作如何與正在顯示的圖象合併起來
MoveToEx 為指定的裝置場景指定一個新的當前畫筆位置。
SetArcDirection 設定圓弧的描繪方向
SetBkColor 為指定的裝置場景設定背景顏色。背景顏色用於填充陰影刷子、虛線畫筆以及字元(如背景模式為OPAQUE)中的空隙。也在點陣圖顏色轉換期間使用。
SetBkMode 指定陰影刷子、虛線畫筆以及字元中的空隙的填充方式
SetMiterLimit 設定裝置場景當前的斜率限制
SetPolyFillMode 設定多邊形的填充模式。
SetROP2 設定指定裝置場景的繪圖模式。與vb的DrawMode屬性完全一致。

同VisualBasic相比較,API提供了功能更強大的繪圖函式。大部分繪圖函式的用法都非常簡單明瞭,只要按其說明使用就可以,覺得沒有必要我多加說明。

WindoesAPI繪圖函式

函 數 說 明
AngleArc 用一個連線弧畫一條線,參考註解
Arc 畫一個圓弧
ArcTo 畫一個圓弧,並更新當前位置
CancelDC 取消另一個執行緒裡的長時間繪圖操作
Chord 畫一條絃線(橢圓的平分線)
Ellipse 描繪一個橢圓,由指定的矩形圍繞。橢圓用當前選擇的畫筆描繪,並用當前選擇的刷子填充
FillRect 用指定的刷子填充一個矩形
FloodFill 用當前選定的刷子在指定的裝置場景中填充一個區域。區域是由顏色crColor定義的
FrameRect 用指定的刷子圍繞一個矩形畫一個邊框(組成一個幀),邊框的寬度是一個邏輯單位
GetPixel 在指定的裝置場景中取得一個指定畫素的當前RGB值
InvertRect 通過反轉每個畫素的值,從而反轉一個裝置場景中指定的矩形
LineDDA 列舉指定線段中的所有點
Pie 畫一個扇形
PolyBezier 繪一條或多條貝塞爾(Bezier)曲線。
PolyBezierTo 繪一條或多條貝塞爾(Bezier)曲線,並將當前畫筆位置設為前一條曲線的終點
PolyDraw 描繪一條複雜的曲線,由線段及貝塞爾曲線組成
Polygon 描繪一個多邊形,由兩點或三點的任意系列構成。windows會將最後一個點與第一個點連線起來,從而封閉多邊形。多邊形的邊框用當前選定的畫筆描繪,多邊形用當前選定的刷子填充
Polyline 用當前畫筆描繪一系列線段。使用PolylineTo函式時,當前位置會設為最後一條線段的終點。它不會由Polyline函式改動
PolylineTo 同上,並設定當前畫筆位置用當前選定畫筆描繪兩個或多個多邊形。根據由SetPolyFillMode函式指定的多邊形填充模式,用當前選定的刷子填充它們。每個多邊形都必須是封閉的
PolyPolygon 用當前選定畫筆描繪兩個或多個多邊形。根據由SetPolyFillMode函式指定的多邊形填充模式,用當前選定的刷子填充它們。每個多邊形都必須是封閉的
PolyPolyline 用當前選定畫筆描繪兩個或多個多邊形
Rectangle 用當前選定的畫筆描繪矩形,並用當前選定的刷子進行填充
RoundRect 用當前選定的畫筆畫一個圓角矩形,並用當前選定的刷子在其中填充。X3和Y3定義了用於生成圓角的橢圓
SetPixel 在指定的裝置場景中設定一個畫素的RGB值,並返回該點的顏色
SetPixelV 在指定的裝置場景中設定一個畫素的RGB值

我把上表中大部分的函式的用法例舉到了本教程附帶的program1.vbp中。另外,
Bezier曲線的用法比較有趣。如果你用過3D Studio三維動畫製作軟體就知道,其中的很多繪圖工作,尤其是二維平面繪圖,就是採用Bezier曲線技術。本教程附帶的program3.vbp
程式簡單展示了這種技術的應用。《前線》網站原始碼解析中的第24號(濾波器演示程式)、第26號(如何用指定顏色填充不規則封閉線框區域)等程式,也是這裡部分函式的好例程,可以下載看看。

Windows還提供了一些更特殊的繪圖函式,你可以在Windows的內部用它們來繪製控制元件外框、標題欄、3D控制元件和桌面等系統物件。

Win32 API其他繪圖函式

函 數 說 明
DrawEdge 用指定的樣式(包括3D效果)描繪一個矩形的邊框
DrawEscape 換碼(Escape)函式將資料直接發至顯示裝置驅動程式(在vb裡使用:能夠使用。但由於Escape對裝置有較強的依賴性,所以除非萬不得以,儘量不要用它)
DrawFocusRect 畫一個焦點矩形。這個矩形是在標誌焦點的樣式中通過異或運算完成的(焦點通常用一個點線表示)。如用同樣的引數再次呼叫這個函式,就表示刪除焦點矩形
DrawFrameControl 這個函式用於描繪一個標準控制元件。例如,可描繪一個按鈕或滾動條的幀
DrawState 這個函式可為一幅圖象或繪圖操作應用各式各樣的效果
GdiFlush 在繪圖操作前注意佇列。 執行任何未決的繪圖操作。註釋
GdiGetBatchLimit 判斷有多少個GDI繪圖命令位於佇列中
GdiSetBatchLimit 指定有多少個GDI繪圖命令能夠進入佇列
PaintDesktop 在指定的裝置場景中描繪桌面牆紙圖案

這裡有幾個函式很有趣,比如DrawEdge、DrawFrameControl。使用他們可以非常輕鬆地繪出按鈕控制元件、編輯框控制元件等的外觀。我已經把常用的函式的用法包含到了附帶程式program2.vbp。



五、路 徑

應當說,路徑是較為高階的話題,儘管它不是難於理解的。我學到的有關路徑的知識,來自於Dan的《Visual Basic 5.0 WIN32開發人員指南》一書中的不到兩頁的內容中,在其他的書中尚未看到。
糟糕的是路徑沒有控制代碼,所以說它不是GDI物件的成員。不過,千萬要記住一點,任何一個裝置場景只有一個路徑。從這一點來看,就算為路徑設定了控制代碼也是多餘的。從我的感覺來看,路徑像是在一個裝置場景中繪出的任意形狀的多邊形區域(儘管它不是區域)。

我在路徑中體驗出的一個好處就是,建立一個路徑後,可以把它轉換為區域。這一點可以用PathToRegion函式來完成。一旦這一步成功了就好辦了,得到區域控制代碼以後就可以和其他區域物件一樣處理了。總而言之,通過路徑我們可以很輕鬆地建立複雜的圖形區域。
建立一個路徑非常簡單。具體形式如下∶

dl& = BeginPath&(Out.hdc)
(繪圖)
dl& = EndPath&(Out.hdc)
在(繪圖)的位置上編寫程式碼來繪出什麼圖形,就能形成什麼樣的路徑了。不過,並非任何繪圖函式都可以產生路徑的。可以用來產生路徑的函式如下所列。

函式 Windows NT Windows 95
AngleArc Yes No
Arc Yes No
ArcTo Yes No
Chord Yes No
Ellipse Yes No
ExtTextOut Yes Yes
LineTo Yes Yes
MoveToEx Yes Yes
Pie Yes No
PolyBezier Yes Yes
PolyBezierTo Yes Yes
PolyDraw Yes No

Polygon Yes Yes
Polyline Yes Yes
PolylineTo Yes Yes
PolyPolygon Yes Yes
PolyPolyline Yes Yes
Rectangle Yes No
RoundRect Yes No
TextOut Yes Yes

看完這個表,我想Windows95的使用者就可能有點心痛∶這麼多函式用不了!嗨,我也沒辦法,只好責怪微軟了。以下是有關路徑的API函式∶

API 路徑函式

函 數 說 明
AbortPath 拋棄選入指定裝置場景中的所有路徑。也取消目前正在進行的任何路徑的建立工作
BeginPath 啟動一個路徑分支。在這個命令後執行的GDI繪圖命令會自動成為路徑的一部分。對線段的連線會結合到一起。裝置場景中任何現成的路徑都會被清除。參考下表,其中列出的函式都可記錄到路徑中
CloseFigure 描繪到一個路徑時,關閉當前開啟的圖形(將當前路徑段轉為閉圖)
EndPath 停止定義一個路徑。如執行成功,BeginPath函式呼叫和這個函式之間發生的所有繪圖操作都會正式成為指定裝置場景的路徑
FillPath 關閉路徑中任何開啟的圖形,並用當前刷子填充
FlattenPath 將一個路徑中的所有曲線都轉換成線段
GetPath 取得對當前路徑進行定義的一系列資料
PathToRegion 將當前選定的路徑轉換到一個區域裡
SelectClipPath 將裝置場景當前的路徑合併到剪下區域裡
StrokeAndFillPath 針對指定的裝置場景,關閉路徑上開啟的所有區域。用當前畫筆描繪路徑的一個輪廓,並用當前刷子填充路徑
StrokePath 用當前畫筆描繪一個路徑的輪廓。開啟的圖形不會被這個函式關閉
好了,其實也沒啥,很簡單的,請閱讀program4.vbp演示程式吧,你會觸目驚心!哇!這就是路徑?!

課後練習

A、製作畫筆和畫刷觀察器。

提示∶

①建立一個圖片框控制元件,用當前畫筆來畫一個矩形(使用Rectangle,巨型的內部將自動被當前刷子填充)。我們要準備依此來觀察當前畫筆和當前刷子。

②設定5個滾動條,用來分別調整畫筆的顏色,畫筆的寬度,畫筆的樣式(實線、虛線實體畫刷顏色,陰影畫刷樣式。畫筆的建立可以用函式CreatePen,實體畫刷的建立可以用函式CreateSolidBrush,陰影畫刷的建立可以用函式CreateHatchBrush。(圖樣刷子的建立留在下一課再討論,暫時可以不練)

通過以上思路,當某個滾動條移動的時候,圖片框中的圖象相應地進行變化,如寬度加厚, 顏色變暗等。

B、建立一個類模組來模仿Check控制元件的最基本功能。

提示∶

①需要新增的屬性有Wight,Height,Value屬性

②需要新增的事件有Click事件。可以用Timer控制元件來作事件源。

③控制元件外觀可以用DrawFrameControl函式來繪製。

說真的,還真的想不起來好例子。如果學完了點陣圖就好了。請等待,下期就是點陣圖。