【问题标题】:Incorrect result with TransparentBltTransparentBlt 的结果不正确
【发布时间】:2014-06-19 08:56:09
【问题描述】:

我正在开发一个透明的树视图控件。为了实现透明度,我对树进行了子类化并覆盖了WM_PAINT(在我的WM_ERASEBKGND 处理程序中,我只返回TRUE。滚动、鼠标滚轮和其他相关消息都得到了正确处理)。 为了让树的背景透明,我使用了以下算法(基于thisCodeGuru 文章):

  1. 让树在内存DC中进行默认绘制(保存在memDC中)。

  2. 在另一个内存DC中获取父母的背景(保存在finalDC中)。

  3. 映射树和父级的坐标,以便我可以抓取父级背景位图的正确部分。

  4. 使用 TransparentBlt 和树的背景颜色 (TransparentBlt( finalDC, ... , memDC, ... );) 组合这两个图像。


在父窗口中,我实现了WM_PRINTCLIENT,因此我可以通过简单的::SendMessage( GetParent(hwnd), WM_PRINTCLIENT, (WPARAM)finalDC, (LPARAM)(PRF_CLIENT) ); 调用将其背景复制到内存DC(步骤2)。我得到的结果在 Windows XPWindows7 上都是正确的:


我通过::DefSubclassProc( hwnd, WM_PAINT, (WPARAM)memDC, 0 ); 调用获得了树的默认位图(步骤1)。在这里,结果在 Windows XPWindows7 上都是正确的:

Windows XP 上(我不知道为什么上传的图片缺少复选框,我的电脑上一切正常):

Windows7 上(我不知道为什么上传的图片缺少复选框,在我的电脑上一切正常):


但是,TransparentBlt() 调用后,最终的图片没有正确绘制:

Windows XP 复选框是问题 ->

Windows7 上,字母中会留下一些白色 ->


这些图片是将位图从设备上下文导出到文件的结果(我已经修改了this code 来实现这一点)。

这是WM_PAINT的代码sn-p:

case WM_PAINT:
    {
        // usual stuff
        PAINTSTRUCT ps;

        RECT rcClient = {0};
        GetClientRect( hwnd, &rcClient );

        HDC hdc = BeginPaint( hwnd, &ps );

        // create helper memory DCs 
        HDC memDC = CreateCompatibleDC(hdc), finalDC = CreateCompatibleDC(hdc);

        // create helper bitmaps
        HBITMAP memBmp,  // default tree's paint
            finalBmp,    // parent's background image
            bmpOld, bmpOldFinal;  // needed for cleanup

        memBmp = CreateCompatibleBitmap( hdc, 
            rcClient.right - rcClient.left,
            rcClient.bottom - rcClient.top );

        bmpOld = (HBITMAP)SelectObject( memDC, memBmp );

        // map parent and child rectangles

        RECT rcParent; 
        GetClientRect( GetParent(hwnd), &rcParent );

        // upper left corners of the treeview, parent window
        POINT ptTreeUL, ptParentUL; 

        // map tree's coordinates
        ptTreeUL.x = rcClient.left;
        ptTreeUL.y = rcClient.top;

        ClientToScreen( hwnd, &ptTreeUL );

        // map parent's coordinates
        ptParentUL.x = rcParent.left;
        ptParentUL.y = rcParent.top;

        ScreenToClient( GetParent(hwnd), &ptParentUL );

        /********* get parent's background image *******/

        finalBmp = CreateCompatibleBitmap( hdc, 
            rcParent.right - rcParent.left,
            rcParent.bottom - rcParent.top );

        bmpOldFinal = (HBITMAP)SelectObject( finalDC, finalBmp );

        ::SendMessage( GetParent(hwnd), WM_PRINTCLIENT,(WPARAM)finalDC, 
            (LPARAM)(PRF_CLIENT) );

        /********* capture default tree image *********/

        ::DefSubclassProc( hwnd, WM_PAINT, (WPARAM)memDC, 0 );

        // get tree's background color 

        COLORREF clrMask = TreeView_GetBkColor(hwnd);

        if( clrMask == -1 )  // this means tree uses default system color
            clrMask = ::GetSysColor(COLOR_WINDOW);

        /**** combine tree's default image with parent's background ****/
        /**** so we can erase default background with parent's background ****/

        TransparentBlt( finalDC, 
            ptParentUL.x + ptTreeUL.x, 
            ptParentUL.y + ptTreeUL.y, 
            rcClient.right - rcClient.left, 
            rcClient.bottom - rcClient.top,
            memDC, 
            0, 0, 
            rcClient.right - rcClient.left, 
            rcClient.bottom - rcClient.top,
            clrMask );

        // draw the result into tree's DC
        BitBlt( hdc, 
            0, 0, 
            rcClient.right - rcClient.left, 
            rcClient.bottom - rcClient.top,
            finalDC, 
            ptParentUL.x + ptTreeUL.x, 
            ptParentUL.y + ptTreeUL.y, SRCCOPY);

        // cleanup
        SelectObject( memDC, bmpOld );
        DeleteDC( memDC );
        DeleteObject( memBmp );
        SelectObject( finalDC, bmpOldFinal );
        DeleteDC( finalDC );
        DeleteObject( finalBmp );

        EndPaint( hwnd, &ps );
    }
    return 0L;

