DirectDraw像素操作

今天我們將分別使用調色板和RGB模式來熟悉DirectDraw的基本圖形。它們有什麼不同呢?如果你曾經在DOS下編程,你可能使用過調色板映射模式。調色板是個顏色查詢表,爲了繪製象素,你將一個單獨的字節寫入視頻內存,通過這個字節你可以索引到一個擁有各種顏色的鏈表,這個顏色的鏈表,或查詢表就叫作調色板。而RGB模式是不同的,因爲它不需要顏色查詢表。在RGB模式下繪製一個象素,你可以直接把紅色、綠色和藍色的值寫入視頻內存。任何色彩深度高於8位的調色板都可以用RGB模式取代。

  編寫本文時,我假設你已經讀過了前面幾章,知道了怎樣設置DirectDraw和創建表面。我們將使用DirectX7,它包含了最新的DirectDraw接口。實際上,DirectX 7 中的DirectDraw接口可能是最後的升級版本了!不用擔心,未來的版本一定會兼容它的,但是未來可能是一個DirectDraw和Direct3D綜合的產品,管它那,我們學的不會沒有用的。

  在開始前我還有最後一件事要提醒你:在我的後續文章中關於調色板的部分可能再也用不到了,所以,如果你對於調色板模式不是很感興趣,你可以跳過文章的前一部分,從象素格式開始看起。調色板的開發和使用是PC中使用的原始視頻系統的內存限制帶來的直接後果。現在由於充足的顯存使調色板模式幾乎廢棄不用了。值得保留調色板模式的一個原因是,執行調色板操作可以實現一些有趣的動畫效果。不羅嗦了,讓我們開始吧!

  創建DirectDraw的調色板

  當你在色彩深度爲8位或低於8位的模式下顯示圖形時,你必須創建調色板,也就是顏色查詢表。更明確的講,對於DirectX,調色板就是PALETTEENTRY結構。要建立一個調色板,我們要做如下三步:

  1、 創建顏色查詢鏈表。

  2、 得到指向IDirectDrawPalette接口的指針。

  3、 把調色板鏈接到DirectDraw表面。

  我假設我們使用的是8位色彩深度。如果你要用16位或更高位的色彩深度編寫遊戲,你就不用繼續看以下這段瘋狂的Windows素材了。總之,8位色彩深度,我們可以有一個256個條目的調色板。所以,創建顏色查詢鏈表,有256個條目在其中:

typedef struct tagPALETTEENTRY {
 // pe
 BYTE peRed;
 BYTE peGreen;
 BYTE peBlue;
 BYTE peFlags;
} PALETTEENTRY;

  頭三個參數很明顯,分別是紅色、綠色和藍色的強度。每一個取值範圍0-255,BYTE是無符號數據類型。最後一個參數是控制標誌,應該設置爲PC_NOCOLLAPSE。原因我就不說了。

  現在,我們需要把256個條目有秩序的排列好,也就是爲了一下能找到,我們爲鏈表設置一個數組,象這樣:

PALETTEENTRY palette[256];

  Ok,我們有了數組了,你可以裝載顏色了。當我工作在調色板模式下時,通常把顏色存儲在一個外部文件裏,然後用一些如下的東東裝載顏色:

FILE* file_ptr;
int x;

if ((file_ptr = fopen("palette.dat", "rb")) != NULL)
{
 fread(palette, sizeof(PALETTEENTRY), 256, file_ptr);
 fclose(file_ptr);
}

  All right,第一步完成了。現在我們需要得到調色板的接口。交給IDirectDraw7::CreatePalette()函數就好了:

