为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+慢,从我目前简单的测试来看,效果还是很好的,效率也很高,有兴趣的朋友可以比较一下。

 

你可能感兴趣的:(函数)