天天看點

用VB寫高效的圖像處理程式

一、為什麼這麼慢?
二、DIB的結構
三、DIB通路函數
四、實戰練習
五、使用DIBSection和模拟指針
六、結合DirectX

一、為什麼這麼慢?

  自盤古開天地以來(好像誇張了點),一直有人抱怨VB程式速度慢。特别是圖像處理,被認為是VB的禁區。說起來也是,市面上的關于VB的圖像處理的數 據都是先講計算公式,再直接用PSet(或API函數SetPixel)逐點畫(至少我見過的書都是這樣)。效果是辦到了,但速度慢得離譜:對一幅 640*480的圖像進行半透明合并就需要10秒鐘;而在PhotoShop中,隻要一設定圖層的透明度,半透明效果立即呈現。難怪有人說VB的閑話。   但這并不表示VB不能寫高速的圖像處理程式,速度慢是因為沒有使用正确的方法。   從VB5開始,能以本機代碼編譯成exe檔案,是以不存在代碼執行速度的問題。那麼,是什麼拖慢了速度呢?就是PSet和SetPixel!PSet 把浮點形式的坐标轉為像素機關,再交給SetPixel處理。而SetPixel呢,坐标系轉化、剪裁區域判斷、将顔色比對為裝置支援的最接近的,最後還 要根據不同的顔色格式尋址、為将顔色寫入其所在位進行位運算。經過這麼多層處理,速度不慢才怪。   那麼,怎樣才能提高處理速度呢?使用DIB,直接對位圖所在記憶體進行操作,速度可以大大提高。現在看看本文提供的範例程式的執行速度,這隻是一個簡單 的色彩示範程式。

圖像尺寸:640*480*24b。機關:毫秒

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
用VB寫高效的圖像處理程式

  從這個表中可看出:

  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)的項目)

BMP檔案頭——BITMAPFILEHEADER

原型定義:
typedef struct tagBITMAPFILEHEADER { // bmfh

    WORD    bfType;

    DWORD   bfSize;

    WORD    bfReserved1;

    WORD    bfReserved2;

    DWORD   bfOffBits;

} BITMAPFILEHEADER;

      
VB聲明:
Type BITMAPFILEHEADER

    bfType(0 to 1) As Byte

    bfSize As Long

    bfReserved1 As Integer

    bfReserved2 As Integer

    bfOffBits As Long

End Type

      
說明:
bfType 訓示檔案的類型,必須是“BM”
bfSize# 訓示檔案的大小,包括BITMAPFILEHEADER
bfReserved1 保留,=0
bfReserved2 保留,=0
bfOffBits# 從檔案頭到位圖資料的偏移位元組數

檔案資訊頭——BITMAPINFOHEADER

原型定義:
typedef struct tagBITMAPINFOHEADER{ // bmih

    DWORD  biSize;

    LONG   biWidth;

    LONG   biHeight;

    WORD   biPlanes;

    WORD   biBitCount;

    DWORD  biCompression;

    DWORD  biSizeImage;

    LONG   biXPelsPerMeter;

    LONG   biYPelsPerMeter;

    DWORD  biClrUsed;

    DWORD  biClrImportant;

} BITMAPINFOHEADER;

      
VB聲明:
Type BITMAPINFOHEADER

    biSize As Long

    biWidth As Long

    biHeight As Long

    biPlanes As Integer

    biBitCount As Integer

    biCompression As Long

    biSizeImage As Long

    biXPelsPerMeter As Long

    biYPelsPerMeter As Long

    biClrUsed As Long

    biClrImportant As Long

End Type

      
說明:
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,表示所有顔色都是重要的

調色闆

  隻有biBitCount等于1、4、8時才有調色闆。調色闆實際上是一個數組,元素的個數由biBitCount和biClrUsed決定。

原型定義:
typedef struct tagRGBQUAD { // rgbq

    BYTE    rgbBlue;

    BYTE    rgbGreen;

    BYTE    rgbRed;

    BYTE    rgbReserved;

} RGBQUAD;

      
VB聲明:
Private Type RGBQUAD

    rgbBlue As Byte

    rgbGreen As Byte

    rgbRed As Byte

    rgbReserved As Byte