HRESULT CreatePalette(
 DWORD dwFlags,
 LPPALETTEENTRY lpColorTable,
 LPDIRECTDRAWPALETTE FAR *lplpDDPalette,
 IUnknown FAR *pUnkOuter
);

  返回類型是HRESULT,你知道它的,所以可以用FAILED()和SUCCEEDED()這兩個宏檢測函數是否調用成功。參數的說明如下:

  ※ DWORD dwFlags:描述調色板對象的標誌常量。當然,你可以用“|”組合它們:

   · DDPCAPS_1BIT:1位色彩,對應2色調色板。

   · DDPCAPS_2BIT:2位色彩,對應4色調色板。

   · DDPCAPS_4BIT:4位色彩,對應16色調色板。

   · DDPCAPS_8BIT:8爲色彩,對應256色調色板。

   · DDPCAPS_8BITENTRIES:指出引用一個8位色彩索引。就是說,每個顏色條目是它本身的到目的表面8位調色板的索引。這叫作被變址的調色板。它必須同DDPCAPS_1BIT、DDPCAPS_2BIT,或者DDPCAPS_4BIT合用。亂套吧!^_^

   · DDPCAPS_ALPHA:每一個PALETTEENTRY的peFlags成員都應該被認爲是阿爾發值。用這些標誌創建的調色板可以被粘貼在Dierct3D紋理表面,因爲DirectDraw本身並不支持阿爾發混合。

   · DDPCAPS_ALLOW256:允許8位調色板的全部256個條目被使用。通常,0指向黑色,255指向白色。

   · DDPCAPS_INITIALIZE:指出應該用PALETTEENTRY的數組初始化調色板。

   · DDPCAPS_PRIMARYSURFACE:調色板將鏈接到主表面,好快速更改顯示顏色。

   · DDPCAPS_VSYNC:一般畫圓時用到它。

  大多數情況,你將使用DDPCAPS_8BIT | DDPCAPS_INITIALIZE,如果你剛好想建立一個空的調色板,稍後再設置它,你可以去掉後者,就是DDPCAPS_INITIALIZE。當然,你還可以使用DDPCAPS_ALLOW256,如果你真的想改變這兩個常用的條目。

  ※ LPPALETTEENTRY lpColorTable:這個指針指向我們創建的查詢表,把數組的名稱傳遞給它就好了。

  ※ LPDIRECTDRAWPALETTE FAR *lplpDDPalette:這是指向IDirectDrawPalette接口指針的地址。如果函數調用成功,它將被初始化。

  ※ IUnkown FAR *pUnkOuter:同以前一樣,這總是爲COM高級應用準備的。設置爲NULL好了。

  不是太糟糕吧!現在我們可以建立我們的調色板對象了。最後一步是把調色板鏈接到一個表面,這只需要一個函數就好了——IDirectDrawSurface7::Setpalette()。它的原形如下:

HRESULT SetPalette(LPDIRECTDRAWPALETTE lpDDPalette);

  很簡單,是不是?你只要把上一步得到的接口指針傳遞給它就可以了。那好,讓我們把學到的綜合到一起,下面我給你一個程序框架,我假設我們已經利用調色板的數組建立了一個索引鏈表,就像我們上一步做的。該框架是建立DirectDraw調色板來控制顏色,並且把它鏈接到主表面(當然,主表面是我們事先做好的):

LPDIRECTDRAWPALETTE lpddpal;
// create the palette object
if (FAILED(lpdd7->CreatePalette(DDPCAPS_8BIT | DDPCAPS_INITIALIZE, palette, &lpddpal, NULL)))
{
 // error-handling code here
}

// attach to primary surface
if (FAILED(lpddsPrimary->SetPalette(lpddpal)))
{
 // error-handling code here
}

  就是這麼簡單。一旦你的調色板建立完成,繪製象素部分同RGB模式就沒有什麼不同了。從此時開始,我將同時介紹RGB模式和調色板模式,在我們真正的顯示圖象前,我需要告訴你什麼是RGB象素格式。

  象素格式

  象我前面說過的,當你把一個調色板模式的象素寫入內存時,你同時分配了一個字節,每個字節表示一個到色彩查詢表的索引。在RGB模式下,你只需要把顏色描述值寫入內存,但每個顏色需要的字節數都要多於一個字節。字節的多少同色彩的深度相關。對於16-bit色彩,你要爲每個象素準備兩個字節(16位),以此類推,你可以猜到32-bit色彩是怎麼回事了,這些都是很容易理解的。32-bit色彩對於一個象素來說,每一位的字符如下:

