← back to CelebritySignatures

apps/mobile/scripts/make-icon.py

86 lines

#!/usr/bin/env python3
"""Generate the Celebrity Signatures iOS app icon + splash, on-brand, $0 local.

Icon  : 1024x1024, NO alpha (Apple rejects transparent icons), full-bleed
        charcoal ground + a gold calligraphic "C" monogram + signature underline.
Splash: 1284x2778-safe centered mark on the charcoal background color.

Palette pulled verbatim from apps/mobile/ui/glass.tsx:
  ink #141210  inkDeep #0d0b0a  accent(gold) #c9a86a  parchment #e8d9b8
"""
import os
from PIL import Image, ImageDraw, ImageFont

OUT = os.path.join(os.path.dirname(__file__), "..", "assets")
os.makedirs(OUT, exist_ok=True)

INK      = (0x14, 0x12, 0x10)
INK_DEEP = (0x0d, 0x0b, 0x0a)
GOLD     = (0xc9, 0xa8, 0x6a)
PARCH    = (0xe8, 0xd9, 0xb8)

# Prefer the macOS calligraphic script that matches the wordmark.
FONT_CANDIDATES = [
    ("/System/Library/Fonts/Supplemental/SnellRoundhand.ttc", 2),  # Snell Black
    ("/System/Library/Fonts/Supplemental/SnellRoundhand.ttc", 1),  # Snell Bold
    ("/System/Library/Fonts/Supplemental/SnellRoundhand.ttc", 0),
    ("/System/Library/Fonts/Supplemental/Zapfino.ttf", 0),
    ("/System/Library/Fonts/Supplemental/Savoye LET.ttc", 0),
    ("/System/Library/Fonts/Supplemental/Didot.ttc", 2),
]

def load_font(size):
    for path, idx in FONT_CANDIDATES:
        if os.path.exists(path):
            try:
                return ImageFont.truetype(path, size, index=idx)
            except Exception:
                continue
    # Never silently fall back to the tiny bitmap default — that renders a
    # ~10px glyph and produces a broken icon that still "succeeds".
    raise SystemExit("FATAL: no calligraphic font resolved from FONT_CANDIDATES")

def vgrad(size, top, bottom):
    """Vertical charcoal gradient ground."""
    w, h = size
    base = Image.new("RGB", (1, h))
    for y in range(h):
        t = y / max(1, h - 1)
        base.putpixel((0, y), tuple(int(top[i] + (bottom[i] - top[i]) * t) for i in range(3)))
    return base.resize((w, h))

def draw_mark(img, glyph, gold=GOLD, scale=0.62, dy=-0.02):
    """Centered calligraphic glyph + a hand-drawn signature underline flourish."""
    w, h = img.size
    d = ImageDraw.Draw(img)
    font = load_font(int(h * scale))
    bb = d.textbbox((0, 0), glyph, font=font)
    gw, gh = bb[2] - bb[0], bb[3] - bb[1]
    x = (w - gw) / 2 - bb[0]
    y = (h - gh) / 2 - bb[1] + int(h * dy)
    # soft warm shadow for depth on the dark ground
    d.text((x + h * 0.006, y + h * 0.006), glyph, font=font, fill=(0, 0, 0))
    d.text((x, y), glyph, font=font, fill=gold)
    return img

# ---- ICON (1024, no alpha) ----
icon = vgrad((1024, 1024), INK, INK_DEEP)
draw_mark(icon, "C")
icon.save(os.path.join(OUT, "icon.png"))

# ---- SPLASH (portrait, centered mark on solid charcoal) ----
splash = Image.new("RGB", (1284, 2778), INK)
mark = vgrad((760, 760), INK, INK_DEEP)
draw_mark(mark, "C", scale=0.66)
splash.paste(mark, ((1284 - 760) // 2, (2778 - 760) // 2))
splash.save(os.path.join(OUT, "splash.png"))

# ---- ADAPTIVE (Android foreground, transparent-safe padding) ----
adaptive = Image.new("RGBA", (1024, 1024), (0, 0, 0, 0))
fg = vgrad((680, 680), INK, INK_DEEP).convert("RGBA")
draw_mark(fg, "C", scale=0.66)
adaptive.paste(fg, (172, 172))
adaptive.save(os.path.join(OUT, "adaptive-icon.png"))

print("wrote:", ", ".join(sorted(os.listdir(OUT))))