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

  • 相关阅读:
    Android WifiDisplay分析一:相关Service的启动
    Android4.2以后,多屏幕的支持 学习(一)
    第十七篇 --ANDROID DisplayManager 服务解析一
    Android Wi-Fi Display(Miracast)介绍
    Ubuntu下 Astah professional 6.9 安装
    JAVA调用c/c++代码
    Application Fundamentals
    说说Android应用的persistent属性
    Tasks and Back stack 详解
    Activity的四种launchMode
  • 原文地址:https://www.cnblogs.com/setoutsoft/p/4086051.html
Copyright © 2011-2022 走看看