AAAA AAAA RRRR RRRR GGGG GGGG BBBB BBBB

  “A”是代表“alpha”(阿爾發),表示一個透明的值,這是爲Direct3D準備的。我以前說過,DirectDraw不支持α混合,所以當你爲DirectDraw創建32-bit色彩時,把高位都設置爲0好了。下一個8位表示紅色強度的值,再下一個8位表示綠色,最後8位表示藍色。
一個32-bit色彩的象素需要32位,所以我們一般用UINT類型來定義相對應的變量類型,這是一個無符號實數類型。通常我用一個宏來把RGB數據轉換成正確的象素格式。讓我給你看看它的樣子,希望這能更好的幫助你理解象素格式:

#define RGB_32BIT(r, g, b) ((r << 16) | (g << 8) | (b))

  就象你看到的,這個宏通過位移在相應的位置寫入了相應的紅、綠、藍的強度值,並且完全符合正確的象素格式。是不是開始感覺有點兒入門了?要建立一個32-bit的象素,你就可以調用這個宏。紅、綠、藍每一個顏色的強度值都是8位,它們的取值範圍都是從0——255。例如建立一個白色的象素,你可以這樣:

UINT white_pixel = RGB_32BIT(255, 255, 255);

  24-bit色彩基本相同,道理實際上是一樣的,只是24-bit沒有關於α的描述,也就是少了α那8位。象素格式如下:

RRRR RRRR GGGG GGGG BBBB BBBB

  所以紅色、綠色、藍色仍然都分別是8位,這就意味着24-bit色彩和32-bit色彩實際上是有相同顏色深度的,只是32-bit多了個α混合。現在,你一定會想,24-bit比32-bit要好,真的是這樣嗎?否,因爲使用24-bit有一些麻煩,事實上沒有24-bit的數據類型,在你建立象素時,你不得不分三步寫入紅、綠、藍的強度值,而不是象32-bit一次就完成。儘管32-bit色彩需要更多的內存,但在大多數的機器上,它要更快一些。實際上,很多顯示卡不支持24-bit色彩模式,因爲每一個象素佔用3個字節是很不方便的。

  現在,輪到16-bit色彩了,它有一點兒小麻煩,因爲對於16-bit色彩,不是每一種顯示卡都使用相同的象素格式!有兩種格式。其中一種,也是比較流行的,紅色佔據5位,綠色佔據6位,藍色佔據剩下的5位。另一種格式是分別都佔據5位,剩下的一位,也就是高位不使用,一些老的顯示卡都使用這種格式。所以這兩種格式看起來是這樣的:

565 format: RRRR RGGG GGGB BBBB
555 format: 0RRR RRGG GGGB BBBB

  當你工作在16-bit色彩深度下,你首先需要檢測顯示卡是支持565格式還是555格式,然後使用適當的方式。這是很討厭的,但你堅持用16-bit色彩,這是沒有辦法避免的。由於存在兩種格式,你就需要兩種宏:

#define RGB_16BIT565(r, g, b) ((r << 11) | (g << 5) | (b))
#define RGB_16BIT555(r, g, b) ((r << 10) | (g << 5) | (b))

  對於565格式,紅色和藍色的取值範圍是0——31,綠色是0——63;對於555格式,取值範圍都是0——31,所以當要創建一個白色象素時,就會有所不同:

USHORT white_pixel_565 = RGB_16BIT565(31, 63, 31);
USHORT white_pixel_555 = RGB_15BIT555(31, 31, 31);

  這個USHORT是無符號短實數類型,對應的變量只有16位。存在兩種格式把事情搞得有些複雜,但在實際的遊戲編程過程中,你將會感覺到這並沒有你想象的那麼討厭。順便說一下,有些時候555格式被稱爲15-bit色彩深度,所以在以後如果我這樣談到了它,你一定要心領神會哦!

  現在或許是告訴你在16-bit色彩深度模式下,怎樣檢測顯示卡到底支持哪種格式的時機了,是555還是565呢?最簡單的辦法就是調用IDirectDrawSurface7接口下的GetPixelFormat()函數,它的原形如下:

