alby-automation-umbrel/render-icon.py
2026-10-13 00:54:13 +02:00

101 lines
3.4 KiB
Python

#!/usr/bin/env python3
"""Render the alby-automation icon. Composes one canonical bolt shape three
times: a large vertical main bolt + two smaller branches rotated outward, so
all three read as the same primitive."""
import math
from PIL import Image, ImageDraw
SIZE = 1024
AMBER = (245, 166, 35, 255)
DARK_BG = (24, 28, 36, 255)
LIGHT_BG = (245, 247, 250, 255)
# Canonical bolt polygon, top tip at origin, descending in +y direction.
# Designed once; transforms compose the icon.
BOLT = [
(50, 0), # top vertex (slightly right of axis for the classic tilt)
(-110, 175), # outer-left shoulder
(-30, 175), # inner-left (after the horizontal cut)
(-50, 350), # bottom tip
(110, 100), # outer-right shoulder (rebound side)
(-10, 100), # inner-right (after the cut)
]
def transform(points, scale=1.0, rotate_deg=0, translate=(0, 0)):
a = math.radians(rotate_deg)
cos_a, sin_a = math.cos(a), math.sin(a)
tx, ty = translate
out = []
for x, y in points:
x *= scale
y *= scale
rx = x * cos_a - y * sin_a
ry = x * sin_a + y * cos_a
out.append((rx + tx, ry + ty))
return out
def mirror_x(points):
return [(-x, y) for (x, y) in points]
def compose_bolts():
# Main bolt: vertical, top portion of canvas, scale 1.5
main = transform(BOLT, scale=1.5, rotate_deg=0, translate=(515, 100))
# Left branch: scale 1.0, rotate +28° (leans down-left), top tip lands at (450, 590)
# so it appears to emerge from the main bolt's lower body
left = transform(BOLT, scale=1.0, rotate_deg=28, translate=(405.9, 566.5))
# Right branch: mirrored bolt, rotated -28°, top tip lands at (575, 590)
right = transform(mirror_x(BOLT), scale=1.0, rotate_deg=-28, translate=(619.1, 566.5))
return main, left, right
def draw_bolts(draw):
for poly in compose_bolts():
draw.polygon(poly, fill=AMBER)
def make_transparent():
img = Image.new("RGBA", (SIZE, SIZE), (0, 0, 0, 0))
draw_bolts(ImageDraw.Draw(img))
return img
def make_with_bg(bg_color, radius_fraction=0.22):
img = Image.new("RGBA", (SIZE, SIZE), (0, 0, 0, 0))
radius = int(SIZE * radius_fraction)
bg = Image.new("RGBA", (SIZE, SIZE), (0, 0, 0, 0))
ImageDraw.Draw(bg).rounded_rectangle((0, 0, SIZE - 1, SIZE - 1), radius=radius, fill=bg_color)
img = Image.alpha_composite(img, bg)
draw_bolts(ImageDraw.Draw(img))
return img
def export_svg(path):
polys = compose_bolts()
parts = ['<?xml version="1.0" encoding="UTF-8"?>',
'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1024 1024">']
for poly in polys:
points = " ".join(f"{x:.1f},{y:.1f}" for x, y in poly)
parts.append(f' <polygon fill="#F5A623" points="{points}"/>')
parts.append('</svg>')
with open(path, "w") as f:
f.write("\n".join(parts) + "\n")
def main():
out = "."
transparent = make_transparent()
transparent.save(f"{out}/icon-transparent-1024.png")
transparent.resize((256, 256), Image.LANCZOS).save(f"{out}/icon-transparent-256.png")
transparent.resize((64, 64), Image.LANCZOS).save(f"{out}/icon-transparent-64.png")
make_with_bg(DARK_BG).save(f"{out}/icon-dark-bg-1024.png")
make_with_bg(LIGHT_BG).save(f"{out}/icon-light-bg-1024.png")
export_svg(f"{out}/icon.svg")
print("rendered 5 PNG variants + SVG")
if __name__ == "__main__":
main()