【问题标题】:Off-screen drawing GDI+离屏绘图 GDI+
【发布时间】:2016-10-31 19:40:42
【问题描述】:

我有一个问题 - 我需要绘制两个 png 文件,一个在另一个上。当我以通常的方式进行操作时,会出现“闪烁”效果(第一张图像在一小段时间内过度绘制了第二张图像)。我使用 GDI+ 库,我的 WM_PAINT 处理如下所示:

case WM_PAINT:
{
    PAINTSTRUCT ps; 
    HDC hdc = BeginPaint( hwnd, & ps );
    displayImage(firstImage, hwnd);
    displayImage(secondImage, hwnd);
    EndPaint( hwnd, & ps );
    break;
}

显示图像功能:

void displayImage(HBITMAP mBmp, HWND mHwnd)
{
    RECT myRect;
    BITMAP bm;
    HDC screenDC, memDC;
    HBITMAP oldBmp;
    BLENDFUNCTION bf;

    GetObject(mBmp, sizeof(bm), &bm);

    bf.BlendOp = AC_SRC_OVER;
    bf.BlendFlags = 0;
    bf.SourceConstantAlpha = 0xff;

    bf.AlphaFormat = AC_SRC_ALPHA;

    screenDC = GetDC(mHwnd);
    GetClientRect(mHwnd, &myRect);

    if (mBmp == NULL)
        FillRect(screenDC, &myRect, WHITE_BRUSH);

    else
    {
        memDC = CreateCompatibleDC(screenDC);
        oldBmp = (HBITMAP)SelectObject(memDC, mBmp);
        AlphaBlend (screenDC, 0, 0, myRect.right,myRect.bottom, memDC, 0, 0, bm.bmWidth,bm.bmHeight, bf);
        SelectObject(memDC, oldBmp);
        DeleteDC(memDC);
        ReleaseDC(mHwnd, screenDC);
    }
}

将文件加载到变量中:

HBITMAP mLoadImg(WCHAR *szFilename)
{
   HBITMAP result=NULL;

   Gdiplus::Bitmap* bitmap = new Gdiplus::Bitmap(szFilename,false);
   bitmap->GetHBITMAP(NULL, &result);
   delete bitmap;
   return result;
}


firstImage = mLoadImg(L"data\\img\\screen.png");
secondImage = mLoadImg(L"data\\img\\screen2.png");

我听说我应该做一个离屏绘图。那应该是什么样子?

【问题讨论】:

  • 创建一个内存 dc,将你的位图加载到其中,然后获取窗口 dc 并以 memorydc 作为参数调用 bitblt
  • 请注意,您的 ReleaseDC 应该在 else 子句之外。
  • @jhbh:这并没有解决这个问题。它要求对两个图像进行 alpha 混合。

标签: c++ winapi


【解决方案1】:

您不需要所有这些。您可以直接使用 GDI+:

static Gdiplus::Image *firstImage;
static Gdiplus::Image *secondImage;

case WM_CREATE: // or WM_INITDIALOG if it's dialog
{
    firstImage = new Gdiplus::Image(L"data\\img\\screen.png");
    secondImage = new Gdiplus::Image(L"data\\img\\screen2.png");
    return 0;
}

case WM_PAINT:
{
    PAINTSTRUCT ps = { 0 };
    HDC hdc = BeginPaint(hwnd, &ps);

    Gdiplus::Graphics gr(hdc);
    gr.DrawImage(firstImage, 0, 0);
    gr.DrawImage(secondImage, 0, 0);//<== this will draw transparently

    EndPaint(hwnd, &ps);

    return 0;
}

但是,此代码仍在背靠背绘制 2 张图像,可能会出现闪烁(就像您的原始代码一样)。在WM_PAINT 中使用双缓冲,以便只完成一个BltBlt。只需更改为:

if (msg == WM_PAINT)
{
    PAINTSTRUCT ps = { 0 };
    HDC hdc = BeginPaint(hwnd, &ps);

    RECT rc;
    GetClientRect(hwnd, &rc);
    HDC memdc = CreateCompatibleDC(hdc);
    HBITMAP hbitmap = CreateCompatibleBitmap(hdc, rc.right, rc.bottom);
    HGDIOBJ oldbmp = SelectObject(memdc, hbitmap);

    FillRect(memdc, &rc, WHITE_BRUSH);
    Gdiplus::Graphics gr(memdc);
    gr.DrawImage(firstImage, 0, 0);
    gr.DrawImage(secondImage, 0, 0);

    BitBlt(hdc, 0, 0, rc.right, rc.bottom, memdc, 0, 0, SRCCOPY);

    SelectObject(memdc, oldbmp);
    DeleteObject(hbitmap);
    DeleteDC(memdc);

    EndPaint(hwnd, &ps);

    return 0;
}

至于原代码:

void displayImage(HBITMAP mBmp, HWND mHwnd)
{
HDC hdc = GetDC(mHwnd);
...
}

