使用 GDI+ 绘制亚洲文本可在分层 window 上提供透明字符

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

我有一个分层的 window,我自己用 WS_EX_LAYERED 扩展样式和 UpdateLayeredWindow 函数创建的。

然后我使用 GDI+ 库 Graphics::DrawString 方法在其中绘制一些文本。

结果是这样的: Screenshot of the layered window.

如您所见,日文、韩文和中文字符是完全透明的。他们甚至让 window 的白色背景变得透明,这根本不是透明的。

只有在 Windows Vista 和 Windows 7 禁用 Desktop Composition(Aero 主题)时才会出现此问题。
在 Windows 10 上它工作正常,因为桌面合成总是在那里启用。

为什么只有东亚字符才会出现这种奇怪的效果?
如何解决?

我没有 Windows 7 机器来测试,所以我不知道 alpha 通道是否是真正的问题,但假设是,您可以通过设置 alpha 来解决它写入错误文本后通道返回正确状态:

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;
}

如果你不关心没有平台更新的 Vista,你可以尝试使用 Direct2D 而不是 GDI+。