如何将默认树位图与父背景正确结合,实现透明而没有视觉伪影?

编辑:

我能够通过放弃 TransparentBlt() 并自己做事来解决复选框问题。

ClearType 字体 仍然是个问题,工件仍然存在。面具创作是个问题。正如成员 arx 所说,背景组合负责这一点。如果我能创建适当的掩码,那么我的问题就会得到解决。

这是整个子类过程:

LRESULT CALLBACK TreeProc( HWND hwnd, UINT message, 
    WPARAM wParam, LPARAM lParam, 
    UINT_PTR uIdSubclass, DWORD_PTR dwRefData )
{
    switch (message)
    {
    // handle messages that paint tree without WM_PAINT
    case WM_TIMER:  // handles autoscrolling when item is partially visible
    case TVM_DELETEITEM:
    case TVM_INSERTITEM: 
    case WM_MOUSEWHEEL: 
    case WM_HSCROLL:  
    case WM_VSCROLL:  
        {
            ::SendMessage( hwnd, WM_SETREDRAW, (WPARAM)FALSE, 0 );

            LRESULT lres = ::DefSubclassProc( hwnd, message, wParam, lParam );

            ::SendMessage( hwnd, WM_SETREDRAW, (WPARAM)TRUE, 0 );

            ::RedrawWindow( hwnd, NULL, NULL, 
                RDW_FRAME | RDW_INVALIDATE | RDW_UPDATENOW );

            return lres;
        }
    case WM_PAINT:
        {
            // usual stuff

            PAINTSTRUCT ps;
            HDC hdc = BeginPaint( hwnd, &ps );

            // get client coordinates of parent and tree window

            RECT rcClient = {0}, rcParent = {0};

            GetClientRect( hwnd, &rcClient );
            GetClientRect( GetParent(hwnd), &rcParent );

            // create helper DCs and bitmaps

            HDC memDC = CreateCompatibleDC(hdc),    //default tree paint
                finalDC = CreateCompatibleDC(hdc),  // parent's WM_PRINTCLIENT
                maskDC = CreateCompatibleDC(hdc);   // DC that holds monochrome mask

            HBITMAP memBmp,  // default tree's paint
                finalBmp,    // parent's background image
                maskBmp,      // monochrome mask
                bmpOld, bmpOldFinal, bmpOldMask;  // needed for cleanup

            memBmp = CreateCompatibleBitmap( hdc, rcClient.right - rcClient.left,
                rcClient.bottom - rcClient.top );

            bmpOld = (HBITMAP)SelectObject( memDC, memBmp );

            /****** get parent's background image *******/

            finalBmp = CreateCompatibleBitmap( hdc, rcParent.right - rcParent.left,
                rcParent.bottom - rcParent.top );

            bmpOldFinal = (HBITMAP)SelectObject( finalDC, finalBmp );

            ::SendMessage( GetParent(hwnd), WM_PRINTCLIENT,(WPARAM)finalDC,
                (LPARAM)(PRF_CLIENT) );

            /****** capture default tree image *********/

            ::SendMessage( hwnd, WM_PRINTCLIENT,(WPARAM)memDC, (LPARAM)(PRF_CLIENT) );

            /********** create monochrome mask *******/

            // get tree's background color 

            COLORREF clrMask = TreeView_GetBkColor(hwnd);

            if( clrMask == -1 )
                clrMask = ::GetSysColor(COLOR_WINDOW);

            maskBmp = CreateBitmap( rcClient.right - rcClient.left,
               rcClient.bottom - rcClient.top, 1, 1, NULL );

            bmpOldMask = (HBITMAP)SelectObject( maskDC, maskBmp );

            SetBkColor( memDC, clrMask ); // this color becomes white, all others black

            BitBlt( maskDC, 0, 0, rcClient.right - rcClient.left, 
                rcClient.bottom - rcClient.top, memDC, 0, 0, SRCCOPY );

            /***** map tree's coordinates to parent window *****/

            POINT ptTreeUL;

            ptTreeUL.x = rcClient.left;
            ptTreeUL.y = rcClient.top;

            ClientToScreen( hwnd, &ptTreeUL );
            ScreenToClient( GetParent(hwnd), &ptTreeUL );

            /***** creating transparent background ********/

            // mask the original image

            SetBkColor( memDC, RGB( 0, 0, 0 ) ); 
            SetTextColor( memDC, RGB( 255, 255, 255 ) );

            BitBlt( memDC, 0, 0, 
                rcClient.right - rcClient.left, 
                rcClient.bottom - rcClient.top,
                maskDC, 0, 0, SRCAND );

            // create transparent treeview image 

            SetBkColor( finalDC, RGB ( 255, 255, 255 ) ); 
            SetTextColor( finalDC, RGB ( 0, 0, 0 ) );

            BitBlt( finalDC, ptTreeUL.x, ptTreeUL.y, 
                rcClient.right - rcClient.left, 
                rcClient.bottom - rcClient.top,
                maskDC, 0, 0, SRCAND );

            BitBlt( finalDC, ptTreeUL.x, ptTreeUL.y, 
                rcClient.right - rcClient.left, 
                rcClient.bottom - rcClient.top,
                memDC, 0, 0, SRCPAINT );

            // display the result 

            BitBlt( hdc, 0, 0, 
                rcClient.right - rcClient.left, 
                rcClient.bottom - rcClient.top,
                finalDC, ptTreeUL.x, ptTreeUL.y, SRCCOPY );

            /***************** cleanup ******************/

            SelectObject( memDC, bmpOld );
            DeleteDC( memDC );
            DeleteObject( memBmp );

            SelectObject( finalDC, bmpOldFinal );
            DeleteDC( finalDC );
            DeleteObject( finalBmp );

            SelectObject( maskDC, bmpOldMask );
            DeleteDC( maskDC );
            DeleteObject( maskBmp );

            EndPaint( hwnd, &ps );
        }
        return 0L;
    case WM_ERASEBKGND:
        return TRUE;
    case WM_NCDESTROY:
        ::RemoveWindowSubclass( hwnd, TreeProc, 0 );
        return ::DefSubclassProc( hwnd, message, wParam, lParam);
    }
    return ::DefSubclassProc( hwnd, message, wParam, lParam);
}