HRESULT GetPixelFormat(LPDDPIXELFORMAT lpDDPixelFormat);

  參數是指向DDPIXELFORMAT結構的指針。你只要聲明它,初始化它,然後傳遞它的地址就一切OK了。這個結構的本身是巨大的,所以我就不列舉它了,但我得告訴你它的三個成員,都是DWORD類型的,它們是dwRBitMask、dwGBitMask、和dwBBitMask。你可以從dwRBitMask、dwGBitMask和dwBBitMask中獲得掩碼值(新東東,先不用太明白)。你也可以用它們檢測顯示卡支持的格式。如果顯示卡支持565,dwGBitMask將爲0x07E0。如果是555格式,dwGbitMask爲0x03E0。

  現在,我們已經學習了所有我們可能用到的象素格式,可以進入在DirectX下顯示圖象的實際階段了。你已經等待了很久了,不是嗎?在把象素放到表面上前,我們需要鎖定表面,至少是鎖定表面的一部分。鎖定表面返回一個指向表面在內存裏位置的指針,然後,我們就可以爲所欲爲了。
  鎖定表面

  沒什麼令人意外的東東,我們將使用的函數是IDirectDrawSurface7::Lock()。讓我們仔細看看它:

HRESULT Lock(
 LPRECT lpDestRect,
 LPDDSURFACEDESC lpDDSurfaceDesc,
 DWORD dwFlags,
 HANDLE hEvent
);

  一定要檢測函數的調用是否成功,否則可能會有大麻煩的:如果鎖定失敗,而返回的指針指向了一個不正確的內存區域,你若操控該區域,很有可能導致系統的混亂。函數的參數有以下這些組成:

  ※ LPRECT lpDestRect:是一個指向RECT結構的指針,它描述了將要被直接訪問的表面上的矩形區。該參數被設置爲NULL,以鎖定整個表面。

  ※ LPDDSURFACEDESC2 lpDDSurfaceDesc:是DDSURFACEDESC2類型的結構變量的地址,它由直接訪問表面內存所必需的全部信息進行填充。在該結構中返回的信息表面的基地址、間距和象素格式。

  ※ DWORD dwFlags:好像沒有幾個DirectX函數沒有這個東東的。下面列出幾個最有用的標誌常量:

   · DDLOCK_READONLY:被鎖定的表面爲只讀。(當然就不能寫入了)

   · DDLOCK_SURFACEMEMORYPTR:表面返回一個指向鎖定矩形左上角座標的有效指針;如果沒有指定矩形,那麼返回表面左上角的座標。此項爲默認且無需顯式的輸入。

   · DDLOCK_WAIT:如果其它線程或進程正在進行位轉換操作,不能鎖定表面內存,則一直等到該表面可以鎖定爲止或返回錯誤信息。

   · DDLOCK_WRITEONLY:被鎖定表面爲可寫。(當然就不能讀取了)

  由於我們使用鎖定去操控象素,你將總會用到DDLOCK_SURFACEMEMORYPTR。即使我們目前還沒有學習位塊操作,但使用DDLOCK_WAIT總是一個好主意。

  ※ HANDLE hEvent:沒用的東東,設置爲NULL好了。

  一旦我們鎖定了表面,我們需要查看一下DDSURFACEDESC2結構來獲取一些表面信息。我們以前介紹過這個結構,但在這裏,針對現在的課題,我們只需要它的兩個成員。由於它們都很重要,我就再重複一遍:

  ※ LONG lPitch:這個lPitch成員表示每個顯示行的字節數,也就是行間距。例如,對於640×480×16模式,每一行有640個象素,每一個象素需要兩個字節存放顏色信息,所以行間距應該爲1280個字節,對不對?Well,對於一些顯示卡,它的長度大於1280,每行上多於的內存不存放任何的圖象數據,但你必須讓它存在,因爲這種顯示卡在某種顯示模式下不能創建線性內存模式。的確,這種顯示卡的比例很小,但你需要考慮到它。

  ※ LPVOID lpSurface:這是指向內存中表面的指針。不管你使用何種顯示模式,DirectDraw都創建一個線性地址模式,使你能夠操控表面上的象素。 這個lpSurface指針是很容易理解的,而行間距是一個需要記住的重要值,因爲你將必須使用它去計算特殊象素的偏移量。
