84 lines
3.4 KiB
Python
84 lines
3.4 KiB
Python
|
|
#!/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 (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 the repository root 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()
|