离屏绘制 GDI+

5

我有一个问题 - 我需要绘制两个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并使用内存DC作为参数调用BitBlt。 - jhbh
请注意,您的ReleaseDC应该在else子句之外。 - Adrian McCarthy
@jhbh:这并没有回答这个问题。它是在询问如何对两张图片进行alpha混合。 - IInspectable
3个回答

6

你不需要那么多内容,可以直接使用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;
}

然而,这段代码仍然会出现可能造成闪烁的两张图片交替绘制(就像你原先的代码一样)。在 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


3

首先,将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图片。最后,一次性将合成的结果传输到实际窗口的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 - Mikołaj
关于 SRCCOPY:这就是我从记忆中打字的结果。已编辑答案进行修复。请注意,如果您经常绘制,则还可以缓存额外的内存 DC 和位图以获得更好的效率。 - Adrian McCarthy

0

自己处理 "WM_ERASEBKGND" 消息。

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

  1. 首先触发 WM_ERASEBKGND,用当前窗口背景颜色填充图像区域。
  2. WM_PAINT 渲染操作。

文档建议为 "WM_ERASEBKGND" 提供默认处理程序以避免闪烁/闪烁。

以下是链接,转到 "不闪烁的控件"。您也有一个示例。

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


似乎你没有理解闪烁的原因以及如何预防它。这个问题中的闪烁是由于连续渲染两张图像造成的,所以你提出的解决方案并不适用。我有点不安,因为我必须因为你不够关心而浪费我的声誉。我从来没有看到过你给出一个有用的答案。在尝试回答下一个问题之前,请确保你完全理解它,并且你知道答案。 - IInspectable
老板,我在一个月内达到了983的声望值。我认为您难以理解我。您来这里并不是为了指挥别人。 - Pavan Chandaka
他正在加载第二张图片。我的回答有两个步骤,分别说明了当他加载第二张图片时会发生什么。第一步解释了这一点。窗口过程将首先使用WM_ERASEBKGND消息重新绘制图像区域的背景。你打开提供的链接并阅读了吗? - Pavan Chandaka
加载图像不会导致任何渲染发生。它从磁盘读取一块字节,然后将其存储在内存中的一个块中。所以你的解释没有任何意义。现在如果你想说的是在渲染第一张和第二张图片之间,那仍然是错误的。OP的代码中没有任何会导致系统生成WM_ERASEBKGND消息的东西在显示这些图片之间。OP的问题和你链接中概述的问题是不同的。 - IInspectable
现在,测试你的评估。编写一个小的测试用例,并观察系统是否在渲染这两个图像之间生成了 WM_ERASEBKGND 消息。如果没有生成,请删除你的答案(因为显然是错误的)。 - IInspectable
显示剩余5条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接