#!/usr/bin/env python3 """Generate AgentCompany app icons (PNG, ICO, ICNS, SVG). Design v3b: Bold "A" monogram that doubles as an org-chart hierarchy. Two terracotta legs meet at an apex, with three warm crossbars of decreasing brightness (CEO → Managers → ICs). All tones stay warm. """ from __future__ import annotations import math import shutil import subprocess from pathlib import Path from PIL import Image, ImageDraw, ImageFilter ROOT = Path(__file__).resolve().parents[1] ASSETS = ROOT / "assets" ICONSET = ASSETS / "icon.iconset" PNG_PATH = ASSETS / "icon.png" ICO_PATH = ASSETS / "icon.ico" ICNS_PATH = ASSETS / "icon.icns" SVG_PATH = ASSETS / "icon.svg" SIZE = 1024 # ── Brand palette ────────────────────────────────────────────────────── TERRACOTTA = (217, 119, 69) # #d97745 SLATE_TOP = (17, 19, 21) # #111315 SLATE_BOT = (27, 31, 35) # #1b1f23 # Bar colours — all warm, decreasing brightness BAR_CEO = (255, 235, 215) # warm cream — brightest BAR_MGR = (235, 180, 140) # warm peach BAR_IC = (190, 140, 105) # muted terracotta def rgba(r: int, g: int, b: int, a: int = 255) -> tuple[int, int, int, int]: return (r, g, b, a) def blend( a: tuple[int, ...], b: tuple[int, ...], t: float, ) -> tuple[int, ...]: return tuple(int(a[i] + (b[i] - a[i]) * t) for i in range(len(a))) def build_icon() -> Image.Image: # ── 1. Gradient background with warm glow ────────────────────────── bg = Image.new("RGBA", (SIZE, SIZE), (0, 0, 0, 0)) px = bg.load() top_c = rgba(*SLATE_TOP) bot_c = rgba(*SLATE_BOT) glow_cx, glow_cy = SIZE * 0.44, SIZE * 0.34 glow_r = SIZE * 0.78 for y in range(SIZE): t = y / (SIZE - 1) base = blend(top_c, bot_c, t) for x in range(SIZE): d = math.hypot(x - glow_cx, y - glow_cy) / glow_r warmth = max(0.0, 1.0 - d) ** 1.8 * 0.13 px[x, y] = blend(base, rgba(*TERRACOTTA), warmth) # ── 2. Rounded-rect mask + shadow ────────────────────────────────── icon = Image.new("RGBA", (SIZE, SIZE), (0, 0, 0, 0)) mask = Image.new("L", (SIZE, SIZE), 0) inset = 64 ImageDraw.Draw(mask).rounded_rectangle( (inset, inset, SIZE - inset, SIZE - inset), radius=224, fill=255, ) shadow_alpha = mask.filter(ImageFilter.GaussianBlur(28)) shadow_layer = Image.new("RGBA", (SIZE, SIZE), (0, 0, 0, 48)) shadow_layer.putalpha(shadow_alpha) icon.alpha_composite(shadow_layer, (0, 12)) icon.alpha_composite(bg) icon.putalpha(mask) # ── 3. Draw the "A" mark ─────────────────────────────────────────── overlay = Image.new("RGBA", (SIZE, SIZE), (0, 0, 0, 0)) draw = ImageDraw.Draw(overlay) cx = SIZE // 2 apex_y = 200 base_y = 800 leg_w = 64 base_half = 250 def leg_x(y: int) -> tuple[float, float]: t = (y - apex_y) / (base_y - apex_y) spread = base_half * t return (cx - spread, cx + spread) ll_top, _ = leg_x(apex_y) ll_bot, _ = leg_x(base_y) _, lr_top = leg_x(apex_y) _, lr_bot = leg_x(base_y) left_leg = [ (ll_top - leg_w * 0.35, apex_y), (ll_top + leg_w * 0.35, apex_y), (ll_bot + leg_w, base_y), (ll_bot - leg_w, base_y), ] right_leg = [ (lr_top - leg_w * 0.35, apex_y), (lr_top + leg_w * 0.35, apex_y), (lr_bot + leg_w, base_y), (lr_bot - leg_w, base_y), ] draw.polygon(left_leg, fill=rgba(*TERRACOTTA, 240)) draw.polygon(right_leg, fill=rgba(*TERRACOTTA, 240)) # ── 4. Three crossbars — warm tones, hierarchy brightness ────────── bar_h = 42 bar_r = 12 bar_pad = 14 tiers = [ (440, rgba(*BAR_CEO)), (545, rgba(*BAR_MGR)), (650, rgba(*BAR_IC)), ] for bar_cy, fill in tiers: left_cx, right_cx = leg_x(bar_cy) bx0 = int(left_cx + bar_pad) bx1 = int(right_cx - bar_pad) draw.rounded_rectangle( (bx0, bar_cy - bar_h // 2, bx1, bar_cy + bar_h // 2), radius=bar_r, fill=fill, ) # ── 5. Apex beacon ───────────────────────────────────────────────── dot_r = 20 dot_cy = apex_y - 2 draw.ellipse( (cx - dot_r, dot_cy - dot_r, cx + dot_r, dot_cy + dot_r), fill=rgba(*BAR_CEO), ) icon.alpha_composite(overlay) return icon def save_svg() -> None: svg = """\ """ SVG_PATH.write_text(svg, encoding="utf-8") def save_png_and_ico(image: Image.Image) -> None: ASSETS.mkdir(parents=True, exist_ok=True) image.save(PNG_PATH, format="PNG") image.save( ICO_PATH, format="ICO", sizes=[(16, 16), (24, 24), (32, 32), (48, 48), (64, 64), (128, 128), (256, 256)], ) def save_icns(image: Image.Image) -> None: if ICONSET.exists(): shutil.rmtree(ICONSET) ICONSET.mkdir(parents=True, exist_ok=True) for size in (16, 32, 64, 128, 256, 512): image.resize((size, size), Image.Resampling.LANCZOS).save( ICONSET / f"icon_{size}x{size}.png", format="PNG", ) image.resize((size * 2, size * 2), Image.Resampling.LANCZOS).save( ICONSET / f"icon_{size}x{size}@2x.png", format="PNG", ) subprocess.run( ["iconutil", "-c", "icns", str(ICONSET), "-o", str(ICNS_PATH)], check=True, ) shutil.rmtree(ICONSET) def main() -> None: ASSETS.mkdir(parents=True, exist_ok=True) save_svg() image = build_icon() save_png_and_ico(image) save_icns(image) print(f"Generated: {PNG_PATH}, {ICO_PATH}, {ICNS_PATH}, {SVG_PATH}") if __name__ == "__main__": main()