145 lines
4.6 KiB
Python
145 lines
4.6 KiB
Python
|
|
# -*- 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")
|