mirror of
https://github.com/Sea-Haven-Industries/stampli-bulk-editor.git
synced 2026-09-30 16:13:13 +00:00
158 lines
4.7 KiB
Python
158 lines
4.7 KiB
Python
#!/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}")
|