stampli-bulk-editor/generate_icon.py

148 lines
4.6 KiB
Python
Raw Normal View History

#!/usr/bin/env python3
"""Generate a macOS .icns app icon for Stampli Bulk Editor."""
import os
import subprocess
import tempfile
from pathlib import Path
from PIL import Image, ImageDraw, ImageFont
SIZE = 1024
def draw_icon(size=SIZE):
img = Image.new("RGBA", (size, size), (0, 0, 0, 0))
draw = ImageDraw.Draw(img)
s = size / 1024 # scale factor
# Rounded-rect background — deep navy blue
bg_color = (22, 37, 67)
margin = int(80 * s)
radius = int(180 * s)
draw.rounded_rectangle(
[margin, margin, size - margin, size - margin],
radius=radius,
fill=bg_color,
)
# Document shape — white, slightly offset right
doc_left = int(280 * s)
doc_top = int(180 * s)
doc_right = int(750 * s)
doc_bottom = int(820 * s)
doc_radius = int(30 * s)
draw.rounded_rectangle(
[doc_left, doc_top, doc_right, doc_bottom],
radius=doc_radius,
fill=(255, 255, 255),
)
# Dog-ear fold on top-right corner of document
fold_size = int(90 * s)
fold_points = [
(doc_right - fold_size, doc_top),
(doc_right, doc_top + fold_size),
(doc_right, doc_top),
]
draw.polygon(fold_points, fill=bg_color)
fold_triangle = [
(doc_right - fold_size, doc_top),
(doc_right - fold_size, doc_top + fold_size),
(doc_right, doc_top + fold_size),
]
draw.polygon(fold_triangle, fill=(200, 210, 225))
# Horizontal lines on document (invoice lines)
line_color = (180, 195, 215)
line_h = int(12 * s)
line_left = int(330 * s)
line_right_short = int(580 * s)
line_right_long = int(700 * s)
y_positions = [340, 400, 460, 520, 580]
for i, y in enumerate(y_positions):
y_scaled = int(y * s)
right = line_right_short if i % 2 else line_right_long
draw.rounded_rectangle(
[line_left, y_scaled, right, y_scaled + line_h],
radius=int(6 * s),
fill=line_color,
)
# Dollar sign — accent color (Stampli teal/green)
accent = (0, 191, 165)
dollar_x = int(380 * s)
dollar_y = int(660 * s)
dollar_size = int(110 * s)
draw.ellipse(
[dollar_x - dollar_size, dollar_y - dollar_size,
dollar_x + dollar_size, dollar_y + dollar_size],
fill=accent,
)
try:
font = ImageFont.truetype("/System/Library/Fonts/Helvetica.ttc", int(130 * s))
except (OSError, IOError):
font = ImageFont.load_default()
draw.text((dollar_x, dollar_y), "$", fill="white", font=font, anchor="mm")
# Checkmark badge — bottom-right
badge_cx = int(680 * s)
badge_cy = int(750 * s)
badge_r = int(85 * s)
draw.ellipse(
[badge_cx - badge_r, badge_cy - badge_r,
badge_cx + badge_r, badge_cy + badge_r],
fill=(46, 204, 113),
outline=bg_color,
width=int(12 * s),
)
check_points = [
(int(640 * s), int(750 * s)),
(int(670 * s), int(785 * s)),
(int(730 * s), int(715 * s)),
]
draw.line(check_points, fill="white", width=int(18 * s), joint="curve")
# Small pencil icon — top-left area, representing "edit"
pencil_color = (255, 193, 7)
px, py = int(220 * s), int(280 * s)
pencil_body = [
(px, py + int(60 * s)),
(px + int(15 * s), py - int(30 * s)),
(px + int(45 * s), py - int(20 * s)),
(px + int(30 * s), py + int(70 * s)),
]
draw.polygon(pencil_body, fill=pencil_color)
pencil_tip = [
(px, py + int(60 * s)),
(px + int(30 * s), py + int(70 * s)),
(px + int(10 * s), py + int(90 * s)),
]
draw.polygon(pencil_tip, fill=(60, 60, 60))
return img
def make_icns(img, output_path):
"""Create .icns from a PIL Image using macOS iconutil."""
with tempfile.TemporaryDirectory() as tmpdir:
iconset = os.path.join(tmpdir, "icon.iconset")
os.makedirs(iconset)
sizes = [16, 32, 64, 128, 256, 512, 1024]
for sz in sizes:
resized = img.resize((sz, sz), Image.LANCZOS)
resized.save(os.path.join(iconset, f"icon_{sz}x{sz}.png"))
if sz <= 512:
double = img.resize((sz * 2, sz * 2), Image.LANCZOS)
double.save(os.path.join(iconset, f"icon_{sz}x{sz}@2x.png"))
subprocess.run(["iconutil", "-c", "icns", iconset, "-o", output_path], check=True)
if __name__ == "__main__":
project = Path(__file__).parent
icon_img = draw_icon()
png_path = project / "app_icon.png"
icon_img.save(str(png_path))
print(f"Saved PNG: {png_path}")
icns_path = project / "app_icon.icns"
make_icns(icon_img, str(icns_path))
print(f"Saved ICNS: {icns_path}")