#!/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()