我們過一會兒在細說,因爲有一件事我們現在必須知道,當對鎖定的表面操作完成後,你需要釋放這個鎖定表面,這個函數IDirectDrawSurface7::Unlock()的原形爲:

HRESULT Unlock(LPRECT lpRect);

  參數同你傳遞給Lock()函數的要保持一致。都準備好了,讓我們畫一些象素吧!

  繪製象素

  首先是確定從Lock()函數得到的指針類型。邏輯上,我們希望指針的大小同象素的大小要保持一致。所以我們爲8-bit色彩深度分配了UCHAR*類型,USHORT*是16-bit的,UINT*是32-bit的。但是24-bit怎麼辦呢?因爲沒有與之相對應的數據類型,我們還是使用UCHAR*類型,但具體操作有一些不同。

  我們也應該把lPitch成員轉換成與指針相同的單位。記得嗎,當我們第一次從DDSURFACEDESC2結構得到lPitch時,它是以字節爲單位。對於16-bit模式,我們應該把它除以2以適應USHORT,對於32-bit我們應該把它除以4以適應UINT。

  在我們進行第二步前先看看實例代碼。假設我們在32-bit模式鎖定主表面來繪製象素。以下是代碼:

// declare and initialize structure
DDSURFACEDESC2 ddsd;
INIT_DXSTRUCT(ddsd);

// lock the surface
lpddsPrimary->Lock(NULL, &ddsd, DDLOCK_WAIT | DDLOCK_SURFACEMEMORYPTR, NULL);

// now convert the pointer and the pitch
UINT* buffer = (UINT*)ddsd.lpSurface;
UINT nPitch = ddsd.lPitch >> 2;

  現在讓我先一步告訴你象素繪製函數,然後我再解釋:

inline void PlotPixel32(int x, int y, UINT color, UINT *buffer, int nPitch)
{
 buffer[y*nPitch + x] = color;
}

  All right,讓我分別解說。首先,你可能已經注意到了我把它聲明爲一個inline函數,目的是消除傳遞所有參數時的輔助操作,例如每次我們想要做些簡單的事情(如繪製一個象素)。在函數裏,僅用了一行就定位了我們要繪製的點和設置了該點的顏色。注意,顏色僅僅是一個值,不是由紅、綠、藍分別組成的,所以我們需要使用宏RGB_32BIT()來設置這個顏色值。

  公式用來定位要繪製象素的具體位置——y*nPitch + x 。nPitch表示行間距,被y乘後就得到了正確的行數,再加上x,就得到了正確的位置。這就是你需要知道的,很簡單吧!讓我再告訴你在8-bit和16-bit模式下繪製象素的函數,它們都十分相象:

inline void PlotPixel8(int x, int y, UCHAR color, UCHAR* buffer, int byPitch)
{
 buffer[y*byPitch + x] = color;
}

inline void PlotPixel16(int x, int y, USHORT color, USHORT* buffer, int nPitch)
{
 buffer[y*nPitch + x] = color;
}

  幾個函數間唯一不同的就是參數數據類型的不同。應該還記得對於8-bit色彩深度,間距是以字節表示,對於16-bit,間距是以USHORT類型表示。現在,只剩下一個模式沒有說了,就是24-bit模式。由於沒有相應的數據類型,我們需要分別傳遞紅、綠、藍三個值,函數看起來應該如下:

inline void PlotPixel24(int x, int y, UCHAR r, UCHAR g, UCHAR b, UCHAR* buffer, int byPitch)
{
 int index = y*byPitch + x*3;

 buffer[index] = r;
 buffer[index+1] = g;
 buffer[index+2] = b;
}

  如你所看到的,它將工作慢一些,因爲它多了一次乘法運算,並且有三次內存寫操作。你可以用其它方法替換x*3加快一些速度,如(x+x+x)或者(x<<1)+x,但是不會有太大效果的。當然,她還沒有到應該放棄的地步。現在你就明白了爲什麼說24-bit色彩深度有點兒討厭了吧!
  關注速度

  你應該採取一些行動使程序儘可能會的運行。

  首先,鎖定一個表面並不是最快的,所以你要試圖鎖定表面上你要操作的最小矩形區域。對於很多操作,包括很簡單的繪製象素演示程序,你都應該鎖定最小的矩形區域。

  第二,讓我們就640×480×16模式來說,間距總是1280個字節,你應該試圖考慮有沒有更好的辦法表述它。當然,1280個字節你是不能改變的,但我們可以使公式最優化,用位移來替代乘法是一貫的加速方法。我們先前的公式是這樣的:

