GDI

常用GDI繪圖函數

 

函   數                                           說   明
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                用當前畫筆描繪一個路徑的輪廓。打開的圖形不會被這個函數



CreateDIBitmap與CreateDIBSection

首先明確最主要區別CreateDIBitmap創建的是設備相關位圖句柄 - HBITMAP.
                              CreateDIBSection創建的是設備無關位圖句柄 - HBITMAP.

DIBDDB之間的相互轉換比較慢(關於DIB與DDB區別詳見《設備相關(DDB)與設備無關(DIB)》),所以我們使用CreateDIBSection()來創建一個DIB區塊。這樣作圖速度快。 
CreateDIBSection()返回的是一個HBITMAP,CreateDIBitmap()返回的也是HBIT MAP。 
兩者的區別在於:CreateDIBSection創建的是一個DIBSECTION結構,
                            CreateDIBitmap創建的是BITMAP結構。 
---------------------------------------------------- 
關於DIB區塊(DIBSECTION)詳見:http://technet.microsoft.com/zh-cn/library/aa930771
可以看到,它包含了一個 位圖結構BITMAP,一個DIB信息頭BITMAPINFOHEADER,一個掩碼錶dsBIT fields[3]. 
還有一個內存映射文件句柄和偏移量。我們不去理睬最後兩個字段。 
因此,使用GDI函數對CreateDIBSection()返回的HBITMAP作圖是沒有什麼問題的。 
 
關於CreateDIBSection與CreateDIBitmap函數的對比可參見:http://blog.csdn.net/strikebone/article/details/5832631
 
一、CreateDIBitmap
 
函數功能:該函數由與設備無關的位圖(DIB)創建與設備有關的位圖(DDB),並且有選擇地爲位圖置位。
所以此函數最終創建的是與設備有關的位圖
原型

HBITMAP CreateDIBitmap(HDC hdc,

CONST BITMAPINFOHEADER *lpbmih,

DWORD fdwlnit,

CONST VOID *lpblnit,

CONST BITMAPINFO *lpbmi,

UINT fuUsage);

參數
hdc:設備環境句柄。由於是設備相關的,所以需要指明DC。
lpbmih:指向位圖信息頭結構的指針,它可以是下列操作系統位圖信息頭之一:
BITMAPINFOHEADER  Windows NT 3.51和早期使用。
BITMAPV4HEADER      Windows NT 4.0和Windows 95使用。
BITMAPV5HEADER      Windows NT 5.0和Windows 98使用。
如果fdwlnit是CBM_INIT,那麼此函數使用此信息息頭結構來獲取位圖所需的寬度、高度以及其他信息。注意高度若是正數,那麼表示是自底向上DIB,而負數表示爲自頂向下DIB,這種情況與CreateDIBitmap函數兼容。
Fdwlnit:位標識集。它指定系統如何對位圖的位進行初始化。
CBM_INIT:如果設置了該標誌,那麼系統將使用lpblnit和lpbmi兩個參數指向的數據來對位圖中的位進行初始化。如果沒有該標誌,那麼表示上述兩個參數指向的數據無效。如果fdwlnit爲0,那麼系統不會對位圖的位進行初始化。
lpblnit:該指針指向包含初始的位圖數據的字節類型數組。數據格式與參數lpbmi指向的BITMAPINFO結構中的成員biBitCount有關。
lpbmi:指向BITMAPINFO結構的旨針。該結構描述了參數lpbmi指向的數組的維數和顏色格式。
fuUsage:表示BITMAPINFO結構的成員bmiColors是否初始化過,並且如果是,那麼bmiColors是否包含明確的紅、綠、藍(RGB)值或調色板索引。參數fuUsage必須取下列值中的一個,這些值的含義爲:
DIB_PAL_COLORS :表示提供一個顏色表,並且該表由該位圖要選入的設備環境的邏輯調色板的16位索引值數組組成。
DIB_RGB_COLORS:表示提供一個顏色表,並且表中包含了原義的RGB值。
 
返回值
如果函數執行成功,返回值則是創建的位圖的句柄;如果函數執行失敗,那麼返回值爲NULL,若想獲取更多錯誤信息,請調用GetLastError函數。
 