End Type

      
說明:
rgbBlue 藍色分量
rgbGreen 綠色分量
rgbRed 紅色分量
rgbReserved# 保留,=0

位圖資料

◆ 掃描行:

  一行的圖像資料叫做一個掃描行。一個掃描行的長度必須是4的倍數(位元組),如果不是,則需要補齊。計算公式:LineBytes=((biWidth*biBitCount+31)And &HFFFFFFE0)/8

  由于BMP設定者認為數學坐标系更總要,是以DIB的掃描行是逆序存儲的(相對于螢幕坐标系而言),即螢幕上的第一行是DIB位圖資料的最後一行。

◆ 1位色:

  用1位表示一個像素,是以一個位元組可以表示8個像素。坐标是從最左邊(最高位)開始的,而不是一般情況下的最低位。在記憶體的擺放形式如下:

位元組 ...
7 6 5 4 3 2 1
像素 1 2 3 4 5 6 7

◆ 4位色:

  用4位表示一個像素,是以一個位元組可以表示2個像素。坐标是從最左邊(最高位)開始的,而不是一般情況下的最低位。在記憶體的擺放形式如下:

位元組 ...
7 6 5 4 3 2 1
像素 1
像素位 3 2 1 3 2 1

◆ 8位色:

  用8位表示一個像素,是以一個位元組剛好隻能表示一個像素。在記憶體的擺放形式如下:

位元組 1 ...
像素 1

◆ 16位色:

  用16位表示一個像素,是以兩個位元組可以表示1個像素。預設情況下16位DIB是555格式,最高位無效(這對VB是個福音,因為VB沒有16位無符号型)。在記憶體的擺放形式如下(PC機使用小端規則(little endian),是低位元組在前):

位元組 1 2 3 ...
7 6 5 4 3 2 1 7 6 5 4 3 2 1 7 6 5 4 3 2 1 7 6 5 4 3 2 1
像素 1
RGB G B x R G G B x R G
RGB位 2 1 4 3 2 1 4 3 2 1 4 3 2 1 4 3 2 1 4 3 2 1 4 3

◆ 24位色:

  用24位表示一個像素,是以三個位元組可以表示1個像素。注意它的順序是B G R ,而不是傳統的R G B 。在記憶體的擺放形式如下:

位元組 1 2 3 4 5 ...
像素 1
RGB B G R B G R

◆ 32位色:

  用32位表示一個像素,是以四個位元組可以表示1個像素。注意絕大多數的GDI函數不會處理Alpha通道(隻有AlphaBlend支援)。在記憶體的擺放形式如下:

位元組 1 2 3 4 5 6 7 ...
像素 1
RGB B G R A B G R A

三、DIB通路函數

SetDIBitsToDevice

原型定義:
int SetDIBitsToDevice(

  HDC hDC,              // handle to device context

  int XDest,            // x-coordinate of upper-left corner of dest. rect.

  int YDest,            // y-coordinate of upper-left corner of dest. rect.

  DWORD dwWidth,        // source rectangle width

  DWORD dwHeight,       // source rectangle height

  int XSrc,             // x-coordinate of lower-left corner of source rect.

  int YSrc,             // y-coordinate of lower-left corner of source rect.

  UINT uStartScan,      // first scan line in array

  UINT cScanLines,      // number of scan lines

  CONST VOID *lpvBits,  // address of array with DIB bits

  CONST BITMAPINFO *lpbmi,  // address of structure with bitmap info.

  UINT fuColorUse       // RGB or palette indexes

);

      
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
DIB_PAL_COLORS 顔色表是一個整數數組,其中包含了與目前選入hDC裝置場景的調色闆相關的索引
DIB_RGB_COLORS 顔色表包含了RGB顔色

StretchDIBits