您应该将函数声明更改为void displayImage(HBITMAP mBmp, HWND mHwnd, HDC hdc),然后您可以直接从WM_PAINT 传递hdc

【讨论】:

    【解决方案2】:

    首先,更改 displayImage 以从调用者那里获取 HDC 和 RECT,而不是 HWND。

    void displayImage(HBITMAP mBmp, HDC hdc, const RECT &myRect)
    {
        if (mBmp == NULL)
            FillRect(screenDC, &myRect, WHITE_BRUSH);
        else
        {
            BITMAP bm;
            GetObject(mBmp, sizeof(bm), &bm);
    
            HDC memDC = CreateCompatibleDC(screenDC);
            HBITMAP oldBmp = (HBITMAP)SelectObject(memDC, mBmp);
    
            BLENDFUNCTION bf;
            bf.BlendOp = AC_SRC_OVER;
            bf.BlendFlags = 0;
            bf.SourceConstantAlpha = 0xff;
            bf.AlphaFormat = AC_SRC_ALPHA;
    
            AlphaBlend(hdc, 0, 0, myRect.right, myRect.bottom, memDC, 0, 0, bm.bmWidth, bm.bmHeight, bf);
    
            SelectObject(memDC, oldBmp);
            DeleteDC(memDC);
        }
    }
    

    然后,在调用者中创建一个兼容的 DC 和位图。这些是您进行合成的屏幕外空间。使用这个新的 DC 调用 displayImage。这将在屏幕外组成 PNG。最后,将合成的结果一次性blit到实际的窗口DC。

    case WM_PAINT:
    {
        PAINTSTRUCT ps; 
        HDC hdc = BeginPaint(hwnd, &ps);
        RECT myRect;
        GetClientRect(hwnd, &myRect);
    
        // Create an off-screen DC for composing the images.
        HDC hdcMem = CreateCompatibleDC(hdc);
        HBITMAP hbmpMem = CreateCompatibleBitmap(hdc, myRect.right, myRect.bottom);
        HBITMAP hbmpOld = (HBITMAP) SelectObject(hdcMem, hbmpMem);
    
        // Compose the images to the offscreen bitmap.
        displayImage(firstImage, hdcMem, myRect);
        displayImage(secondImage, hdcMem, myRect);
    
        // Blit the resulting composition to the window DC.
        BitBlt(hdc, 0, 0, myRect.right, myRect.bottom,
               hdcMem, 0, 0, SRCCOPY);
    
        // Clean up the offscreen stuff.
        SelectObject(hdcMem, hbmpOld);
        DeleteObject(hbmpMem);
        DeleteDC(hdcMem);
    
        EndPaint(hwnd, &ps);
        break;
    }
    

    最后,如果您仍然看到背景颜色的闪光,请参阅 Pavan Chandaka 的回答。

    【讨论】:

    • 太棒了!有用。注意:BitBlt函数中应该有SRCCOPY而不是SRC_COPY
    • 回复SRCCOPY:这就是我从记忆中打字的结果。编辑答案以修复。请注意,如果您经常绘画,您还可以缓存额外的内存 DC 和位图以提高效率。
    【解决方案3】:

    自行处理“WM_ERASEBKGND”消息。

    实际上在加载第二张图片之前发生了两件事。

    1. 首先触发 WM_ERASEBKGND 以填充图像区域,无论当前 Windows 背景颜色是什么。
    2. WM_PAINT 渲染动作。

    文档说要避免闪烁/Flickr,请为“WM_ERASEBKGND”提供默认处理程序。

    以下是链接,转到“不闪烁的控件”。你也有例子。

    https://msdn.microsoft.com/en-us/library/ms969905.aspx

    【讨论】:

    • 您似乎没有理解导致闪烁的原因以及如何防止它。此问题中的闪烁是由背靠背渲染两个图像引起的,因此您提出的解决方案不适用。我觉得我不得不在你身上浪费我的名声有点不安,只是因为你不够关心。我从来没有见过你一个有用的答案。在尝试回答下一个问题之前,请确保您完全理解它,并且您知道答案。
    • Boss 我在一个月内获得了 983 的声望。我想你很难理解我。而且你不是在这里发号施令..
    • 看到您在第一个月就获得了Peer Pressure 徽章,也许不仅仅是我认为您的贡献值得怀疑。 (这个提议的答案肯定完全没有解决这个问题。)
    • 在“C++”标签中有很多人喜欢你。谁干脆投反对票。即使我知道答案,我也完全停止尝试该标签。
    • 好吧,既然你声称知道答案,你介意更新这个来解释一下吗,为什么WM_ERASEBKGND 提供一个默认处理程序可以解决 OP 的问题,并且怎么样?
    猜你喜欢
    • 1970-01-01
    • 2021-08-03
    • 1970-01-01
    • 2011-02-14
    • 1970-01-01
    • 2012-05-22
    • 2020-02-16
    • 2012-08-20
    • 1970-01-01
    相关资源
    最近更新 更多