Skip to content

Instantly share code, notes, and snippets.

@remino
Last active July 10, 2025 13:14
Show Gist options
  • Save remino/ee50cba70365b3e48f2d21dab4dc75a6 to your computer and use it in GitHub Desktop.
Save remino/ee50cba70365b3e48f2d21dab4dc75a6 to your computer and use it in GitHub Desktop.
#!/bin/sh
set -eu
log() {
echo "[genanim] $@"
}
require() {
missing_bin=0
for bin in "$@"; do
if ! which "$bin" > /dev/null 2>&1; then
missing_bin=1
_error "Required: $bin"
fi
done
if [ $missing_bin -ne 0 ]; then
_fatal "$E_MISSING_APP" "One or more executables or apps are missing."
fi
}
which magick > /dev/null 2>&1 || (echo "ImageMagick 7 required." >&2 && exit 16)
log "Starting animated GIF generation…"
TMPDIR=$(mktemp -d)
cleanup() {
[ -n "${TMPDIR:-}" ] && [ -d "$TMPDIR" ] && rm -rf "$TMPDIR"
log "Cleaned up temporary files."
}
trap cleanup EXIT INT
svg() {
cat << SVG
<svg width="300" height="300" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="g" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="black"/>
<stop offset="33.3333%" stop-color="white"/>
<stop offset="66.6667%" stop-color="black"/>
<stop offset="100%" stop-color="white"/>
</linearGradient>
</defs>
<rect x="0" y="0" width="300" height="300" fill="url(#g)" />
</svg>
SVG
}
SIZE=240
FRAMES=48
FPS=12
DURATION=$(awk "BEGIN {print 100.0 / $FPS}")
OUTPUT=anim.gif
log "Temp folder: $TMPDIR"
log "Creating SVG file..."
svg > "$TMPDIR/grad.svg"
log "Creating diagonal gradient base…"
magick \
-density 300 \
"$TMPDIR/grad.svg" \
-resize "$((SIZE * 3))x$((SIZE * 3))" \
"$TMPDIR/gradient.png"
log "Applying ordered dithering (B/W)…"
magick "$TMPDIR/gradient.png" \
-ordered-dither o8x8 \
"$TMPDIR/dithered.png"
log "Generating $FRAMES frames…"
for i in $(seq 0 $((FRAMES - 1))); do
OFFSET=$(awk "BEGIN {print int($i * ($SIZE * 2) / $FRAMES)}")
magick "$TMPDIR/dithered.png" \
-crop "${SIZE}x${SIZE}+$OFFSET+$OFFSET" +repage \
"$TMPDIR/frame_$(printf "%02d" "$i").gif"
log "Frame $i → offset: $OFFSET"
done
log "Assembling animation…"
magick -delay "$DURATION" -loop 0 "$TMPDIR"/frame_*.gif \
-filter point -resize 960x960\! anim.gif
if which image_optim > /dev/null 2>&1; then
log "Found image_optim."
log "Optimizing with image_optim…"
image_optim "$OUTPUT"
else
log "No image_optim found. Skipped optimization."
fi
log "Done! Output: $OUTPUT"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment