摘要
這篇文章討論了在 Microsoft Windows 圖形環境中用位圖達到透明和屏蔽效果的幾種方法,包括通過仿真和使用特殊的驅動器功能。包含其中的一個小樣本應用程序 TRANSBLT 詳細闡明了這篇文章討論的大多數方法。
介紹
使用透明(TRANSPARENT)背景模式(用SetBrMode函數設置),一個應用程序就可以用透明文本,透明風格的線條和透明形狀的刷子。令人悲傷的是,Microsoft Windows圖形環境并沒有為透明位圖提供一個簡單的接口。(是的,它提供了,但是并沒有對它進行廣泛的支持,就象在下文中“容易的位圖透明性”中提到的)。幸運的是,通過使用一個屏蔽位圖和幾次調用具有經過仔細選擇的光柵操作的BitBlt,一個應用出現可以模仿這種效果。
到底什么是透明的位圖呢?它是一個位圖,通過它目的文件的一部分仍然可以看得見。一個簡單的例子就是類似于控制面板圖象等基于Windows的圖象。控制面板圖象本身基本上是個長方形。當它被最小化時,通過這個長方形圖象位圖的部分可以看見桌面。從理想化的角度講,這圖象位圖被設計成長方形其中有些象素被指定為透明的以至于當位圖被使用,那些象素就不會擋住目的文件。透明的位圖可以通過移動的、非矩形圖象變得更為有趣。下面將要描述得模仿方式可以用來完成這些透明效果。
符號
這篇文章使用透明和不透明這兩個詞來描繪源位圖中得象素。透明象素是那些不會影響目標文件的象素。不透明象素是那些畫在目標文件上并取代該位置上原來的東西的象素。
白色和黑色分別被假定為全1和全0的值。這在所有已知的Windows顯示驅動器上都是正確的,包括調色板設備。
基本的操作涉及到從源文件到目標文件 的塊傳遞,額外的與單色屏蔽有關的塊傳遞也是需要的。源文件和目標文件由他們的設備上下文代表hdcSrc和hdcDest即可以是位圖也可以是設備表面本身。有hdcMask提到的屏蔽被假定為被選進兼容DC的單色位圖。
背景概念
在討論實際的透明模仿之前,我們應該定義和復習一些基本圖形概念。
光柵操作
BitBlt函數的最后一個參數指定了一個光柵操作(ROP),它明確定義了如何將源文件、目標文件和模式(由現在選出的刷子畫筆定義)的位組合去形成一個目標文件。因為一個位圖只是一個位值的集合,光柵操作(ROP)只是一個在位上操作的布爾等式。相應使用的設備,位圖中的代表不同的事物。
- 在多色設備上,每個象素由一個位集合代表,他們要么形成一個指向顏色的索引,要么直接代表一種顏色。
- 在單色設備上,每個象素由一個位來代表,0表示黑色并且1代表白色。
對于所有的設備類型,光柵操作(ROP)只簡單地在位展示上進行而不考慮他們的實際意義。
有一個技巧,就是用一種有意義的方式合并位。在Windows3.1版軟件開發包中的程序員參考,第三頁:消息、結構和宏中的附錄A列舉了256種可能的三重光柵操作(ROP)。光柵操作(ROP)提供了多種合并位圖數據的方法,而且你經常可以使用不只一種的方法得到你想要的效果。這篇文章只討論其中的四種。
預先定義的名字
|
布爾操作
|
透明仿真中的用途
|
SRCCOPY
|
src
|
直接將源拷貝到目的
|
SRCAND
|
src AND dest
|
將目標文件中對應于源文件黑色區域的部分變黑,將對應于白色區域的部分留著不動
|
SRCINVERT
|
src XOR dest
|
將源插入到目標。二次使用時,將目標恢復到它原來的狀態。在某種條件下可以代替SRCPAINT 操作
|
SRCPAINT
|
src OR dest
|
將源文件中的非白色區域刷到目標文件中。源中的黑色區域不轉換到目標中。
|
一些打印機不支持某些光柵操作,尤其是那些涉及到目標文件的光柵操作。因為這一點,這篇文章中描寫的技巧特地以顯示為目的而且有可能在某些打印機設備上不能工作,比如PostScript打印機。
透明屏蔽
在這篇文章中,“面具”一詞不是指蝙蝠俠戴在臉上的東西。它指的是一個限制其他位圖可見部分的一個位圖。“面具”有兩個控件:不透明部分(黑色),在這一部分源位圖是可見的和透明部分(黑色),在這一部分目標文件保持未動。因為“面具”只由兩種顏色 組成,所以它可以很方便地由一種單色位圖代表,雖然它可以是一個黑白色位圖 。就象在下面的“真正的屏蔽方法”和“使源文件變黑的方法”中要討論的,數據塊傳遞屏蔽被用作多數數據塊傳遞處理的一部分,它的源位圖的最終透明數據塊傳遞設置目標文件。TRANSBLT樣本應用程序使用帶設置為1的透明色素和設置為0的非透明色素的單色屏蔽。如果需要應用程序可以轉換這兩個值,并將在這一部分中的一些將要描述的單色到彩色的轉換中進行補充。
除了為透明性提供方便以外,屏蔽在模仿復雜的剪裁操作時也很有用。這種剪裁操作不能被有效地在使用區域中處理。一個被屏蔽的數據塊傳遞的網狀效應將裁減掉源位圖的一部分。比如:要只顯示位圖中的一個圓形區域,可創建一個象源文件一樣大小的屏蔽并且在相應的區域畫一個透明位的圓形。執行這一被屏蔽的數據塊傳遞的機制將在后面的“真正的屏蔽方法”和“使源文件變黑的方法”中描述。
單色到 彩色的轉換
透明仿真也牽涉到基于Windows的單色位圖到彩色位圖的轉換機制。反之亦然。基于Windows的文本前景色和背景色的概念被用于在兩種格式之間進行轉換。在對一彩色目標文件進行位傳遞操作的時候,一個單色的源位圖(和/或當可應用時一個畫刷)在實際的光柵操作(ROP)中在這一位上實現之前被轉換成背景色。相反的,當目標文件是單色時,Windows 把彩色源轉換成單色。在這種情況下,彩色位圖中所有和背景色一致的象素都變成1,其他的象素都被轉換成0。因為下面所要涉及到的例子都使用單色屏蔽,所以對一個應用程序而言,在執行塊操作之前正確地設置背景色和前景色是非常重要的。
性能和屏幕閃爍
增強的位圖操作變得比較慢的原因完全是因為被影響的位的數目。而且當直接對屏幕進行操作導致閃爍的事實又使得這一點更為嚴重。當被影響的區域增大尺寸時,事情只會變得更加糟糕,雖然沒有什么辦法可以魔術般的提高速度,但是可以通過使用陰影位圖來刪除可見的閃爍 。首先,應用程序把將要被影響的屏幕區域復制到存儲位圖,然后應用程序在陰影位圖而不是在屏幕上實現位操作(例如,透明效果 )。最后,陰影又被復制到屏幕上。結果只有一個塊傳遞影響屏幕,所以閃爍沒有了。很明顯,兩個額外的塊操作引起了速度減慢(雖然在有些設備上存儲器塊傳遞能比已經訪問了屏幕的塊傳遞要塊一些),但是依靠位圖的尺寸和驚奇的操作,操作可能會因為閃爍消失而讓人感覺到快了一些。事物也因為沒有讓人混亂的閃爍而變得更為清楚。陰影操作是否恰當取決于應用程序特定的需要。
真正的屏蔽塊傳遞并不需要對即將有用的源位圖的某部分做任何的修改。被屏蔽的塊傳遞涉及到了步操作并且屏蔽把所有透明的元素都設置為1,所以不透明的元素設置為0。下面是有關基本代碼:
// Set up destination for monochrome blt (only needed for monochrome
// mask). These are the default values and may not need to be
// changed. They should also be restored.
SetBkColor(hdcDest, RGB(255, 255, 255)); // 1s --> 0xFFFFFF
SetTextColor(hdcDest, RGB(0, 0, 0)); // 0s --> 0x000000
// Do the real work.
BitBlt(hdcDest, x, y, dx, dy, hdcSrc, x0, y0, SRCINVERT);
BitBlt(hdcDest, x, y, dx, dy, hdcMask, 0, 0, SRCAND);
BitBlt(hdcDest, x, y, dx, dy, hdcSrc, x0, y0, SRCINVERT);
被屏蔽的塊傳遞處理中的三步操作如下:
- 第一步(帶SRCINVERT的位塊傳遞)將源位圖異或到目標文件。這看起來有點意思,但第二步異或有把目標文件恢復成原始狀態的效果。
- 第二步(帶SRCAND的位塊傳遞)是一個屏蔽操作。當屏蔽與目標文件相與,所有的透明象素都不會改變目標文件的象素,而不透明象素則直接把目標文件變為黑。現在目標文件中有了一個源文件的不透明部分給勾勒出來的圖象,而它自身在透明部分中的異或圖象。
- 第三步(帶SRCINVERT的位塊傳遞)與源文件異或送到目標文件。透明象素被恢復成源狀態(兩步異或就能做到),不透明象素則季節從源文件上復制。
不幸的是,當三個步驟執行時,目標文件確實有一陣看起來相當難看,而直接對屏幕執行三次塊傳遞又會引起屏幕閃爍。
使源文件變黑的方法
只要在創建源位圖時稍稍計劃一下,透明性塊傳遞就可以減少到只有兩個調用。屏幕仍和上面的例子一樣保持不變,但是源文件則必須在屏蔽的塊傳遞代碼看起來象這樣:
// Set up destination for monochrome blt. These are the default
// values and may not need to be changed. They should also be
// restored.
SetBkColor(hdcDest, RGB(255, 255, 255)); // 1s --> 0xFFFFFF
SetTextColor(hdcDest, RGB(0, 0, 0)); // 0s --> 0x000000
// Do the real work.
BitBlt(hdcDest, x, y, dx, dy, hdcMask, 0, 0, SRCAND);
BitBlt(hdcDest, x, y, dx, dy, hdcSrc, x0, y0, SRCPAINT);
屏蔽第二次用來使不透明的象素變黑而保持剩下的不變。然后源文件再這個的向上與之相或,并在目標文件現在為黑的部分上畫畫。因為源文件在想要透明的地方只有黑象素,或操作使得目標文件在那些透明區域保持不變。注意SRCINVERT POP可以在第二個塊傳遞調用時代替SRCPAINT并取得多樣的效果。源屏蔽設置刪除了的可能性,這是XOR不同于OR的唯一情形。
用這種方法屏幕閃爍變得不再那么引人注目,而且一旦源文件已經再正確的位置被設置為黑,透明性看起來非常好。Windows也使用這種機制在屏幕上顯示圖象。圖標被分成兩部分存儲在.ICO文件中,這兩部分“XOR MASK”和位圖本身。因為位圖和圖標一樣小,所以實現透明性非常順利。
位圖透明性
位圖透明性通常指的是一種處理,這種處理取出一幅位圖,并使位圖中的一種顏色變為透明,從而當位圖被塊傳遞到屏幕時,目標文件可以通過位圖的透明色被看見。一個應用程序可以通過構造一個合適的屏蔽和使用在前面“真正的屏蔽方法”和“使源文件變黑的方法”中描述過的屏蔽技術來模仿這種操作。下面的章節描述了如何為不能執行透明塊傳遞的顯示設備模仿位圖透明性構造一個屏蔽。
從彩色位圖中構造一個單色屏蔽相當容易,因為位塊傳遞的內置彩色單色轉換自動完成所有這些工作。目標是一個所有不透明元素都設置為0和所有透明元素都設置為1的屏蔽。把背景色設置為透明色恰好做到了這一點。沒有必要設置文本前景色因為他不能用于彩色單色轉換(所以的非背景色素都被設置成0),下面的代碼完成了這項工作:
SetBkColor(hdcSrc, rgbTransparent);
BitBlt(hdcMask, 0, 0, dx, dy, hdcSrc, x0, y0, SRCCOPY);
代碼構造了一個源文件相當于透明色時為1和其他地方為0的屏蔽。這復制了上面使用過的屏蔽。
使用屏蔽
現在是使用上面描述過的屏蔽方法的時候了。真正的屏蔽方法不要求額外的工作:創建 屏蔽并且源文件不需要處理。三個位塊傳遞確實引起了屏幕閃爍,但現在只有三個的一個。
另一方面,使源文件變黑的方法要求對源位圖做一些額外的工作來得到正確的輸入方案透明位需要變黑。當然,如果透明色一開始就是黑色,那位圖就已經準備好要運行了。在源文件上把透明色素涂黑非常類似于在目標文件上把不透明象素涂黑,并且在使用屏蔽時已經這樣作了,如下所示:
SetBkColor(hdcSrc, RGB(0,0,0)); // 1s --> black (0x000000)
SetTextColor(hdcSrc, RGB(255,255,255)); // 0s --> white (0xFFFFFF)
BitBlt(hdcSrc, x0, y0, dx, dy, hdcMask, 0, 0, SRCAND);
現在有兩個位塊傳遞被用于透明的位塊傳遞。
一旦現實的透明位塊傳遞完成了,源位圖應該被恢復到它的初始色:
SetBkColor(hdcSrc, rgbTransparent); // 1s --> transparent color
SetTextColor(hdcSrc, RGB(0,0,0)); // 0s --> black (0x000000)
BitBlt(hdcSrc, x0, y0, dx, dy, hdcMask, 0, 0, SRCPAINT);
因為源位圖必須被更改,然后再恢復,相關的位塊傳遞的總數是4。這使得處理變慢,但因為位塊傳遞中的兩個是在存儲位圖中進行而不是在屏幕上作的,相比較于真正的屏幕方法屏幕閃爍減少了。如果源位圖能保持透明位被設置為黑色,則兩個轉換塊傳遞可以一起被避免而且也只需要兩個塊傳遞用于操作;這事實上是動畫制作必要的。
簡易的位圖透明性
有些設備驅動器直接支持塊傳遞。一個驅動器指示使用CAPSI能力的CI-TRANSPARENT的能力由GetDevicePaps函數返回。一個特殊的背景模式,NEWTRANSPARENT指示后來的塊傳遞是透明的塊傳遞。目標文件的現在的背景色是透明色。當這個能力適用于驅動器時,基本的透明塊傳遞可以按下面的代碼執行:
// Only attempt this if device supports functionality.
if(GetDeviceCaps(hdcDest, CAPS1) & C1_TRANSPARENT)
{
// Special transparency background mode
oldMode = SetBkMode(hdcDest, NEWTRANSPARENT);
rgbBk = SetBkColor(hdcDest, rgbTransparent);
// Actual blt is a simple source copy; transparency is automatic.
BitBlt(hdcDest, x, y, dx, dy, hdcSrc, x0, y0, SRCCOPY);
SetBkColor(hdcDest, rgbBk);
SetBkMode(hdcDest, oldMode);
}
這確實使得事情變得簡單了。不幸的是,目前沒有多少設備驅動器支持透明的塊傳遞那些用Windows3.1版本安裝的塊傳遞沒有這個功能。這應該在不遠的將來有所改變。
并且目前,WINDOWS。H并不包含對任意這些新變量的定義。取而代之的是MMSYSTEM。H文件提供定義,這個文件能Windows3.1版本的軟件開發包(SDK)中找到。
帶有設備獨立位圖(DIB)的透明性
如果源位圖是設備獨立位圖(DIB)格式,那么,整個屏蔽處理可以通過用一個設備獨立位圖(DIB)當作源文件。屏蔽和對色彩表的簡單操作就可以大大地獲得簡化了。處理過程與上面討論的相同,處理應用程序可通過改變色彩表執行所有的彩色單色和單色彩色的轉換,如下:
save a copy of the color table;
// Build the mask.
for (every color in the color table)
{
if (color == rgbTransparent)
color = white;
else
color = black;
}
// Prepare destination by blting the mask.
StretchDIBits(hdcDest, lpDIB, SRCAND); // (Yes, there are more
// parameters.)
// Now prepare "blacked out" source for the mask blt.
for (every color in the color table)
{
if (color == white) // (white from above change)
color = black;
else
color = original color from color table;
}
// Transparently blt the source.
StretchDIBits(hdcDest, lpDIB, SRCPAINT); // (Yes, there are more
// parameters.)
// To restore DIB to original state, restore original color table.
這種方法需要注意的關鍵之處在于只需要位圖的一個備份因為它依靠色彩表轉換即充當屏幕又充當源文件。不管,把設備獨立位圖(DIB)格式轉換成設備依賴格式的額外不便仍然存在。
from: http://www.chinaitpower.com/A/2001-10-04/680.html