一、為什麼這麼慢? |
二、DIB的結構 |
三、DIB通路函數 |
四、實戰練習 |
五、使用DIBSection和模拟指針 |
六、結合DirectX |
一、為什麼這麼慢?
自盤古開天地以來(好像誇張了點),一直有人抱怨VB程式速度慢。特别是圖像處理,被認為是VB的禁區。說起來也是,市面上的關于VB的圖像處理的數 據都是先講計算公式,再直接用PSet(或API函數SetPixel)逐點畫(至少我見過的書都是這樣)。效果是辦到了,但速度慢得離譜:對一幅 640*480的圖像進行半透明合并就需要10秒鐘;而在PhotoShop中,隻要一設定圖層的透明度,半透明效果立即呈現。難怪有人說VB的閑話。 但這并不表示VB不能寫高速的圖像處理程式,速度慢是因為沒有使用正确的方法。 從VB5開始,能以本機代碼編譯成exe檔案,是以不存在代碼執行速度的問題。那麼,是什麼拖慢了速度呢?就是PSet和SetPixel!PSet 把浮點形式的坐标轉為像素機關,再交給SetPixel處理。而SetPixel呢,坐标系轉化、剪裁區域判斷、将顔色比對為裝置支援的最接近的,最後還 要根據不同的顔色格式尋址、為将顔色寫入其所在位進行位運算。經過這麼多層處理,速度不慢才怪。 那麼,怎樣才能提高處理速度呢?使用DIB,直接對位圖所在記憶體進行操作,速度可以大大提高。現在看看本文提供的範例程式的執行速度,這隻是一個簡單 的色彩示範程式。
Debug | Relase | 說 明 | |
1_PSet | 1156.7042 | 936.2807 | 在VB使用 PSet 畫的 |
2_SetPixelV | 484.7545 | 460.5382 | 在VB使用 SetPixelV 畫的 |
3_DIB | 118.6978 | 3.8317 | 在VB使用 DIB 畫的 |
4_DIB_Ptr | 107.5791 | 4.3545 | 在VB使用 DirectDraw + 模拟指針 畫的 |
5_DX7Ptr | 108.1261 | 4.5503 | 在VB使用 DirectDraw + 模拟指針 畫的 |
6_DX7Arr | 131.8148 | 7.5506 | 在VB使用 DIBSection + GetLockedArray 畫的 |
VC | 2.8535 | 1.8994 | 用Visual C++ 6.0寫的 |
CPU | AMD Athlon XP 1700+(實際頻率:1463 MHz (11 x 133)) |
記憶體 | Kingston DDR266 256MB *2(兩根) |
顯示卡 | nVIDIA GeForce2 MX/MX 400(AGP 4X,顯存32MB) |
測試環境 | Windows XP sp2 |

