143 lines
5.1 KiB
Python
143 lines
5.1 KiB
Python
#!/usr/bin/env python3
|
|
"""Generate the Terminal app icon set (macOS + Pillow)."""
|
|
|
|
import json
|
|
import os
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
|
|
from PIL import Image, ImageDraw, ImageFilter, ImageFont
|
|
|
|
ROOT = os.path.dirname(os.path.abspath(__file__))
|
|
MASTER = os.path.join(ROOT, "icon-master.png")
|
|
ASSETS = os.path.join(ROOT, "Assets.xcassets")
|
|
ICONSET = os.path.join(ASSETS, "DisplayIcon.appiconset")
|
|
OUT = os.path.join(ROOT, "out")
|
|
|
|
SIZE = 1024
|
|
|
|
# Background gradient (deep blue-black to black).
|
|
img = Image.new("RGB", (SIZE, SIZE), (5, 7, 10))
|
|
pix = img.load()
|
|
for y in range(SIZE):
|
|
t = y / (SIZE - 1)
|
|
r = int(8 + 10 * t)
|
|
g = int(13 + 15 * t)
|
|
b = int(20 + 22 * t)
|
|
for x in range(SIZE):
|
|
# A very subtle vertical/horizontal vignette.
|
|
edge = min(x, SIZE - 1 - x, y, SIZE - 1 - y) / (SIZE / 2)
|
|
v = 0.85 + 0.15 * min(1.0, edge)
|
|
pix[x, y] = (int(r * v), int(g * v), int(b * v))
|
|
|
|
draw = ImageDraw.Draw(img)
|
|
|
|
# Terminal card.
|
|
card = (112, 154, 912, 870)
|
|
radius = 96
|
|
draw.rounded_rectangle(card, radius=radius, fill=(13, 17, 23), outline=(48, 54, 61), width=10)
|
|
|
|
# Top bar with traffic-light dots.
|
|
top = (112, 154, 912, 300)
|
|
draw.rounded_rectangle(top, radius=radius, fill=(22, 27, 34))
|
|
draw.rectangle((112, 250, 912, 300), fill=(22, 27, 34))
|
|
draw.line((112, 300, 912, 300), fill=(48, 54, 61), width=6)
|
|
for cx, color in ((206, (255, 95, 86)), (286, (255, 189, 46)), (366, (39, 201, 63))):
|
|
draw.ellipse((cx - 28, 201, cx + 28, 257), fill=color)
|
|
|
|
# Green prompt glow.
|
|
glow = Image.new("RGBA", (SIZE, SIZE), (0, 0, 0, 0))
|
|
gdraw = ImageDraw.Draw(glow)
|
|
try:
|
|
font = ImageFont.truetype("/System/Library/Fonts/Menlo.ttc", 400, index=1)
|
|
except Exception:
|
|
font = ImageFont.truetype("/System/Library/Fonts/Menlo.ttc", 400, index=0)
|
|
text = ">_"
|
|
bbox = gdraw.textbbox((0, 0), text, font=font)
|
|
tw, th = bbox[2] - bbox[0], bbox[3] - bbox[1]
|
|
tx = (SIZE - tw) // 2 - bbox[0]
|
|
ty = 430 - bbox[1]
|
|
gdraw.text((tx, ty), text, font=font, fill=(57, 211, 83, 255))
|
|
glow = glow.filter(ImageFilter.GaussianBlur(34))
|
|
img = Image.alpha_composite(img.convert("RGBA"), glow).convert("RGB")
|
|
draw = ImageDraw.Draw(img)
|
|
|
|
# Crisp prompt and a terminal cursor block.
|
|
draw.text((tx, ty), text, font=font, fill=(63, 185, 80))
|
|
cursor_x = tx + tw + 28
|
|
cursor_y = ty + int(th * 0.58)
|
|
draw.rounded_rectangle((cursor_x, cursor_y, cursor_x + 92, cursor_y + 30), radius=8, fill=(63, 185, 80))
|
|
|
|
# A faint bottom label.
|
|
try:
|
|
label_font = ImageFont.truetype("/System/Library/Fonts/SFNSMono.ttf", 44)
|
|
except Exception:
|
|
label_font = ImageFont.truetype("/System/Library/Fonts/Menlo.ttc", 54)
|
|
label = "Terminal"
|
|
lbbox = draw.textbbox((0, 0), label, font=label_font)
|
|
lw = lbbox[2] - lbbox[0]
|
|
draw.text(((SIZE - lw) // 2, 780), label, font=label_font, fill=(88, 166, 255))
|
|
|
|
img.save(MASTER)
|
|
|
|
# Asset catalog.
|
|
if os.path.isdir(OUT):
|
|
shutil.rmtree(OUT)
|
|
os.makedirs(OUT, exist_ok=True)
|
|
if os.path.isdir(ASSETS):
|
|
shutil.rmtree(ASSETS)
|
|
os.makedirs(ICONSET, exist_ok=True)
|
|
with open(os.path.join(ASSETS, "Contents.json"), "w") as f:
|
|
json.dump({"info": {"author": "xcode", "version": 1}}, f, indent=2)
|
|
|
|
# (idiom, size, scale, filename)
|
|
specs = [
|
|
("iphone", "20x20", "2x", "DisplayIcon20x20@2x.png"),
|
|
("iphone", "20x20", "3x", "DisplayIcon20x20@3x.png"),
|
|
("iphone", "29x29", "2x", "DisplayIcon29x29@2x.png"),
|
|
("iphone", "29x29", "3x", "DisplayIcon29x29@3x.png"),
|
|
("iphone", "40x40", "2x", "DisplayIcon40x40@2x.png"),
|
|
("iphone", "40x40", "3x", "DisplayIcon40x40@3x.png"),
|
|
("iphone", "60x60", "2x", "DisplayIcon60x60@2x.png"),
|
|
("iphone", "60x60", "3x", "DisplayIcon60x60@3x.png"),
|
|
("ipad", "20x20", "1x", "DisplayIcon20x20~ipad.png"),
|
|
("ipad", "20x20", "2x", "DisplayIcon20x20@2x~ipad.png"),
|
|
("ipad", "29x29", "1x", "DisplayIcon29x29~ipad.png"),
|
|
("ipad", "29x29", "2x", "DisplayIcon29x29@2x~ipad.png"),
|
|
("ipad", "40x40", "1x", "DisplayIcon40x40~ipad.png"),
|
|
("ipad", "40x40", "2x", "DisplayIcon40x40@2x~ipad.png"),
|
|
("ipad", "76x76", "1x", "DisplayIcon76x76~ipad.png"),
|
|
("ipad", "76x76", "2x", "DisplayIcon76x76@2x~ipad.png"),
|
|
("ipad", "83.5x83.5", "2x", "DisplayIcon83.5x83.5@2x~ipad.png"),
|
|
("ios-marketing", "1024x1024", "1x", "DisplayIcon1024.png"),
|
|
]
|
|
images = []
|
|
for idiom, size, scale, name in specs:
|
|
points = float(size.split("x")[0])
|
|
px = int(round(points * int(scale.rstrip("x"))))
|
|
resized = img.resize((px, px), Image.LANCZOS)
|
|
resized.save(os.path.join(ICONSET, name))
|
|
images.append({"filename": name, "idiom": idiom, "scale": scale, "size": size})
|
|
with open(os.path.join(ICONSET, "Contents.json"), "w") as f:
|
|
json.dump({"images": images, "info": {"author": "xcode", "version": 1}}, f, indent=2)
|
|
|
|
# Compile the asset catalog with Xcode's actool.
|
|
subprocess.run([
|
|
"xcrun", "actool",
|
|
"--compile", OUT,
|
|
"--platform", "iphoneos",
|
|
"--minimum-deployment-target", "15.0",
|
|
"--app-icon", "DisplayIcon",
|
|
"--output-partial-info-plist", os.path.join(OUT, "partial-info.plist"),
|
|
"--target-device", "iphone",
|
|
"--target-device", "ipad",
|
|
ASSETS,
|
|
], check=True)
|
|
|
|
print("master:", MASTER)
|
|
print("assets.car:", os.path.join(OUT, "Assets.car"))
|
|
print("generated files:")
|
|
for name in sorted(os.listdir(OUT)):
|
|
print(" ", name)
|