【问题标题】:Is it possible to render antialiased text onto a transparent background with pure GDI?是否可以使用纯 GDI 将抗锯齿文本渲染到透明背景上?
【发布时间】:2014-11-19 08:29:38
【问题描述】:

我最近一直在问很多关于文本别名、行别名和透明度的问题,因为我想为 Go 编写一个与平台无关的矢量图形系统; Windows 代码是用 C 编写的。预乘恶作剧使我将重点转移到仅呈现文本上(这样我就可以访问系统字体)。

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

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

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

【问题讨论】:

  • 有可能,只是不要指望它看起来像抗锯齿文本。典型的结果是黑色斑点,背景是黑色且 alpha 为 0 的结果。从黑色到黑色的抗锯齿。核心问题当然是你永远无法猜测窗口或位图背后的实际背景,这需要时间机器。请注意 Windows 如何解决玻璃问题,窗口的标题呈现在与文本形状大致相同的乳白色背景上。 DrawThemeTextEx() 用 DTT_GLOWSIZE 做到这一点。
  • 我一直想知道是否有可能,使用 maths 的强大功能,将包含针对已知纯色背景颜色进行抗锯齿处理的文本的位图转换为具有阿尔法通道。如果您想出办法,请告诉我们:)
  • 检测抗锯齿像素应该很容易(只需搜索任何不是输入颜色的颜色),尽管当所讨论的颜色足够接近白色时会产生问题。 @Hans 我知道我的 blob 颜色是全白色的,因为我正在用 memset() 手动填写 ppvBits,但除此之外,是的,这正是我的问题!
  • 我不得不问这个问题:为什么不简单地使用 Cairo 和 go-cairo 绑定?或者为 AGG (Anti-Grain Geometry) 编写绑定?
  • 我已经在 Unix 系统上使用 cairo。我的想法是保持无依赖性。 AGG 可能会起作用,也许,嗯...

标签: c winapi gdi alpha text-rendering


【解决方案1】:

我为此编写了一些代码。

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;

【讨论】:

  • @andlabs 我没有提到我的代码会计算透明度。它对你有用吗?
  • 我还没有测试过,但我相信你的话。 +1
  • (抱歉耽搁了。)似乎对我有用,但它只是我还是这个逗号太透明了? Helvetica,12 点,抗锯齿或 ClearType 质量(它们看起来与逗号相同)。 imgur.com/A7GYmmO(在酒中)再次感谢!这是一个聪明的方法,我希望我能想到 =P
  • @andlabs 干杯。代码中的计算假设线性亮度,因此您可以通过添加伽马校正来改善结果。也就是说,位图中的逗号对我来说看起来不错。您还可以通过在同一位图上使用TextOut 进行绘制来创建更客观的测试。
  • 我会尽快记录下来,然后再报告。
猜你喜欢
  • 1970-01-01
  • 2013-03-07
  • 1970-01-01
  • 1970-01-01
  • 2012-03-08
  • 2021-01-07
  • 2010-11-20
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多