explodorama / files / tools / compose-icons.sh
compose-icons.sh
| 1 | #!/bin/sh |
| 2 | # Builds the power-up icons that are a known sprite plus a modifier badge in the upper right |
| 3 | # (user request, 2026-10-22): a green disc with a dark rim, the modifier's white symbol on it. |
| 4 | # Sources are the Kenney files already in assets/; the results go to assets/composed/ and are |
| 5 | # committed, so the build needs nothing extra. Needs ImageMagick 7 (`magick`). |
| 6 | # |
| 7 | # sh tools/compose-icons.sh |
| 8 | set -e |
| 9 | cd "$(dirname "$0")/.." |
| 10 | K=assets/kenney |
| 11 | I=assets/kenney-icons/White |
| 12 | B=assets/kenney-board-icons |
| 13 | OUT=assets/composed |
| 14 | mkdir -p "$OUT" |
| 15 | |
| 16 | # ink <white icon> <name>: the same shape in the UI's ink colour, for light backgrounds (CSS |
| 17 | # turns it white again where the background is dark: class "mono" in controls.ts). |
| 18 | # Always 128 × 128, like every board sprite: LittleJS cuts tiles of 128 out of each texture, and a |
| 19 | # 100 px Game Icons file (the padlock) made its tile invalid, which crashed the game (2026-10-23). |
| 20 | ink() { |
| 21 | magick "$1" -resize 128x128 -fill '#353541' -colorize 100 "$OUT/$2.png" |
| 22 | echo " $OUT/$2.png" |
| 23 | } |
| 24 | |
| 25 | # The Move and Wait buttons (user request, 2026-10-22: the astronaut was confusing next to the |
| 26 | # player). Wait is the hourglass (it was the figure with an ✕ for a while). |
| 27 | ink "$B/character.png" icon_move |
| 28 | ink "$B/hourglass.png" icon_wait |
| 29 | # The fuse in the top bar and its info: the timer of the Fuse +1 badge (user request, 2026-10-22). |
| 30 | ink "$B/timer_CW_75.png" icon_fuse |
| 31 | |
| 32 | # badge <base sprite> <symbol> <name>: 128 × 128, the badge 56 px across at the top right. |
| 33 | badge() { |
| 34 | magick "$1" \ |
| 35 | \( -size 128x128 xc:none -fill '#2cc987' -stroke '#353541' -strokewidth 6 -draw 'circle 96,32 96,4' \) -composite \ |
| 36 | \( "$2" -resize 36x36 \) -geometry +78+14 -composite \ |
| 37 | "$OUT/$3.png" |
| 38 | echo " $OUT/$3.png" |
| 39 | } |
| 40 | |
| 41 | badge "$K/bomb.png" "$I/plus.png" powerup_bomb_plus |
| 42 | badge "$K/bomb.png" "$I/larger.png" powerup_fire_plus |
| 43 | badge "$K/bomb.png" "$B/timer_CW_75.png" powerup_fuse_plus |
| 44 | badge "$OUT/icon_move.png" "$I/fastForward.png" powerup_run |
| 45 | badge "$K/bomb.png" "$I/arrowRight.png" powerup_kick |
| 46 | badge "$K/bomb.png" "$I/upRight.png" powerup_throw |
| 47 | badge "$OUT/icon_move.png" "$I/export.png" powerup_teleport |
| 48 | # Diagonal: the Move figure with four diagonal arrows (a charge until 2026-10-23). |
| 49 | badge "$OUT/icon_move.png" "$B/arrow_diagonal_cross.png" powerup_diagonal |
| 50 | badge "$K/bomb.png" "$I/exclamation.png" powerup_detonator |
| 51 | badge "$K/block_planks.png" "$I/cross.png" powerup_wall_pass |
| 52 | badge "$K/bomb.png" "$B/shield.png" powerup_bomb_pass |
| 53 | badge "$K/fireball.png" "$B/shield.png" powerup_flame_pass |
| 54 | |
| 55 | # The charges (user request, 2026-10-23: they work on pickup now). Pierce and Power reuse the |
| 56 | # Crate Pass and Fire+ icons above; the rest: |
| 57 | badge "$K/bomb.png" "$B/skull.png" charge_nuke |
| 58 | ink "$I/locked.png" charge_freeze |
| 59 | ink "$B/pawns.png" charge_decoy |
| 60 | # The power-ups and charge added 2026-10-24 (user request): RPG, a bomb with a forward arrow; |
| 61 | # Proximity alert, a bomb with a warning sign; Slide, the Move figure with a two-way arrow; the |
| 62 | # ICBM charge, a bomb with a target. |
| 63 | badge "$K/bomb.png" "$I/forward.png" powerup_rpg |
| 64 | badge "$K/bomb.png" "$I/warning.png" powerup_proximity |
| 65 | badge "$OUT/icon_move.png" "$B/arrow_horizontal.png" powerup_slide |
| 66 | badge "$K/bomb.png" "$I/target.png" charge_icbm |
| 67 | # TNT (user request, 2026-10-25): Fire +2 — the fireball itself with the Fire +1 "larger" badge. |
| 68 | badge "$K/fireball.png" "$I/larger.png" powerup_tnt |
| 69 | # Acid (PLAN.md §19.4, 2026-10-25): a bomb with a flask. |
| 70 | badge "$K/bomb.png" "$B/flask_full.png" charge_acid |
| 71 | |
| 72 | # A metal crate (PLAN.md §19.3): the strong crate in steel grey. |
| 73 | magick "$K/block_strong_empty.png" -modulate 165,6 -fill '#9fb4cc' -tint 70 "$OUT/block_metal.png" |
| 74 | echo " $OUT/block_metal.png" |
| 75 | |
| 76 | # Oil (PLAN.md §19.3): a splat from the Splat Pack, near black, with a glossy highlight, so it |
| 77 | # can't be taken for a stain. |
| 78 | magick assets/kenney-splat/splat05.png -fill '#2b2631' -colorize 100 \ |
| 79 | -fill 'rgba(190,200,230,0.55)' -stroke none -draw 'ellipse 52,46 16,7 0,360' \ |
| 80 | -fill 'rgba(190,200,230,0.35)' -draw 'ellipse 76,70 7,3 0,360' \ |
| 81 | \( assets/kenney-splat/splat05.png -alpha extract \) -compose copyopacity -composite PNG32:"$OUT/oil.png" |
| 82 | echo " $OUT/oil.png" |
| 83 | |
| 84 | # Bushes (user request, 2026-10-26): Foliage Sprites' white shaded shapes, tinted green (its shading |
| 85 | # kept by multiplying) and given the platformer art's dark outline, at 128 × 128. Tall grass tufts, |
| 86 | # and round canopies; a few of each, so a clump doesn't repeat one picture. |
| 87 | FOL=assets/kenney-foliage |
| 88 | foliage() { |
| 89 | magick "$FOL/sprite_00$1.png" -colorspace sRGB -type TrueColorAlpha -resize 112x112 -background none -gravity center -extent 128x128 PNG32:"$TMPF/shape.png" |
| 90 | magick "$TMPF/shape.png" \( +clone -fill "$2" -colorize 100 \) -compose multiply -composite \ |
| 91 | \( "$TMPF/shape.png" -alpha extract \) -compose copyopacity -composite PNG32:"$TMPF/tinted.png" |
| 92 | magick "$TMPF/tinted.png" \( +clone -alpha extract -morphology dilate disk:5 -blur 0x0.6 -level 40%,60% \ |
| 93 | -background '#353541' -alpha shape \) +swap -compose over -composite PNG32:"$OUT/$3.png" |
| 94 | echo " $OUT/$3.png" |
| 95 | } |
| 96 | TMPF=$(mktemp -d) |
| 97 | foliage 52 '#45c060' grass_a |
| 98 | foliage 53 '#3db457' grass_b |
| 99 | foliage 62 '#4cc866' grass_c |
| 100 | foliage 63 '#41b85b' grass_d |
| 101 | foliage 90 '#45c060' canopy_a |
| 102 | foliage 91 '#3db457' canopy_b |
| 103 | rm -r "$TMPF" |
| 104 | |
| 105 | # Shield: the Move figure inside an empty ring (the board-game timer at 0). |
| 106 | magick "$B/timer_0.png" -fill '#353541' -colorize 100 \ |
| 107 | \( "$OUT/icon_move.png" -resize 84x84 \) -gravity center -composite \ |
| 108 | "$OUT/charge_shield.png" |
| 109 | echo " $OUT/charge_shield.png" |
| 110 | |
| 111 | # Developer mode's extra gem (user request, 2026-10-24: a very special gem, so it can't be taken |
| 112 | # for the level's own): the green gem's body in a rainbow, red to violet from left to right, |
| 113 | # blended over its own shading in grey (Overlay keeps the facets); the dark outline stays as it is. |
| 114 | TMP=$(mktemp -d) |
| 115 | magick "$K/gem_green.png" -modulate 100,0 "$TMP/grey.png" |
| 116 | magick -size 24x128 xc:'#ff4848' \ |
| 117 | \( -size 128x20 gradient:'#ff4848'-'#ffd23b' -rotate -90 \) \ |
| 118 | \( -size 128x20 gradient:'#ffd23b'-'#3bd96b' -rotate -90 \) \ |
| 119 | \( -size 128x20 gradient:'#3bd96b'-'#3ba8ff' -rotate -90 \) \ |
| 120 | \( -size 128x20 gradient:'#3ba8ff'-'#c060ff' -rotate -90 \) \ |
| 121 | -size 24x128 xc:'#c060ff' +append \ |
| 122 | "$TMP/grey.png" -compose overlay -composite "$TMP/rainbow.png" |
| 123 | magick "$K/gem_green.png" "$TMP/rainbow.png" \( "$TMP/grey.png" -alpha off -threshold 30% \) -compose over -composite \ |
| 124 | \( "$K/gem_green.png" -alpha extract \) -compose copyopacity -composite PNG32:"$OUT/gem_rainbow.png" |
| 125 | rm -r "$TMP" |
| 126 | echo " $OUT/gem_rainbow.png" |
| 127 | |
| 128 | # The app's icons for installing it (user request, 2026-10-23): the bomb on the sky blue, in |
| 129 | # static/icons/. "Maskable" keeps the bomb inside the middle 60%, so any mask shape fits round it. |
| 130 | mkdir -p static/icons |
| 131 | appicon() { |
| 132 | magick -size "$1x$1" xc:'#c3e3ff' \( "$K/bomb.png" -resize "$2x$2" \) -gravity center -composite "static/icons/$3.png" |
| 133 | echo " static/icons/$3.png" |
| 134 | } |
| 135 | appicon 192 150 icon-192 |
| 136 | appicon 512 400 icon-512 |
| 137 | appicon 512 300 icon-maskable-512 |
| 138 | appicon 180 140 apple-touch-icon |