Skip to content

Instantly share code, notes, and snippets.

@wittman
Last active May 9, 2023 15:30
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save wittman/ab90f0eca5124233fa5163a32f890220 to your computer and use it in GitHub Desktop.
Save wittman/ab90f0eca5124233fa5163a32f890220 to your computer and use it in GitHub Desktop.
imgopt
#!/usr/bin/env bash
set -e
set -o nounset
set -o errexit
RESTORE=$(echo -en '\001\033[0m\002')
RED=$(echo -en '\001\033[00;31m\002')
GREEN=$(echo -en '\001\033[00;32m\002')
YELLOW=$(echo -en '\001\033[00;33m\002')
timestamp() {
date "+%Y-%m-%d-%H-%M-%S"
}
validParams() {
cat <<-EOF
${YELLOW}--wh 0${RESTORE} # w(idth)h(eight) - max height and width in pixels
${YELLOW}--w 0${RESTORE} # w(idth) max in pixels
${YELLOW}--h 0${RESTORE} # h(ight) max in pixels
${YELLOW}--pngQualityQ 20-50${RESTORE} # PNG Pngquant quality min-max 0to100-0to100
${YELLOW}--pngQualityX 3${RESTORE} # PNG Oxipng optimize factor (1-6)
${YELLOW}--webpQ 60${RESTORE} # webP quality (0to100, default 75)
${YELLOW}--webpAlphaQ 80${RESTORE} # webP alpha channel quality (0to100, default 100)
${RED}Note:${RESTORE} ${YELLOW}Customize quality settings for JPG and GIF files in desktop app ImageOptim Preferences.${RESTORE}
${YELLOW}--name${RESTORE} # Filter: Only select file names containing --name value
${YELLOW}--saveversion Y${RESTORE} # Save optimize run in a timestamped directory 'version' (_imgopt-YYYY-mm-dd-H-M-S)
(--saveversion with any string param value is save version 'On'. Do not include --saveversion param at all for 'Off')
${YELLOW}--heictojpg Y${RESTORE} # Convert .HEIC to .JPG format
(--heictojpg with any string param value is save version 'On'. Do not include --heictojpg param at all for 'Off')
${YELLOW}--pngtojpg Y${RESTORE} # Convert .PNG to .JPG format
(--pngtojpg with any string param value is 'On'. Do not include --pngtojpg param at all for 'Off')
${YELLOW}--jpgtopng Y${RESTORE} # Convert .JPG to .PNG format
(--jpgtopng with any string param value is 'On'. Do not include --jpgtopng param at all for 'Off')
${YELLOW}--towebp Y${RESTORE} # Convert (.PNG, .JPG) to .WebP format
(--towebp with any string param value is 'On'. Do not include --towebp param at all for 'Off')
EOF
}
if [ $# -eq 0 ]; then
cat <<-EOF
${YELLOW}=== Prerequisites ===${RESTORE}
OS X
ImageOptim (desktop app) <https://imageoptim.com/mac>
pngquant <https://github.com/kornelski/pngquant>
brew install pngquant
oxipng <https://github.com/shssoichiro/oxipng>
brew install oxipng
${YELLOW}=== Usage ===${RESTORE}
${YELLOW}imgopt.sh <dir> <opt> <opt> ...${RESTORE}
${YELLOW}<dir>${RESTORE}: directory of images to optimize
${YELLOW}<opt>${RESTORE}: optional parameter
Output: ${YELLOW}<dir>/_imgopt${RESTORE} (subdir '_imgopt' will be created by this script).
Original image files will remain unchanged.
${YELLOW}=== Available parameters (shown with default value [if non-boolean type]) ===${RESTORE}
EOF
validParams
cat <<-EOF
${YELLOW}=== Examples ===${RESTORE}
# Resize to max width: 600px
${YELLOW}./imgopt.sh ~/project/original-images --w 600${RESTORE}
# Processed files will be created in ~/project/original-images/_imgopt subdirectory.
# Resize to max height: 450px
${YELLOW}./imgopt.sh ~/project/original-images --h 450${RESTORE}
# Resize to max width and height: 500px
${YELLOW}./imgopt.sh ~/project/original-images --wh 500${RESTORE}
# Use specific pngQualityQ (min-max default shown)
${YELLOW}./imgopt.sh ~/project/original-images --pngQualityQ 20-50${RESTORE}
# Example pngquant min (20) and max (50) are numbers in range 0 (worst) to 100 (perfect)
# Use specific pngQualityX (default shown)
${YELLOW}./imgopt.sh ~/project/original-images --pngQualityX 3${RESTORE}
# Example oxipng 3 is an integer between 1 and 6 (lower is faster, higher is better compression - higher than 3 is unlikely to give any extra compression gains)
# Use specific webP quality (0..100)
${YELLOW}./imgopt.sh ~/project/original-images --webpQ 60${RESTORE}
# Example --webpQ 60 would run cwebp -q 60
# Use specific webP alpha channel quality (0..100)
${YELLOW}./imgopt.sh ~/project/original-images --webpAlphaQ 80${RESTORE}
# Example --webpAlphaQ 80 would run cwebp -alpha_q 80
# Filter: select only filenames to optimize that contain substring
${YELLOW}./imgopt.sh ~/project/original-images --name background${RESTORE}
# Convert HEIC image format to JPG
${YELLOW}./imgopt.sh ~/project/original-images --heictojpg Y${RESTORE}
# Convert PNG image format to JPG
${YELLOW}./imgopt.sh ~/project/original-images --pngtojpg Y${RESTORE}
# Convert JPG image format to PNG
${YELLOW}./imgopt.sh ~/project/original-images --jpgtopng Y${RESTORE}
# Convert (PNG, JPG) image format to WebP
${YELLOW}./imgopt.sh ~/project/original-images --towebp Y${RESTORE}
EOF
exit 0
fi
param_dir_src=$1
w=${w:-0}
h=${h:-0}
wh=${wh:-0}
pngQualityQ=${pngQualityQ:-20-50}
pngQualityX=${pngQualityX:-3}
name=${name:-}
saveversion=${saveversion:-}
heictojpg=${heictojpg:-}
pngtojpg=${pngtojpg:-}
jpgtopng=${jpgtopng:-}
towebp=${towebp:-}
webpQ=${webpQ:-75}
webpAlphaQ=${webpAlphaQ:-100}
containsElement() {
local e match="$1"
shift
for e; do [[ "$e" == "$match" ]] && return 0; done
return 1
}
okparams=("--w" "--h" "--wh" "--pngQualityQ" "--pngQualityX" "--webpQ" "--webpAlphaQ" "--name" "--saveversion" "--heictojpg" "--pngtojpg" "--jpgtopng" "--towebp")
while [ $# -gt 0 ]; do
if [[ $1 == *"--"* ]]; then
param="${1/--/}"
declare "$param"="$2"
if ! containsElement "--${param}" "${okparams[@]}"; then
echo "Valid Parameters:"
validParams
cat <<-EOF
${RED}'--${param}' is not a valid parameter.${RESTORE}
(${RED}Script Exit [2]${RESTORE})
EOF
exit
fi
fi
shift
done
# MAIN PROCEDURES
echo "Directory parameter value: $param_dir_src"
echo "cd $param_dir_src"
cd "$param_dir_src"
dir_src=$(pwd -P)
dir_out="_imgopt"
if [ -n "$saveversion" ]; then
dir_out="_imgopt-$(timestamp)"
fi
[[ -d "$dir_out" ]] || mkdir "$dir_out"
echo "Output dir: ${YELLOW}$dir_out${RESTORE}"
if [ -n "$name" ]; then
# Only copy/process images with filename containing substring --name param
while IFS= read -r -d '' file; do
if [[ "$file" =~ $name ]]; then
echo "${file} contains $name"
cp "$file" "$dir_out"
fi
done < <(find "$dir_src" -type f -maxdepth 1 -print0 -iname '*.png' -or -iname '*.jpeg' -or -iname '*.jpg' -or -iname '*.gif' -or -iname '*.heic')
else
# Copy/process all images
set +e # disables set -o errexit
cp ./*.{png,jpeg,jpg,gif,heic,HEIC} "$dir_out" 2>/dev/null
set -e # reenables set -o errexit
fi
cd "$dir_out"
if [[ (($w -gt 0)) && ((${h} -gt 0)) ]]; then
cat <<-EOF
(${RED}Params --w and --h cannot be used together. Use --wh with one pixel value to have both height and width have a max size.${RESTORE})
Example: ./imgopt.sh --wh 500
(${RED}Script Exit [3]${RESTORE})
EOF
exit
fi
if [ -n "$heictojpg" ]; then
echo ""
echo "${YELLOW}Convert HEIC files to JPG:${RESTORE}"
# filename=$("$1" | sed 's/.HEIC//')
find "$dir_src/$dir_out" -iname '*.HEIC' -exec sh -c '
filename=${1//".HEIC"/""}
filename=${filename//".heic"/""}
echo "sips --setProperty format jpeg $1 --out ${filename}.jpg"
sips --setProperty format jpeg "$1" --out "${filename}.jpg"
rm "$1"
' sh {} \;
fi
if [ -n "$pngtojpg" ]; then
echo ""
echo "${YELLOW}Convert PNG files to JPG:${RESTORE}"
find "$dir_src/$dir_out" -iname '*.png' -exec sh -c '
filename=${1//".png"/""}
filename=${filename//".png"/""}
echo "sips --setProperty format jpeg $1 --out ${filename}.jpg"
sips --setProperty format jpeg "$1" --out "${filename}.jpg"
rm "$1"
' sh {} \;
fi
if [ -n "$jpgtopng" ]; then
echo ""
echo "${YELLOW}Convert JPG files to PNG:${RESTORE}"
find "$dir_src/$dir_out" -iname '*.jpg' -exec sh -c '
filename=${1//".jpg"/""}
filename=${filename//".jpg"/""}
echo "sips --setProperty format png $1 --out ${filename}.png"
sips --setProperty format png "$1" --out "${filename}.png"
rm "$1"
' sh {} \;
find "$dir_src/$dir_out" -iname '*.jpeg' -exec sh -c '
filename=${1//".jpeg"/""}
filename=${filename//".jpeg"/""}
echo "sips --setProperty format png $1 --out ${filename}.png"
sips --setProperty format png "$1" --out "${filename}.png"
rm "$1"
' sh {} \;
fi
if [ -n "$towebp" ]; then
echo ""
echo "${YELLOW}Convert image files to WebP:${RESTORE}"
find "$dir_src/$dir_out" -iname '*.jpg' -exec sh -c '
filename=${1//".jpg"/""}
filename=${filename//".jpg"/""}
echo "cwebp $1 -q $2 -alpha_q $3 -o $filename.webp"
cwebp "$1" -q $2 -alpha_q $3 -o "${filename}.webp"
rm "$1"
' sh {} $webpQ $webpAlphaQ \;
find "$dir_src/$dir_out" -iname '*.jpeg' -exec sh -c '
filename=${1//".jpeg"/""}
filename=${filename//".jpeg"/""}
echo "cwebp $1 -q $2 -alpha_q $3 -alpha_q $3 -o $filename.webp"
cwebp "$1" -q $2 -alpha_q $3 -o "${filename}.webp"
rm "$1"
' sh {} $webpQ $webpAlphaQ \;
find "$dir_src/$dir_out" -iname '*.png' -exec sh -c '
filename=${1//".png"/""}
filename=${filename//".png"/""}
echo "cwebp $1 -q $2 -alpha_q $3 -o $filename.webp"
cwebp "$1" -q $2 -alpha_q $3 -o "${filename}.webp"
rm "$1"
' sh {} $webpQ $webpAlphaQ \;
fi
if [[ $wh -gt 0 ]]; then
cat <<-EOF
${YELLOW}Resize Files:${RESTORE}
${YELLOW}sips -Z ${wh} ./*${RESTORE}
EOF
sips -Z "${wh}" ./*
elif [[ $w -gt 0 ]]; then
cat <<-EOF
${YELLOW}Resize Files:${RESTORE}
${YELLOW}sips --resampleWidth ${w} ./*${RESTORE}
EOF
sips --resampleWidth "${w}" ./*
elif [[ $h -gt 0 ]]; then
cat <<-EOF
${YELLOW}Resize Files:${RESTORE}
${YELLOW}sips --resampleHeight ${h} ./*${RESTORE}
EOF
sips --resampleHeight "${h}" ./*
else
echo ""
echo "${GREEN}No size change.${RESTORE}"
fi
echo ""
echo "${YELLOW}Process with ImageOptim: JPEGs${RESTORE}"
find "$dir_src/$dir_out" -name '*.jpeg' -exec sh -c '
echo "${1}"
open -a ImageOptim.app "$1"
' sh {} \;
find "$dir_src/$dir_out" -name '*.jpg' -exec sh -c '
echo "${1}"
open -a ImageOptim.app "$1"
' sh {} \;
echo ""
echo "${YELLOW}Process with ImageOptim: GIFs${RESTORE}"
find "$dir_src/$dir_out" -name '*.gif' -exec sh -c '
echo "${1}"
open -a ImageOptim.app "$1"
' sh {} \;
echo ""
echo "${YELLOW}Process with pngquant & oxipng: PNGs${RESTORE}"
count=0
while IFS= read -r -d '' file; do
((count++))
echo "${file}"
pngquant --ext=.png --force --quality "$pngQualityQ" "$file"
oxipng -o "$pngQualityX" -i 0 --strip safe "$file"
done < <(find "$dir_src/$dir_out" -name '*.png' -print0)
echo "Processed PNG Count: (${count})"
# Open output dir in Finder
open .
# Return source dir
cd ..
echo "${GREEN}Done.${RESTORE}"
@wittman
Copy link
Author

wittman commented Mar 31, 2022

Add feature: --name (optional parameter):
A filter to select only filenames to optimize that contain substring.

@wittman
Copy link
Author

wittman commented Apr 7, 2022

Add feature: --saveversion Y (optional parameter):
Save optimize run in a timestamped directory 'version' (_imgopt-YYYY-mm-dd-H-M-S)

@wittman
Copy link
Author

wittman commented Jun 25, 2022

Add feature: ----heictojpg Y (optional parameter):
Convert HEIC images to JPEG

@wittman
Copy link
Author

wittman commented Jan 24, 2023

Add feature: ----pngtojpg Y (optional parameter):
Convert PNG images to JPEG

@wittman
Copy link
Author

wittman commented Jan 31, 2023

Add feature: ----jpgtopng Y (optional parameter):
Convert JPEG images to PNG

@wittman
Copy link
Author

wittman commented May 5, 2023

Add feature: --towebp Y (optional parameter):
Convert (JPEG, PNG) images to webP

Related Optional parameter: Quality
--webpQ 90

Related Optional parameter: Alpha Channel Quality
--webpAlphaQ 65

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment