Files

150 lines
4.8 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""Disegna il Regolino, pixel per pixel, senza immagini sorgenti.
python3 tools/favicon.py
Scrive assets/favicon/ e /tmp/regolo-shots/favicon-anteprima.png.
La matrice 16×16 è il disegno originale: ogni simbolo è un pixel.
Tutti gli ingrandimenti sono interi e usano NEAREST, anche nell'ICO.
Colori e carattere riprendono regolo() in src/engine/briscola-render.js.
"""
from pathlib import Path
from PIL import Image, ImageDraw, ImageFont
ROOT = Path(__file__).resolve().parent.parent
OUTPUT = ROOT / "assets" / "favicon"
PREVIEW = Path("/tmp/regolo-shots/favicon-anteprima.png")
BACKGROUND = "#1b1420"
PALETTE = {
".": (0, 0, 0, 0),
"#": "#38271f", # contorno del dorso
"G": "#dba32b", # oro
"H": "#ffe396", # luce sulla corona
"R": "#aa4034", # rubino
"L": "#94b365", # testa verde salvia
"S": "#487348", # ombra / spira
"V": "#a3bc72", # luce sulla spira
"B": "#d0cd83", # ventre
"W": "#fff0cd", # occhi crema
}
# Corona a tre punte, grande testa sorridente, collo e coda arrotolata.
# Nessun tratto viene interpolato: anche gli occhi sono singoli pixel.
PIXELS = (
"....#..##..#....",
"...#H##HH##H#...",
"...#HGGGGGGH#...",
"....#GGRGGG#....",
"...##########...",
"..#LLLLLLLLLL#..",
".#LLWWLLWWLLLL#.",
".#LLW#LL#WLLLS#.",
".#LLLLLLLLLLLS#.",
"..#L#LLLLL#LS#..",
"...#L#####LS#...",
"....###BS###....",
"..###SSBSLSS##..",
".#SVV##BS##SSS#.",
".#SVVVVVVVVSS#..",
"..###########...",
)
def sprite():
"""Interpreta la matrice letterale, mantenendo solo alpha 0 o 255."""
if len(PIXELS) != 16 or any(len(row) != 16 for row in PIXELS):
raise ValueError("Il disegno deve contenere esattamente 16×16 pixel")
result = Image.new("RGBA", (16, 16))
for y, row in enumerate(PIXELS):
for x, symbol in enumerate(row):
color = PALETTE[symbol]
if isinstance(color, str):
color = tuple(bytes.fromhex(color[1:])) + (255,)
result.putpixel((x, y), color)
return result
def enlarge(source, factor):
return source.resize(
(source.width * factor, source.height * factor), Image.Resampling.NEAREST
)
def preview(icons):
"""Mostra ogni PNG a 1× e 8×, sia su chiaro sia su scuro.
Le sei righe includono anche le icone grandi: il foglio è volutamente
ampio, così la scala dichiarata resta esatta per tutti i file.
"""
margin, gap, label_height = 24, 24, 32
largest = max(icon.width for _, icon in icons)
panel_width = margin * 2 + largest + gap + largest * 8
height = margin + sum(label_height + icon.height * 8 + gap for _, icon in icons)
sheet = Image.new("RGB", (panel_width * 2, height))
draw = ImageDraw.Draw(sheet)
font = ImageFont.load_default(size=16)
for panel, (background, foreground) in enumerate(
(("#f5f1e9", "#38271f"), (BACKGROUND, "#fff0cd"))
):
left = panel * panel_width
draw.rectangle((left, 0, left + panel_width - 1, height - 1), fill=background)
y = margin
for name, icon in icons:
draw.text(
(left + margin, y),
f"{name} | {icon.width} x {icon.height} | 1x e 8x",
fill=foreground,
font=font,
)
y += label_height
sheet.paste(icon, (left + margin, y), icon)
magnified = enlarge(icon, 8)
sheet.paste(magnified, (left + margin + icon.width + gap, y), magnified)
y += icon.height * 8 + gap
PREVIEW.parent.mkdir(parents=True, exist_ok=True)
sheet.save(PREVIEW, optimize=True)
def main():
OUTPUT.mkdir(parents=True, exist_ok=True)
base = sprite()
icons = []
for size in (16, 32, 48):
icon = enlarge(base, size // 16)
name = f"favicon-{size}.png"
icon.save(OUTPUT / name, optimize=True)
icons.append((name, icon))
# 16×9 = 144: margini di 18 px, centratura esatta sul fondo opaco.
apple = Image.new("RGBA", (180, 180), BACKGROUND)
apple.alpha_composite(enlarge(base, 9), (18, 18))
apple = apple.convert("RGB")
apple.save(OUTPUT / "apple-touch-icon.png", optimize=True)
icons.append(("apple-touch-icon.png", apple.convert("RGBA")))
for size in (192, 512):
icon = enlarge(base, size // 16)
name = f"icon-{size}.png"
icon.save(OUTPUT / name, optimize=True)
icons.append((name, icon))
# Fornire tutti i frame evita il ridimensionamento implicito di Pillow.
icons[2][1].save(
OUTPUT / "favicon.ico",
format="ICO",
sizes=[(16, 16), (32, 32), (48, 48)],
append_images=[icons[0][1], icons[1][1]],
)
preview(icons)
for name, _ in icons:
print(OUTPUT / name)
print(OUTPUT / "favicon.ico")
print(PREVIEW)
if __name__ == "__main__":
main()