2
votes

I have a layered window that I create myself with the WS_EX_LAYERED extended style and the UpdateLayeredWindow function.

Then I draw some text in it using GDI+ library, Graphics::DrawString method.

And the result is this: Screenshot of the layered window.

As you can see, the japanese, korean and chinese characters are completely transparent. They even make the window's white background transparent, which is not transparent at all.

The problem occurs only on Windows Vista and Windows 7 when Desktop Composition (Aero theme) is disabled.
On Windows 10 it works fine, as Desktop Composition is always enabled there.

Why does this strange effect happen only with East-Asian characters?
And how can this be solved?

1
You are drawing on a 32bpp bitmap? I'm guessing the font engine changes the alpha values for some reason.Anders
@Anders, yes, it's a 32bit DIB section created with CreateDIBSection.HHR
You could verify that the alpha is incorrectly set to 0 by using a custom fill function that only sets the alpha values back to 255 after writing the strings.Anders
@Anders, that's going to be problematic because I do need some parts of the window to be completely transparent and also the white background can be replaced by any other color, even semi-transparent.HHR
What's even more strange is that when I change the text color to white over a dark background, the problem DOES NOT happen.HHR

1 Answers

0
votes

I don't have a Windows 7 machine to test on so I don't know if the alpha channel is the real issue but assuming that it is, you can work around it by setting the alpha channel back to the correct state after writing the buggy text:

enum { WIDTH = 255 * 3, HEIGHT = 25 };
#define CalcStride(w, bpp) ( ((((w) * (bpp)) + 31) & ~31) >> 3 )
#define PMC(c, a) ( (c) = ((int)(c) * (a) / 255) )
#define PM(q) PMC( (q).rgbRed, (q).rgbReserved), PMC( (q).rgbGreen, (q).rgbReserved), PMC( (q).rgbBlue, (q).rgbReserved)

RGBQUAD* GetPxPtr32(void*pBits, UINT x, UINT y)
{
    return ((RGBQUAD*) ( ((char*)pBits) + (y * CalcStride(WIDTH, 32)) )) + x;
}

void SaveAlpha32(void*pBits, BYTE*buf)
{
    for (UINT x = 0; x < WIDTH; ++x)
        for (UINT y = 0; y < HEIGHT; ++y)
            buf[(y * WIDTH) + x] = GetPxPtr32(pBits, x, y)->rgbReserved;
}

void RestoreAlpha32(void*pBits, const BYTE*buf)
{
    for (UINT x = 0; x < WIDTH; ++x)
        for (UINT y = 0; y < HEIGHT; ++y)
            GetPxPtr32(pBits, x, y)->rgbReserved = buf[(y * WIDTH) + x];
}

void Draw(HDC hDC, HBITMAP hBM, void*pBits, UINT w, UINT h, bool isDwmActive)
{
    // Fill with white and a silly gradient alpha channel:
    for (UINT y = 0; y < h; ++y)
        for (UINT x = 0; x < w; ++x)
            (*(UINT32*)GetPxPtr32(pBits, x, y)) = 0xffffffff, GetPxPtr32(pBits, x, y)->rgbReserved = max(42, x % 255);

    BYTE *alphas = isDwmActive ? 0 : (BYTE*) LocalAlloc(LPTR, sizeof(BYTE) * w * h), fillWithRed = true;
    if (!isDwmActive) SaveAlpha32(pBits, alphas);
    HGDIOBJ hBmOld = SelectObject(hDC, hBM);
    RECT r = { 0, 0, WIDTH, HEIGHT };
    int cbk = SetBkColor(hDC, RGB(255, 0, 0));
    if (fillWithRed) ExtTextOut(hDC, 0, 0, ETO_OPAQUE, &r, NULL, 0, NULL);
    int ctx = SetTextColor(hDC, RGB(0, 0, 0));
    int mode = SetBkMode(hDC, TRANSPARENT);
    DrawText(hDC, TEXT("Hello World Hello World Hello World Hello World Hello World"), -1, &r, DT_SINGLELINE|DT_VCENTER|DT_CENTER); // Plain GDI always destroys the alpha
    SetBkMode(hDC, mode), SetBkColor(hDC, cbk), SetTextColor(hDC, ctx);
    SelectObject(hDC, hBmOld), GdiFlush();
    if (!isDwmActive) RestoreAlpha32(pBits, alphas), LocalFree(alphas);
    for (UINT y = 0; y < h; ++y) for (UINT x = 0; x < w; ++x) PM(*GetPxPtr32(pBits, x, y));
}

int main()
{
    const INT w = WIDTH, h = HEIGHT, bpp = 32, x = 222, y = 222;
    HWND hWnd = CreateWindowEx(WS_EX_LAYERED|WS_EX_TOPMOST, WC_STATIC, 0, WS_VISIBLE|WS_POPUP, x, y, WIDTH, HEIGHT, 0, 0, 0, 0);
    SetWindowLong(hWnd, GWLP_WNDPROC, (LONG_PTR) DefWindowProc); // HACK
    BITMAPINFO bi;
    ZeroMemory(&bi, sizeof(bi));
    BITMAPINFOHEADER&bih = bi.bmiHeader;
    bih.biSize = sizeof(BITMAPINFOHEADER);
    bih.biWidth = w, bih.biHeight = -h;
    bih.biPlanes = 1, bih.biBitCount = bpp;
    bih.biCompression = BI_RGB;
    void*bits;
    HBITMAP hBmp = CreateDIBSection(NULL, &bi, DIB_RGB_COLORS, &bits, NULL, 0);
    HDC hDCScreen = GetDC(NULL), hDC = CreateCompatibleDC(hDCScreen);
    Draw(hDC, hBmp, bits, w, h, false);
    HGDIOBJ hBmOld = SelectObject(hDC, hBmp);
    BLENDFUNCTION blend = { 0 };
    blend.BlendOp = AC_SRC_OVER, blend.AlphaFormat = AC_SRC_ALPHA, blend.SourceConstantAlpha = 255;
    POINT location = { x, y }, srcpt = { 0, 0 };
    SIZE szWnd = { w, h };
    UpdateLayeredWindow(hWnd, hDCScreen, &location, &szWnd, hDC, &srcpt, 0, &blend, ULW_ALPHA);
    SelectObject(hDC, hBmOld), DeleteObject(hBmp);
    DeleteDC(hDC), ReleaseDC(NULL, hDCScreen);
    struct Closer { Closer(HWND h) { SetTimer(h, 1, 1000 * 11, TP); } static void CALLBACK TP(HWND h,UINT,UINT_PTR,DWORD) { ExitProcess(666); } } closer(hWnd); // HACK
    for (MSG msg; GetMessage(&msg, 0, 0, 0); ) DispatchMessage(&msg);
    return 666;
}

If you don't care about Vista without the platform update, you can try using Direct2D instead of GDI+.