- ./gradlew assembleDebug produces a 13 MB arm64-v8a APK (libtsjet/libllama/ libggml/libc++_shared bundled; JNI compiles cleanly against llama.cpp b10333) - Add tools/gen_launcher_icons.py so the launcher icon is reproducible from art - README: mark build verified, add sideload/install section Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
72 lines
2.5 KiB
Python
72 lines
2.5 KiB
Python
#!/usr/bin/env python3
|
|
"""Regenerate Android launcher icons from piti-icon.png.
|
|
|
|
Usage: python3 tools/gen_launcher_icons.py
|
|
Requires: Pillow (pip install Pillow)
|
|
|
|
Produces, under app/src/main/res/:
|
|
- mipmap-<dpi>/ic_launcher.png legacy square (rounded), all densities
|
|
- mipmap-<dpi>/ic_launcher_round.png legacy round, all densities
|
|
- mipmap-<dpi>/ic_launcher_foreground.png adaptive-icon foreground (padded)
|
|
The adaptive XMLs (mipmap-anydpi-v26/) and the black background drawable are
|
|
checked in and don't need regenerating.
|
|
"""
|
|
import os
|
|
from PIL import Image, ImageDraw
|
|
|
|
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
SRC = os.path.join(ROOT, "piti-icon.png")
|
|
RES = os.path.join(ROOT, "app", "src", "main", "res")
|
|
BG = (0, 0, 0, 255) # matches the app's black theme / ic_launcher_background.xml
|
|
|
|
brain = Image.open(SRC).convert("RGBA")
|
|
|
|
LEGACY = {"mdpi": 48, "hdpi": 72, "xhdpi": 96, "xxhdpi": 144, "xxxhdpi": 192}
|
|
ADAPTIVE = {"mdpi": 108, "hdpi": 162, "xhdpi": 216, "xxhdpi": 324, "xxxhdpi": 432}
|
|
|
|
|
|
def fit(img, box):
|
|
w, h = img.size
|
|
s = box / max(w, h)
|
|
return img.resize((max(1, int(w * s)), max(1, int(h * s))), Image.LANCZOS)
|
|
|
|
|
|
def rounded_mask(size, radius):
|
|
m = Image.new("L", (size, size), 0)
|
|
ImageDraw.Draw(m).rounded_rectangle([0, 0, size - 1, size - 1], radius=radius, fill=255)
|
|
return m
|
|
|
|
|
|
def circle_mask(size):
|
|
m = Image.new("L", (size, size), 0)
|
|
ImageDraw.Draw(m).ellipse([0, 0, size - 1, size - 1], fill=255)
|
|
return m
|
|
|
|
|
|
def compose(size, mask, content_scale):
|
|
canvas = Image.new("RGBA", (size, size), BG)
|
|
fg = fit(brain, int(size * content_scale))
|
|
canvas.paste(fg, ((size - fg.width) // 2, (size - fg.height) // 2), fg)
|
|
out = Image.new("RGBA", (size, size), (0, 0, 0, 0))
|
|
out.paste(canvas, (0, 0), mask)
|
|
return out
|
|
|
|
|
|
def save(img, folder, name):
|
|
d = os.path.join(RES, folder)
|
|
os.makedirs(d, exist_ok=True)
|
|
img.save(os.path.join(d, name))
|
|
|
|
|
|
for bucket, size in LEGACY.items():
|
|
save(compose(size, rounded_mask(size, int(size * 0.18)), 0.86), f"mipmap-{bucket}", "ic_launcher.png")
|
|
save(compose(size, circle_mask(size), 0.86), f"mipmap-{bucket}", "ic_launcher_round.png")
|
|
|
|
for bucket, size in ADAPTIVE.items():
|
|
fg_canvas = Image.new("RGBA", (size, size), (0, 0, 0, 0))
|
|
fg = fit(brain, int(size * 0.64)) # keep within the adaptive-icon safe zone
|
|
fg_canvas.paste(fg, ((size - fg.width) // 2, (size - fg.height) // 2), fg)
|
|
save(fg_canvas, f"mipmap-{bucket}", "ic_launcher_foreground.png")
|
|
|
|
print("launcher icons regenerated under app/src/main/res/")
|