-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathmake_icon.py
More file actions
84 lines (67 loc) · 2.81 KB
/
Copy pathmake_icon.py
File metadata and controls
84 lines (67 loc) · 2.81 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
"""Generates icon.icns for the app: a heat signature in the inferno palette."""
import os
import subprocess
import sys
import numpy as np
from matplotlib import cm
from PIL import Image, ImageDraw, ImageFilter
S = 1024
OUT = os.path.dirname(os.path.abspath(__file__))
def gauss(cx, cy, sx, sy, amp, yy, xx):
return amp * np.exp(-(((xx - cx) ** 2) / (2 * sx**2) + ((yy - cy) ** 2) / (2 * sy**2)))
def build():
y, x = np.mgrid[0:S, 0:S].astype(np.float32)
# The heat field: one large warm blob plus a couple of secondary sources.
f = np.zeros((S, S), np.float32)
f += gauss(0.60 * S, 0.56 * S, 0.20 * S, 0.22 * S, 1.00, y, x)
f += gauss(0.36 * S, 0.40 * S, 0.13 * S, 0.14 * S, 0.55, y, x)
f += gauss(0.72 * S, 0.30 * S, 0.09 * S, 0.10 * S, 0.35, y, x)
f += gauss(0.30 * S, 0.74 * S, 0.16 * S, 0.11 * S, 0.28, y, x)
# A slight background gradient, so the corners are not flatly black.
f += 0.10 * (1.0 - y / S) * 0.5
f = np.clip(f / f.max(), 0, 1)
f = f**0.85
rgba = (cm.inferno(f) * 255).astype(np.uint8)
img = Image.fromarray(rgba, "RGBA")
img = img.filter(ImageFilter.GaussianBlur(radius=S * 0.006))
# Crosshair on the hottest point.
d = ImageDraw.Draw(img, "RGBA")
hy, hx = np.unravel_index(np.argmax(f), f.shape)
r = int(S * 0.085)
w = max(2, int(S * 0.011))
d.ellipse([hx - r, hy - r, hx + r, hy + r], outline=(255, 255, 255, 230), width=w)
arm, gap = int(S * 0.155), int(S * 0.052)
for dx, dy in ((1, 0), (-1, 0), (0, 1), (0, -1)):
d.line(
[hx + dx * gap, hy + dy * gap, hx + dx * arm, hy + dy * arm],
fill=(255, 255, 255, 230),
width=w,
)
# A macOS-style rounded mask plus a thin inner stroke.
pad = int(S * 0.085)
mask = Image.new("L", (S, S), 0)
ImageDraw.Draw(mask).rounded_rectangle(
[pad, pad, S - pad, S - pad], radius=int(S * 0.225), fill=255
)
img.putalpha(mask)
ring = Image.new("RGBA", (S, S), (0, 0, 0, 0))
ImageDraw.Draw(ring).rounded_rectangle(
[pad, pad, S - pad, S - pad],
radius=int(S * 0.225),
outline=(255, 255, 255, 46),
width=max(2, int(S * 0.004)),
)
return Image.alpha_composite(img, ring)
def main():
base = build()
iconset = os.path.join(OUT, "icon.iconset")
os.makedirs(iconset, exist_ok=True)
for px in (16, 32, 128, 256, 512):
base.resize((px, px), Image.LANCZOS).save(f"{iconset}/icon_{px}x{px}.png")
base.resize((px * 2, px * 2), Image.LANCZOS).save(f"{iconset}/icon_{px}x{px}@2x.png")
base.resize((512, 512), Image.LANCZOS).save(os.path.join(OUT, "icon-preview.png"))
icns = os.path.join(OUT, "icon.icns")
subprocess.run(["iconutil", "-c", "icns", iconset, "-o", icns], check=True)
print(f"Done: {icns}")
if __name__ == "__main__":
sys.exit(main())