收口字体渲染路径以降低 atlas 体积并直接输出 RGB565

将位图字体从 RGBA8888 atlas 改为 1-bit mask,避免继续为字体存储无效颜色信息。
生成端现在按 row-major、MSB-first 将 8 个像素打包进 1 个 uint8_t;运行时由
draw_text 提供目标颜色,并在 DrawContext 中直接把 bit=1 的像素写入 RGB565 framebuffer。同步补充了回归测试与文档,确保颜色替换、背景填充和裁剪语义保持稳定。

Constraint: 本次只收口字体路径,不改 sprite/tilemap 资源格式
Constraint: 运行时保持纯二值绘制语义,不引入 alpha blending 或抗锯齿
Rejected: 继续兼容 RGBA8888 字体输入 | 会保留双路径复杂度且无实际收益
Confidence: high
Scope-risk: moderate
Reversibility: clean
Directive: 后续字体资源必须继续使用 row-major + MSB-first 的 1-bit mask;修改位序前先同步生成脚本、运行时读取和测试
Tested: cmake --build build-win --config Release
Tested: build-win/Release/render_pipeline_tests.exe
Tested: ctest -C Release --output-on-failure
Tested: font_atlas_mask 字节数校验(2808 == ceil(208*108/8))
Not-tested: Demo/设备上的人工目视文字显示效果
This commit is contained in:
2026-06-09 08:20:38 +08:00
parent 5ac61dca0e
commit 23a5b50aec
10 changed files with 379 additions and 1451 deletions
+19 -14
View File
@@ -11,8 +11,8 @@
字体: 优先使用 assets/font/ndrtyyl-prqyys-undertale-hebrew-uppercase.ttf,
不存在时回退到系统等宽字体(Courier New / Consolas / monospace)
像素格式: (R << 24) | (G << 16) | (B << 8) | A
生成端会把 alpha 阈值化为 0/255,匹配当前 DrawContext::draw_text 的像素风绘制路径。
输出格式: 1-bit row-major bit-packed mask(MSB-first)
生成端会把 alpha 阈值化为 0/255,再将整张 atlas 逐行打包成遮罩位图。
"""
import os
@@ -91,16 +91,21 @@ def generate(output_dir: str):
png_path = os.path.join(output_dir, "font_atlas.png")
img.save(png_path)
# 生成 C 头文件:将每行像素打包成 bit 行(每像素 1 bit,白色=1,透明=0)
# 这样每个 glyph 的一行只需要 char_w/8 个字节
# 但为了简单,这里直接存储为 uint32_t RGBA 像素数组
# 使用时白色像素用指定颜色替换,透明像素跳过
pixels = []
mask_bits = []
packed = 0
bit_count = 0
for y in range(atlas_h):
for x in range(atlas_w):
r, g, b, a = img.getpixel((x, y))
pixels.append((r << 24) | (g << 16) | (b << 8) | a)
_, _, _, a = img.getpixel((x, y))
packed = (packed << 1) | (1 if a != 0 else 0)
bit_count += 1
if bit_count == 8:
mask_bits.append(packed)
packed = 0
bit_count = 0
if bit_count != 0:
mask_bits.append(packed << (8 - bit_count))
h_path = os.path.join(output_dir, "font_atlas.h")
with open(h_path, "w", encoding="utf-8") as f:
@@ -112,11 +117,11 @@ def generate(output_dir: str):
f.write(f"static const int32_t font_char_h = {char_h};\n")
f.write(f"static const int32_t font_first_char = {FIRST_CHAR};\n")
f.write(f"static const int32_t font_columns = {COLUMNS};\n\n")
f.write(f"static const uint32_t font_atlas_pixels[] = {{\n")
f.write(f"static const uint8_t font_atlas_mask[] = {{\n")
for i in range(0, len(pixels), 16):
chunk = pixels[i:i + 16]
line = ", ".join(f"0x{p:08X}" for p in chunk)
for i in range(0, len(mask_bits), 16):
chunk = mask_bits[i:i + 16]
line = ", ".join(f"0x{value:02X}" for value in chunk)
f.write(f" {line},\n")
f.write("};\n")