1. 程式人生 > >透明點陣圖Ron Gery Microsoft 網路開發技術小組

透明點陣圖Ron Gery Microsoft 網路開發技術小組

摘要

這篇文章討論了在 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)格式轉換成裝置依賴格式的額外不便仍然存在。