Bild-Optimierung für Webseiten

Um die Ladezeit für Websites zu verkürzen, lohnt es sich über Optimierung der eingesetzten Bilder nachzudenken. Es gibt sehr viele Werkzeuge für die Optimierung der Bilder direkt auf dem Linux-Server.

JPEG Optimierung mit Google Guetzli

Google Guetzli ist ein JPEG-Encoder, der darauf ausgelegt ist, Bilder mit hoher visueller Qualität und gleichzeitig geringerer Dateigröße zu erzeugen.

Installation unter Ubuntu

sudo apt update && sudo apt install guetzli -y

Script zur Nutzung

Folgendes Script durchsucht ein Verzeichnis rekursiv nach JPEG Bildern und optimiert diese.

#!/bin/bash
set -euo pipefail

if [ $# -eq 0 ]; then
  echo "Error: no directory given"
  exit 1
fi

DIR=$1
if [ ! -d "$DIR" ]; then
  echo "Error: directory '$DIR' does not exist"
  exit 2
fi

REALDIR=$(realpath "$DIR")
echo "Optimizing directory: $REALDIR"

optimize_with_guetzli() {
  local input="$1"
  local output="$2"
  if guetzli --quality 95 --nomemlimit "$input" "$output" >/dev/null 2>&1; then
    mv "$output" "$input"
    return 0
  else
    rm -f "$output"
    return 1
  fi
}

export -f optimize_with_guetzli

find "$REALDIR" -type f -name "*.jpg" -print0 | while IFS= read -r -d '' IMAGE; do
  echo "Optimizing: $IMAGE"
  echo "Before: $(du -h "$IMAGE" | awk '{print $1}')"

  if optimize_with_guetzli "$IMAGE" "$IMAGE.new"; then
    echo " ... Success (Guetzli)"
  else
    echo " ... Guetzli failed, retrying with ImageMagick"
    TMPPNG="$IMAGE.png"
    if convert "$IMAGE" "$TMPPNG"; then
      if optimize_with_guetzli "$TMPPNG" "$IMAGE.new"; then
        rm "$TMPPNG"
        echo " ... Success (ImageMagick + Guetzli)"
      else
        echo " ... Error: Guetzli failed again on $IMAGE"
        rm -f "$TMPPNG"
      fi
    else
      echo " ... Error: ImageMagick conversion failed for $IMAGE"
    fi
  fi

  echo "After: $(du -h "$IMAGE" | awk '{print $1}')"
done

PNG Optimierung mit OptiPNG

Installation unter Ubuntu

sudo apt update && sudo apt install optipng -y

Script zur Nutzung

Das Script durchsucht einen angegebenen Pfad rekursiv und optimiert alle gefundenen PNG-Bilder bis keine Optimierung mehr möglich ist.

#!/bin/bash
# png-optimize-lossless.sh
# Optimiert PNGs rekursiv nur mit optipng (verlustfrei), bis keine Reduktion mehr möglich ist.
# Nutzung:  ./png-optimize-lossless.sh /pfad/zum/verzeichnis
#
# Optional:
#   BACKUP_ORIGINALS=1   -> behält .bak-Kopien der Originale
#   STRIP_META=1         -> entfernt Metadaten (empfohlen)

set -u -o pipefail

TARGET_DIR="${1:-}"
if [[ -z "$TARGET_DIR" || ! -d "$TARGET_DIR" ]]; then
  echo "Fehler: Bitte existierenden Zielpfad angeben."
  echo "Nutzung: $0 /pfad/zum/verzeichnis"
  exit 1
fi

BACKUP_ORIGINALS="${BACKUP_ORIGINALS:-0}"
STRIP_META="${STRIP_META:-1}"

total_bytes() {
  find "$TARGET_DIR" -type f -iname '*.png' -print0 \
    | xargs -0 stat -c '%s' 2>/dev/null | awk '{s+=$1} END{print (s==""?0:s)}'
}

human() {
  local b=${1:-0}
  awk -v b="$b" 'function h(x){s="B KB MB GB";split(s,a," ");i=1;while(x>=1024&&i<length(a)){x/=1024;i++}return sprintf("%.2f %s",x,a[i])}BEGIN{print h(b)}'
}

make_tmp() { mktemp "${TMPDIR:-/tmp}/optipng.XXXXXXXX.png"; }

OPTIPNG_ARGS=(-o7 -quiet)
[[ "$STRIP_META" == "1" ]] && OPTIPNG_ARGS+=(-strip all)

# --- Main loop ---
pass=0
while :; do
  pass=$((pass+1))
  echo "Durchlauf #$pass …"
  changes=0
  before_total=$(total_bytes)

  while IFS= read -r -d '' file; do
    [[ -w "$file" ]] || { echo "  ⏭️  keine Schreibrechte: $file"; continue; }

    orig_size=$(stat -c '%s' "$file" 2>/dev/null || echo 0)
    tmp="$(make_tmp)"

    if optipng "${OPTIPNG_ARGS[@]}" -out "$tmp" -- "$file" >/dev/null 2>&1; then
      new_size=$(stat -c '%s' "$tmp" 2>/dev/null || echo 999999999)
      if [[ "$new_size" -lt "$orig_size" ]]; then
        [[ "$BACKUP_ORIGINALS" == "1" ]] && cp -p -- "$file" "$file.bak"
        if mv -f -- "$tmp" "$file"; then
          delta=$((orig_size - new_size))
          echo "  ✅ ${file}: -$(human "$delta") (neu: $(human "$new_size"))"
          changes=$((changes+1))
        else
          echo "  ⚠️  Konnte Datei nicht ersetzen: $file"
          rm -f "$tmp"
        fi
      else
        rm -f "$tmp"
      fi
    else
      rm -f "$tmp"
    fi
  done < <(find "$TARGET_DIR" -type f -iname '*.png' -print0)

  after_total=$(total_bytes)
  if [[ "$changes" -eq 0 || "$after_total" -ge "$before_total" ]]; then
    saved=$((before_total - after_total))
    echo "Fertig nach $pass Durchlauf/Durchläufen."
    echo "Gesamtersparnis: $(human "$saved") (neu: $(human "$after_total"))."
    break
  else
    echo "Durchlauf #$pass abgeschlossen: $(human $((before_total - after_total))) eingespart."
  fi
done