Drawing a partially transparent GDI+ Bitmap to a b

2019-04-02 18:17发布

I'm trying to create a non-rectangular window with semi-transparent pixels. The image does not come from a PNG but is drawn on-the-fly using GDI+ calls.

I create the window as follows:

WNDCLASSEX wc = WNDCLASSEX();
wc.cbSize = sizeof(wc);
HINSTANCE instance = GetModuleHandle(nullptr);
std::wstring classname(L"gditest ui window class");

if (!GetClassInfoEx(instance, classname.c_str(), &wc)) {
    //wc.cbSize;
    //wc.style = CS_DROPSHADOW;
    wc.lpfnWndProc = process_messages;
    //wc.cbClsExtra;
    //wc.cbWndExtra;
    wc.hInstance = instance;
    wc.hIcon;
    wc.hCursor = LoadCursor(0, IDC_ARROW);
    //wc.hbrBackground;
    //wc.lpszMenuName;
    wc.lpszClassName = classname.c_str();
    wc.hIconSm;

    if (!RegisterClassEx(&wc))
        throw GetLastError();
}

m_window = CreateWindowEx(WS_EX_APPWINDOW | WS_EX_LAYERED,
    classname.c_str(), L"User Interface",
    WS_VISIBLE,
    CW_USEDEFAULT, CW_USEDEFAULT, 640, 480,
    HWND_DESKTOP, 0, instance, this);

if (!m_window)
    throw GetLastError();

update_window();

The update_window() function looks like this:

void user_interface::update_window()
{
    RECT r;
    GetClientRect(m_window, &r);

    Bitmap buf(r.right - r.left, r.bottom - r.top, PixelFormat32bppARGB);

    Graphics gfx(&buf);
    Rect rect(r.left, r.top, r.right - r.left, r.bottom - r.top);
    SolidBrush b(Color(0x7f00ff00));
    gfx.FillRectangle(&b, rect);

/*  CLSID clsid;
    UINT numbytes = 0, numenc = 0;
    GetImageEncodersSize(&numenc, &numbytes);

    std::vector<char> encoders(numbytes, 0);
    ImageCodecInfo *encoderptr = (ImageCodecInfo *)&encoders[0];
    GetImageEncoders(numenc, numbytes, encoderptr);

    clsid = encoderptr[4].Clsid;

    buf.Save(L"test.png", &clsid);
*/
    HDC gfxdc = gfx.GetHDC();
    HDC scrndc = GetDC(HWND_DESKTOP);

    BLENDFUNCTION blend;
    blend.BlendOp = AC_SRC_OVER;
    blend.BlendFlags = 0;
    blend.SourceConstantAlpha = 255;
    blend.AlphaFormat = AC_SRC_ALPHA;

    POINT src = POINT(), dst;
    SIZE size;

    GetWindowRect(m_window, &r);
    dst.x = r.left;
    dst.y = r.top;
    size.cx = buf.GetWidth();
    size.cy = buf.GetHeight();

    if (!UpdateLayeredWindow(m_window, scrndc, &dst, &size, gfxdc, &src, 0, &blend, ULW_ALPHA)) {
        throw GetLastError();
    }

    ReleaseDC(HWND_DESKTOP, scrndc);
    gfx.ReleaseHDC(gfxdc);
}

The commented piece of code saves the Bitmap object to a PNG, which I wrote just to confirm the bitmap is drawn properly.

No errors occur, however the result on screen is not what I intended. Instead of a nice 50% transparent green square I get a barely visible white square: screenshot.

Another weird thing is that clicks on the window fall through to whatever is underneath, eventhough it is slightly visible...

What am I doing wrong here?

标签: c++ windows gdi+
2条回答
▲ chillily
2楼-- · 2019-04-02 18:46

Managed to solve this myself by rewriting the update_window() method as such:

void user_interface::update_window()
{
    RECT r;
    GetClientRect(m_window, &r);

    HDC scrndc = GetDC(HWND_DESKTOP);
    HDC memdc = CreateCompatibleDC(scrndc);
    HBITMAP bmp = CreateCompatibleBitmap(scrndc, r.right - r.left, r.bottom - r.top);
    HBITMAP oldbmp = (HBITMAP)SelectObject(memdc, bmp);

    Graphics gfx(memdc);
    Rect rect(r.left, r.top, r.right - r.left, r.bottom - r.top);
    SolidBrush b(Color(0x7f00ff00));
    gfx.FillRectangle(&b, rect);

    BLENDFUNCTION blend;
    blend.BlendOp = AC_SRC_OVER;
    blend.BlendFlags = 0;
    blend.SourceConstantAlpha = 255;
    blend.AlphaFormat = AC_SRC_ALPHA;

    POINT src = POINT(), dst;
    SIZE size;

    size.cx = r.right - r.left;
    size.cy = r.bottom - r.top;

    GetWindowRect(m_window, &r);
    dst.x = r.left;
    dst.y = r.top;

    if (!UpdateLayeredWindow(m_window, scrndc, &dst, &size, memdc, &src, 0, &blend, ULW_ALPHA)) {
        throw GetLastError();
    }

    SelectObject(memdc, oldbmp);
    DeleteObject(bmp);
    DeleteDC(memdc);
    ReleaseDC(HWND_DESKTOP, scrndc);
}

Probably not the most efficient way to do it, but it works. Could probably keep the HBITMAP, memdc and Graphics object around for longer. Figuring this out is left as an exercise for the reader. ;)

查看更多
放我归山
3楼-- · 2019-04-02 18:52

The source bitmap for UpdateLayeredWindow() must be compatible to the screen, otherways your visual result is hardware-dependent. GDI and GDIplus seem to be able to draw into any bitmap, but bitblt operations generally cannot handle different (i.e. incompatible) formats. The reason is speed.

Unluckily, Windows documentation doesn't point out these facts very clearly.

查看更多
登录 后发表回答