Mikołaj Mikołaj - 1 month ago 17
C++ Question

Off-screen drawing GDI+

I have a problem - I need to draw two png files, one on the other. When I do it usual way, there is a "blinking" effect (first image overdraws the second one for small time period). I use GDI+ library and my WM_PAINT handling looks like this:

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


displayImage function:

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


Loading files to variables:

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


I've heard that I should do a off-screen drawing. How should that look like?

Answer

First, change displayImage to take the HDC and RECT from the caller instead of the 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);
    }
}

Then, in the caller create a compatible DC and bitmap. These are your off-screen space for doing the composting. Make the calls to displayImage with this new DC. This will compose the PNGs offscreen. Finally, blit the composed result to the actual window DC in one go.

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;
}

Finally, if you're still seeing a flash of the background color, see Pavan Chandaka's answer.