feat: the apps' icons as one family, drawn by one tool (ADR 0031)

tool/app_icons.py at the root replaces Learn's and Todo's scripts: each
app's Lucide glyph (lucide-static 1.50.0) drawn by librsvg, fading from
cobalt through the colours ADR 0031 names, then every platform's
launcher icon through flutter_launcher_icons, Windows' .ico with every
size, and Learn's snap icon.

Learn's icon becomes the graduation cap, its window's on Linux too, with
Lucide's notice in its licence list; the kingfisher stays the emblem on
the rail and the launch screen. Todo's becomes list-todo, Train's bike.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
Tizian 「ティツィアーン」 2026-10-28 10:42:47 +01:00
commit ece9ce9697
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64

View file

@ -1,85 +0,0 @@
#!/usr/bin/env python3
"""Makes the app's icons for every platform from its own, the kingfisher on white
(halkyon_learn_flutter/assets/icon/halkyon_icon.png).
- The snap's, snap/gui/halkyon-learn.png at the repository's root (512 x 512
pixels, the Snap Store's size), which the Store and the desktop file show as they
are: the picture with rounded corners, as other apps' white tiles have (radius
142.222 of 512, about 28 %).
- Through flutter_launcher_icons, configured in the app's pubspec: Android's adaptive
icon and its older square one, iOS's, macOS's (the rounded tile on Apple's grid,
824 of 1024 pixels) and Windows' (the rounded tile).
- Windows' .ico once more, with every size Windows shows (16 to 256 pixels): the tool
writes only the 256 one and leaves Windows to scale it down for its small views.
- The iOS project file as it was: the tool sets every build setting with ASSETCATALOG
in its name to the icon set's name, Xcode's yes-or-no
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS too, which breaks the
project (flutter_launcher_icons issue 634); the project names its icon set already.
The derived images it hands to the tool go to halkyon_learn_flutter/build/icons/,
which git ignores. The rounded corners' mask is drawn at four times the size and
scaled down, so their edges are smooth.
Run from apps/learn with Python 3 and Pillow (python3-pil on Ubuntu), the
app's dependencies fetched:
python3 tool/app_icons.py
"""
import subprocess
from pathlib import Path
from PIL import Image, ImageDraw
APP = Path("halkyon_learn_flutter")
SOURCE = APP / "assets/icon/halkyon_icon.png"
DERIVED = APP / "build/icons"
SNAP_ICON = Path("../../snap/gui/halkyon-learn.png")
WINDOWS_ICON = APP / "windows/runner/resources/app_icon.ico"
IOS_PROJECT = APP / "ios/Runner.xcodeproj/project.pbxproj"
RADIUS = 142.222 / 512
SUPERSAMPLING = 4
# Apple's grid: the tile is 824 of the icon's 1024 pixels, centred.
MACOS_CANVAS = 1024
MACOS_TILE = 824
WINDOWS_SIZES = [16, 20, 24, 32, 40, 48, 64, 128, 256]
def rounded(size: int) -> Image.Image:
"""The icon at [size] pixels, its corners rounded off to transparency."""
icon = Image.open(SOURCE).convert("RGBA").resize((size, size), Image.LANCZOS)
large = size * SUPERSAMPLING
mask = Image.new("L", (large, large), 0)
ImageDraw.Draw(mask).rounded_rectangle(
(0, 0, large - 1, large - 1), radius=RADIUS * large, fill=255
)
mask = mask.resize((size, size), Image.LANCZOS)
# Keeps what the icon itself makes transparent, and takes the corners away.
transparent = Image.new("L", (size, size), 0)
icon.putalpha(Image.composite(icon.getchannel("A"), transparent, mask))
return icon
def main() -> None:
rounded(512).save(SNAP_ICON, optimize=True)
print(f"Wrote {SNAP_ICON}.")
DERIVED.mkdir(parents=True, exist_ok=True)
rounded(1024).save(DERIVED / "rounded.png")
macos = Image.new("RGBA", (MACOS_CANVAS, MACOS_CANVAS), (0, 0, 0, 0))
margin = (MACOS_CANVAS - MACOS_TILE) // 2
macos.alpha_composite(rounded(MACOS_TILE), (margin, margin))
macos.save(DERIVED / "macos.png")
ios_project = IOS_PROJECT.read_bytes()
subprocess.run(["dart", "run", "flutter_launcher_icons"], cwd=APP, check=True)
IOS_PROJECT.write_bytes(ios_project)
rounded(256).save(
WINDOWS_ICON, format="ICO", sizes=[(size, size) for size in WINDOWS_SIZES]
)
print(f"Wrote {WINDOWS_ICON} with {len(WINDOWS_SIZES)} sizes.")
if __name__ == "__main__":
main()