實例:以下代碼完成從位圖文件中讀取DIB位圖,並轉換成DDB位圖:
HBITMAP LoadBitmapEx(LPCTSTR lpszFile)
{
if(lpszFile == NULL) return NULL;
 
 
HBITMAP hBitmap;
HANDLE hf;
BITMAPFILEHEADER* pbmfh;
DWORD dwBytesRead, dwFileSize, dwFileSizeHigh;
BOOL bSuccess;
// 打開一個bmp文件
hf = CreateFile(lpszFile, GENERIC_READ, FILE_SHARE_READ,
NULL, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, NULL);
if( hf == INVALID_HANDLE_VALUE)
{
TRACE("Open file filed with error %d ", GetLastError());
return NULL;
}
// 得到這個文件大小
dwFileSize = GetFileSize(hf, &dwFileSizeHigh);
if( dwFileSizeHigh )
{
CloseHandle(hf);
return NULL;
}
// 分配內存,大小爲該文件的大小
pbmfh = (BITMAPFILEHEADER*)malloc(dwFileSize);
if( !pbmfh )
{
CloseHandle(hf);
return NULL;
}
// 讀取數據
bSuccess = ReadFile(hf, pbmfh, dwFileSize, &dwBytesRead, NULL);
CloseHandle(hf);
// 效驗文件大小和文件格式
if( !bSuccess || dwFileSize != dwBytesRead || pbmfh->bfType != 0x4D42 || pbmfh->bfSize != dwFileSize)
{
free((void*)pbmfh);
return NULL;
}
// 進行DIB轉換
hBitmap = CreateDIBitmap(GetWindowDC(NULL),
(BITMAPINFOHEADER*)(pbmfh + 1),
CBM_INIT,
(BYTE*)pbmfh + pbmfh->bfOffBits,
(BITMAPINFO*)(pbmfh + 1),
DIB_RGB_COLORS);
free((void*)pbmfh);
return hBitmap;
}

  

其中使用到了位圖信息頭結構BITMAPFILEHEADER)及位圖信息結構BITMAPINFO)。
兩者的關係詳見《BITMAPFILEHEADER 與 BITMAPINFO》。
 
再看一個使用CreateDIBitmap函數如何由裸像素數據得到位圖句柄 - HBITMAP:
HBITMAP MakeBitmap(HDC hDc, LPBYTE lpBits, long lWidth, long lHeight, WORD wBitCount)
{
BITMAPINFO bitinfo;
memset(&bitinfo, 0, sizeof(BITMAPINFO));
 
bitinfo.bmiHeader.biSize          = sizeof(BITMAPINFOHEADER);
bitinfo.bmiHeader.biWidth         = lWidth;    
bitinfo.bmiHeader.biHeight        = lHeight;  
bitinfo.bmiHeader.biPlanes        = 1; 
bitinfo.bmiHeader.biBitCount      = wBitCount;  
bitinfo.bmiHeader.biCompression   = BI_RGB;  
bitinfo.bmiHeader.biSizeImage     = lWidth*lHeight*(wBitCount/8); 
bitinfo.bmiHeader.biXPelsPerMeter = 96;
bitinfo.bmiHeader.biYPelsPerMeter = 96;  
bitinfo.bmiHeader.biClrUsed       = 0;  
bitinfo.bmiHeader.biClrImportant  = 0;
   
return CreateDIBitmap(hDc, &bitinfo.bmiHeader, CBM_INIT, lpBits, &bitinfo, DIB_RGB_COLORS);
}
 
一、CreateDIBSection
函數功能該函數創建應用程序可以直接寫入的、與設備無關的位圖(DIB)。該函數返回一個位圖句柄。

 

函數原型    

HBITMAP CreateDIBSection(HDC                  hdc,
        CONST BITMAPINFO *   pbmi,
        UINT                 iUsage,
        VOID *               ppvBits,
        HANDLE               hSection,
        DWORD                dwOffset
 
);

參數   

hdc:設備環境句柄。如果iUsage的值是DIB_PAL_COLORS,那麼函數使用該設備環境的邏輯調色板對與設備無關位圖的顏色進行初始化。

pbmi:指向BITMAPINFO結構的指針,該結構指定了設備無關位圖的各種屬性,其中包括位圖的尺寸和顏色。

iUsage:指定由pbmi參數指定的BITMAPINFO結構中的成員bmiColors數組包含的數據類型(要麼是邏輯調色板索引值,要麼是原文的RGB值)。下列值是系統定義的,其含義爲:

描述

DIB_RGB_COLORS

根據BITMAPINFOHEADER 的biCompression 成員決定BITMAPINFO 結構包含位掩碼還是調色板數組,在呈現位圖時使用該數組值。DIB_RGB_COLORS 可以在任何位數的位圖上使用。

DIB_PAL_COLORS

BITMAPINFO.bmiColors 數組被取消,在呈現位圖時使用目標調色板。DIB_PAL_COLORS只能在8bpp位圖中指定。

ppvBits:指向一個變量的指針,該變量接收一個指向DIB位數據值的指針。

hSection:該參數設置爲NULL。

dwOffset:參數取消。

返回值

   成功,返回值是一個指向剛剛創建的設備無關位圖的句柄,並且*ppvBits指向該位圖的位數據值;失敗,那麼返回值爲NULL,並且*ppvBit也爲NULL,若想獲得更多錯誤信息,請調用GetLastError函數。

 

備註:

   系統爲設備獨立位圖分配內存。如果在之後調用DeleteObject來刪除設備獨立位圖,系統自動關閉內句柄。

   在Windows CE 2.0及其以後版本,如果圖像是調色板模式(通常是1,2,4和8格式)的,BITMAPINFO 結構中必須包含一個顏色表。對於16bpp或32bpp非調色板圖像,顏色表必須是3個入口的長度,這3個入口必須指定紅、綠、藍色掩碼。 而且,BITMAPINFOHEADER 結構的biCompression 成員應該被設置爲BI_BITFIELDS。 這些位深不支持BI_RGB。 GDI取消24bpp圖像的顏色表,他們的像素必須被存儲爲 藍-綠-紅 (BGR)格式。

   Windows CE 不支持332位閾設備。

   在Windows CE 1.0 和 1.01版本,pbmi指向的BITMAPINFO結構必須指定每個像素點爲1或2位。

 

