Il favicon: un Regolino in pixel art con la corona (Codex, tools/favicon.py), da 16 a 512 pixel e per l'iPhone

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01A1dxvtsZx8NE9fwjXUNDBr
This commit is contained in:
2026-10-26 18:51:11 +01:00
co-authored by Claude Opus 5.5
parent f1781afea8
commit 111b892916
9 changed files with 154 additions and 0 deletions
+149
View File
@@ -0,0 +1,149 @@
#!/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()