buffer[y*nPitch + x] = color;

  如果我們知道nPitch將會是640(由於nPitch是USHORT類型,不是字節),我們就可以加速它(我們本來就知道它是640)。640不是一個理想的位移數字,但512是2的9次冪,128是2的7次冪,你猜到了吧,512+128=640。^_^ 很棒吧?我們就可以用下面這個更快的公式取代先前的公式:

buffer[(y<<9) + (y<<7) + x] = color;

  多數的解決辦法都是分解成2的幾次冪,有的需要動一點兒腦筋,如800×600(512+256+32=800),小菜一碟哦!位移是我們應用的最快的運算符。

  最後,如果你要使用兩個函數—— 一個做乘法運算,一個做位移運算,要將比較判斷放到循環的外部,不能象下面這樣:

for (x=0; x<1000; x++)
{
if (nPitch == 640)
PlotPixelFast16();
else
PlotPixel16();
}

  判斷部分使你的優勢殆盡,你應該這樣做:

if (nPitch == 640)
{
for (x=0; x<1000; x++)
PlotPixelFast16( parameters );
}
else
{
for (x=0; x<1000; x++)
PlotPixel16( parameters );
}

  有意義吧?無論何時用大的循環,都應該儘量把判斷放到循環的外部,沒有必要進行上千次同樣的比較判斷。同理,如果你要繪製象素,形成有規律的圖案,如水平線或垂直線,甚至是斜線,你都沒有必要每一次都重複確定象素的位置。看看下面的例子,畫一條任意顏色的直線:

for (x=0; x<640; x++)
PlotPixel16(x, 50, color, buffer, pitch);

  函數每次都重複計算正確的行,你可以一次就把行指定好。下面是快一點兒的做法:

// get the address of the line
USHORT* temp = &buffer[50*pitch];

// plot the pixels
for (x=0; x<640; x++)
{
 *temp = color;
 temp++;
}

  你可能認爲節省這麼一點點時間意義不大,但當你進行千萬次的循環時,意義就很大了。遊戲程序員總是想辦法提高遊戲的速度的。
看看以前的文章,我們已經進行了好長時間的鋪墊了。現在,我們知道了怎樣繪製象素了,讓我們看看能用現在學到的做些什麼。

  淡出操作

  在遊戲中最常用到的屏幕操作就是淡出成黑色,或者從黑色淡入。兩種方式是同樣的機理:你簡單畫出你的圖象,然後申請一些屏幕轉換來改變圖象的亮度。對於淡出,你減少亮度從100%——0%;對於淡入,你增加亮度從0%——100%。如果你工作在調色板模式,這很容易做到,你只要改變你的調色板的顏色就可以了。如果你工作在RGB模式下,你得考慮一些其它方法。

  現在,我將說一說屏幕淡入、淡出相對好一些的方法。你可以使用Direct3D,它支持α混合,先設定每一幀的紋理,然後設置透明層;或者,更容易的方法,你可以使用DirectDraw的color/gamma控制。但是,如果你僅僅希望屏幕的一部分進行淡入或淡出的操作,或者淡入或淡出一種非黑色的顏色,而且你又不是一個Direct3D的高手——我本人就不是!——那麼具體做法的手冊就在你眼前。現在,你所需要做的最基本的就是讀取每一個你需要控制的象素,然後把它分解成紅色、綠色和藍色,然後你把三個值分別乘以要淡出或淡入的級別,再合成RGB值,把新的顏色值寫回緩衝區。聽起來很複雜?別害怕,沒有想象的那麼壞。看看下面這段演示代碼,它演示了屏幕左上角200×200區域的淡出效果,是16-bit色彩深度和565格式:

