Search code examples
cwinapitransparencychromium-embeddedalphablending

Windows AlphaBlend with buffer usage


I would like to draw a buffer (with alpha information!) within a given window. The drawing is done outside of WM_PAINT (it is done in CefRenderHandler::OnPaint method called from Chromium-Embedded-Framework).

The problem, that I have are:

  • old content of the window is not cleared (if buffer is changed, I get old content drawn and new content drawn).
  • the alpha channel is wrongly interpreted - I think, that even though the pixel has the alpha information, it is painted as it would not have the alpha information

This is what I have so far:

OnPaint(...):

HDC screen_dc = GetDC(windowHandle);
RECT rcWin;
GetClientRect(windowHandle, &rcWin);

BITMAPINFO info;
ZeroMemory(&info, sizeof(BITMAPINFO));
info.bmiHeader.biBitCount = 32;
info.bmiHeader.biWidth = width;
info.bmiHeader.biHeight = -height;
info.bmiHeader.biPlanes = 1;
info.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
info.bmiHeader.biSizeImage = width*height * 4;
info.bmiHeader.biCompression = BI_RGB;

void *buf;
HBITMAP hDib = CreateDIBSection(screen_dc, &info, DIB_RGB_COLORS, (void **)&buf, 0, 0);
memcpy(buf, buffer, width * height * 4); //buffer contains bitmap to draw
HDC hDibDC = CreateCompatibleDC(screen_dc);
HGDIOBJ hOldObj = SelectObject(hDibDC, hDib);
BLENDFUNCTION blendFunction_;
blendFunction.BlendOp = AC_SRC_OVER;
blendFunction.BlendFlags = 0;
blendFunction.SourceConstantAlpha = 255;
blendFunction.AlphaFormat = AC_SRC_ALPHA;
AlphaBlend(screen_dc, 0, 0, width, height, hDibDC, 0, 0, width, height, blendFunction);

SelectObject(hDibDC, hOldObj);
ReleaseDC(windowHandle, screen_dc);
DeleteObject(hDib);
DeleteDC(hDibDC);

Window creation:

WNDCLASSEX wcex = {0};
wcex.cbSize = sizeof(wcex);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = BrowserWindowWndProc;
wcex.hInstance = hinstance;
wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
wcex.hbrBackground = WHITE_BRUSH;
wcex.lpszClassName = BROWSER_WINDOW_CLASS;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
RegisterClassEx(&wcex);

DWORD exStyle{0};
exStyle |= WS_EX_TOOLWINDOW;
exStyle |= WS_EX_LAYERED;

DWORD style {0};
style |= WS_SYSMENU;
style |= WS_VISIBLE;

HWND hWnd = CreateWindowEx(
    exStyle,
    BROWSER_WINDOW_CLASS, 
    BROWSER_WINDOW_CLASS,
    style,
    100,
    100,
    300,
    300,
    nullptr,
    nullptr, 
    hinstance,
    nullptr
);
...
SetLayeredWindowAttributes(hWnd, RGB(255, 255, 255), 255, LWA_COLORKEY);

Could you help me with these problems?

Thank you in advance.


Solution

  • It's not necessary to use both AlphaBlend and layered windows. Use layered windows only:

    void OnPaint(HDC hdc, int width, int height, HBITMAP hbitmap)
    {
        HDC memdc = CreateCompatibleDC(hdc);
        auto oldbmp = SelectObject(memdc, hbitmap);
    
        BITMAP bm;
        GetObject(hbitmap, sizeof(bm), &bm);
        BitBlt(hdc, 0, 0, bm.bmWidth, bm.bmHeight, memdc, 0, 0, SRCCOPY);
    
        SelectObject(memdc, oldbmp);
        DeleteDC(memdc);
    }
    

    Where hbitmap is a handle to the bitmap created earlier. The white areas of the bitmap should appear as transparent when using SetLayeredWindowAttributes(hwnd, RGB(255,255,255), 255, LWA_COLORKEY);

    Or use LWA_COLORKEY | LWA_ALPHA to adjust both transparency and alpha level.

    Assuming OnPaint is a response to WM_PAINT, use BeginPaint/EndPaint instead of GetDC/ReleaseDC

    Note that WHITE_BRUSH is zero, so wcex.hbrBackground = WHITE_BRUSH; sets the background brush to zero. Assign a brush handle instead.

    Alternatively you can use TransparentBlt in the same window:

    HDC memdc = CreateCompatibleDC(hdc);
    auto oldbmp = SelectObject(memdc, hbitmap);
    
    BITMAP bm;
    GetObject(hbitmap, sizeof(bm), &bm);
    TransparentBlt(hdc, 0, 0, width, height, 
        memdc, 0, 0, bm.bmWidth, bm.bmHeight, RGB(255, 255, 255));
    
    SelectObject(memdc, oldbmp);
    DeleteDC(memdc);