在父窗口中从WM_PAINT 绘制图像以响应WM_PRINTCLIENT(请记住使用(HDC)wParam 而不是BeginPaint,并使用return (LRESULT)0;)。我画了一个渐变,如上图所示。

在父窗口中,您必须在 WM_NOTIFY 处理程序中添加以下内容:

case WM_NOTIFY:
    {
        if( ((LPNMHDR)lParam)->code == TVN_SELCHANGING )
        {
            InvalidateRect( ((LPNMHDR)lParam)->hwndFrom, NULL, FALSE );
            break;
        }

        if( ((LPNMHDR)lParam)->code == TVN_ITEMEXPANDING )
        {
            InvalidateRect( ((LPNMHDR)lParam)->hwndFrom, NULL, FALSE );
            break;
        }
    }
    break;

只有字体有待修复。

编辑结束

【问题讨论】:

  • 如果您仍然遇到麻烦,我有一些代码可以帮助您,而无需更改您的大部分代码,但是我需要您提供一些您自己的代码(以重现此问题)。这将通过手动操作位图来完成。可以自己贴出来,但是我的代码目前正在重写,有些功能还不能用,所以我得专门为你写功能。
  • @Helix:我在复选框方面取得了进展,但 ClearType 字体正在扼杀我。这是唯一剩下的东西......经过一点研究后,我开始担心修复它是不可能的。不过,如果您有兴趣,我可以发布整个子类程序甚至更好,一个小型 SSCCE 项目?感谢您提供帮助,我一如既往地感谢您。
  • 嗯,我有兴趣修复你的代码,所以如果你能发布它,那就太好了。
  • @Helix:我用子类程序编辑了我的帖子。测试时不要忘记为WM_NOTIFY添加东西,并注意WM_PRINTCLIENT(我不知道你之前是否使用过这个消息,所以我必须警告你)。希望你能想出办法。在那之前最好的问候。
  • 这实际上几乎是不可能的,看来您需要自己绘制文字:/