void ApplyFade16_565(float pct, USHORT* buffer, int pitch)
{
 int x, y;
 UCHAR r, g, b;
 USHORT color;

 for (y=0; y<200; y++)
 {
  for (x=0; x<200; x++)
  {
   // first, get the pixel
   color = buffer[y*pitch + x];

   // now extract red, green, and blue
   r = (color & 0xF800) >> 11;
   g = (color & 0x0730) >> 5;
   b = (color & 0x001F);

   // apply the fade
   r = (UCHAR)((float)r * pct);
   g = (UCHAR)((float)g * pct);
   b = (UCHAR)((float)b * pct);

   // write the new color back to the buffer
   buffer[y*pitch + x] = RGB_16BIT565(r, g, b);
  }
 }
}

  現在,這個函數有很多不穩妥的地方。首先,計算象素的位置公式不但包含在循環中,而且還出現了兩次!你可以在整個程序中只計算它一次,但現在的代碼計算了它80000次!下面是你應該做的:在函數的開始部分,你應該聲明一個USHORT*的變量,讓它等於buffer(如USHORT* temp = buffer;)。在內部循環裏,增加一個指針使其能得到下一個象素;在外部循環,增加一行(temp+=jump;),使其能轉入下一行。下面是修改後的代碼:

void ApplyFade16_565(float pct, USHORT* buffer, int pitch)
{
 int x, y;
 UCHAR r, g, b;
 USHORT color;
 USHORT* temp = buffer;
 int jump = pitch - 200;

 for (y=0; y<200; y++)
 {
  for (x=0; x<200; x++, temp++) // move pointer to next pixel each time
  {
   // first, get the pixel
   color = *temp;

   // now extract red, green, and blue
   r = (color & 0xF800) >> 11;
   g = (color & 0x0730) >> 5;
   b = (color & 0x001F);

   // apply the fade
   r = (UCHAR)((float)r * pct);
   g = (UCHAR)((float)g * pct);
   b = (UCHAR)((float)b * pct);

   // write the new color back to the buffer
   *temp = RGB_16BIT565(r, g, b);
  }

  // move pointer to beginning of next line
  temp+=jump;
 }
}

  這就好一些了吧!jump值是USHORT類型,是表示從200個象素寬的末尾(200個象素沒有佔滿一行)到下一行開始的值。儘管如此,對於浮點運算和提取/還原顏色計算並沒有提高速度。應該有辦法的,看看這個:

USHORT clut[65536][20];

  如果你要求一個DOS程序員把這麼大的數組放入他的程序中,他可能痛苦的會哭出聲來,甚至當場昏死過去,起碼也要加速自然死亡。但在Windows中,如果你需要這樣做,不會遇到什麼麻煩的。因爲你擁有整個系統的可利用內存。如果把整個的內循環替換成下面這一行,是不是很美妙的一件事呢?

*temp = clut[*temp][index];

  這樣做,又快了一些!^_^ 你可以傳遞一個0——100間的整數來替代浮點數傳遞給函數。如果爲100,就不需要淡出的操作了,所以就返回“什麼事兒也不用做”;如果爲0,就用ZeroMemory()函數處理所有的工作好了。另外,把傳遞的數除以5,作爲數組的第二個下標。

  如果你對於我知道查詢表的尺寸感到好奇,我就告訴你好了,65536是2的16次冪,所以在16-bit模式下,就有65536種顏色。既然我們的顏色值是無符號的值,它們的範圍從0——65535,那麼我們就用20作爲淡出的增量值好了,反正考慮到相關的內存,我覺得挺合適的。
對於24-bit和32-bit模式,你顯然不能直接使用顏色查詢表,因爲數組太巨大了,所以你只有使用三個小一點的數組:

