Files
podlite/tools/make_icons.py
T
XoséandClaude Opus 5.5 c673a0ba5d Podlite 1.9: radio icon
New launcher icon (the Podcasts Radio pack, master in art/icon-master.png). The status-bar icon
was cut out of the old headphones artwork; make_icons.py now draws a white radio silhouette.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-19 18:57:02 +02:00

100 lines
3.8 KiB
Python

"""Draws Podlite's PNG icons for ldpi (Galaxy Mini), mdpi and hdpi.
The launcher icon (res/drawable-*/ic_launcher.png) comes from the supplied
"Podcasts Radio" icon pack (master in art/icon-master.png) and is not generated
here. The notification icon is a white silhouette of that radio, as Android
2.3's status bar expects.
Android 2.x has no vector drawables, so the player icons are rendered at 4x and
downsampled for smooth edges.
"""
import os
import sys
from PIL import Image, ImageDraw
RES = sys.argv[1]
ROOT = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..")
DENS = {"ldpi": 0.75, "mdpi": 1.0, "hdpi": 1.5}
LILAC = (188, 138, 252, 255) # the icon's sound-wave colour (#BC8AFC)
WHITE = (255, 255, 255, 255)
SS = 4 # supersampling
def canvas(px):
s = px * SS
return Image.new("RGBA", (s, s), (0, 0, 0, 0)), s
def save(img, px, folder, name):
out = img.resize((px, px), Image.LANCZOS)
d = os.path.join(RES, "drawable-" + folder)
os.makedirs(d, exist_ok=True)
out.save(os.path.join(d, name + ".png"))
def status_silhouette():
"""White silhouette of the launcher icon's old radio (handle, grille, two knobs) on transparency:
Android 2.3's status bar expects a light glyph, not the colour artwork."""
S = 24 * 32 # drawn on a 24-unit grid, supersampled
u = lambda v: int(round(v * 32))
m = Image.new("L", (S, S), 0)
d = ImageDraw.Draw(m)
d.arc((u(6), u(1.2), u(18), u(11)), 200, 340, fill=255, width=u(1.8)) # carrying handle
d.rounded_rectangle((u(1.5), u(6.5), u(22.5), u(21.5)), radius=u(3.5), fill=255)
d.rounded_rectangle((u(4), u(9), u(14), u(19)), radius=u(1.2), fill=0) # grille window
for i in range(3): # grille bars
y = 10.6 + i * 2.9
d.rounded_rectangle((u(5.2), u(y), u(12.8), u(y + 1.5)), radius=u(0.7), fill=255)
for cy, r in ((11.6, 2.4), (17.0, 2.0)): # knobs as rings
d.ellipse((u(18 - r), u(cy - r), u(18 + r), u(cy + r)), fill=0)
d.ellipse((u(18 - r + 1.1), u(cy - r + 1.1), u(18 + r - 1.1), u(cy + r - 1.1)), fill=255)
out = Image.new("RGBA", (S, S), (255, 255, 255, 0))
out.putalpha(m)
return out
def status(sil, px, folder):
pad = max(1, round(px * 0.06))
inner = sil.resize((px - 2 * pad, px - 2 * pad), Image.LANCZOS)
img = Image.new("RGBA", (px, px), (0, 0, 0, 0))
img.paste(inner, (pad, pad))
d = os.path.join(RES, "drawable-" + folder)
img.save(os.path.join(d, "ic_stat.png"))
def play(px, folder, name, color):
img, s = canvas(px)
d = ImageDraw.Draw(img)
d.polygon([(s * 0.32, s * 0.2), (s * 0.32, s * 0.8), (s * 0.82, s * 0.5)], fill=color)
save(img, px, folder, name)
def pause(px, folder, name, color):
img, s = canvas(px)
d = ImageDraw.Draw(img)
d.rectangle([s * 0.26, s * 0.2, s * 0.43, s * 0.8], fill=color)
d.rectangle([s * 0.57, s * 0.2, s * 0.74, s * 0.8], fill=color)
save(img, px, folder, name)
def refresh(px, folder):
img, s = canvas(px)
d = ImageDraw.Draw(img)
w = int(s * 0.1)
box = [s * 0.2, s * 0.2, s * 0.8, s * 0.8]
d.arc(box, 30, 320, fill=WHITE, width=w)
# arrow head at the arc's end (top right)
d.polygon([(s * 0.86, s * 0.14), (s * 0.88, s * 0.48), (s * 0.56, s * 0.36)], fill=WHITE)
save(img, px, folder, "ic_refresh")
sil = status_silhouette()
for folder, k in DENS.items():
status(sil, round(25 * k), folder)
play(round(32 * k), folder, "ic_play", WHITE) # on the purple play button
pause(round(32 * k), folder, "ic_pause", WHITE)
play(round(38 * k), folder, "ic_play_small", LILAC) # mini player bar, on dark
pause(round(38 * k), folder, "ic_pause_small", LILAC)
refresh(round(30 * k), folder)
print("icons ok")