原型定義:
int StretchDIBits(

  HDC hDC,                // handle to device context

  int XDest,              // x-coordinate of upper-left corner of dest. rectangle

  int YDest,              // y-coordinate of upper-left corner of dest. rectangle

  int nDestWidth,         // width of destination rectangle

  int nDestHeight,        // height of destination rectangle

  int XSrc,               // x-coordinate of upper-left corner of source rectangle

  int YSrc,               // y-coordinate of upper-left corner of source rectangle

  int nSrcWidth,          // width of source rectangle

  int nSrcHeight,         // height of source rectangle

  CONST VOID *lpBits,            // address of bitmap bits

  CONST BITMAPINFO *lpBitsInfo,  // address of bitmap data

  UINT iUsage,                   // usage flags

  DWORD dwRop                    // raster operation code

);

      
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
DIB_PAL_COLORS 顔色表是一個整數數組,其中包含了與目前選入hDC裝置場景的調色闆相關的索引
DIB_RGB_COLORS 顔色表包含了RGB顔色
dwRop 欲進行的光栅運算

CreateDIBitmap

原型定義:
HBITMAP CreateDIBitmap(

  HDC hDC,                  // handle to device context

  CONST BITMAPINFOHEADER *lpbmih,  // pointer to bitmap size and format data

  DWORD fdwInit,            // initialization flag

  CONST VOID *lpbInit,      // pointer to initialization data

  CONST BITMAPINFO *lpbmi,  // pointer to bitmap color-format data

  UINT fuUsage              // color-data usage

);

      
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
DIB_PAL_COLORS 顔色表是一個整數數組,其中包含了與目前選入hDC裝置場景的調色闆相關的索引
DIB_RGB_COLORS 顔色表包含了RGB顔色

CreateDIBSection

原型定義:
HBITMAP CreateDIBSection(

  HDC hDC,          // handle to device context

  CONST BITMAPINFO *lpbmi,

                    // pointer to structure containing bitmap size, format, and color data

  UINT iUsage,      // color data type indicator: RGB values or palette indexes

  VOID *ppvBits,    // pointer to variable to receive a pointer to  the bitmap's bit values

  HANDLE hSection,  // optional handle to a file mapping object

  DWORD dwOffset    // offset to the bitmap bit values within the file mapping object

);

      
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
DIB_PAL_COLORS 顔色表是一個整數數組,其中包含了與目前選入hDC裝置場景的調色闆相關的索引
DIB_RGB_COLORS 顔色表包含了RGB顔色
ppvBits 用于得到DIBSection資料區的記憶體位址
hSection 指向一個檔案映射對象的可選句柄,位圖将在其中建立。如設為零,Windows會自動配置設定記憶體
dwOffset 如指定了句柄,就用這個參數指定位圖資料在檔案映射對象中的偏移量

GetDIBits

原型定義:
int GetDIBits(

  HDC hDC,           // handle to device context

  HBITMAP hbmp,      // handle to bitmap

  UINT uStartScan,   // first scan line to set in destination bitmap

  UINT cScanLines,   // number of scan lines to copy

  LPVOID lpvBits,    // address of array for bitmap bits

  LPBITMAPINFO lpbmi,// address of structure with bitmap data

  UINT uUsage        // RGB or palette index

);

      
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
DIB_PAL_COLORS 顔色表是一個整數數組,其中包含了與目前選入hDC裝置場景的調色闆相關的索引
DIB_RGB_COLORS 顔色表包含了RGB顔色

SetDIBits

原型定義:
int SetDIBits(

  HDC hDC,                  // handle to device context

  HBITMAP hbmp,             // handle to bitmap

  UINT uStartScan,          // starting scan line

  UINT cScanLines,          // number of scan lines

  CONST VOID *lpvBits,      // array of bitmap bits

  CONST BITMAPINFO *lpbmi,  // address of structure with bitmap data

  UINT uUsage               // type of color indexes to use

);

      
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
DIB_PAL_COLORS 顔色表是一個整數數組,其中包含了與目前選入hDC裝置場景的調色闆相關的索引
DIB_RGB_COLORS 顔色表包含了RGB顔色

GetDIBColorTable

原型定義:
UINT GetDIBColorTable(

  HDC hDC,          // handle to device context whose DIB is of interest

  UINT uStartIndex, // color table index of first entry to retrieve

  UINT cEntries,    // number of color table entries to retrieve

  RGBQUAD *pColors  // pointer to buffer that receives color table entries

);

      
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 這個結構數組用于裝載顔色表資訊的第一個條目

SetDIBColorTable

原型定義:
UINT SetDIBColorTable(

  HDC hDC,                // handle to device context whose DIB is of interest

  UINT uStartIndex,       // color table index of first entry to set

  UINT cEntries,          // number of color table entries to set

  CONST RGBQUAD *pColors  // pointer to array of color table entries

);

      
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”中,它就是管繪制的。

