anvilsign in

collin/browser-terminal-extension

1#!/usr/bin/env python3
2"""Generate the extension icons.
3
4Design: a dark terminal tile with a green `>_` prompt, and a thin accent bar
5down the right side standing for the sidebar the terminal lives in. The prompt
6is the part that has to survive 16px, so at small sizes the accent bar and the
7corner rounding are simplified rather than shrunk into mush.
8
9Colours come from the dark theme in extension/lib/theme.js.
10
11 python3 assets/make-icons.py
12"""
13
14from PIL import Image, ImageDraw
15
16BG = (20, 22, 26, 255) # --bg #14161a
17EDGE = (45, 49, 56, 255) # --border #2d3138
18GREEN = (110, 193, 119, 255) # ok #6ec177
19FG = (215, 218, 224, 255) # --fg #d7dae0
20ACCENT = (59, 110, 165, 255) # --accent #3b6ea5
21
22# Draw everything on a 128-unit grid at 8x, then downsample.
23GRID = 128
24SS = 8
25
26SIZES = [16, 32, 48, 96, 128]
27
28
29def draw_icon(size: int) -> Image.Image:
30 small = size <= 20
31 w = GRID * SS
32 img = Image.new("RGBA", (w, w), (0, 0, 0, 0))
33 d = ImageDraw.Draw(img)
34
35 def u(v: float) -> float:
36 """Grid units -> supersampled pixels."""
37 return v * SS
38
39 # --- tile ---------------------------------------------------------------
40 # Less rounding at 16px: a large radius eats the glyph's breathing room.
41 radius = 20 if small else 27
42 d.rounded_rectangle(
43 [u(2), u(2), u(GRID - 2), u(GRID - 2)],
44 radius=u(radius),
45 fill=BG,
46 outline=EDGE,
47 width=int(u(2)),
48 )
49
50 # --- sidebar accent bar -------------------------------------------------
51 # Dropped at 16px, where it would be a single muddy pixel column.
52 if not small:
53 d.rounded_rectangle(
54 [u(102), u(34), u(109), u(94)],
55 radius=u(3.5),
56 fill=ACCENT,
57 )
58
59 # --- prompt: >_ ---------------------------------------------------------
60 # Shifted right slightly when there's no accent bar, to stay centred.
61 dx = 6 if small else 0
62 stroke = int(u(11 if small else 10))
63
64 d.line(
65 [(u(30 + dx), u(44)), (u(53 + dx), u(65)), (u(30 + dx), u(86))],
66 fill=GREEN,
67 width=stroke,
68 joint="curve",
69 )
70 # Round the ends by hand; PIL only rounds joints, not caps.
71 r = stroke / 2
72 for px, py in [(u(30 + dx), u(44)), (u(30 + dx), u(86))]:
73 d.ellipse([px - r, py - r, px + r, py + r], fill=GREEN)
74
75 # Underscore / cursor.
76 bar_left, bar_right = u(62 + dx), u(92 + dx if not small else 96)
77 d.rounded_rectangle(
78 [bar_left, u(78), bar_right, u(88)],
79 radius=u(5),
80 fill=FG,
81 )
82
83 return img.resize((size, size), Image.LANCZOS)
84
85
86def main() -> None:
87 import pathlib
88
89 out = pathlib.Path(__file__).resolve().parent.parent / "extension" / "icons"
90 out.mkdir(parents=True, exist_ok=True)
91 for size in SIZES:
92 path = out / f"icon{size}.png"
93 draw_icon(size).save(path)
94 print(f"wrote {path.relative_to(out.parent.parent)}")
95
96
97if __name__ == "__main__":
98 main()