UCHAR red[256];
UCHAR green[256];
UCHAR blue[256];

  然後,每當你讀取一個象素,就把它分解出的顏色值放入相應的數組,使其形成自己的查詢表,經過變化,再組合到一起,得到RGB色彩值。有很多辦法可以實現程序的優化,最好的辦法是根據你的目的不斷地測試哪一種是最適合你的程序的,然後總結經驗,記住它。我下面將簡單的介紹一下你可能用得着的其它的轉換。

  透明操作

  把一個透明的圖象覆蓋在非透明的圖象上,你就不能使用顏色查詢表了,因爲它總共需要有65536個查詢表,一臺普通的電腦就需要8.6GB的內存來處理這個龐然大物。^_^ 所以你不得不計算每一個象素。我將給你一個基本的思路。假設你要用圖象A覆蓋圖象B,圖象A的透明百分比爲pct,這是一個0——1之間的浮點數,當爲0時是完全不可見的,當爲1時是完全可見的。那麼,讓我們把圖象A的象素稱作pixelA,相對應,圖象B的象素稱作pixelB。你將應用下面這個公式:

color = (pixelA * pct) + (pixelB * (1-pct));

  基本上,這是一個兩個象素顏色的平均值。所以,你實際上看到每個象素有6個浮點乘法運算。你可以用一些小型的查詢表降低你的工作量。你真的應該試一試!

  你或許想做的另一件事情是建立一個部分透明的純色窗口。那種效果用一個顏色查詢表完全可以達到。因爲對於“地球人”,我只需要爲屏幕上可能出現的顏色提供藍色。實際上,我就是用查詢表完成的。我將告訴你我實際的意思:

void Init_CLUT(void)
{
 int x, y, bright;
 UCHAR r, g, b;

 // calculate textbox transparency CLUT
 for (x=0; x<65536; x++)
 {
  // transform RGB data
  if (color_depth == 15)
  {
   r = (UCHAR)((x & 0x7C00) >> 10);
   g = (UCHAR)((x & 0x03E0) >> 5);
   b = (UCHAR)(x & 0x001F);
  }
  else // color_depth must be 16
  {
   r = (UCHAR)((x & 0xF800) >> 11);
   g = (UCHAR)((x & 0x07E0) >> 6); // shifting 6 bits instead of 5 to put green
   b = (UCHAR)(x & 0x001F); // on a 0-31 scale instead of 0-63
  }

  // find brightness as a weighted average
  y = (int)r + (int)g + (int)b;
  bright = (int)((float)r * ((float)r/(float)y) + (float)g * ((float)g/(float)y) + (float)b * ((float)b/(float)y) + .5f);

  // write CLUT entry as 1 + one half of brightness
  clut[x] = (USHORT)(1 + (bright>>1));
 }
}

  這段代碼來源於“地球人”,用查詢表創建了一個文本框。爲了安全起見,隨處都使用了類型修飾。這段代碼還能再快一些,但我沒有很認真的優化,因爲我只在遊戲的最開始的部分調用了它一次。首先,紅、綠、藍的亮度值被提取出來,由於是16-bit模式,注意我們用了一個color_depth變量檢測了顯示卡是555還是565格式。然後,用下面公式計算了象素的亮度:

y = r + g + b;
brightness = r*(r/y) + g*(g/y) + b*(b/y);

  這是一個理想的平均值。我不能確定是否顏色亮度值這樣得到就正確,但它看起來符合邏輯,並且實際效果很好。在公式的最後我加了一個.5,因爲當你把浮點數變爲整數時,小數部分被去掉,加上.5使其湊整。最後,我把亮度除以2再加上1,這樣不會使文本框太亮,加1使文本框不會全黑。由於16-bit模式的低位是藍色,我可以只把顏色設置爲藍色,就不用宏了。理解了嗎?最後,結束之前,我給你演示怎樣創建文本框:

int Text_Box(USHORT *ptr, int pitch, LPRECT box)
{
 int x, y, jump;
 RECT ibox;

 // leave room for the border
 SetRect(&ibox, box->left+3, box->top+3, box->right-3, box->bottom-3);

 // update surface pointer and jump distance
 ptr += (ibox.top * pitch + ibox.left);
 jump = pitch - (ibox.right - ibox.left);

 // use CLUT to apply transparency
 for (y=ibox.top; y {
  for (x=ibox.left; x   *ptr = clut[*ptr];
   ptr += jump;
 }
 return(TRUE);
}

  這就是一個查詢表,看起來更象淡出操作的代碼了,就是查詢表的控制值與前面的不一樣了。這裏用一個計算代替了20。 順便說一下,對於查詢表的一個聲明,象下面這個:

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