實例:下面這段代碼實現由已存在的圖像裸數據得到CBitmap位圖:

CBitmap bitmap ;
BITMAPINFO bmpInfo; //創建位圖
bmpInfo.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bmpInfo.bmiHeader.biWidth = 480;//寬度
bmpInfo.bmiHeader.biHeight = 480;//高度
bmpInfo.bmiHeader.biPlanes = 1;
bmpInfo.bmiHeader.biBitCount = 24;
bmpInfo.bmiHeader.biCompression = BI_RGB;
 
UINT uiTotalBytes = 480 * 480 * 3;
void *pArray = new BYTE(uiTotalBytes );
HBITMAP hbmp = CreateDIBSection(NULL, &bmpInfo, DIB_RGB_COLORS, &pArray, NULL, 0);//創建DIB
ASSERT(hbmp != NULL);
//! 將裸數據複製到bitmap關聯的像素區域
memcpy(pArray, pImageData, uiTotalBytes);
 
bitmap.Attach(hbmp);
其中pImageData即是已知的圖像裸數據。

注意

 

這裏想說聲sorry,之前沒太深入理解CreateDIBSection函數的工作機理,在上面的例子中使用new爲pArray開闢了一塊空間,然後將pArray傳入CreateDIBSection函數,其實這種方式是錯誤的,在Debug下面調試之後發現,你使用new開闢的pArray,

 

在使用之後並不能正常delete[]掉。CreateDIBSection參數ppvBits正常的使用應該只要傳入一個指針即可(就像上面實例中那樣

 

,只要定義一個BYTE *pArray,不需要自己分配空間,直接傳給CreateDIBSection即可)。

 

其實通過ppvBits參數類型(二級指針)也就明白了CreateDIBSection函數的基本使用原理 - CreateDIBSection函數使用hDC及

 

BITMAPINFO結構信息創建一個指定大小等信息的位圖,系統自動爲其開闢所需的像素空間,開闢的像素空間地址就是用ppvBits參數

 

返回的。而且此位圖空間系統會自動釋放。

 

之所以需要返回此地址,是因爲CreateDIBSection調用時只是根據我們提供的一些信息創建合適大小、格式的位圖並開闢控件

 

,而並不會填充實際像素值(這也是爲什麼我們直接將指向真實像素值的指針傳入此函數中無效的原因),返回這個指針只是爲了

 

之後使用真實像素值填充此段空間(就像上面示例中的memcpy那樣)!

 

 

1、關於CreateDIBSection函數的使用需要注意一點就是參數ppvBits的使用:傳給CreateDIBSection函數的不是一個new

 

來一塊的空間,也不是直接傳入真實像素指針pImageData,而只是傳入一個指針變量(如本例中的pArray)用以接收CreateDIBSection自動開闢的像素區域指針!如果你傳入了自己手動new出來的一塊區域,在Debug調試運行時會發現一個delete []錯誤,此錯誤指明你之前new出來的空間無法釋放,原因是堆損壞,這部分需要了解下new、delete(或內存檢測)原理,詳見《new/delete內存分配及釋放檢測》。

 

2、像素數據的複製必須在調用了CreateDIBSection函數之後進行,如本例所示,memcpy(pArray, pImageData, uiTotalBytes);是在HBITMAP hbmp = CreateDIBSection(NULL, &bmpInfo, DIB_RGB_COLORS, &pArray, NULL, 0);之後進行的。可能是由於CreateDIBSection函數會對傳入的像素空間進行“清除”工作,所以在調用此函數前就先進行像素複製的話,你會發現最終繪製的圖像時一片漆黑!

 

說明:CreateDIBSection函數具有內存映射文件功能,但是一般我們不會使用!所以此處忽略討論!

 

此外還需說明的是,有時候在使用CreateDIBSection函數時會發生內存泄露的問題。當初本人開發一個項目時曾經碰到過此問題,在一個窗口進入特定模式後會頻繁的調用此函數生成特定的圖像,並用此圖像去重繪窗口背景。最後調試正是此函數發生了內存泄露,但是代碼中確是對產生的HBITMAP及DC進行了釋放。當時是使用了另一種方式解決了此問題,但是對於CreateDIBSection函數發生的泄露問題卻一直沒有深入研究。

 

雖然如此,對於使用此函數之後善後處理這裏僅提幾點:

1、最後一次使用完CreateDIBSection函數創建位圖之後將其從DC中換出memdc.SelectObject(pOldBmp);

2、在最後一次使用完CreateDIBSection函數創建的位圖之後纔可delete[] pArray釋放掉像素空間。

3、注意釋放位圖資源bitmap.DeleteObject();

4、釋放內存memdc.ReleaseDC();


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章