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>
This commit is contained in:
xockoandClaude Opus 5.5 committed 2026-10-15 10:07:49 +02:00
1 parent 0fea07f1b2
commit 2c23ec9131
36 files changed
+70 -65

No files matched your search

+38 -37
View File
@@ -1,6 +1,11 @@
"""Draws MiniABS's PNG icons for ldpi (Galaxy Mini), mdpi and hdpi.
Android 2.x has no vector drawables, so every icon is rendered here at 4x and
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
@@ -8,9 +13,9 @@ 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}
AMBER = (245, 166, 35, 255)
DARK = (18, 21, 25, 255)
LILAC = (188, 138, 252, 255) # the icon's sound-wave colour (#BC8AFC)
WHITE = (255, 255, 255, 255)
SS = 4 # supersampling
@@ -27,36 +32,32 @@ def save(img, px, folder, name):
out.save(os.path.join(d, name + ".png"))
def broadcast(d, s, cx, cy, scale, color, bg):
"""Podcast mark: a dot with two arcs on each side."""
r = s * 0.075 * scale
d.ellipse([cx - r, cy - r, cx + r, cy + r], fill=color)
w = max(1, int(s * 0.07 * scale))
for k in (0.22, 0.37):
rr = s * k * scale
box = [cx - rr, cy - rr, cx + rr, cy + rr]
d.arc(box, 140, 220, fill=color, width=w)
d.arc(box, -40, 40, fill=color, width=w)
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 launcher(px, folder):
img, s = canvas(px)
d = ImageDraw.Draw(img)
m = s * 0.06
d.rounded_rectangle([m, m, s - m, s - m], radius=s * 0.2, fill=DARK, outline=AMBER, width=int(s * 0.04))
broadcast(d, s, s / 2, s * 0.44, 1.0, AMBER, DARK)
# little stand under the mark
w = s * 0.05
d.rectangle([s / 2 - w / 2, s * 0.5, s / 2 + w / 2, s * 0.74], fill=AMBER)
d.rounded_rectangle([s * 0.36, s * 0.72, s * 0.64, s * 0.78], radius=w / 2, fill=AMBER)
save(img, px, folder, "ic_launcher")
def status(px, folder):
img, s = canvas(px)
d = ImageDraw.Draw(img)
broadcast(d, s, s / 2, s / 2, 1.25, WHITE, None)
save(img, px, folder, "ic_stat")
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):
@@ -85,12 +86,12 @@ def refresh(px, folder):
save(img, px, folder, "ic_refresh")
sil = status_silhouette()
for folder, k in DENS.items():
launcher(round(48 * k), folder)
status(round(25 * k), folder)
play(round(32 * k), folder, "ic_play", DARK)
pause(round(32 * k), folder, "ic_pause", DARK)
play(round(38 * k), folder, "ic_play_small", AMBER)
pause(round(38 * k), folder, "ic_pause_small", AMBER)
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")