最近兩周使用透明、半透明窗口比較多,在此之前我寫代碼都借助封裝好了的皮膚庫,而現在都是“手寫”的——石器時代大冒險,遇到了一些困難,在此做總結。
1、GDI函數對Alpha值的忽視。
GDI函數只有AlphaBlend api可以提供alpha通道的繪制,使用AlphaBlend可以實現32位位圖的繪制。因為只有這個api能識別alpha通道,如果在一個MemDC上用DrawText繪制文本,這些文字區域的alpha值都為0,之后再使用AlphaBlend api把MemDC的位圖拷貝到實際DC,就會導致文字區域異常,要么是透明了,要么是變成純白色了。解決辦法是:1、在能達到目的的前提下,從MemDC拷貝到實際DC時,不用AlphaBlend混合拷貝,而是使用Bitblt簡單拷貝;2、1可能無法達到想要的半透明效果,那么就只能在混合拷貝之前遍歷文字區域的每一個像素,把它的alpha值修改為255[可參考http://bbs.csdn.net/topics/360021944];3、最方便的做法是引入GDI+,使用CImage,GDI+的文本繪制帶有Alpha屬性值。
2、AlphaBlend API和透明度
Alpha通道值。0表示完全透明,255表示完全不透明。
AlphaBlend函數里的BLENDFUNCTION結構體里的SourceConstantAlpha成員,指示的是整個圖片的Alpha值。0,表示源圖片完全透明,255,表示使用源圖片的每個像素自己的Alpha值。
AlphaBlend函數產生的圖片讓源、目標、SourceConstantAlpha混合。有一套復雜的計算:http://msdn.microsoft.com/en-us/library/windows/desktop/dd183393(v=vs.85).aspx
計算涉及到這么幾種情況:
1、沒有設置AC_SRC_ALPHA 屬性,只有SourceConstantAlpha做混合;
2、設置了AC_SRC_ALPHA 屬性,同時把SourceConstantAlpha設置為0xFF,這時候,只使用源圖片每個像素的Alpha值做混合;
3、設置了AC_SRC_ALPHA屬性,同時把SourceConstantAlpha設置為非0xFF,這時候既使用SourceConstantAlpha的值也使用源圖片每個像素的Alpha值做混合。
按照MSDN公式的介紹,當SourceConstantAlpha的值為0的時候,則源DC的圖片在目標DC上完全不顯示了,源DC的RGB值都丟失了。當SourceConstantAlpha為255時,則使用源DC的圖片的Alpha值。
當使用AlphaBlend的時候,只是把數據從一個DC拷貝到另一個DC。所以,如果是要把一個圖片貼到目標DC,必須是先把圖片選入到MemDC,然后再使用AlphaBlend函數把圖片從MemDC拷貝到實際DC。
使用這個API,可以實現圖片切換時的平緩過渡效果,上一個圖片慢慢的消失,下一個圖片慢慢的呈現。
3、AlphaBlend失敗的可能原因
參數錯誤失敗原因1: 如果位圖的bmBitsPixel為24,那么是無法使用該函數的。
所以對於來源未知的圖片,安全的做法是
BITMAP bitmapInfo;
:: GetObject(hBitMap, sizeof( BITMAP), &bitmapInfo);
if (bitmapInfo. bmBitsPixel == 24) //24位的位圖就直接使用Bitblt/StretchBlt
{...........}
參數錯誤失敗原因2:可能參數使用錯了,源dc的位圖跟參數里面指示源位圖高度、寬度的值不相符。
繪制效果很奇怪:之前以為使用了AlphaBlend之后就沒法使用Bitblt了,實際上是可以的,並不會導致alpha通道丟失。倒是這樣子會有問題:通過alphablend往memdc上繪制圖片,然后使用DrawText往memdc上繪制文字,最后使用alphablend把memdc的內容拷貝到實際DC,這就會導致文字變成白色的。所以,最后一步不能使用alphablend,必須使用bitblt這類函數。
4、GDI+繪制文本
GDI+的DrawString可以繪制文本,如果用GDI+繪制文本再加上使用WS_EX_LAYER屬性,那么GDI+繪制的文本必須使用UpdateLayeredWindow拷貝到目標區域。不能直接在有WS_EX_LAYER屬性的窗口上使用GDI+繪制,必須先繪制然后使用UpdateLayeredWindow函數拷貝。
5、使用GDI+
VS2008需要加上頭文件:#include <objbase.h>
GDI+初始化:
ULONG_PTR m_GdiToken;
GdiplusStartupInput m_GdiInput;
GdiplusStartup
(&
m_GdiToken
,&
m_GdiInput
,
NULL
);
GDI+銷毀
GdiplusShutdown
(
m_GdiToken
);
6、WS_EX_LAYERED窗口無法顯示
只有WS_EX_LAYERED屬性,而沒有調用SetLayeredWindowAttribute或者UpdateLayeredWindow函數,那將導致窗口無法顯示。
7、如何繪制部分透明的窗口
1)切出部分透明的圖;
2)創建有WS_EX_LAYERED屬性的窗口;
3)把圖片選進MemDC,使用AlphaBlend函數把圖片混合拷貝到另一個MemDC;
4)由於GDI的DrawText函數會把該區域像素的alpha值清0,導致透明,所以建議使用GDI+的DrawString函數;
5)最后使用UpdateLayeredWindow把MemDC拷貝到實際DC。
8、SetLayeredWindowAttributes和UpdateLayeredWindow的區別
兩者在win7以下的版本都要求必須是帶有WS_EX_LAYERED屬性的頂層窗口(非WS_CHILD屬性的窗口),windows 8之后的版本開始支持非頂層窗口。
SetLayeredWindowAttributes函數可以實現:1、對某一個RGB值實現透明;2、可以實現對整個窗口都使用同一個透明度。
UpdateLayeredWindow函數可以實現局部半透明。而且這個函數是GDI+的函數,使用時需要初始化GDI+。
9、 UpdateLayeredWindow不支持局部更新
UpdateLayeredWindow函數包含了SetWindowPos設置窗口位置、大小的功能。這個函數不支持局部更新繪制區域。
10、不能把MAKEINTRESOURCE之后的值賦值給wstring
因為這不是一個string,如果發生賦值,會導致觸發WM_PAINT消息(很莫名其妙),導致錯誤。