101 lines
3.4 KiB
Python
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()
|