vb6/1_PSet/FrmMain.frm中DrawIt

'繪制

Private Sub DrawIt()

    Dim I As Long, J As Long

    Static K As Long

    

    For I = 0 To ImgHeight - 1 'Y

        For J = 0 To ImgWidth - 1 'X

            PicView.PSet (J, I), RGB(J And &HFF, I And &HFF, (J + K) And &HFF)

        Next J

    Next I

    

    K = (K + 1) And &HFF

    

End Sub

      

  其實我這個示範程式蠻簡單的: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。

vb6/3_DIB/FrmMain.frm中DrawIt

'繪制

Private Sub DrawIt()

    Dim I As Long, J As Long

    Static K As Long

    Dim iLinePtr As Long, iCurPtr As Long

    

    iLinePtr = (m_BI.biHeight - 1) * m_LineBytes 'DIB是逆序存儲的

    For I = 0 To ImgHeight - 1 'Y

        iCurPtr = iLinePtr

        For J = 0 To ImgWidth - 1 'X

            'PicView.PSet (J, I), RGB(J And &HFF, I And &HFF, (J + K) And &HFF)

            m_MapData(iCurPtr + 2) = J And &HFF       'Red

            m_MapData(iCurPtr + 1) = I And &HFF       'Green

            m_MapData(iCurPtr + 0) = (J + K) And &HFF 'Blue

            iCurPtr = iCurPtr + 3 '24位

        Next J

        iLinePtr = iLinePtr - m_LineBytes

    Next I

    

    K = (K + 1) And &HFF

    

End Sub

      

  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:

vb6/4_DIB_Ptr/FrmMain.frm中DrawIt

'繪制

Private Sub DrawIt()

    Dim I As Long, J As Long

    Static K As Long

    Dim pByte() As Byte, pBytePtr As SAFEARRAY1D

    Dim iLinePtr As Long

    

    ' check Image

    Debug.Assert m_pDIB <> 0

    

    '建立模拟指針

    MakePoint VarPtrArray(pByte), pBytePtr, 1

    

    Ptr(pBytePtr) = m_pDIB + (m_BI.biHeight - 1) * m_LineBytes 'DIB是逆序存儲的

    iLinePtr = pBytePtr.pvData

    For I = 0 To ImgHeight - 1 'Y

        pBytePtr.pvData = iLinePtr

        For J = 0 To ImgWidth - 1 'X

            'PicView.PSet (J, I), RGB(J And &HFF, I And &HFF, (J + K) And &HFF)

            pByte(2) = J And &HFF       'Red

            pByte(1) = I And &HFF       'Green

            pByte(0) = (J + K) And &HFF 'Blue

            pBytePtr.pvData = pBytePtr.pvData + 3 '24位

        Next J

        iLinePtr = iLinePtr - m_LineBytes

    Next I

    

    '釋放模拟指針

    FreePoint VarPtrArray(pByte)

    

    K = (K + 1) And &HFF

    

End Sub

      

  1.為了示範DIBSection能夠像HBITMAP一樣,我在Form_Load中建立了DC、将DIBSection選入DC。同時為了釋放,Form_UnLoad中寫了釋放代碼。

  2.在DrawIt中,注意處理部分的代碼并沒有與VB_DIB差多少,隻不過把索引計算改為位址運算而已。

  再來比較vc的代碼,感覺跟真正的指針用法差不多嘛:

vc/ImgTestDlg.cpp中CImgTestDlg::DrawIt

void CImgTestDlg::DrawIt() 

{

	int i=0,j=0;

	static int k=0;

	BYTE *pByte, *pLinePtr;



	pLinePtr = (BYTE*)pDIB + (bi.biHeight-1)*LineBytes;

	for(i=0; i<IMGHEIGHT; i++)

	{

		pByte = pLinePtr;

		for(j=0; j<IMGWIDTH; j++)

		{

			pByte[2] = j & 0xff;

			pByte[1] = i & 0xff;

			pByte[0] = (j+k) & 0xff;

			pByte = pByte + 3;

		}

		pLinePtr = pLinePtr - LineBytes;

	}



	k = (k+1) & 0xff;



}

      

