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:
1 parent
0fea07f1b2
commit
2c23ec9131
36 files changed
+70
-65
No files matched your search
+38
-37
@@ -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")
|
||||
Reference in new issue
Block a user