mirror of
https://github.com/Kravickas/PS4-apps.git
synced 2026-10-17 14:00:53 +02:00
87 lines
5.3 KiB
Python
87 lines
5.3 KiB
Python
#!/usr/bin/env python3
|
|
"""UI atlas for the on-screen panels: glyphs of the logo's font (URW Gothic Demi) + DS4 button icons.
|
|
|
|
python3 tools/make_ui_atlas.py FONT.otf (FONT: URW Gothic Demi; not shipped)
|
|
|
|
Writes assets/ui/ui_atlas.bin ('UIA1', u32 width, u32 height, RGBA8 rows top-down, sRGB colours,
|
|
straight alpha: glyphs white with coverage alpha, icons in colour) and src/ui_atlas.h (glyph rects,
|
|
bearings and advances for ASCII 32..126 plus the multiplication sign at index 127; icon rects).
|
|
Icons: tools/ui_icons/ (from the DS4 icon pack), resampled with premultiplied alpha."""
|
|
import os, sys
|
|
import numpy as np
|
|
from PIL import Image, ImageDraw, ImageFont
|
|
|
|
HERE = os.path.dirname(os.path.abspath(__file__)); ROOT = os.path.dirname(HERE)
|
|
FONT_PX = 19 # cap height 14 px: the PS4 debug overlay's text size at 1080p
|
|
ICON_PX = 30 # scaled with the font (44 x 19 / 28), fits the 38 px line pitch
|
|
ICONS = ["Cross_Color", "Circle_Color", "Square_Color", "Triangle_Color", "L1", "R1", "L2", "R2",
|
|
"Dpad_UP", "Dpad_Down", "Dpad_Left", "Dpad_Right", "Options", "L_2D", "R_2D",
|
|
"Left_Stick_Click", "Right_Stick_Click"]
|
|
W = 1024
|
|
|
|
|
|
def resize_premul(im, size):
|
|
"""Crop to the visible content (alpha > 0), centre on a square canvas, resample to size."""
|
|
im = im.convert("RGBA"); bb = im.getchannel("A").getbbox()
|
|
if bb:
|
|
im = im.crop(bb); side = max(im.size); sq = Image.new("RGBA", (side, side), (0, 0, 0, 0))
|
|
sq.paste(im, ((side - im.size[0]) // 2, (side - im.size[1]) // 2)); im = sq
|
|
a = np.asarray(im, np.float64) / 255.0
|
|
lin = np.where(a[..., :3] <= 0.04045, a[..., :3] / 12.92, ((a[..., :3] + 0.055) / 1.055) ** 2.4) * a[..., 3:]
|
|
ch = [Image.fromarray(np.float32(c), "F").resize((size, size), Image.LANCZOS) for c in np.moveaxis(np.concatenate([lin, a[..., 3:]], 2), 2, 0)]
|
|
r = np.stack([np.asarray(c, np.float64) for c in ch], 2); al = np.clip(r[..., 3:], 0, 1)
|
|
rgb = np.clip(r[..., :3] / np.maximum(al, 1e-6), 0, 1)
|
|
rgb = np.where(rgb <= 0.0031308, 12.92 * rgb, 1.055 * rgb ** (1 / 2.4) - 0.055)
|
|
return np.round(np.concatenate([rgb, al], 2) * 255).astype(np.uint8)
|
|
|
|
|
|
def main():
|
|
font = ImageFont.truetype(sys.argv[1], FONT_PX)
|
|
chars = [chr(c) for c in range(32, 127)] + ["\u00d7"]
|
|
ascent, descent = font.getmetrics()
|
|
cap = -font.getbbox("H", anchor="ls")[1] # cap height above the baseline
|
|
glyphs = []; x = y = 0; row_h = 0; tiles = []
|
|
for ch in chars:
|
|
l, t, r, b = font.getbbox(ch, anchor="ls") # relative to the baseline
|
|
w, h = max(r - l, 0), max(b - t, 0)
|
|
if x + w + 2 > W: x = 0; y += row_h + 2; row_h = 0
|
|
glyphs.append(dict(x=x, y=y, w=w, h=h, xoff=l, yoff=t, adv=int(round(font.getlength(ch)))))
|
|
if w and h:
|
|
im = Image.new("L", (w, h), 0); ImageDraw.Draw(im).text((-l, -t), ch, font=font, fill=255, anchor="ls")
|
|
tiles.append((x, y, im))
|
|
x += w + 2; row_h = max(row_h, h)
|
|
y += row_h + 4; x = 0; icons = []
|
|
for n in ICONS:
|
|
if x + ICON_PX > W: x = 0; y += ICON_PX + 2
|
|
icons.append(dict(x=x, y=y, w=ICON_PX, h=ICON_PX, name=n))
|
|
x += ICON_PX + 2
|
|
H = y + ICON_PX + 2
|
|
atlas = np.zeros((H, W, 4), np.uint8)
|
|
for (gx, gy, im) in tiles:
|
|
a = np.asarray(im); atlas[gy:gy + a.shape[0], gx:gx + a.shape[1], :3] = 255; atlas[gy:gy + a.shape[0], gx:gx + a.shape[1], 3] = a
|
|
for ic in icons:
|
|
atlas[ic["y"]:ic["y"] + ICON_PX, ic["x"]:ic["x"] + ICON_PX] = resize_premul(Image.open(os.path.join(HERE, "ui_icons", "T_P4_%s.png" % ic["name"])), ICON_PX)
|
|
os.makedirs(os.path.join(ROOT, "assets", "ui"), exist_ok=True)
|
|
with open(os.path.join(ROOT, "assets", "ui", "ui_atlas.bin"), "wb") as f:
|
|
f.write(b"UIA1" + np.uint32(W).tobytes() + np.uint32(H).tobytes() + atlas.tobytes())
|
|
L = ["/* Generated by tools/make_ui_atlas.py - do not edit. UI atlas (assets/ui/ui_atlas.bin):",
|
|
" URW Gothic Demi %d px (cap %d, ascent %d, descent %d) glyphs for ASCII 32..126 + the" % (FONT_PX, cap, ascent, descent),
|
|
" multiplication sign at index 127, and DS4 icons %d px. yoff = top relative to the baseline. */" % ICON_PX,
|
|
"#pragma once", "#include <stdint.h>", "#define UI_ATLAS_W %d" % W, "#define UI_ATLAS_H %d" % H,
|
|
"#define UI_FONT_PX %d" % FONT_PX, "#define UI_FONT_CAP %d" % cap, "#define UI_FONT_ASCENT %d" % ascent, "#define UI_FONT_DESCENT %d" % descent,
|
|
"#define UI_ICON_PX %d" % ICON_PX, "#define UI_CHAR_TIMES 127",
|
|
"typedef struct { int16_t x, y, w, h, xoff, yoff, adv; } UiGlyph;",
|
|
"static const UiGlyph ui_glyph[96] = {"]
|
|
for g, ch in zip(glyphs, chars):
|
|
L.append(" {%d, %d, %d, %d, %d, %d, %d}, /* %s */" % (g["x"], g["y"], g["w"], g["h"], g["xoff"], g["yoff"], g["adv"],
|
|
"U+00D7" if ch == "\u00d7" else ("'%s'" % ch if ch not in "\\*/" else "0x%02x" % ord(ch))))
|
|
L.append("};")
|
|
L.append("enum { " + ", ".join("UI_ICON_%s" % n.upper().replace("_COLOR", "") for n in ICONS) + ", UI_ICON_COUNT };")
|
|
L.append("static const int16_t ui_icon_rect[UI_ICON_COUNT][2] = {" + ", ".join("{%d, %d}" % (i["x"], i["y"]) for i in icons) + "};")
|
|
open(os.path.join(ROOT, "src", "ui_atlas.h"), "w").write("\n".join(L) + "\n")
|
|
print("ui_atlas.bin %dx%d (%d glyphs, %d icons); src/ui_atlas.h" % (W, H, len(glyphs), len(icons)))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|