标签: c++ winapi bitmap


【解决方案1】:

在 Windows 7 上,文本通过抗锯齿进行平滑处理。 Windows 不会在纯色背景上绘制黑色文本像素,它会在黑色和背景色之间混合绘制。

TransparentBlt 将单一纯色视为透明。因此它不会将文本的抗锯齿边缘视为透明的,因此这些边缘在最终位图中可见。

要解决此问题,您可以选择禁用抗锯齿的字体,但显然这会给您带来更多块状文本。

XP 的问题是复选框的角与背景颜色相同。您可以通过将背景更改为不冲突的颜色(例如洋红色)来解决此问题。

【讨论】:

  • 你不会喜欢这个,但这个问题没有简单的解决方案。确保没有渲染工件的唯一方法是自己完成所有绘图(使用自定义绘图),即使那样,您也需要修复所有问题。我应该知道,我自己已经完成了 - 事后看来,编写自己的树控件会更容易。
  • @JonathanPotter:“编写我自己的树控件”是什么意思?我有兴趣尝试这种方法,但不知道您指的是什么。你能提供一篇文章/代码示例/教程来帮助我吗?
  • 我的意思是实现你自己的控制,模仿 shell 提供的控制。这将是一个合理的工作量,尽管您不一定需要实现它的所有功能,只需要实现那些您真正需要的功能。这种方法是否值得取决于您 - 使用自定义绘图、子分类和大量胶水和胶带的组合,可以通过系统控制获得透明度。
  • 那些自定义控件是一种错觉,您需要做的就是跟踪鼠标在您自己窗口的“按钮”区域中的移动,并根据用户操作(单击、离开、悬停等)进行相应的绘制。对于绘画,您可能想编写自己的绘图代码(这就是我正在做的,也是最难的部分),因为有些部分可能会让您发疯。使用自定义绘图代码,您将获得简单的混合和透明度选项。如果你愿意等,我计划在2个月后完成自定义控件模板的重写,我想我会愿意分享的。
  • @Helix:谢谢。我将尝试修复ClearType 字体问题,因为这是我唯一需要解决的问题。在放弃 TransparentBlt 并手动执行透明度复选框后,问题就消失了。如果我能修复字体,我的问题就会解决!问题在于 mask bitmap -> 抗锯齿“欺骗”它以获取源背景颜色。正如成员 arx 所指出的,当我将源blit 成单色DC 文本背景时,由于它被组合,因此没有将其转换为适当的颜色。如果我能以某种方式创建适当的面具,一切都会得到解决......
猜你喜欢
  • 2013-09-27
  • 2019-09-21
  • 2013-03-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-05-15
  • 1970-01-01
相关资源
最近更新 更多