SDL - 如何从只有 alpha 的位图中呈现文本?

SDL - how to render text from an alpha-only bitmap?

我正在尝试使用 SDL 呈现文本。显然 SDL 本身不支持渲染文本,所以我采用了这种方法:

第一步是使用 FreeType 库处理的。它可以为多种字体生成位图,并提供大量有关字形的额外信息。 FreeType 生成的位图(默认情况下)仅为 alpha 通道。对于每个字形,我基本上得到一个 0 - 255 范围内的 A 值的二维数组。为简单起见,下面的 MCVE 只需要 SDL,我已经在源代码中嵌入了 FreeType 生成的位图。

现在的问题是:我应该如何管理由这些位图组成的纹理?

#include <string>
#include <stdexcept>

#include <SDL.h>

constexpr unsigned char pixels[] = {
  0,   0,   0,   0,   0,   0,   0,  30,  33,   0,   0,   0,   0,   0,   0,
  0,   0,   0,   0,   0,   1, 169, 255, 155,   0,   0,   0,   0,   0,   0,
  0,   0,   0,   0,   0,  83, 255, 255, 229,   1,   0,   0,   0,   0,   0,
  0,   0,   0,   0,   0, 189, 233, 255, 255,  60,   0,   0,   0,   0,   0,
  0,   0,   0,   0,  33, 254,  83, 250, 255, 148,   0,   0,   0,   0,   0,
  0,   0,   0,   0, 129, 227,   2, 181, 255, 232,   3,   0,   0,   0,   0,
  0,   0,   0,   2, 224, 138,   0,  94, 255, 255,  66,   0,   0,   0,   0,
  0,   0,   0,  68, 255,  48,   0,  15, 248, 255, 153,   0,   0,   0,   0,
  0,   0,   0, 166, 213,   0,   0,   0, 175, 255, 235,   4,   0,   0,   0,
  0,   0,  16, 247, 122,   0,   0,   0,  88, 255, 255,  71,   0,   0,   0,
  0,   0, 105, 255, 192, 171, 171, 171, 182, 255, 255, 159,   0,   0,   0,
  0,   0, 203, 215, 123, 123, 123, 123, 123, 196, 255, 239,   6,   0,   0,
  0,  44, 255, 108,   0,   0,   0,   0,   0,  75, 255, 255,  77,   0,   0,
  0, 142, 252,  22,   0,   0,   0,   0,   0,   5, 238, 255, 164,   0,   0,
  5, 234, 184,   0,   0,   0,   0,   0,   0,   0, 156, 255, 242,   8,   0,
 81, 255,  95,   0,   0,   0,   0,   0,   0,   0,  68, 255, 255,  86,   0,
179, 249,  14,   0,   0,   0,   0,   0,   0,   0,   3, 245, 255, 195,   0
};

[[noreturn]] inline void throw_error(const char* desc, const char* sdl_err)
{
    throw std::runtime_error(std::string(desc) + sdl_err);
}

void update_pixels(
    SDL_Texture& texture,
    const SDL_Rect& texture_rect,
    const unsigned char* src_alpha,
    int src_size_x,
    int src_size_y)
{
    void* pixels;
    int pitch;
    if (SDL_LockTexture(&texture, &texture_rect, &pixels, &pitch))
        throw_error("could not lock texture: ", SDL_GetError());

    auto pixel_buffer = reinterpret_cast<unsigned char*>(pixels);
    for (int y = 0; y < src_size_y; ++y) {
        for (int x = 0; x < src_size_x; ++x) {
            // this assumes SDL_PIXELFORMAT_RGBA8888
            unsigned char* const rgba = pixel_buffer + x * 4;
            unsigned char& r = rgba[0];
            unsigned char& g = rgba[1];
            unsigned char& b = rgba[2];
            unsigned char& a = rgba[3];
            r = 0xff;
            g = 0xff;
            b = 0xff;
            a = src_alpha[x];
        }

        src_alpha += src_size_y;
        pixel_buffer += pitch;
    }

    SDL_UnlockTexture(&texture);
}

