為GDI函數增加透明度處理


用戶對客戶端的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+慢,從我目前簡單的測試來看,效果還是很好的,效率也很高,有興趣的朋友可以比較一下。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM