用戶對客戶端的UI的要求越來越高,采用alpha通道對前景背景做混合是提高UI質量的重要手段。
UI開發離不開GDI,然后要用傳統的GDI函數來處理alpha通道通常是一個惡夢:雖然有AlphaBlend這個API可以做alpha混合,但是前提必須是操作的DC中的位圖有alpha通道的數據,問題的關鍵在於GDI函數在操作的地方會把原來的alpha通道清空。
使用GDI做alpha混合還要增加透明度關鍵要解決2個問題:
1、需要把內容畫到一個臨時位圖上,同時保護好alpha通道。
2、在於把臨時位圖的數據和原位圖做混合,而且不能改變鏤空部分原位圖的alpha通道的值。
在SOUI的render-gdi中我采用下面的類來實現GDI的半透明。
class DCBuffer { public: DCBuffer(HDC hdc,LPCRECT pRect,BYTE byAlpha,BOOL bCopyBits=TRUE) :m_hdc(hdc) ,m_byAlpha(byAlpha) ,m_pRc(pRect) ,m_bCopyBits(bCopyBits) { m_nWid = pRect->right-pRect->left; m_nHei = pRect->bottom-pRect->top; m_hBmp = SBitmap_GDI::CreateGDIBitmap(m_nWid,m_nHei,(void**)&m_pBits); m_hMemDC = ::CreateCompatibleDC(hdc); ::SetBkMode(m_hMemDC,TRANSPARENT); ::SelectObject(m_hMemDC,m_hBmp); ::SetViewportOrgEx(m_hMemDC,-pRect->left,-pRect->top,NULL); //從原DC中獲得畫筆,畫刷,字體,顏色等 m_hCurPen = ::SelectObject(hdc,GetStockObject(BLACK_PEN)); m_hCurBrush = ::SelectObject(hdc,GetStockObject(BLACK_BRUSH)); m_hCurFont = ::SelectObject(hdc,GetStockObject(DEFAULT_GUI_FONT)); COLORREF crCur = ::GetTextColor(hdc); //將畫筆,畫刷,字體設置到memdc里 ::SelectObject(m_hMemDC,m_hCurPen); ::SelectObject(m_hMemDC,m_hCurBrush); ::SelectObject(m_hMemDC,m_hCurFont); ::SetTextColor(m_hMemDC,crCur); if(m_bCopyBits) ::BitBlt(m_hMemDC,pRect->left,pRect->top,m_nWid,m_nHei,m_hdc,pRect->left,pRect->top,SRCCOPY); //將alpha全部強制修改為0xFF。 BYTE * p= m_pBits+3; for(int i=0;i<m_nHei;i++)for(int j=0;j<m_nWid;j++,p+=4) *p=0xFF; } ~DCBuffer() { //將alpha為0xFF的改為0,為0的改為0xFF BYTE * p= m_pBits+3; for(int i=0;i<m_nHei;i++)for(int j=0;j<m_nWid;j++,p+=4) *p=~(*p); BLENDFUNCTION bf={AC_SRC_OVER,0,m_byAlpha,AC_SRC_ALPHA }; BOOL bRet=::AlphaBlend(m_hdc,m_pRc->left,m_pRc->top,m_nWid,m_nHei,m_hMemDC,m_pRc->left,m_pRc->top,m_nWid,m_nHei,bf); ::DeleteDC(m_hMemDC); ::DeleteObject(m_hBmp); //恢復原DC的畫筆,畫刷,字體 ::SelectObject(m_hdc,m_hCurPen); ::SelectObject(m_hdc,m_hCurBrush); ::SelectObject(m_hdc,m_hCurFont); } operator HDC() { return m_hMemDC; } protected: HDC m_hdc; HDC m_hMemDC; HBITMAP m_hBmp; LPBYTE m_pBits; BYTE m_byAlpha; LPCRECT m_pRc; int m_nWid,m_nHei; BOOL m_bCopyBits; HGDIOBJ m_hCurPen; HGDIOBJ m_hCurBrush; HGDIOBJ m_hCurFont; };
下面以實現DrawText的半透明為例來分析如何實現GDI函數的半透明。
HRESULT SRenderTarget_GDI::DrawText( LPCTSTR pszText,int cchLen,LPRECT pRc,UINT uFormat) { if(uFormat & DT_CALCRECT) { ::DrawText(m_hdc,pszText,cchLen,pRc,uFormat); return S_OK; } if(cchLen == 0) return S_OK; { DCBuffer dcBuf(m_hdc,pRc,m_curColor.a); ::DrawText(dcBuf,pszText,cchLen,pRc,uFormat); } return S_OK; }
首先來看如何解決alpha通道的保護問題。
為了在目標HDC上調用DrawText繪制文字,先聲明一個DCBuffer對象:dcBuf。
DCBuffer的構造函數中,我們會創建一個臨時的32位位圖。
再將原DC中的數據復制到臨時位圖中(注意,原位圖也是32位的)。
一個非常重要的工作在於在調用GDI的DrawText之前,DCBuffer會先把臨時位圖中alpha通道置為255。這樣做的目的在於標識哪些像素被DrawText修改過。
在調用了::DrawText后,SRenderTarget_GDI::DrawText會進入DCBuffer的析構函數。
在析構函數中,首先對alpha通道中的值取反,經過這一步操作,被::DrawText清空的點的alpha通道值被修改成255,而那些需要透明的點的alpha值則變成了0。
到這里已經實現了對alpha通道的保護。
有了前面的基礎,做第二步的alphablend就簡單了,這里只需要直接調用API:AlphaBlend,注意BLENDFUNCTION中幾個參數的設置。
下面解釋一下為什么需要做上面的處理就可以實現GDI函數的半透明。
首先如DrawText這樣的GDI函數通常會產生透明效果:即矩形中的一部分點變色,而其它點不變色。
GDI函數只會將那些變色的點的alpha通道清0。我們的目標則是將變色的點的RGB值與目標做alpha混合。
通過將臨時位圖中的alpha值做取反處理,被GDI函數修改過的點的alpha變為255,而需要鏤空的點的alpha則變為了0。
此時再調用用AlphaBlend做混合,對於那些需要鏤空的點,由於臨時位圖的alpha為0,混合后根據AlphaBlend的公式,即不會改變原來的RGB值,也不會改變原來的alpha值。
對於那些被GDI函數改變過的點,由於其alpha值都變成了255,其RGB部分,AlphaBlend會根據BLENDFUNCTION中指定的alpha值來和原值混合,而alpha部分則被修改為255。
最終達到半透明效果。
注:DCBuffer中CopyBits這一步有時候不是必須的。不過很多函數如DrawText需要做反鋸齒處理,反鋸齒處理的關鍵也是和背景色做混合,因此從原位圖復制出數據也是很有必要的。
如果用GDI+也可以達到相同的效果,但是GDI+出了名的效率低,不知道GDI函數經過如此處理后效率會不會比GDI+慢,從我目前簡單的測試來看,效果還是很好的,效率也很高,有興趣的朋友可以比較一下。