六、結合DirectX

  在GDI中,我們可以用DIB直接操作圖像資料,那麼号稱能直接通路顯存的DirectX呢?   一查DirectX SDK,發現IDirectDrawSurface接口的Lock函數可以鎖定表面,進而直接通路該位圖資料。再在VB的對象浏覽器中仔細觀察 “DirectX 7 for Visual Basic Type Library”,發現DirectDrawSurface7對象也有該方法。   看看5_DX7Ptr:

vb6/5_DX7Ptr/FrmMain.frm中Render

'渲染

Private Sub Render()

    Dim I As Long, J As Long

    Static K As Long

    Dim ddsdInfo As DDSURFACEDESC2

    Dim IsOK As Boolean

    Dim cbPitch As Long

    Dim cbPixel As Long

    Dim iIdxR As Long

    Dim iIdxG As Long

    Dim iIdxB As Long

    Dim iMaxX As Long, iMaxY As Long

    Dim pByte() As Byte, pBytePtr As SAFEARRAY1D

    Dim iLinePtr As Long

    

    ' check Image

    'Debug.Assert m_pDIB <> 0

    Debug.Assert Not m_ddsRender Is Nothing

    

    ' Main

    With m_ddsRender

        'Render

        Call .Lock(m_rctSurf, ddsdInfo, DDLOCK_SURFACEMEMORYPTR Or DDLOCK_WRITEONLY Or DDLOCK_NOSYSLOCK Or DDLOCK_WAIT, 0)

        IsOK = CheckPixelFormat(ddsdInfo.ddpfPixelFormat)

        If IsOK Then

            With ddsdInfo

                Debug.Assert .lpSurface

                cbPitch = .lPitch

                With .ddpfPixelFormat

                    cbPixel = (.lRGBBitCount) / 8

                    iIdxR = MaskToRShift(.lRBitMask) / 8

                    iIdxG = MaskToRShift(.lGBitMask) / 8

                    iIdxB = MaskToRShift(.lBBitMask) / 8

                End With

                iMaxX = .lWidth - 1

                iMaxY = .lHeight - 1

            End With

            

            '建立模拟指針

            MakePoint VarPtrArray(pByte), pBytePtr, 1

            

            Ptr(pBytePtr) = ddsdInfo.lpSurface

            iLinePtr = pBytePtr.pvData

            For I = 0 To iMaxY 'Y

                pBytePtr.pvData = iLinePtr

                For J = 0 To iMaxX 'X

                    'PicView.PSet (J, I), RGB(J And &HFF, I And &HFF, (J + K) And &HFF)

                    pByte(iIdxR) = J And &HFF       'Red

                    pByte(iIdxG) = I And &HFF       'Green

                    pByte(iIdxB) = (J + K) And &HFF 'Blue

                    pBytePtr.pvData = pBytePtr.pvData + cbPixel '下一個像素

                Next J

                iLinePtr = iLinePtr + cbPitch '下一個掃描行

            Next I

            

            '釋放模拟指針

            FreePoint VarPtrArray(pByte)

            

        End If

        Call .Unlock(m_rctSurf)

        

        'Error Pixel Format

        If IsOK = False Then

            Call .BltColorFill(m_rctSurf, vbBlack)

            Call .SetForeColor(vbWhite)

            Call .DrawText(0, &H20, "Error Pixel Format!", False)

        End If

        

    End With

    

    K = (K + 1) And &HFF

    

End Sub

      

  1.由于硬體裝置性能不同,是以不要指望像DIB那樣可以認定RGB位元組順序。應該根據Lock方法傳回的DDSURFACEDESC2結構來确認RGB位元組順序(iIdxR、iIdxG、iIdxB)、像素所占位元組(cbPixel)及寬距(cbPitch)。

  2.由于我們有了模拟指針技術,是以這段代碼與跟先前4_DIB_Ptr差不多,特别是與在VC中使用IDirectDrawSurface::Lock的處理代碼差不多。

  仔細觀察對象浏覽器的人會發現,DirectDrawSurface7對象GetLockedArray可以取得鎖定後的位圖資料:

vb6/6_DX7Arr/FrmMain.frm中Render

'渲染