從這個表中可看出:
1.VC比3_DIB、4_DIB_Ptr快一些,這是因為SafeArray結構的數組比真正的指針慢,但也不是某些人所說的70~100倍;
2.4_DIB_Ptr比3_DIB慢一點,這是因為模拟指針本來就是靠SafeArray結構的數組,需要在運作時動态修改數組資料位址,是以速度慢一點;
3.真正差了70~100倍是1_PSet和2_SetPixelV。
4.在VB IDE中解釋執行程式非常慢,3_DIB就存在30倍的速度差距。是以經常一些無聊的人拿 VC Debug編譯的程式 與 VB IDE中解釋執行程式 比較速度。
5.4_DIB_Ptr、5_DX7Ptr速度一樣,這是因為都是利用模拟指針技術直接通路記憶體來做圖像處理的。而6_DX7Arr使用的是GetLockedArray傳回的二維數組,二維數組比一維數組慢。 以上可證,速度慢的原因是SetPixelV非常低效,而并不是VB的問題。雖然VC的的确比較快,但是我寫這篇文章不是為了讨論速度極限(否則這篇文章會改名為《如何用彙編 寫高速的圖像處理程式》),而是為了告訴大家如何在VB中寫能夠實時處理的圖像處理程式。 同時,決定代碼速度的不是程式設計語言或編譯器,而是算法。如果算法寫得差的話,無論你用什麼程式設計語言或編譯器,那程式速度依然很慢(你在VC中用SetPixelV寫圖像處理程式試試)。而現在的硬體配置已經足夠好,用VB完全可以寫高速的圖像處理程式。
二、DIB的結構
在 Windows 3.0 以前,Windows系統用的是DDB(裝置有關位圖)。DDB沒有調色闆,顯示的顔色依賴硬體,處理色彩很不友善。是以 Microsoft 在 Windows 3.0中 重新定義了BMP檔案格式(BMP 3.0),使其支援裝置無關位圖——也就是DIB。 時至今日,BMP的版本号已升至5.0(Windows NT 4.0、Windows95 定義了 BMP 4.0,Windows 98、Windows 2000 定義了 BMP 5.0),但基本結構沒有變——仍是 BMP檔案頭 和 DIB 組成:
BMP檔案 | BITMAPFILEHEADER | BMP檔案頭 | |
DIB | BITMAPINFOHEADER | 位圖資訊頭 | BITMAPINFO |
RGBQUAD[] | 調色闆 | ||
位圖資料 |
(# 代表可以不填(=0)的項目)
原型定義: | |
| |
VB聲明: | |
| |
說明: | |
bfType | 訓示檔案的類型,必須是“BM” |
bfSize# | 訓示檔案的大小,包括BITMAPFILEHEADER |
bfReserved1 | 保留,=0 |
bfReserved2 | 保留,=0 |
bfOffBits# | 從檔案頭到位圖資料的偏移位元組數 |
原型定義: | |
| |
VB聲明: | |
| |
說明: | |
biSize | BITMAPINFOHEADER結構的大小。BMP有多個版本,就靠biSize來差別: BMP3.0:BITMAPINFOHEADER(=40) BMP4.0:BITMAPV4HEADER(=108) BMP5.0:BITMAPV5HEADER(=124) |
biWidth | 位圖的高度,機關是像素 |
biHeight | 位圖的寬度,機關是像素 |
biPlanes | 裝置的位平面數。現在都是1 |
biBitCount | 圖像的顔色位數 0:當biCompression=BI_JPEG時必須為0(BMP 5.0) 1:單色位圖 4:16色位圖 8:256色位圖 16:增強色位圖,預設為555格式 24:真彩色位圖 32:32位位圖,預設情況下Windows不會處理最高8位,可以将它作為自己的Alpha通道 |
biCompression | 壓縮方式 BI_RGB:無壓縮 BI_RLE8:行程編碼壓縮,biBitCount必須等于8 BI_RLE4:行程編碼壓縮,biBitCount必須等于4 BI_BITFIELDS:指定RGB掩碼,biBitCount必須等于16、32 BI_JPEG:JPEG壓縮(BMP 5.0) BI_PNG:PNG壓縮(BMP 5.0) |
biSizeImage# | 實際的位圖資料所占位元組(biCompression=BI_RGB時可以省略) |
biXPelsPerMeter# | 目标裝置的水準分辨率,機關是每米的像素個數 |
biYPelsPerMeter# | 目标裝置的垂直分辨率,機關是每米的像素個數 |
biClrUsed# | 使用的顔色數(當biBitCount等于1、4、8時才有效)。如果該項為0,表示顔色數為2^biBitCount |
biClrImportant# | 重要的顔色數。如果該項為0,表示所有顔色都是重要的 |
原型定義: | |
| |
VB聲明: | |
| |
說明: | |
rgbBlue | 藍色分量 |
rgbGreen | 綠色分量 |
rgbRed | 紅色分量 |
rgbReserved# | 保留,=0 |
◆ 掃描行: 一行的圖像資料叫做一個掃描行。一個掃描行的長度必須是4的倍數(位元組),如果不是,則需要補齊。計算公式:LineBytes=((biWidth*biBitCount+31)And &HFFFFFFE0)/8 由于BMP設定者認為數學坐标系更總要,是以DIB的掃描行是逆序存儲的(相對于螢幕坐标系而言),即螢幕上的第一行是DIB位圖資料的最後一行。 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
◆ 1位色: 用1位表示一個像素,是以一個位元組可以表示8個像素。坐标是從最左邊(最高位)開始的,而不是一般情況下的最低位。在記憶體的擺放形式如下:
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
◆ 4位色: 用4位表示一個像素,是以一個位元組可以表示2個像素。坐标是從最左邊(最高位)開始的,而不是一般情況下的最低位。在記憶體的擺放形式如下:
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
◆ 8位色: 用8位表示一個像素,是以一個位元組剛好隻能表示一個像素。在記憶體的擺放形式如下:
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
◆ 16位色: 用16位表示一個像素,是以兩個位元組可以表示1個像素。預設情況下16位DIB是555格式,最高位無效(這對VB是個福音,因為VB沒有16位無符号型)。在記憶體的擺放形式如下(PC機使用小端規則(little endian),是低位元組在前):
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
◆ 24位色: 用24位表示一個像素,是以三個位元組可以表示1個像素。注意它的順序是B G R ,而不是傳統的R G B 。在記憶體的擺放形式如下:
| ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
◆ 32位色: 用32位表示一個像素,是以四個位元組可以表示1個像素。注意絕大多數的GDI函數不會處理Alpha通道(隻有AlphaBlend支援)。在記憶體的擺放形式如下:
|
三、DIB通路函數
原型定義: | |||||
| |||||
VB聲明: | |||||
Declare Function SetDIBitsToDevice Lib "gdi32.dll" (ByVal hDC As Long, ByVal XDest As Long, ByVal YDest As Long, ByVal dwWidth As Long, ByVal dwHeight As Long, ByVal XSrc As Long, ByVal YSrc As Long, ByVal uStartScan As Long, ByVal cScanLines As Long, lpvBits As Any, lpbmi As Any, ByVal fuColorUse As Long) As Long | |||||
說明: | |||||
将一幅與裝置無關位圖的全部或部分資料直接複制到一個裝置。這個函數在裝置中定義了一個目标矩形,以便接收位圖資料。它也在DIB中定義了一個源矩形,以便從中提取資料 | |||||
傳回值: | |||||
如函數執行成功,傳回欲複制的掃描線的數量;如傳回常數GDI_ERROR,表示出錯 | |||||
參數: | |||||
hDC | 一個裝置場景的句柄。該場景用于接收位圖資料 | ||||
XDest | 指定繪制區域的左上角X坐标 | ||||
YDest | 指定繪制區域的左上角Y坐标 | ||||
dwWidth | 指定繪制區域的高度 | ||||
dwHeight | 指定繪制區域的寬度 | ||||
XSrc | 矩形在DIB中的起點X坐标 | ||||
YSrc | 矩形在DIB中的起點Y坐标 | ||||
uStartScan | lpvBits中第一條掃描線的編号。如lpbmi之BITMAPINFOHEADER部分的biHeight字段是正數,那麼這條掃描線就會從位圖的底部開始計算;如果是負數,就從頂部開始計算 | ||||
cScanLines | 欲複制的掃描線數量 | ||||
lpvBits | 指向一個緩沖區的指針。這個緩沖區包含了以DIB格式描述的位圖資料;這種格式是由lpbmi指定的 | ||||
lpbmi | 指向BITMAPINFO(為相容BMP4/5而聲明成Any),對DIB的格式和顔色進行描述的一個結構 | ||||
fuColorUse |
|
原型定義: | |||||
| |||||
VB聲明: | |||||
Declare Function StretchDIBits Lib "gdi32" (ByVal hDC As Long, ByVal XDest As Long, ByVal YDest As Long, ByVal nDestWidth As Long, ByVal nDestHeight As Long, ByVal XSrc As Long, ByVal YSrc As Long, ByVal nSrcWidth As Long, ByVal nSrcHeight As Long, lpBits As Any, lpBitsInfo As Any, ByVal wUsage As Long, ByVal dwRop As Long) As Long | |||||
說明: | |||||
根據一幅與裝置無關的位圖建立一幅與裝置有關的位圖 | |||||
傳回值: | |||||
執行成功傳回位圖句柄,零表示失敗 | |||||
參數: | |||||
hDC | 一個裝置場景的句柄,該裝置場景定義了要建立的與裝置有關位圖的配置資訊 | ||||
XDest | 指定繪制區域的左上角X坐标 | ||||
YDest | 指定繪制區域的左上角Y坐标 | ||||
nDestWidth | 指定繪制區域的高度 | ||||
nDestHeight | 指定繪制區域的寬度 | ||||
XSrc | 矩形在DIB中的起點X坐标 | ||||
YSrc | 矩形在DIB中的起點Y坐标 | ||||
nSrcWidth | 指定原位圖繪制區域的左上角X坐标 | ||||
nSrcHeight | 指定原位圖繪制區域的左上角Y坐标 | ||||
lpBits | 指向一個緩沖區的指針。這個緩沖區包含了以DIB格式描述的位圖資料;這種格式是由lpBitsInfo指定的 | ||||
lpBitsInfo | 指向BITMAPINFO(為相容BMP4/5而聲明成Any),對DIB的格式和顔色進行描述的一個結構 | ||||
iUsage |
| ||||
dwRop | 欲進行的光栅運算 |
原型定義: | |||||
| |||||
VB聲明: | |||||
Declare Function CreateDIBitmap Lib "gdi32" (ByVal hDC As Long, lpbmih As Any, ByVal fdwInit As Long, lpbInit As Any, lpbmi As Any, ByVal fuUsage As Long) As Long | |||||
說明: | |||||
将一幅與裝置無關位圖的全部或部分資料直接複制到一個裝置。這個函數在裝置中定義了一個目标矩形,以便接收位圖資料。它也在DIB中定義了一個源矩形,以便從中提取資料 | |||||
傳回值: | |||||
執行成功則傳回掃描線的數量,零表示失敗。會設定GetLastError | |||||
參數: | |||||
hDC | 一個裝置場景的句柄。該場景用于接收位圖資料 | ||||
lpbmih | BITMAPINFOHEADER(為相容BMP4/5而聲明成Any),對DIB的格式進行描述的一個結構 | ||||
fdwInit | 如不應對位圖資料進行初始化,那麼設為零。如設為CBM_INIT,表示根據lpbInit和 lpbmi參數對位圖進行初始化 | ||||
lpbInit | 指向一個緩沖區的指針。這個緩沖區包含了以DIB格式描述的位圖資料;這種格式是由lpbmi指定的 | ||||
lpbmi | 指向BITMAPINFO(為相容BMP4/5而聲明成Any),對DIB的格式和顔色進行描述的一個結構 | ||||
fuUsage |
|
原型定義: | |||||
| |||||
VB聲明: | |||||
Declare Function CreateDIBSection Lib "gdi32" (ByVal hDC As Long, lpbmi As Any, ByVal iUsage As Long, ByRef ppvBits As Long, ByVal hSection As Long, ByVal dwOffset As Long) As Long | |||||
說明: | |||||
CreateDIBSection 能建立一種特殊的DIB,稱為DIB項(DIBSection),然後傳回一個GDI位圖的句柄。它提供了DIB和GDI位圖的最好的特性。這樣我們可以 直接通路DIB的記憶體,可以利用位圖句柄和記憶體裝置環境,我們甚至還可以在DIB中調用GDI函數來繪圖 | |||||
傳回值: | |||||
執行成功傳回DIBSection位圖的句柄,零表示失敗。會設定GetLastError | |||||
參數: | |||||
hDC | 一個裝置場景的句柄。如dw設為DIB_PAL_COLORS,那麼DIB顔色表就會用來自邏輯調色闆的顔色進行初始化 | ||||
lpbmi | 指向BITMAPINFO(為相容BMP4/5而聲明成Any),這個結構初始化成欲建立的那幅位圖的配置資料 | ||||
iUsage |
| ||||
ppvBits | 用于得到DIBSection資料區的記憶體位址 | ||||
hSection | 指向一個檔案映射對象的可選句柄,位圖将在其中建立。如設為零,Windows會自動配置設定記憶體 | ||||
dwOffset | 如指定了句柄,就用這個參數指定位圖資料在檔案映射對象中的偏移量 |
原型定義: | |||||
| |||||
VB聲明: | |||||
Declare Function GetDIBits Lib "gdi32" (ByVal hDC As Long, ByVal hbmp As Long, ByVal uStartScan As Long, ByVal cScanLines As Long, lpvBits As Any, lpbmi As Any, ByVal uUsage As Long) As Long | |||||
說明: | |||||
該函數利用申請到的記憶體,由GDI位圖得到DIB位圖資料。通過該函數,可以對DIB的格式進行控制,可以制定顔色的位數,而且可以指定是否進行壓縮。如果采用了壓縮方式,則必須調用該函數兩次,一次為了得到所需記憶體,另外一次為了得到位圖資料 | |||||
傳回值: | |||||
執行成功則傳回掃描線的數量,零表示失敗。會設定GetLastError | |||||
參數: | |||||
hDC | 定義了與裝置有關位圖hBitmap的配置資訊的一個裝置場景的句柄 | ||||
hbmp | 源位圖的句柄 | ||||
uStartScan | 欲複制到DIB中的第一條掃描線的編号 | ||||
cScanLines | 欲複制的掃描線數量 | ||||
lpvBits | 指向一個緩沖區的指針。這個緩沖區包含了以DIB格式描述的位圖資料;這種格式是由lpbmi指定的 | ||||
lpbmi | 指向BITMAPINFO(為相容BMP4/5而聲明成Any).對DIB的格式及顔色進行說明的一個結構。在BITMAPINFOHEADER結構中,從biSize到biCompression之間的所有字段都必須初始化 | ||||
uUsage |
|
原型定義: | |||||
| |||||
VB聲明: | |||||
Declare Function SetDIBits Lib "gdi32" (ByVal hDC As Long, ByVal hbmp As Long, ByVal uStartScan As Long, ByVal cScanLines As Long, lpvBits As Any, lpbmi As Any, ByVal uUsage As Long) As Long | |||||
說明: | |||||
将來自與裝置無關位圖的二進制位複制到一幅與裝置有關的位圖裡 | |||||
傳回值: | |||||
執行成功則傳回掃描線的數量,零表示失敗。會設定GetLastError | |||||
參數: | |||||
hDC | 定義了與裝置有關位圖hBitmap的配置資訊的一個裝置場景的句柄 | ||||
hbmp | 源位圖的句柄 | ||||
uStartScan | 欲複制到DIB中的第一條掃描線的編号 | ||||
cScanLines | 欲複制的掃描線數量 | ||||
lpvBits | 指向一個緩沖區的指針。這個緩沖區包含了以DIB格式描述的位圖資料;這種格式是由lpbmi指定的 | ||||
lpbmi | 指向BITMAPINFO(為相容BMP4/5而聲明成Any).對DIB的格式及顔色進行說明的一個結構。在BITMAPINFOHEADER結構中,從biSize到biCompression之間的所有字段都必須初始化 | ||||
uUsage |
|
原型定義: | |
| |
VB聲明: | |
Declare Function GetDIBColorTable Lib "gdi32" (ByVal hDC As Long, ByVal uStartIndex As Long, ByVal cEntries As Long, pColors As RGBQUAD) As Long | |
說明: | |
從選入裝置場景的DIBSection中取得顔色表資訊 | |
傳回值: | |
取回的顔色條目數量,零表示失敗。會設定GetLastError | |
參數: | |
hDC | 已選入了一個DIBSection對象的裝置場景 |
uStartIndex | 顔色表中欲取回的第一個條目的索引 |
cEntries | 欲取回的條目數量 |
pColors | 這個結構數組用于裝載顔色表資訊的第一個條目 |
原型定義: | |
| |
VB聲明: | |
Declare Function SetDIBColorTable Lib "gdi32" (ByVal hDC As Long, ByVal uStartIndex As Long, ByVal cEntries As Long, pColors As RGBQUAD) As Long | |
說明: | |
從選入裝置場景的DIBSection中取得顔色表資訊 | |
傳回值: | |
取回的顔色條目數量,零表示失敗。會設定GetLastError | |
參數: | |
hDC | 已選入了一個DIBSection對象的裝置場景 |
uStartIndex | 顔色表中欲取回的第一個條目的索引 |
cEntries | 欲取回的條目數量 |
pColors | 這個結構數組用于裝載顔色表資訊的第一個條目 |
四、實戰練習
用DIB寫圖像處理程式的時候,首先要明确一點:DIB并不是圖像處理算法,而是一種繪圖方法。圖像處理算法是DIB進階,管理坐标和顔色的運算;而 DIB隻是為了繪制。是以此時處理算法的效率是速度的關鍵。 利用DIB繪制圖像并沒有比用PSet/SetPixel繪制差多少,它隻是把坐标運算改成位址運算而已。很多人知道指針是一個危險的東西,就是因為 它能直接通路記憶體,如果指針不小心指錯地方的話,Windows立即報告一般保護性錯誤。是以,在位址運算的時候一定要小心,同時要注意随時儲存,因為此 時的非法操作的發生率非常高,否則辛辛苦苦寫的代碼一瞬間沒了可别怪我沒提醒啊。
好了,現在開始! 由于處理算法起指導作用,是以現在先講解1_PSet。所有的代碼都在FrmMain.frm中。其他的過程的代碼可以不看,現在将注意力集中在“DrawIt”中,它就是管繪制的。
|
其實我這個示範程式蠻簡單的:R分量延着水準方向增加,G分量延着垂直方向增加,B分量則從右往左滾動。什麼?!“And &HFF”是什麼意思?!這可是基礎啊……(下略&HFFFF...字)。“&H”表示十六進制數,而And表示按位 與。&HFF是二進制的“1111 1111”,正好覆寫了低8位,這時用And進行按位與,隻會得到低8位,與RGB分量需要的8位正好符合(對于“(J + K) And &HFF”來說,可以實作滾動效果)。 If Not 看明白了 Then Goto 前兩段 好!現在打開3_DIB。(由于24位能直接指定RGB分量,是以這裡是用的是24位DIB) 看了前面的“DIB的結構”,是不是有點昏呢?其實DIB也沒什麼,就是 一個表述位圖資訊的BITMAPINFO結構 和 一個存儲位圖資料的資料緩沖區,頂多再用SetDIBitsToDevice繪制,是以3_DIB與1_PSet相比隻是多了 SetDIBitsToDevice、BITMAPINFOHEADER(24位DIB沒有調色闆,是以用BITMAPINFOHEADER就行)和一些 常數的聲明而已。由于這個示範程式不需要改變圖像大小和色深,是以可以把有關變量作為窗體級變量,再在Form_Load中初始化。由于DIB并沒有向系 統申請資源(數組的記憶體是VB配置設定的,會自動釋放),是以不需要寫釋放代碼。 現在來看DrawIt。
|
1.雖然可以逐點把坐标映射成位址再寫,但是這樣效率太低了,可以利用坐标處理的連續性進行優化。
2.由于我這裡用的是數組,是以這裡用(數組元素)索引代替位址。
3.最開始要注意DIB是逆序存儲的,要将索引設為最後一行第一個像素的索引。
4.由于DIB的RGB順序是B、G、R,是以“m_MapData(CurIdx + ?)”的順序是2、1、0。
5.設定好一個像素的顔色後,要注意把索引改為下個像素的索引。
6.由于DIB是逆序存儲的,移到下一個掃描行是“iLinePtr = iLinePtr - m_LineBytes”
“好了,代碼看懂了,按F5運作看看效果。”
“咦?速度好像沒快多少啊?”
這是由于程式在VB環境下是以解釋方式運作的,而解釋方式對做圖像處理所需要大規模循環和大量的算術運算的執行效率很低,是以要編譯成(本機代碼)exe再運作。此時還要注意編譯優化,可以把“進階優化”的所有勾打上,速度可提升20%左右。
五、使用DIBSection和模拟指針
雖然有GetDIBits/SetDIBits函數,但是DIB與GDI位圖之間的資料交換還是很不友善,特别處理過程中需要調用GDI函數來處理的 時候。而且就算你不怕麻煩,但這樣做的處理效率很低。是以Windows為我們提供了DIBSection。DIBSection是一種特殊的DIB,它 除了可以像DIB一樣直接對位圖資料所占記憶體進行操作,它還可以選入DC、能用GDI函數繪制,非常靈活。但在VB下使用DIBSection還是有困難 的,因為用CreateDIBSection建立DIBSection時,得到的是位圖資料的位址,而VB沒有指針。 所幸在VB下可以利用SafeArray結構的數組模拟指針。關于模拟指針的原理、方法,網上的資料多的是,比如AdamBear 的文章“VB真是想不到系列之四:VB指針葵花寶典之SafeArray ”。但這些文章都隻是講一般性的應用,不能像真正的指針一樣随意改變位址(他們都是使用CopyMemory改的)。而在圖像進行中,由于點運算的頻繁,“像真正的指針一樣随意改變位址”的功能很重要。 其實“像真正的指針一樣随意改變位址”并沒有技術難度(對于已經學會模拟指針的人來說),就看想得到不:直接将一個動态數組(設pByte)指向一個SAFEARRAY結構體變量(設pBytePtr)。模拟指針子產品的代碼在mPoint.bas 。 現在來看4_DIB_Ptr:
|
1.為了示範DIBSection能夠像HBITMAP一樣,我在Form_Load中建立了DC、将DIBSection選入DC。同時為了釋放,Form_UnLoad中寫了釋放代碼。
2.在DrawIt中,注意處理部分的代碼并沒有與VB_DIB差多少,隻不過把索引計算改為位址運算而已。
再來比較vc的代碼,感覺跟真正的指針用法差不多嘛:
|
六、結合DirectX
在GDI中,我們可以用DIB直接操作圖像資料,那麼号稱能直接通路顯存的DirectX呢? 一查DirectX SDK,發現IDirectDrawSurface接口的Lock函數可以鎖定表面,進而直接通路該位圖資料。再在VB的對象浏覽器中仔細觀察 “DirectX 7 for Visual Basic Type Library”,發現DirectDrawSurface7對象也有該方法。 看看5_DX7Ptr:
|
1.由于硬體裝置性能不同,是以不要指望像DIB那樣可以認定RGB位元組順序。應該根據Lock方法傳回的DDSURFACEDESC2結構來确認RGB位元組順序(iIdxR、iIdxG、iIdxB)、像素所占位元組(cbPixel)及寬距(cbPitch)。
2.由于我們有了模拟指針技術,是以這段代碼與跟先前4_DIB_Ptr差不多,特别是與在VC中使用IDirectDrawSurface::Lock的處理代碼差不多。
仔細觀察對象浏覽器的人會發現,DirectDrawSurface7對象GetLockedArray可以取得鎖定後的位圖資料:
|
這個就是官方推薦做法,使用一個二維數組來處理位圖資料。但是就是因為其使用的是二維數組,是以它比一維數組的模拟指針慢一些。如果你不想使用非正常技術(模拟指針),可以使用GetLockedArray。
注意到這點沒有,GetLockedArray是一個方法而不是一個屬性,我們需要将數組變量傳遞過去,然後就可以利用該數組直接操作位圖資料了。啊 哈!明白了GetLockedArray也是利用模拟指針技術實作的,隻不過它填充的是2維的SAFEARRAY結構而已。
注意:隻操作位于系統記憶體的表面,千萬别操作對顯存中的表面。這是因為CPU通路顯存比返問記憶體要慢許多!這個建議并不是絕對的,如果對該表面的Blt的調用次數遠超過自己寫的圖像處理操作的話,可以将該表面放在顯存,或者是在記憶體中計算好後再一次性送出到顯存。
本程式建立圖像處理操作的表面的代碼:
|