c++ - TransparentBlt 结果不正确

标签 c++ winapi bitmap

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

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

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

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

  4. 使用 TransparentBlt 合并这两张图片和树的背景颜色 ( TransparentBlt( finalDC, ... , memDC, ... ); )。


在父窗口中我实现了WM_PRINTCLIENT ,所以我可以用一个简单的 ::SendMessage( GetParent(hwnd), WM_PRINTCLIENT, (WPARAM)finalDC, (LPARAM)(PRF_CLIENT) ); 将它的背景复制到内存 DC(第 2 步)中称呼。我得到的结果在 Windows XPWindows7 上都是正确的:

enter image description here


我用 ::DefSubclassProc( hwnd, WM_PAINT, (WPARAM)memDC, 0 ); 获得树的默认位图(步骤 1)称呼。同样,结果在 Windows XPWindows7 上都是正确的:

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

enter image description here

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

enter image description here


但是,在TransparentBlt()之后调用,最终图片绘制不正常:

Windows XP 上,复选框是问题 -> enter image description here

Windows7 上,字母中留有一些白色 -> enter image description here


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

这是 WM_PAINT 的代码片段:

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;

我怎样才能正确地将默认树位图与 parent 的背景结合起来,实现没有视觉伪影的透明度?

编辑:

我能够通过放弃 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 而不是 BeginPaintreturn (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;

只有字体有待修复。

编辑结束

最佳答案

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

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

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

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

关于c++ - TransparentBlt 结果不正确,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23432110/

相关文章:

c - WSAConnectByName 超时

位图上的 C++ 模糊效果有效但颜色已更改

c++ - 如何围绕它的一个顶点旋转一条线

c++ - 如何使用密码(C/C++)锁定Windows 7计算机屏幕

winapi - 我可以使用 svchost.exe 来托管我自己的服务吗?

android - 从自定义 SurfaceView 获取位图

java - 将 4 位灰度字节数组转换为位图 Android

c++ - boost::multi_index 来自同一对象的多个键

c++ - 类包含其他类问题的字段

c++ - 使 C++ 变量成为 const