Private Sub Render()

    Dim I As Long, J As Long

    Static K As Long

    Dim ddsdInfo As DDSURFACEDESC2

    Dim IsOK As Boolean

    Dim cbPitch As Long

    Dim cbPixel As Long

    Dim iIdxR As Long

    Dim iIdxG As Long

    Dim iIdxB As Long

    Dim iMaxX As Long, iMaxY As Long

    Dim pByte() As Byte

    Dim iCurPtr As Long

    

    ' check Image

    'Debug.Assert m_pDIB <> 0

    Debug.Assert Not m_ddsRender Is Nothing

    

    ' Main

    With m_ddsRender

        'Render

        Call .Lock(m_rctSurf, ddsdInfo, DDLOCK_SURFACEMEMORYPTR Or DDLOCK_WRITEONLY Or DDLOCK_NOSYSLOCK Or DDLOCK_WAIT, 0)

        IsOK = CheckPixelFormat(ddsdInfo.ddpfPixelFormat)

        If IsOK Then

            With ddsdInfo

                Debug.Assert .lpSurface

                cbPitch = .lPitch

                With .ddpfPixelFormat

                    cbPixel = (.lRGBBitCount) / 8

                    iIdxR = MaskToRShift(.lRBitMask) / 8

                    iIdxG = MaskToRShift(.lGBitMask) / 8

                    iIdxB = MaskToRShift(.lBBitMask) / 8

                End With

                iMaxX = .lWidth - 1

                iMaxY = .lHeight - 1

            End With

            

            Call .GetLockedArray(pByte)

            

            For I = 0 To iMaxY 'Y

                iCurPtr = 0

                For J = 0 To iMaxX 'X

                    'PicView.PSet (J, I), RGB(J And &HFF, I And &HFF, (J + K) And &HFF)

                    pByte(iCurPtr + iIdxR, I) = J And &HFF      'Red

                    pByte(iCurPtr + iIdxG, I) = I And &HFF      'Green

                    pByte(iCurPtr + iIdxB, I) = (J + K) And &HFF 'Blue

                    iCurPtr = iCurPtr + cbPixel '下一個像素

                Next J

            Next I

            

        End If

        Call .Unlock(m_rctSurf)

        

        'Error Pixel Format

        If IsOK = False Then

            Call .BltColorFill(m_rctSurf, vbBlack)

            Call .SetForeColor(vbWhite)

            Call .DrawText(0, &H20, "Error Pixel Format!", False)

        End If

        

    End With

    

    K = (K + 1) And &HFF

    

End Sub

      

  這個就是官方推薦做法,使用一個二維數組來處理位圖資料。但是就是因為其使用的是二維數組,是以它比一維數組的模拟指針慢一些。如果你不想使用非正常技術(模拟指針),可以使用GetLockedArray。

   注意到這點沒有,GetLockedArray是一個方法而不是一個屬性,我們需要将數組變量傳遞過去,然後就可以利用該數組直接操作位圖資料了。啊 哈!明白了GetLockedArray也是利用模拟指針技術實作的,隻不過它填充的是2維的SAFEARRAY結構而已。

  注意:隻操作位于系統記憶體的表面,千萬别操作對顯存中的表面。這是因為CPU通路顯存比返問記憶體要慢許多!這個建議并不是絕對的,如果對該表面的Blt的調用次數遠超過自己寫的圖像處理操作的話,可以将該表面放在顯存,或者是在記憶體中計算好後再一次性送出到顯存。

  本程式建立圖像處理操作的表面的代碼:

vb6/5_DX7Ptr/FrmMain.frm中CreateSurfaces

' init Image

    With ddsdInfo

        .lFlags = DDSD_CAPS Or DDSD_HEIGHT Or DDSD_WIDTH Or DDSD_PIXELFORMAT

        With .ddsCaps

            .lCaps = DDSCAPS_OFFSCREENPLAIN Or DDSCAPS_SYSTEMMEMORY 'CPU通路記憶體比通路顯存快

        End With

        .lWidth = ImgWidth

        .lHeight = ImgHeight

        .ddpfPixelFormat = m_ddsdInfo.ddpfPixelFormat

    End With

    Set m_ddsRender = m_dxDraw.CreateSurface(ddsdInfo)