是否可以使用纯GDI将抗锯齿文本渲染到透明背景上?

时间:2014-09-24 18:19:45

标签: c winapi gdi alpha text-rendering

我最近一直在问很多关于文本别名和行别名和透明度的问题,因为我想为Go编写一个平台无关的矢量图形系统; Windows代码是用C编写的。预乘的恶作剧让我把注意力转移到只渲染文本上(所以我可以访问系统字体)。

现在我有一些东西可以将文本绘制到屏幕外的位图。除了抗锯齿位之外,这是有效的。在我的代码中,当我用0xFF填充内存缓冲区来翻转alpha字节(GDI为绘制的像素设置为0x00)时,抗锯齿是白色的。 Other people have seen antialiasing to black. ANTIALIASED_QUALITYCLEARTYPE_QUALITY都会发生这种情况。

在这种情况下,我使用TextOut()绘制到DIB中。 DIB由屏幕DC(GetDC(NULL))的副本支持。

我能做些什么来让文字变得透明吗?我可以以某种方式检测白色像素,将它们混合并将其转换为alpha吗?对于与白色太相似的颜色,我该怎么做?

1 个答案:

答案 0 :(得分:4)

我写了一些代码来做这件事。

AntialiasedText功能将消除锯齿的文本绘制到离屏位图上。它计算透明度,以便使用AlphaBlend API函数将文本与任何背景混合。

该函数后跟一个WM_PAINT处理程序,说明其用途。

// Yeah, I'm lazy...
const int BitmapWidth = 500;
const int BitmapHeight = 128;

// Draw "text" using the specified font and colour and return an anti-aliased bitmap
HBITMAP AntialiasedText(LOGFONT* plf, COLORREF colour, LPCWSTR text)
{
    BITMAPINFO bmi = {0};
    bmi.bmiHeader.biSize = sizeof(bmi.bmiHeader);
    bmi.bmiHeader.biWidth = BitmapWidth;
    bmi.bmiHeader.biHeight = BitmapHeight;
    bmi.bmiHeader.biPlanes = 1;
    bmi.bmiHeader.biBitCount = 32;

    LPBYTE pBits;

    HBITMAP hDIB = CreateDIBSection(0, &bmi, DIB_RGB_COLORS, (LPVOID*)&pBits, 0, 0);

    // Don't want ClearType
    LOGFONT lf = *plf;
    lf.lfQuality = ANTIALIASED_QUALITY;
    HFONT hFont = CreateFontIndirect(&lf);

    HDC hScreenDC = GetDC(0);
    HDC hDC = CreateCompatibleDC(hScreenDC);
    ReleaseDC(0, hScreenDC);

    HBITMAP hOldBMP = (HBITMAP)SelectObject(hDC, hDIB);
    HFONT hOldFont = (HFONT)SelectObject(hDC, hFont);

    RECT rect = {0, 0, BitmapWidth, BitmapHeight};
    FillRect(hDC, &rect, WHITE_BRUSH);

    TextOut(hDC, 2, 2, text, wcslen(text));

    // Flush drawing
    GdiFlush();

    // Calculate alpha
    LPBYTE pixel = pBits;
    int pixelCount = BitmapWidth * BitmapHeight;
    BYTE r = GetRValue(colour);
    BYTE g = GetGValue(colour);
    BYTE b = GetBValue(colour);
    for (int c = 0; c != pixelCount; ++c)
    {
        // Set alpha
        BYTE alpha = 255 - pixel[0];
        pixel[3] = alpha;
        // Set colour
        pixel[0] = b * alpha / 255;
        pixel[1] = g * alpha / 255;
        pixel[2] = r * alpha / 255;
        pixel += 4;
    }

    SelectObject(hDC, hOldFont);
    SelectObject(hDC, hOldBMP);

    DeleteDC(hDC);

    DeleteObject(hFont);

    return hDIB;
}

这是一个WM_PAINT处理程序来执行该功能。它会两次绘制相同的文本,首先使用TextOut,然后使用消除锯齿的位图。它们看起来大致相同,但不如ClearType好。

case WM_PAINT:
    {
        LPCWSTR someText = L"Some text";

        hdc = BeginPaint(hWnd, &ps);

        LOGFONT font = {0};
        font.lfHeight = 40;
        font.lfWeight = FW_NORMAL;
        wcscpy_s(font.lfFaceName, L"Comic Sans MS");

        // Draw the text directly to compare to the bitmap
        font.lfQuality = ANTIALIASED_QUALITY;
        HFONT hFont = CreateFontIndirect(&font);
        font.lfQuality = 0;
        HFONT hOldFont = (HFONT)SelectObject(hdc, hFont);
        TextOut(hdc, 2, 10, someText, wcslen(someText));
        SelectObject(hdc, hOldFont);
        DeleteObject(hFont);

        // Get an antialiased bitmap and draw it to the screen
        HBITMAP hBmp = AntialiasedText(&font, RGB(0, 0, 0), someText);
        HDC hScreenDC = GetDC(0);
        HDC hBmpDC = CreateCompatibleDC(hScreenDC);
        ReleaseDC(0, hScreenDC);

        HBITMAP hOldBMP = (HBITMAP)SelectObject(hBmpDC, hBmp);

        BLENDFUNCTION bf;
        bf.BlendOp = AC_SRC_OVER;
        bf.BlendFlags = 0;
        bf.SourceConstantAlpha = 255;
        bf.AlphaFormat = AC_SRC_ALPHA;

        int x = 0;
        int y = 40;

        AlphaBlend(hdc, x, y, BitmapWidth, BitmapHeight, hBmpDC, 0, 0, BitmapWidth, BitmapHeight, bf);

        SelectObject(hBmpDC, hOldBMP);
        DeleteDC(hBmpDC);

        DeleteObject(hBmp);

        EndPaint(hWnd, &ps);
    }
    break;