I'm working on a Win32 app with layered windows. The window contents are drawn using Cairo. The transparency is very unpredictable however. Sometimes everything is correct; opaque parts are opaque, transparent parts transparent. Other times what should be opaque becomes transparent or vanishes completely.
This is the most minimal example I could reduce the problem to (based on this answer). It should draw a square in the upper left corner of the screen, with the left half transparent grey and the right half opaque black.
import core.sys.windows.windows;
import std.utf;
import std.stdio;
import cairo;
import cairo_win32;
const UINT WIDTH = 500;
const UINT HEIGHT = 500;
extern(Windows)
LRESULT WndProc(HWND hwnd, uint msg, WPARAM wParam, LPARAM lParam) nothrow {
return DefWindowProc(hwnd, msg, wParam, lParam);
}
void main(string[] args) {
WNDCLASS wndclass;
wndclass.lpszClassName = "Test".toUTF16z;
wndclass.lpfnWndProc = &WndProc;
RegisterClass(&wndclass);
HINSTANCE hInstance = GetModuleHandle(NULL);
HWND hwnd = CreateWindowEx(WS_EX_TOPMOST | WS_EX_LAYERED | WS_EX_TOOLWINDOW, wndclass.lpszClassName, "Test".toUTF16z, WS_POPUP, 50, 50, WIDTH, HEIGHT, NULL, NULL, hInstance, NULL);
ShowWindow(hwnd, SW_SHOWNA);
HDC hdcScreen = GetDC(NULL);
// Offscreen hdc for painting
HDC hdcMem = CreateCompatibleDC(hdcScreen);
HBITMAP hbmMem = CreateCompatibleBitmap(hdcScreen, WIDTH, HEIGHT);
auto hOld = SelectObject(hdcMem, hbmMem);
// Draw using offscreen hdc
auto surface = cairo_win32_surface_create(hdcMem);
auto cr = cairo_create(surface);
// Transparent grey
cairo_rectangle(cr, 0, 0, 250, HEIGHT);
cairo_set_source_rgba(cr, 0.4, 0.4, 0.4, 0.8);
cairo_fill(cr);
// Black
cairo_rectangle(cr, 250, 0, 250, HEIGHT);
cairo_set_source_rgba(cr, 0, 0, 0, 1);
cairo_fill(cr);
cairo_destroy(cr);
cairo_surface_destroy(surface);
// Show on screen
BLENDFUNCTION blend = { 0 };
blend.BlendOp = AC_SRC_OVER;
blend.SourceConstantAlpha = 255;
blend.AlphaFormat = AC_SRC_ALPHA;
RECT win_rect;
GetWindowRect(hwnd, &win_rect);
POINT ptZero = POINT(0, 0);
POINT win_pos = POINT(win_rect.left, win_rect.top);
SIZE win_dims = SIZE(WIDTH, HEIGHT);
UpdateLayeredWindow(hwnd, hdcScreen, &win_pos, &win_dims, hdcMem, &ptZero, RGB(0, 0, 0), &blend, ULW_ALPHA);
// Reset offscreen hdc to default bitmap
SelectObject(hdcMem, hOld);
// Cleanup
DeleteObject(hbmMem);
DeleteDC (hdcMem);
ReleaseDC(NULL, hdcScreen);
MSG msg;
while(GetMessage(&msg, NULL, 0, 0)) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
This works, as you can see here.
However, when I now switch the order of the two Cairo draw calls like this:
// Opaque black
cairo_rectangle(cr, 250, 0, 250, HEIGHT);
cairo_set_source_rgba(cr, 0.01, 0.01, 0.01, 1.0);
cairo_fill(cr);
// Transparent grey
cairo_rectangle(cr, 0, 0, 250, HEIGHT);
cairo_set_source_rgba(cr, 0.4, 0.4, 0.4, 0.8);
cairo_fill(cr);
Then the opaque right half becomes completely transparent.
What's going on here? I assume I must be doing something more fundamentally wrong, probably with the whole HDC and UpdateLayeredWindow
business.
Update: I tried to create an image surface with CAIRO_FORMAT_ARGB32
, used the exact same drawing operations and wrote it to a PNG file. In that case both images look correct and identical.
It seems like the first operation can't have an alpha of exactly 1.0. If I set the first rectangle's alpha to 1.0, the second (black opaque) rectangle vanishes. If I set the first rectangle's alpha to 0.99 instead, the second rectangle is drawn correctly.