Files
podlite/tools/make_icons.py
xockoandClaude Opus 5.5 2c23ec9131 MiniABS 1.5: Audiobookshelf icon and purple theme
- Launcher icon from the Audiobookshelf icon pack (art/icon_master_512.png),
  including an xhdpi size
- Notification icon cut out of the same artwork; player icons redrawn in the
  icon's lilac (tools/make_icons.py)
- Purple colour scheme taken from the icon: gradient accent buttons, lilac
  text accents (accent_light), purple-tinted backgrounds
- README: where the icon and colours come from

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 10:07:49 +02:00

98 lines
3.4 KiB
Python

"""Draws MiniABS's PNG icons for ldpi (Galaxy Mini), mdpi and hdpi.
The launcher icon (res/drawable-*/ic_launcher.png) comes from the supplied
Audiobookshelf icon pack and is not generated here. The notification icon is
cut out of that same artwork (art/icon_master_512.png): the white headphones
and equalizer become a white silhouette, 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 mask of the icon's light parts (headphones, bars, waves) on transparency."""
src = Image.open(os.path.join(ROOT, "art", "icon_master_512.png")).convert("RGBA")
out = Image.new("RGBA", src.size, (0, 0, 0, 0))
sp, op = src.load(), out.load()
for y in range(src.size[1]):
for x in range(src.size[0]):
r, g, b, a = sp[x, y]
# the purple background has a low green channel; the white/lilac artwork a high one
level = min(255, max(0, (g - 80) * 255 // 50))
op[x, y] = (255, 255, 255, level * a // 255)
box = out.getbbox()
out = out.crop(box)
side = max(out.size)
sq = Image.new("RGBA", (side, side), (0, 0, 0, 0))
sq.paste(out, ((side - out.size[0]) // 2, (side - out.size[1]) // 2))
return sq
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")