K1Guitar/tools/gen_charge_pct_glyphs.py

145 lines
4.6 KiB
Python
Raw Normal View History

# -*- coding: utf-8 -*-
"""Generate MiSans-Semibold 0-9/% with shared baseline + matched cap height."""
from pathlib import Path
from PIL import Image, ImageDraw, ImageFont
ROOT = Path(r"C:\Users\qjyu\Documents\SoundWalker\一诺国际吉他")
FONT = next((ROOT / "Doc" / "UI" / "MiSans").rglob("MiSans-Semibold.ttf"))
OUT_C = ROOT / "Code" / "YNGJ-GT1-M - AT32F403ARCT7" / "device" / "LCD_ILI9341" / "Drv_ILI9341_Lcd_Image_ChargePct.c"
OUT_H = ROOT / "Code" / "YNGJ-GT1-M - AT32F403ARCT7" / "device" / "LCD_ILI9341" / "Drv_ILI9341_Lcd_Image_ChargePct.h"
OUT_PREV = ROOT / "Code" / "YNGJ-GT1-M - AT32F403ARCT7" / "tools" / "out" / "_charge_pct_glyphs.png"
H = 20
FONT_PX = 18
WHITE = 0xFFFF
BLACK = 0x0000
TOP_PAD = 1
def render_on_baseline(ch: str, font: ImageFont.FreeTypeFont) -> Image.Image:
"""Render char into HxW with shared baseline = H - 2."""
ascent, descent = font.getmetrics()
baseline = H - 2
# wide scratch
scratch = Image.new("L", (48, H + 8), 0)
dr = ImageDraw.Draw(scratch)
# Pillow text y is top of em-box approx; use anchor if available
try:
dr.text((4, baseline), ch, font=font, fill=255, anchor="ls")
except TypeError:
dr.text((4, baseline - ascent), ch, font=font, fill=255)
bbox = scratch.getbbox()
if bbox is None:
return Image.new("L", (8, H), 0)
l, t, r, b = bbox
# keep full height H strip from y=0..H-1 of scratch (aligned)
strip = scratch.crop((l, 0, r, H))
# 1-bit
bw = strip.point(lambda v: 255 if v >= 140 else 0)
# add 1px side pad
out = Image.new("L", (bw.size[0] + 2, H), 0)
out.paste(bw, (1, 0))
return out
def to_rgb565_be(im: Image.Image) -> bytes:
w, h = im.size
out = bytearray(w * h * 2)
i = 0
for y in range(h):
for x in range(w):
c = WHITE if im.getpixel((x, y)) else BLACK
out[i] = (c >> 8) & 0xFF
out[i + 1] = c & 0xFF
i += 2
return bytes(out)
font = ImageFont.truetype(str(FONT), FONT_PX)
chars = list("0123456789%")
glyphs = {ch: render_on_baseline(ch, font) for ch in chars}
# Verify ink y-ranges share baseline
for ch in "50%":
im = glyphs[ch]
ys = [y for y in range(im.size[1]) for x in range(im.size[0]) if im.getpixel((x, y))]
print(ch, "size", im.size, "ink y", min(ys), max(ys))
total_w = sum(glyphs[c].size[0] for c in "50%") + 4
prev = Image.new("L", (total_w, H + 4), 0)
x = 2
for ch in "50%":
prev.paste(glyphs[ch], (x, 2))
x += glyphs[ch].size[0]
OUT_PREV.parent.mkdir(parents=True, exist_ok=True)
prev.resize((prev.size[0] * 5, prev.size[1] * 5), Image.NEAREST).save(OUT_PREV)
prev.resize((prev.size[0] * 5, prev.size[1] * 5), Image.NEAREST).save(
OUT_PREV.with_name("_charge_pct_prev_ascii.png")
)
print("preview", OUT_PREV)
h_lines = [
"#ifndef __DRV_ILI9341_LCD_IMAGE_CHARGE_PCT_H",
"#define __DRV_ILI9341_LCD_IMAGE_CHARGE_PCT_H",
"",
'#include "stdint.h"',
"",
f"#define CHARGE_PCT_GLYPH_H {H}",
"",
]
c_lines = [
'#include "Drv_ILI9341_Lcd_Image_ChargePct.h"',
"",
"/* Auto-generated by tools/gen_charge_pct_glyphs.py — MiSans Semibold, shared baseline */",
"",
]
for ch in chars:
im = glyphs[ch]
w, h = im.size
data = to_rgb565_be(im)
name = "Pct" if ch == "%" else f"Digit{ch}"
arr = f"gImage_Charge_{name}_{w}x{h}"
h_lines.append(f"#define CHARGE_{name.upper()}_W {w}")
h_lines.append(f"#define CHARGE_{name.upper()}_H {h}")
h_lines.append(f"extern const unsigned char {arr}[{len(data)}];")
h_lines.append("")
c_lines.append(f"const unsigned char {arr}[{len(data)}] = {{ /* {w}x{h} RGB565 BE */")
hexes = [f"0x{b:02X}" for b in data]
for i in range(0, len(hexes), 16):
c_lines.append(" " + ",".join(hexes[i : i + 16]) + ",")
c_lines.append("};")
c_lines.append("")
h_lines += [
"typedef struct {",
" const unsigned char *data;",
" uint8_t w;",
" uint8_t h;",
"} ChargePctGlyph;",
"",
"extern const ChargePctGlyph g_ChargePctDigits[10];",
"extern const ChargePctGlyph g_ChargePctPercent;",
"",
"#endif",
"",
]
c_lines.append("const ChargePctGlyph g_ChargePctDigits[10] = {")
for d in range(10):
im = glyphs[str(d)]
w, h = im.size
c_lines.append(f" {{ gImage_Charge_Digit{d}_{w}x{h}, {w}, {h} }},")
c_lines.append("};")
c_lines.append("")
im = glyphs["%"]
w, h = im.size
c_lines.append(
f"const ChargePctGlyph g_ChargePctPercent = {{ gImage_Charge_Pct_{w}x{h}, {w}, {h} }};"
)
c_lines.append("")
OUT_H.write_text("\n".join(h_lines), encoding="utf-8", newline="\n")
OUT_C.write_text("\n".join(c_lines), encoding="utf-8", newline="\n")
print("wrote", OUT_C.stat().st_size, "bytes")