Search code examples
winapiwindows-7gdi+text-renderinglayered-windows

Drawing asian text with GDI+ gives transparent characters on a layered window


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?


Solution

  • 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+.