#!/usr/bin/env bash # ash-wallpaper.sh — turn a color photo into Ash-palette wallpapers: # grayscale body tinted toward the Ash tones, with warm "ember" highlights # (reds/oranges/golds) selectively preserved. Pleasantville-style. # # usage: tools/ash-wallpaper.sh INPUT [OUTBASE] # # By default BOTH variants are rendered: # OUTBASE-dark.png (void -> chalk ramp) # OUTBASE-light.png (stone -> paper ramp) # Set DARK=1 to render only dark, LIGHT=1 to render only light. # OUTBASE defaults to INPUT-without-extension + "-ash". # # tuning via environment (defaults shown): # HUE_MAX=13% warm band: hue <= HUE_MAX # HUE_MIN=94% ... or hue >= HUE_MIN (catches pure reds) # SAT_MIN=12% below this saturation pixels lose the highlight # SAT_MAX=45% at/above this saturation the highlight is full # EMBER_BOOST=130 saturation multiplier (%) applied to highlight areas # SENSITIVITY=8x45% sigmoidal contrast of the final mask # BASE_LO= BASE_HI= override the grey ramp endpoints (applies to both # rendered variants unless only one is generated) set -euo pipefail IN=${1:?usage: ash-wallpaper.sh INPUT [OUTBASE]} OUTBASE=${2:-${IN%.*}-ash} HUE_MAX=${HUE_MAX:-13%} HUE_MIN=${HUE_MIN:-94%} SAT_MIN=${SAT_MIN:-12%} SAT_MAX=${SAT_MAX:-45%} EMBER_BOOST=${EMBER_BOOST:-130} SENSITIVITY=${SENSITIVITY:-8x45%} if command -v magick >/dev/null; then IM=magick; else IM=convert; fi if [[ ${DARK:-0} == 1 ]]; then VARIANTS=(dark) elif [[ ${LIGHT:-0} == 1 ]]; then VARIANTS=(light) else VARIANTS=(dark light); fi TMP=$(mktemp -d) trap 'rm -rf "$TMP"' EXIT # warmth mask: (hue in warm band) x (saturation ramp) "$IM" "$IN" -colorspace HSL -channel R -separate "$TMP/hue.png" "$IM" "$IN" -colorspace HSL -channel G -separate "$TMP/sat.png" "$IM" "$TMP/hue.png" -threshold "$HUE_MAX" -negate "$TMP/low.png" "$IM" "$TMP/hue.png" -threshold "$HUE_MIN" "$TMP/high.png" "$IM" "$TMP/low.png" "$TMP/high.png" -compose Lighten -composite "$TMP/band.png" "$IM" "$TMP/sat.png" -level "$SAT_MIN","$SAT_MAX" "$TMP/satmask.png" "$IM" "$TMP/band.png" "$TMP/satmask.png" -compose Multiply -composite \ -gaussian-blur 0x2 -sigmoidal-contrast "$SENSITIVITY" "$TMP/mask.png" for preset in "${VARIANTS[@]}"; do case "$preset" in dark) lo=${BASE_LO:-#100f12}; hi=${BASE_HI:-#f3ede1} ;; light) lo=${BASE_LO:-#b7ae98}; hi=${BASE_HI:-#f2ecdf} ;; esac # ash base + ember-touched highlights, joined by the mask "$IM" "$IN" \ \( -clone 0 -modulate 100,0 +level-colors "$lo","$hi" \) \ \( -clone 0 -modulate "100,$EMBER_BOOST" \) \ -delete 0 \ \( +clone "$TMP/mask.png" -alpha off -compose CopyOpacity -composite \) \ -delete 1 -compose Over -composite \ -colorspace sRGB "$OUTBASE-$preset.png" echo "wrote $OUTBASE-$preset.png" done