scripts/enlarge_brand_icons.py
Rama main · réplica de lectura del repositorio original.
#!/usr/bin/env python3
"""Generate web, desktop and Android branding from branding/aoede.png."""
from pathlib import Path
from PIL import Image, ImageChops, ImageOps
ROOT = Path(__file__).resolve().parents[1]
WEB = ROOT / "Aoede/assets" if (ROOT / "Aoede").is_dir() else ROOT / "frontend-source/assets"
ANDROID = ROOT / "android/app/src/main/res" if (ROOT / "android").is_dir() else ROOT / "app/src/main/res"
def main():
source = Image.open(ROOT / "branding/aoede.png").convert("RGBA")
bounds = source.getchannel("A").point(lambda alpha: 255 if alpha > 32 else 0).getbbox()
if bounds is None:
raise ValueError("The Aoede master icon is empty")
source = source.crop(bounds)
def render(size, box, artwork=source):
left, top, right, bottom = box
scaled = ImageOps.contain(artwork, (right - left, bottom - top), Image.Resampling.LANCZOS)
icon = Image.new("RGBA", (size, size))
icon.alpha_composite(
scaled, (left + (right - left - scaled.width) // 2, top + (bottom - top - scaled.height) // 2)
)
return icon
# Keep the existing pixel sizes, visual bounds and alignment in each client.
render(1254, (118, 110, 1137, 1091)).save(WEB / "icon.png", optimize=True)
desktop = ROOT / "desktop/resources/icons/appIcon.png"
if desktop.parent.is_dir():
render(1024, (96, 90, 928, 891)).save(desktop, optimize=True)
icons = {}
for size in (16, 32, 48, 64, 128, 180, 192, 256, 512, 1024):
side = min(size - 2, round(size * 0.92))
margin = (size - side) // 2
icons[size] = render(size, (margin, margin, margin + side, margin + side))
for size in (16, 32):
icons[size].save(WEB / f"favicon-{size}x{size}.png", optimize=True)
icons[256].save(
WEB / "favicon.ico",
sizes=[(s, s) for s in (16, 32, 48, 64, 128, 256)],
append_images=[icons[s] for s in (16, 32, 48, 64, 128)],
)
for size in (192, 512):
icons[size].save(WEB / f"android-chrome-{size}x{size}.png", optimize=True)
# Safari Add to Dock needs a high-resolution, opaque maskable icon. Its
# background reaches every edge; the colored mark stays in the safe zone.
for size, name in ((180, "apple-touch-icon.png"), (1024, "app-icon-maskable-1024.png")):
opaque = Image.new("RGBA", (size, size), "#181b2a")
opaque.alpha_composite(icons[size])
opaque.convert("RGB").save(WEB / name, optimize=True)
# The supplied silver A/waveform is brighter than its dark glass tile.
# Extract it for Android's flat, system-tinted notification and themed icons.
mask = ImageChops.multiply(
ImageOps.grayscale(source).point(lambda value: 255 if value > 160 else 0), source.getchannel("A")
)
mark = Image.new("RGBA", source.size, "white")
mark.putalpha(mask)
if mask.getbbox() is None:
raise ValueError("The Aoede monochrome mark is empty")
notification = mark.crop(mask.getbbox())
resources = ANDROID
densities = (
("mdpi", 48, (6, 6, 42, 40), 24),
("hdpi", 72, (10, 9, 62, 60), 36),
("xhdpi", 96, (13, 12, 83, 80), 48),
("xxhdpi", 144, (20, 19, 124, 120), 72),
("xxxhdpi", 192, (26, 25, 166, 159), 96),
)
for density, size, box, small in densities:
folder = resources / f"mipmap-{density}"
icon = render(size, box)
for name in ("ic_launcher", "ic_launcher_round", "ic_launcher_foreground"):
icon.save(folder / f"{name}.png", optimize=True)
render(size, box, mark).save(folder / "ic_launcher_monochrome.png", optimize=True)
folder = resources / f"drawable-{density}"
folder.mkdir(exist_ok=True)
inset = small // 8
render(small, (inset, inset, small - inset, small - inset), notification).save(
folder / "ic_notification.png", optimize=True
)
if __name__ == "__main__":
main()