屏幕外绘制GDI+

Off-screen drawing GDI+

本文关键字:GDI+ 绘制 屏幕      更新时间:2023-10-16

我有一个问题-我需要绘制两个png文件,一个在另一个上。当我用通常的方法做的时候,会有一个"闪烁"的效果(第一张图片在一小段时间内透支了第二张图片)。我使用GDI+库,我的WM_PAINT处理看起来像这样:

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

displayImage功能:

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");

我听说我应该做一个屏幕外的绘画。应该是什么样子呢?

您不需要所有这些。你可以直接使用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

首先,更改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的答案。

自行处理"WM_ERASEBKGND"消息

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

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

文档说,为了避免blink/Flickr,为"WM_ERASEBKGND"提供一个默认处理程序。

下面是链接,选择"不闪烁的控件"。你也有一个例子。

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