import os, random, sys from PIL import Image, ImageChops, ImageDraw, ImageFilter, ImageFont HERE = os.path.dirname(os.path.abspath(__file__)) ROOT = os.path.abspath(os.path.join(HERE, "..", "..")) NAVY = (0x1B, 0x2A, 0x49); DEEP = (0x10, 0x1B, 0x33); GOLD = (0xD4, 0xA6, 0x4A) FONT = os.path.join(ROOT, "assets", "fonts", "Inter-Bold.otf") COIN = Image.open(os.path.join(HERE, "logo_coin_master.png")).convert("RGBA") def gradient(size, c1, c2): w, h = size g = Image.new("RGB", (w, h)) px = g.load() for y in range(h): for x in range(w): t = (x / w + y / h) / 2 px[x, y] = tuple(round(c1[i] + (c2[i] - c1[i]) * t) for i in range(3)) return g def background(S, style): bg = gradient((S, S), NAVY, DEEP).convert("RGBA") ov = Image.new("RGBA", (S, S), (0, 0, 0, 0)) d = ImageDraw.Draw(ov) if style == "circles": # Large soft discs and one thin gold ring, partly off the tile. d.ellipse([S * 0.46, -S * 0.42, S * 1.34, S * 0.46], fill=(255, 255, 255, 20)) d.ellipse([-S * 0.42, S * 0.52, S * 0.42, S * 1.36], fill=(255, 255, 255, 14)) d.ellipse([S * 0.58, S * 0.60, S * 1.18, S * 1.20], fill=(212, 166, 74, 22)) d.ellipse([S * 0.04, S * 0.04, S * 0.96, S * 0.96], outline=(212, 166, 74, 46), width=max(1, round(S * 0.006))) elif style == "endings": rnd = random.Random(74) f = ImageFont.truetype(FONT, round(S * 0.115)) cw, ch = S * 0.36, S * 0.19 gold = {(1, 1), (3, 3), (0, 4), (2, 5)} layer = Image.new("RGBA", (S * 2, S * 2), (0, 0, 0, 0)) ld = ImageDraw.Draw(layer) for r in range(int(S * 2 / ch) + 1): for c in range(int(S * 2 / cw) + 1): t = "%04d" % rnd.randint(0, 9999) x = c * cw + (cw / 2 if r % 2 else 0) - S * 0.2 y = r * ch - S * 0.1 fill = GOLD + (150,) if (c, r) in gold else (255, 255, 255, 26) ld.text((x, y), t, font=f, fill=fill) layer = layer.rotate(-12, resample=Image.BICUBIC) o = S // 2 layer = layer.crop((o, o, o + S, o + S)) ov = layer return Image.alpha_composite(bg, ov) def icon(px, style, ratio, shape, ss=4): S = px * ss bg = background(S, style) cx = cy = S / 2 R = S * ratio / 2 if style == "endings": # Calm the middle so the coin reads cleanly. m = Image.new("L", (S, S), 255) ImageDraw.Draw(m).ellipse([cx - R * 1.55, cy - R * 1.55, cx + R * 1.55, cy + R * 1.55], fill=70) m = m.filter(ImageFilter.GaussianBlur(S * 0.07)) veil = Image.new("RGBA", (S, S), NAVY + (255,)) veil.putalpha(Image.eval(m, lambda v: 255 - v)) bg = Image.alpha_composite(bg, veil) glow = Image.new("RGBA", (S, S), (0, 0, 0, 0)) ImageDraw.Draw(glow).ellipse([cx - R, cy - R + S * 0.02, cx + R, cy + R + S * 0.02], fill=GOLD + (130,)) glow = glow.filter(ImageFilter.GaussianBlur(S * 0.035)) bg = Image.alpha_composite(bg, glow) coin = COIN.resize((round(R * 2), round(R * 2)), Image.LANCZOS) bg.alpha_composite(coin, (round(cx - R), round(cy - R))) if shape == "rounded": mask = Image.new("L", (S, S), 0) ImageDraw.Draw(mask).rounded_rectangle([0, 0, S - 1, S - 1], radius=S * 0.22, fill=255) bg.putalpha(mask) return bg.resize((px, px), Image.LANCZOS) def write_icons(style): web = os.path.join(ROOT, "web") jobs = [ ("icons/Icon-512.png", 512, 0.62, "rounded"), ("icons/Icon-192.png", 192, 0.62, "rounded"), ("icons/Icon-maskable-512.png", 512, 0.52, "full"), ("icons/Icon-maskable-192.png", 192, 0.52, "full"), ("icons/apple-touch-icon.png", 180, 0.62, "full"), ("favicon.png", 64, 0.78, "rounded"), ] for name, px, ratio, shape in jobs: icon(px, style, ratio, shape).save(os.path.join(web, name), optimize=True) print(name, px) def write_preview(): out = os.path.join(HERE, "preview") os.makedirs(out, exist_ok=True) for style in ("endings", "circles"): sheet = Image.new("RGB", (512 + 40 + 192 + 40 + 64 + 40 + 32, 512 + 40), (244, 242, 236)) big = icon(512, style, 0.62, "rounded") sheet.paste(big, (20, 20), big) mid = icon(192, style, 0.62, "rounded") sheet.paste(mid, (512 + 40, 20), mid) small = icon(64, style, 0.78, "rounded") sheet.paste(small, (512 + 40 + 192 + 20, 20), small) tiny = icon(32, style, 0.78, "rounded") sheet.paste(tiny, (512 + 40 + 192 + 20 + 64 + 20, 20), tiny) sheet.save(os.path.join(out, f"icon_{style}.png")) print("preview", style) if __name__ == "__main__": if len(sys.argv) > 1 and sys.argv[1] == "preview": write_preview() else: write_icons(sys.argv[1] if len(sys.argv) > 1 else "circles")