c++ - SDL - 如何从仅 alpha 的位图中呈现文本?
问题描述
我正在尝试使用 SDL 呈现文本。显然 SDL 本身不支持渲染文本,所以我采用了这种方法:
- 加载字体文件
- 字体中的光栅字形到位图
- 将所有位图打包成一个大纹理,形成一个字形精灵表
- 将文本渲染为一系列字形精灵:将矩形从纹理复制到目标
第一步是使用 FreeType 库处理的。它可以为多种字体生成位图,并提供很多关于字形的额外信息。FreeType 生成的位图(默认情况下)仅是 Alpha 通道。对于每个字形,我基本上得到一个 0 - 255 范围内 A 值的二维数组。为简单起见,下面的 MCVE 只需要 SDL,我已经在源代码中嵌入了 FreeType 生成的位图。
现在,问题是:我应该如何管理由这些位图组成的纹理?
- 我应该使用什么混合模式?
- 我应该使用什么调制?
- 纹理应该填充什么?FreeType 只提供 alpha 通道,SDL 通常需要 RGBA 像素的纹理。我应该为 RGB 使用什么值?
- 如何以特定颜色绘制文本?我不想为每种颜色制作单独的纹理。
- FreeType 文档说:为了在屏幕上实现最佳渲染,位图应用作线性混合与伽马校正的 alpha 通道。SDL 混合模式文档没有列出任何名为线性混合的内容,因此我使用了自定义的,但不确定是否正确。
- 我不确定我是否得到了一些 SDL 调用,因为其中一些调用记录不充分(我已经知道在 Direct3D 上用空矩形锁定会崩溃),尤其是如何使用
SDL_LockTexture
.
#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
,即每个人都使用的“标准”“src-alpha, one-minus-src-alpha”公式(请注意,它不会混合 dst alpha 通道本身,也不会在任何程度上使用它;混合时,我们只关心 src alpha)。
最后还有一种方法:你可以把你的字形亮度值(alpha,不管它叫什么,关键是它只有一个通道)并将它放入每个通道。这样你就可以在不使用 alpha 的情况下进行加法混合,甚至不需要 RGBA 纹理。字形颜色仍然可以与颜色模式相乘。SDL_ttf 就是这样实现的。
推荐阅读
- c# - 当我死时,一颗心不会被移走,但不会有错误
- laravel - Laravel 输入循环
- vue.js - 如何将其他组件应用到 Vue Router 的一个路径
- javascript - 使用Javascript的Json中特定属性的总和
- airflow - 气流调度程序如何配置检查新文件的间隔?
- wordpress - 如何在管理员中使用复选框在 dropdwon 中显示 wp 标签
- windows - 从 DLL 获取导入表
- python - 反转字典中的键值(Python 中的高级反转字符串)
- javascript - 执行onload时如何更改变量
- laravel - 如何使用 Vue 在 Laravel 中上传文件 PDF