4187c8f38d
- App store icon (icon.png) and favicon, drawn by tools/make_icon.py - Clock can show a weather entity with today's high and low from weather.get_forecasts - Side menu gets an icon-only rail style (now the default): full height, centered icons, active marker, names as hover/focus tooltips; phones keep a labelled bottom tab bar Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
75 lines
3.0 KiB
Python
75 lines
3.0 KiB
Python
"""Render the Dashboard Studio app icon: a dashboard with one lamp-lit tile."""
|
|
import sys
|
|
from PIL import Image, ImageDraw, ImageFilter
|
|
|
|
S = 1024 # draw large, downscale for anti-aliasing
|
|
|
|
|
|
def vgradient(w, h, top, bottom):
|
|
g = Image.new("RGBA", (w, h))
|
|
px = g.load()
|
|
for y in range(h):
|
|
t = y / (h - 1)
|
|
c = tuple(int(top[i] + (bottom[i] - top[i]) * t) for i in range(3)) + (255,)
|
|
for x in range(w):
|
|
px[x, y] = c
|
|
return g
|
|
|
|
|
|
def rounded_mask(size, box, r):
|
|
m = Image.new("L", size, 0)
|
|
ImageDraw.Draw(m).rounded_rectangle(box, r, fill=255)
|
|
return m
|
|
|
|
|
|
img = Image.new("RGBA", (S, S), (0, 0, 0, 0))
|
|
# background squircle
|
|
bg = vgradient(S, S, (34, 44, 78), (14, 17, 30))
|
|
img.paste(bg, (0, 0), rounded_mask((S, S), (0, 0, S - 1, S - 1), 230))
|
|
|
|
pad, gap = 150, 56
|
|
left = (pad, pad, 492, S - pad) # tall lit tile
|
|
rt = (492 + gap, pad, S - pad, 492 - gap // 2) # graph tile
|
|
rb = (492 + gap, 492 + gap // 2, S - pad, S - pad) # switch tile
|
|
|
|
# warm glow behind the lit tile
|
|
glow = Image.new("RGBA", (S, S), (0, 0, 0, 0))
|
|
ImageDraw.Draw(glow).rounded_rectangle(left, 64, fill=(255, 196, 92, 170))
|
|
img = Image.alpha_composite(img, glow.filter(ImageFilter.GaussianBlur(60)))
|
|
|
|
# lit tile with a warm gradient
|
|
lit = vgradient(left[2] - left[0], left[3] - left[1], (255, 236, 190), (255, 196, 96))
|
|
img.paste(lit, left[:2], rounded_mask(lit.size, (0, 0, lit.size[0] - 1, lit.size[1] - 1), 64))
|
|
|
|
# translucent shapes go on a layer that is blended in, not pasted over
|
|
layer = Image.new("RGBA", (S, S), (0, 0, 0, 0))
|
|
ld = ImageDraw.Draw(layer)
|
|
for box in (rt, rb):
|
|
ld.rounded_rectangle(box, 64, fill=(255, 255, 255, 26), outline=(255, 255, 255, 40), width=4)
|
|
cx = left[0] + 100
|
|
ld.ellipse((cx - 52, left[1] + 48, cx + 52, left[1] + 152), fill=(255, 255, 255, 140))
|
|
bar = (left[0] + 48, left[3] - 128, left[2] - 48, left[3] - 64)
|
|
ld.rounded_rectangle(bar, 32, fill=(122, 82, 16, 50))
|
|
img = Image.alpha_composite(img, layer)
|
|
|
|
d = ImageDraw.Draw(img)
|
|
# lamp in the badge and the filled part of the brightness bar
|
|
d.ellipse((cx - 20, left[1] + 80, cx + 20, left[1] + 120), fill=(122, 82, 16, 255))
|
|
d.rounded_rectangle((bar[0], bar[1], bar[0] + int((bar[2] - bar[0]) * 0.68), bar[3]), 32, fill=(140, 94, 20, 255))
|
|
|
|
# graph tile: a line in the accent blue
|
|
pts = [(rt[0] + 44, rt[3] - 70), (rt[0] + 110, rt[3] - 120), (rt[0] + 170, rt[3] - 96), (rt[0] + 240, rt[3] - 170), (rt[2] - 44, rt[1] + 70)]
|
|
d.line(pts, fill=(90, 170, 255, 255), width=22, joint="curve")
|
|
for p in (pts[0], pts[-1]):
|
|
d.ellipse((p[0] - 11, p[1] - 11, p[0] + 11, p[1] + 11), fill=(90, 170, 255, 255))
|
|
|
|
# switch tile: an "on" switch
|
|
sw = (rb[0] + 50, (rb[1] + rb[3]) // 2 - 46, rb[2] - 50, (rb[1] + rb[3]) // 2 + 46)
|
|
d.rounded_rectangle(sw, 46, fill=(48, 209, 88, 255))
|
|
d.ellipse((sw[2] - 86, sw[1] + 6, sw[2] - 6, sw[3] - 6), fill=(255, 255, 255, 255))
|
|
|
|
out = sys.argv[1]
|
|
img.resize((256, 256), Image.LANCZOS).save(out)
|
|
img.resize((128, 128), Image.LANCZOS).save(out.replace(".png", "-128.png"))
|
|
print("saved", out)
|