int main(int /* argc */, char* /* argv */[])
{
    if (SDL_Init(SDL_INIT_VIDEO) < 0)
        throw_error("could not init SDL: ", SDL_GetError());

    SDL_Window* window = SDL_CreateWindow("Hello World",
        SDL_WINDOWPOS_UNDEFINED,
        SDL_WINDOWPOS_UNDEFINED,
            1024, 768,
            SDL_WINDOW_RESIZABLE);

    if (!window)
        throw_error("could not create window: ", SDL_GetError());

    SDL_Renderer* renderer = SDL_CreateRenderer(window, -1, 0);
    if (!renderer)
        throw_error("could not create renderer: ", SDL_GetError());

    SDL_Texture* texture = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_STREAMING, 512, 512);
    if (!texture)
        throw_error("could not create texture: ", SDL_GetError());

    SDL_SetTextureColorMod(texture, 255, 0, 0);

    SDL_Rect src_rect;
    src_rect.x = 0;
    src_rect.y = 0;
    src_rect.w = 15;
    src_rect.h = 17;
    update_pixels(*texture, src_rect, pixels, src_rect.w, src_rect.h);

    /*
     * FreeType documentation: For optimal rendering on a screen the bitmap should be used as
     * an alpha channel in linear blending with gamma correction.
     *
     * The blending used is therefore:
     * dstRGB = (srcRGB * srcA) + (dstRGB * (1 - srcA))
     * dstA = (srcA * 0) + (dstA * 1) = dstA
     */
    SDL_BlendMode blend_mode = SDL_ComposeCustomBlendMode(
        SDL_BLENDFACTOR_SRC_ALPHA, SDL_BLENDFACTOR_ONE_MINUS_SRC_ALPHA, SDL_BLENDOPERATION_ADD,
        SDL_BLENDFACTOR_ZERO, SDL_BLENDFACTOR_ONE, SDL_BLENDOPERATION_ADD);

    if (SDL_SetTextureBlendMode(texture, blend_mode))
        throw_error("could not set texture blending: ", SDL_GetError());

    while (true) {
        SDL_SetRenderDrawColor(renderer, 255, 255, 0, 255);
        SDL_RenderClear(renderer);

        SDL_Rect dst_rect;
        dst_rect.x = 100;
        dst_rect.y = 100;
        dst_rect.w = src_rect.w;
        dst_rect.h = src_rect.h;
        SDL_RenderCopy(renderer, texture, &src_rect, &dst_rect);

        SDL_RenderPresent(renderer);
        SDL_Delay(16);

        SDL_Event event;
        while (SDL_PollEvent(&event)) {
            switch (event.type) {
                case SDL_KEYUP:
                    switch (event.key.keysym.sym) {
                        case SDLK_ESCAPE:
                            return 0;
                    }
                    break;
                case SDL_QUIT:
                    return 0;
            }
        }
    }
    return 0;
}

预期结果:黄底红字“A”。

实际结果:黄底黑方块内红线畸形。

我怀疑线断了,因为 update_pixels 中的指针算法存在错误,但我不知道是什么导致了黑色方块。

首先,部分内容已经在 SDL_ttf 库中完成。您可以使用它来将字形栅格化到表面或生成多字符文本表面。

您的 src_alpha += src_size_y; 不正确 - 您逐行复制,但按列长度而不是行长度跳过。应该是src_size_x。这会导致每行的偏移量不正确,只有复制图像的第一行是正确的。

你写纹理时的颜色包装是反的。参见 https://wiki.libsdl.org/SDL_PixelFormatEnum#order - Packed component order (high bit -> low bit): SDL_PACKEDORDER_RGBA,意思是 R 被打包在最高位,而 A 被打包在最低位。因此,当用 unsigned char* 表示时,第一个字节是 A,第四个字节是 R:

        unsigned char& r = rgba[3];
        unsigned char& g = rgba[2];
        unsigned char& b = rgba[1];
        unsigned char& a = rgba[0];

不需要自定义混合,使用SDL_BLENDMODE_BLEND,即'standard'“src-alpha,one-minus-src-alpha”公式大家都用(注意不是混合dst alpha 通道本身,也没有在任何程度上使用它;混合时,我们只关心 src alpha)。

最后还有另一种方法:你可以把你的字形亮度值(alpha,不管它叫什么,关键是它只有一个通道)并把它放入 every 渠道。这样你就可以在根本不使用 alpha 的情况下进行加法混合,甚至不需要 RGBA 纹理。字形颜色仍然可以与颜色 mod 相乘。 SDL_ttf 实现了这一点。