Можно ли визуализировать сглаженный текст на прозрачном фоне с помощью чистого GDI?

В последнее время я задавал много вопросов о сглаживании текста, сглаживании строк и прозрачности, потому что я хотел написать платформенно-независимую систему векторной графики для Go; код Windows написан на C. Из-за махинаций с предварительным умножением я сменил фокус на простую визуализацию текста (чтобы я мог получить доступ к системным шрифтам).

Прямо сейчас у меня есть что-то, что рисует текст на растровом изображении вне экрана. Это работает, за исключением битов сглаживания. В моем коде, когда я заполняю буфер памяти значением 0xFF, чтобы перевернуть альфа-байт (который GDI устанавливает в 0x00 для нарисованного пикселя), сглаживание становится белым. Другие люди видели сглаживание до черного. Это происходит как с ANTIALIASED_QUALITY, так и с CLEARTYPE_QUALITY.

В этом случае я рисую с помощью TextOut() в DIB. DIB поддерживается копией экрана DC (GetDC(NULL)).

Что я могу сделать, чтобы текст был прозрачным? Могу ли я каким-то образом обнаружить белые пиксели, разделить их и преобразовать в альфа-канал? Как бы я сделал это для цветов, слишком похожих на белый?


person andlabs    schedule 24.09.2014    source источник
comment
Возможно, только не рассчитывайте, что это будет выглядеть как сглаженный текст. Типичный результат - черная клякса, в результате чего фон становится черным с альфа-каналом 0. Сглаживание от черного к черному. Основная проблема, конечно же, в том, что вы никогда не сможете угадать реальный фон за окном или растровым изображением, для чего нужна машина времени. Обратите внимание, как Windows решила это для стекла: заголовок окна отображается на молочном фоне, который примерно такой же формы, как и текст. DrawThemeTextEx () делает это с помощью DTT_GLOWSIZE.   -  person Hans Passant    schedule 24.09.2014
comment
Я всегда задавался вопросом, возможно ли, используя возможности математики, преобразовать растровое изображение, содержащее текст, сглаженный по известному сплошному цвету фона, в текст с альфа-каналом. Если вы придумаете, как это сделать, дайте нам знать :)   -  person Jonathan Potter    schedule 24.09.2014
comment
Обнаружение пикселей сглаживания должно быть простым (просто найдите любой цвет, который не является цветом ввода), хотя это создает проблемы, когда рассматриваемый цвет достаточно близок к белому. @Hans Я знаю, что цвет моей капли полностью белый, потому что я вручную заполняю ppvBits на memset(), но кроме этого, да, это именно моя проблема!   -  person andlabs    schedule 24.09.2014
comment
Я должен спросить: почему бы просто не использовать привязки Cairo и go-cairo? Или написать привязки для AGG (Anti-Grain Geometry)?   -  person datenwolf    schedule 25.09.2014
comment
Я уже использую cairo в системах Unix. Моя идея заключалась в том, чтобы оставаться без зависимости. Может, AGG сработает, хм ...   -  person andlabs    schedule 25.09.2014


Ответы (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;
person arx    schedule 24.09.2014
comment
@andlabs Я забыл упомянуть, что мой код вычисляет прозрачность. Работает ли это для вас? - person arx; 25.09.2014
comment
Я еще не тестировал, но верю вам на слово. +1 - person γηράσκω δ' αεί πο; 25.09.2014
comment
(Приносим извинения за задержку.) Кажется, у меня работает, хотя это только у меня или эта запятая слишком прозрачна? Helvetica, 12 пунктов, качество сглаживания или ClearType (они выглядят одинаково для запятой). imgur.com/A7GYmmO (в вине) Еще раз спасибо! Это умный подход, и я хотел бы подумать об этом = P - person andlabs; 30.09.2014
comment
@andlabs Ура. Расчеты в коде предполагают линейную яркость, поэтому вы можете улучшить результат, добавив гамма-коррекцию. Тем не менее, запятая в вашем растровом изображении мне нравится. Вы можете создать более объективный тест, нарисовав TextOut на том же растровом изображении. - person arx; 30.09.2014
comment
Я сделаю это в ближайшее время и сообщу об этом. - person andlabs; 30.09.2014