aboutsummaryrefslogtreecommitdiffstats
path: root/tools/optimize_images.sh
blob: 6538448fb7fd84fdbb178accfa8806ef4df4f1c6 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
#!/usr/bin/env bash
# Optimize site images in place. Lossy but visually conservative.
#
#   - JPEG : strip metadata, progressive, quality 82
#   - PNG  : strip metadata, max zlib recompression (lossless)
#   - GIF  : gifsicle -O3 (lossless frame optimization); --lossy on big ones
#   - A short list of oversized tile PNGs is downscaled to a sane max width,
#     since the layout never displays them larger than ~320px.
#
# Originals are tarred to "$BACKUP_DIR" before anything is touched.
# Re-running is safe: already-small files are left alone.
#
# Env:
#   BACKUP_DIR   where to write the pre-run tarball (default /tmp)
#   DRY_RUN=1    print what would change, touch nothing
set -u
cd "$(dirname "$0")/.."
ROOT="$(pwd)"
BACKUP_DIR="${BACKUP_DIR:-/tmp}"
DRY="${DRY_RUN:-0}"

have() { command -v "$1" >/dev/null 2>&1; }
have convert || { echo "need ImageMagick (convert)"; exit 1; }

# Tile PNGs the layout caps at ~320px wide; 640 keeps a 2x retina buffer.
# Section heroes are capped wider (display size rarely exceeds ~960px).
declare -A MAXW=(
  [assets/img/runescape.png]=640
  [assets/img/soldierfront.png]=640
  [assets/img/multibox.png]=640
  [assets/img/stepmania.png]=640
  [startpage/videogame.jpg]=720
  [computers/laptop-compaq.jpg]=960
  [gaming/runescape/runescape.png]=640
)

fmt() { numfmt --to=iec "$1" 2>/dev/null || echo "$1"; }
size() { stat -c%s "$1"; }

if [ "$DRY" != "1" ]; then
  ts="$(date +%Y%m%d-%H%M%S)"
  tar -czf "$BACKUP_DIR/images-pre-optimize-$ts.tar.gz" \
    assets/img images startpage computers gaming vblog mstartpage 2>/dev/null \
    && echo "backup: $BACKUP_DIR/images-pre-optimize-$ts.tar.gz"
fi

total_before=0; total_after=0
process() {
  local f="$1" before after; before="$(size "$f")"; total_before=$((total_before+before))
  local tmp="$f.opt.$$"
  local fl
  fl="$(printf '%s' "$f" | tr '[:upper:]' '[:lower:]')"
  case "$fl" in
    *.jpg|*.jpeg)
      convert "$f" -strip -interlace Plane -quality 82 "$tmp" 2>/dev/null ;;
    *.png)
      local w="${MAXW[$f]:-}"
      [ -z "$w" ] && w="${MAXW[$fl]:-}"
      if [ -n "$w" ]; then
        convert "$f" -strip -resize "${w}x${w}>" -define png:compression-level=9 -define png:compression-filter=5 "$tmp" 2>/dev/null
      else
        convert "$f" -strip -define png:compression-level=9 -define png:compression-filter=5 "$tmp" 2>/dev/null
      fi ;;
    *.gif)
      if ! have gifsicle; then total_after=$((total_after+before)); return; fi
      if [ "$before" -gt 400000 ]; then
        gifsicle -O3 --lossy=60 "$f" -o "$tmp" 2>/dev/null
      else
        gifsicle -O3 "$f" -o "$tmp" 2>/dev/null
      fi ;;
    *) total_after=$((total_after+before)); return ;;
  esac
  if [ ! -s "$tmp" ]; then rm -f "$tmp"; total_after=$((total_after+before)); return; fi
  after="$(size "$tmp")"
  if [ "$after" -lt "$before" ]; then
    if [ "$DRY" = "1" ]; then
      printf '  %-40s %8s -> %8s\n' "$f" "$(fmt "$before")" "$(fmt "$after")"; rm -f "$tmp"
    else
      mv "$tmp" "$f"
    fi
    total_after=$((total_after+after))
  else
    rm -f "$tmp"; total_after=$((total_after+before))
  fi
}

while IFS= read -r -d '' f; do process "$f"; done < <(
  find assets/img images startpage computers gaming vblog mstartpage \
    -type f \( -iname '*.jpg' -o -iname '*.jpeg' \
    -o -iname '*.png' -o -iname '*.gif' \) -print0 2>/dev/null
)

echo "total: $(fmt "$total_before") -> $(fmt "$total_after")"