mirror of
https://code.gri.mw/GUI/grim.git
synced 2026-07-12 17:58:55 +00:00
icons: regenerate every platform icon from the canonical Goblin sources
All app icons now derive from two sources, end to end:
img/goblin-icon.png gradient app icon (yellow gradient + black mascot)
img/goblin-mark-black.svg black mascot mark (vector) — Android adaptive fg
- scripts/gen_icons.sh rewritten: one run regenerates the desktop/egui window
icon (img/icon.png), the Linux AppImage AppDir icon, all Android launcher +
adaptive-foreground mipmaps, the WiX installer icon, and the macOS .icns.
Dropped the dead goblin-mask*.png pipeline (the adaptive foreground is now the
SVG mark composited by the OS over #FFD60A), so the script no longer references
files that were removed.
- scripts/make-icns.py: new, dependency-free multi-resolution .icns builder
(iconutil/png2icns aren't always present; ImageMagick alone emits one size).
- wix/Product.ico REPLACED with the Goblin logo (was a stale icon).
- Regenerated macOS AppIcon.icns, Linux goblin.png, all Android mipmaps,
img/icon.png. Tracked the goblin-mark-{black,white}.svg sources.
This commit is contained in:
+32
-19
@@ -1,32 +1,45 @@
|
||||
#!/usr/bin/env bash
|
||||
# Generate all Goblin app icons from img/goblin-icon.png (app icon)
|
||||
# and img/goblin-mask.png (black mascot art on transparency).
|
||||
# Requires ImageMagick (magick).
|
||||
# Regenerate EVERY platform's app icon from two canonical sources:
|
||||
# img/goblin-icon.png the gradient app icon (yellow gradient + black mascot)
|
||||
# img/goblin-mark-black.svg the black mascot mark, vector, transparent bg
|
||||
# (mirror of site/assets/goblin-mark-black.svg)
|
||||
#
|
||||
# Square icons (desktop window, Linux AppImage, Android launcher, Windows .ico,
|
||||
# macOS .icns) come from the gradient PNG. The Android *adaptive* foreground is
|
||||
# the black mascot on transparency, composited by the OS over the yellow
|
||||
# background color (res/values/ic_launcher_background.xml = #FFD60A) — which
|
||||
# reproduces the gradient icon's look.
|
||||
#
|
||||
# Requires ImageMagick (magick) and python3 (for the .icns container).
|
||||
set -euo pipefail
|
||||
cd "$(dirname "$0")/.."
|
||||
|
||||
ICON=img/goblin-icon.png
|
||||
MASK=img/goblin-mask.png
|
||||
MARK=img/goblin-mark-black.svg
|
||||
RES=android/app/src/main/res
|
||||
|
||||
# Desktop window icon + in-app embeds.
|
||||
magick "$ICON" -resize 256x256 img/icon.png
|
||||
magick "$ICON" -resize 512x512 img/goblin-icon-512.png
|
||||
magick "$MASK" -channel RGB -fill white -colorize 100 img/goblin-mask-white.png
|
||||
magick img/goblin-mask-white.png -resize 128x128 img/goblin-mask-128.png
|
||||
magick img/goblin-mask-white.png -resize 64x64 img/goblin-mask-64.png
|
||||
# --- Desktop window icon (egui, src/main.rs) + Linux AppImage AppDir icon ---
|
||||
magick "$ICON" -resize 256x256 PNG32:img/icon.png
|
||||
cp img/icon.png linux/Goblin.AppDir/goblin.png
|
||||
|
||||
# Android launcher icons.
|
||||
declare -A SIZES=( [mdpi]=48 [hdpi]=72 [xhdpi]=96 [xxhdpi]=144 [xxxhdpi]=192 )
|
||||
# --- Android launcher icons (gradient square) + adaptive foreground (mascot) ---
|
||||
declare -A SIZES=( [mdpi]=48 [hdpi]=72 [xhdpi]=96 [xxhdpi]=144 [xxxhdpi]=192 )
|
||||
declare -A FG_SIZES=( [mdpi]=108 [hdpi]=162 [xhdpi]=216 [xxhdpi]=324 [xxxhdpi]=432 )
|
||||
for d in mdpi hdpi xhdpi xxhdpi xxxhdpi; do
|
||||
s=${SIZES[$d]}; fg=${FG_SIZES[$d]}
|
||||
# mascot occupies ~52% of the adaptive canvas (safe zone is 66%)
|
||||
art=$(( fg * 52 / 100 ))
|
||||
magick "$ICON" -resize ${s}x${s} "$RES/mipmap-$d/ic_launcher.png"
|
||||
magick "$ICON" -resize ${s}x${s} "$RES/mipmap-$d/ic_launcher_round.png"
|
||||
magick "$MASK" -resize ${art}x${art} -background none \
|
||||
-gravity center -extent ${fg}x${fg} "$RES/mipmap-$d/ic_launcher_foreground.png"
|
||||
# Mascot fills ~60% of the adaptive canvas — inside the ~61% safe zone, so no
|
||||
# launcher mask (circle/squircle) ever clips it.
|
||||
art=$(( fg * 60 / 100 ))
|
||||
magick "$ICON" -resize "${s}x${s}" PNG32:"$RES/mipmap-$d/ic_launcher.png"
|
||||
magick "$ICON" -resize "${s}x${s}" PNG32:"$RES/mipmap-$d/ic_launcher_round.png"
|
||||
magick -background none "$MARK" -resize "${art}x${art}" \
|
||||
-gravity center -extent "${fg}x${fg}" PNG32:"$RES/mipmap-$d/ic_launcher_foreground.png"
|
||||
done
|
||||
|
||||
echo "icons generated"
|
||||
# --- Windows installer + file-type icon (WiX wix/Product.ico) ---
|
||||
magick "$ICON" -define icon:auto-resize=256,128,64,48,32,24,16 wix/Product.ico
|
||||
|
||||
# --- macOS app bundle icon (Goblin.app) ---
|
||||
python3 scripts/make-icns.py "$ICON" macos/Goblin.app/Contents/Resources/AppIcon.icns
|
||||
|
||||
echo "icons generated from $ICON + $MARK"
|
||||
|
||||
Executable
+60
@@ -0,0 +1,60 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Build a macOS .icns from a square PNG, dependency-free.
|
||||
|
||||
macOS has `iconutil` and Linux distros have `png2icns`, but neither is reliably
|
||||
present, and ImageMagick's own .icns writer only emits a single size. So we
|
||||
assemble the multi-resolution PNG-payload .icns container by hand (the format
|
||||
macOS 10.7+ accepts): the `icns` magic + big-endian length, then one entry per
|
||||
OSType, each carrying an 8-bit PNG. ImageMagick (`magick`) does the resizing.
|
||||
|
||||
Usage: make-icns.py <source.png> <out.icns>
|
||||
"""
|
||||
|
||||
import struct
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
# OSType -> pixel size. PNG-payload entries. Sizes above the source are
|
||||
# Lanczos-upscaled (soft but acceptable for the few large Dock/Finder slots).
|
||||
SLOTS = [
|
||||
(b"icp4", 16),
|
||||
(b"icp5", 32),
|
||||
(b"icp6", 64),
|
||||
(b"ic07", 128),
|
||||
(b"ic08", 256),
|
||||
(b"ic11", 32), # 16@2x
|
||||
(b"ic12", 64), # 32@2x
|
||||
(b"ic13", 256), # 128@2x
|
||||
(b"ic09", 512), # 512
|
||||
(b"ic14", 512), # 256@2x
|
||||
]
|
||||
|
||||
|
||||
def render(src, size):
|
||||
out = "/tmp/_icns_%d.png" % size
|
||||
subprocess.run(
|
||||
["magick", src, "-resize", "%dx%d" % (size, size),
|
||||
"-filter", "Lanczos", "-depth", "8", "PNG32:%s" % out],
|
||||
check=True,
|
||||
)
|
||||
return open(out, "rb").read()
|
||||
|
||||
|
||||
def main():
|
||||
if len(sys.argv) != 3:
|
||||
sys.exit("usage: make-icns.py <source.png> <out.icns>")
|
||||
src, out = sys.argv[1], sys.argv[2]
|
||||
cache, entries = {}, []
|
||||
for ostype, size in SLOTS:
|
||||
if size not in cache:
|
||||
cache[size] = render(src, size)
|
||||
data = cache[size]
|
||||
entries.append(ostype + struct.pack(">I", 8 + len(data)) + data)
|
||||
body = b"".join(entries)
|
||||
with open(out, "wb") as f:
|
||||
f.write(b"icns" + struct.pack(">I", 8 + len(body)) + body)
|
||||
print("wrote %s (%d entries)" % (out, len(entries)))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user