mirror of
https://github.com/Kravickas/PS4-apps.git
synced 2026-10-17 16:01:46 +02:00
88 lines
4.3 KiB
Python
88 lines
4.3 KiB
Python
#!/usr/bin/env python3
|
|
"""Glyph signed-distance atlas for the clock mode's glass text (URW Gothic Demi, like the UI).
|
|
|
|
python3 tools/make_clock_sdf.py FONT.otf (FONT: URW Gothic Demi; not shipped)
|
|
|
|
Three sizes: 300 px (digits, ':'), 110 px (digits, ':', ' ', A P M) and 52 px (the date:
|
|
letters, digits, space). Each glyph is rendered at 2x, thresholded at half coverage, and the
|
|
exact Euclidean distance transform gives the signed distance in target pixels (negative inside),
|
|
clamped to +-16 and stored as R8: 128 + 8 d (1/8 px steps). 16 px of padding around every glyph.
|
|
Writes assets/ui/clock_sdf.bin ('CSD1', u32 width, u32 height, R8 rows top-down) and
|
|
src/clock_sdf.h (per glyph: size class, character, atlas rect, offset of the rect from the pen
|
|
position on the baseline, advance)."""
|
|
import os, sys
|
|
import numpy as np
|
|
from PIL import Image, ImageDraw, ImageFont
|
|
from scipy import ndimage as nd
|
|
|
|
HERE = os.path.dirname(os.path.abspath(__file__)); ROOT = os.path.dirname(HERE)
|
|
SIZES = [(300, "0123456789:"), (110, "0123456789: APM"),
|
|
(52, " 0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz")]
|
|
PAD, CLAMP, W = 16, 16.0, 2048
|
|
|
|
|
|
def glyph_sdf(font2, ch, px):
|
|
"""signed distance (target px) of one glyph with PAD around it; the rect's offset from the
|
|
pen position on the baseline (target px); the advance"""
|
|
asc, desc = font2.getmetrics()
|
|
l, t, r, b = font2.getbbox(ch, anchor="ls")
|
|
adv = font2.getlength(ch) / 2.0
|
|
if r <= l or b <= t: # space: no ink
|
|
return None, 0, 0, adv
|
|
l0, t0 = l - l % 2, t - t % 2 # even 2x origin: whole-pixel offsets at 1x
|
|
w2, h2 = (r - l0) + 4 * PAD, (b - t0) + 4 * PAD
|
|
w2 += w2 % 2; h2 += h2 % 2
|
|
im = Image.new("L", (w2, h2), 0)
|
|
ImageDraw.Draw(im).text((2 * PAD - l0, 2 * PAD - t0), ch, font=font2, fill=255, anchor="ls")
|
|
m = np.asarray(im) >= 128
|
|
d = (nd.distance_transform_edt(~m) - nd.distance_transform_edt(m)) / 2.0 # 2x -> target px
|
|
d = d.reshape(h2 // 2, 2, w2 // 2, 2).mean((1, 3))
|
|
return d, (l0 // 2 - PAD), (t0 // 2 - PAD), adv
|
|
|
|
|
|
def main():
|
|
glyphs = []
|
|
for px, chars in SIZES:
|
|
f2 = ImageFont.truetype(sys.argv[1], 2 * px)
|
|
for ch in chars:
|
|
d, xo, yo, adv = glyph_sdf(f2, ch, px)
|
|
glyphs.append((px, ch, d, xo, yo, adv))
|
|
# shelf packing, tallest first
|
|
order = sorted(range(len(glyphs)), key=lambda i: -(glyphs[i][2].shape[0] if glyphs[i][2] is not None else 0))
|
|
x = y = shelf = 0; place = {}
|
|
for i in order:
|
|
d = glyphs[i][2]
|
|
if d is None:
|
|
place[i] = (0, 0, 0, 0); continue
|
|
h, w = d.shape
|
|
if x + w > W:
|
|
x, y, shelf = 0, y + shelf, 0
|
|
place[i] = (x, y, w, h); x += w; shelf = max(shelf, h)
|
|
H = y + shelf; H += (-H) % 4
|
|
atlas = np.full((H, W), 255, np.uint8)
|
|
for i, (x, y, w, h) in place.items():
|
|
d = glyphs[i][2]
|
|
if d is not None:
|
|
atlas[y:y + h, x:x + w] = np.clip(np.round(128 + 8 * np.clip(d, -CLAMP, CLAMP - 0.125)), 0, 255)
|
|
os.makedirs(os.path.join(ROOT, "assets", "ui"), exist_ok=True)
|
|
with open(os.path.join(ROOT, "assets", "ui", "clock_sdf.bin"), "wb") as f:
|
|
f.write(b"CSD1" + np.array([W, H], "<u4").tobytes() + atlas.tobytes())
|
|
with open(os.path.join(ROOT, "src", "clock_sdf.h"), "w") as f:
|
|
f.write("/* Generated by tools/make_clock_sdf.py - the clock's glyph distance atlas\n"
|
|
" (assets/ui/clock_sdf.bin: R8, 128 + 8 d, d in px, negative inside). */\n#pragma once\n")
|
|
f.write("#define CLOCK_SDF_W %d\n#define CLOCK_SDF_H %d\n#define CLOCK_SDF_PAD %d\n" % (W, H, PAD))
|
|
f.write("typedef struct {\n short px; /* font size class: 300 / 110 / 52 */\n char ch;\n"
|
|
" short x, y, w, h; /* atlas rect (0 size: no ink) */\n"
|
|
" float xoff, yoff; /* rect's top left from the pen position on the baseline */\n"
|
|
" float adv;\n} ClockGlyph;\n")
|
|
f.write("static const ClockGlyph clock_glyph[%d] = {\n" % len(glyphs))
|
|
for i, (px, ch, d, xo, yo, adv) in enumerate(glyphs):
|
|
x, y, w, h = place[i]
|
|
f.write(" {%d, '%s', %d, %d, %d, %d, %.3ff, %.3ff, %.3ff},\n" % (px, ch, x, y, w, h, xo, yo, adv))
|
|
f.write("};\n")
|
|
print("atlas %d x %d, %d glyphs, %.0f KB" % (W, H, len(glyphs), W * H / 1024))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|