Last active
May 13, 2022 00:16
-
-
Save caiguanhao/4528926 to your computer and use it in GitHub Desktop.
Recursively crush/shrink/optimize/losslessly compress PNGs, JPEGs and GIFs.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/bash | |
# This is an improved script of pngfix.sh (https://gist.github.com/404909) | |
# which can also crush/shrink/optimize/losslessly compress JPEGs and GIFs. | |
# It is recommended you backup your image files before executing this script. | |
# Operation will be skipped if output file is bigger in size. | |
# | |
# use chmod +x crushimg.sh to make it executable and then ./crushimg.sh to run, or | |
# bash ./crushimg.sh to run it directly | |
# | |
# ./crushimg.sh [FILE] - (default to current directory) | |
# ./crushimg.sh example/ ./crushimg.sh example/*.jpg | |
# | |
# Install these software first: | |
# Arch Linux: pacman -S bc optipng libjpeg gifsicle | |
# and pngcrush: http://sourceforge.net/projects/pmt/files/pngcrush/ | |
# Debian/Ubuntu: sudo apt-get install pngcrush optipng libjpeg-progs gifsicle | |
# Fedora: sudo yum -y install pngcrush optipng libjpeg-turbo-utils gifsicle | |
# FreeBSD: pkg_add -r pngcrush optipng gifsicle | |
# Mac OS X: brew install pngcrush optipng jpeg | |
# and gifsicle: http://dept.psych.columbia.edu/~peter/gifsicle/gifsicle-1.64.dmg | |
# exit if software not found | |
set -e | |
output() { # text_left, text_right, text_right_color | |
NORMAL=$(tput sgr0) | |
case $3 in | |
0) COLOR=$(tput setaf 7);; #grey | |
1) COLOR=$(tput setaf 2);; #green | |
2) COLOR=$(tput setaf 1);; #red | |
*) COLOR=${NORMAL} | |
esac | |
MSG="$1" | |
let COL=$(tput cols)-${#MSG}+${#COLOR}+${#NORMAL} | |
printf "%s%${COL}s\n" "$MSG" "$COLOR[$2]$NORMAL" | |
} | |
if [[ $(date +%N) == "N" ]]; then | |
TIME_CMD="+%s" | |
else | |
TIME_CMD="+%s.%N" | |
fi | |
START=$(date $TIME_CMD) | |
CRUSH=$(which pngcrush) | |
OPTS1="-rem cHRM -rem gAMA -rem iCCP -rem sRGB -q" | |
OPTI=$(which optipng) | |
OPTS2="-quiet -fix -o4" | |
TRAN=$(which jpegtran) | |
OPTS3="-copy none -optimize -progressive -perfect" | |
GIFS=$(which gifsicle) | |
OPTS4="-O3 --careful -w" | |
BC=$(which bc) | |
TEMP="/tmp/tempimgfile" | |
# pngcrush may still output warnings/errors even with -q argument | |
# for example: Warning: versions are different between png.h and png.c | |
# therefore, restore default and then redirect all pngcrush output to /dev/null | |
set +e | |
# check jpegtran's -perfect option | |
if test "${OPTS3/-perfect/}" != "$OPTS3" | |
then | |
TRANHELP=$($TRAN -h >/dev/stdout 2>/dev/stdout) | |
if test "${TRANHELP/-perfect/}" == "$TRANHELP" | |
then | |
echo "Notice: jpegtran's -perfect option does not exist." | |
OPTS3=${OPTS3/-perfect/} | |
fi | |
fi | |
stat -f%z . >/dev/null 2>/dev/null | |
if [[ $? -eq 0 ]]; then | |
STAT_CMD="stat -f%z" | |
else | |
STAT_CMD="stat -c%s" | |
fi | |
BEFORE=0 | |
AFTER=0 | |
MAXDIFF=0 | |
FILES=("$@") | |
if [[ $# -lt 2 ]] && [ -d $FILES ]; then | |
if [[ $# -eq 0 ]]; then FILES="."; fi | |
OLDIFS=$IFS | |
IFS=$'\n' | |
FILES=( | |
$(find $FILES -iregex '.*\.jpg$' -type f) | |
$(find $FILES -iregex '.*\.jpeg$' -type f) | |
$(find $FILES -iregex '.*\.png$' -type f) | |
$(find $FILES -iregex '.*\.gif$' -type f) | |
) | |
IFS=$OLDIFS | |
fi | |
LEN=${#FILES[@]} | |
if [[ $LEN == 0 ]]; then | |
echo "Nothing to do." | |
exit | |
fi | |
for (( i=0; i<${LEN}; i++ )); do | |
FILESIZE=$($STAT_CMD "${FILES[$i]}") | |
BEFORE=$(( $BEFORE + $FILESIZE )) | |
done | |
echo "Found ${LEN} files. Total size is" $(printf "%'d" $BEFORE) "bytes." | |
printf "%*s\n" "${COLUMNS:-$(tput cols)}" "" | tr " " - | |
for (( i=0; i<${LEN}; i++ )); do | |
FILEBEFORE=$($STAT_CMD "${FILES[$i]}") | |
FN=$(echo "${FILES[$i]}" | sed -e "s/^\.\///") | |
if [[ $FILEBEFORE -eq 0 ]]; then | |
output "$FN" "EMPTY INPUT" 2 | |
continue | |
else | |
output "$FN" "PROCESSING..." 0 | |
fi | |
if [[ ${FILES[$i]: -4} == ".png" ]]; then | |
$CRUSH $OPTS1 -- "${FILES[$i]}" $TEMP >/dev/null 2>/dev/null | |
$OPTI $OPTS2 -- $TEMP | |
elif [[ ${FILES[$i]: -4} == ".gif" ]]; then | |
$GIFS < "${FILES[$i]}" $OPTS4 > $TEMP | |
else | |
$TRAN $OPTS3 "${FILES[$i]}" > $TEMP | |
fi | |
FILESIZE=$($STAT_CMD "$TEMP") | |
DIFF=$(( $FILEBEFORE - $FILESIZE )) | |
if [[ $DIFF -gt 0 ]]; then | |
AFTER=$(( $AFTER + $FILESIZE )) | |
else | |
AFTER=$(( $AFTER + $FILEBEFORE )) | |
fi | |
PERCENT=$(echo "scale=2; ${DIFF} * 100 / ${FILEBEFORE}" | $BC) | |
DIFF_FORMAT=$(printf "%'d" $DIFF) | |
printf "\e[1A" | |
if [[ $FILESIZE -eq 0 ]]; then | |
output "$FN" "EMPTY OUTPUT, SKIPPED" 2 | |
AFTER=$(( $AFTER + $FILEBEFORE )) | |
else | |
if [[ $DIFF -gt 0 ]]; then | |
cp $TEMP "${FILES[$i]}" | |
output "$FN" "${PERCENT}% (${DIFF_FORMAT}B) OK" 1 | |
elif [[ $DIFF -lt 0 ]]; then | |
output "$FN" "SKIPPED" 2 | |
else | |
output "$FN" "SKIPPED" 0 | |
fi | |
fi | |
done | |
rm -f $TEMP | |
END=$(date $TIME_CMD) | |
DIFFSIZE=$(( $BEFORE - $AFTER )) | |
printf "%*s\n" "${COLUMNS:-$(tput cols)}" "" | tr " " - | |
echo "Total size now is" $(printf "%'d" $AFTER) "bytes." | |
if [[ $BEFORE -eq 0 || $DIFFSIZE -eq 0 ]]; then | |
echo "No reduction." | |
else | |
echo "Reduction:" $(printf "%'d" $DIFFSIZE) "bytes ("$(echo "scale=2; $DIFFSIZE * 100 / $BEFORE" | $BC)"%)." | |
fi | |
echo "Time used:" $(echo "$END - $START" | $BC) "seconds." | |
# pngcrush : http://pwet.fr/man/linux/commandes/pngcrush | |
# Remove all the color-correction data (gamma, white balance, ICC color profile, | |
# standard RGB color profile) | |
# -q : quiet, the opposite of verbose. | |
# | |
# OptiPNG : http://linux.die.net/man/1/optipng | |
# -quiet : Run in quiet mode | |
# -fix : Enable error recovery. This option has no effect on valid input files. | |
# -o level : The optimization levels 2 and higher enable multiple IDAT | |
# compression trials; the higher the level, the more trials. | |
# | |
# jpegtran : http://www.gsp.com/cgi-bin/man.cgi?section=1&topic=jpegtran | |
# -copy none : Copy no extra markers from source file. This setting suppresses | |
# all comments and other excess baggage present in the source file. | |
# -optimize : Perform optimization of entropy encoding parameters. | |
# -progressive : Create progressive JPEG file. | |
# -perfect : Fails with an error if the transformation is not perfect. | |
# | |
# Gifsicle : http://www.lcdf.org/gifsicle/man.html | |
# -O1 : Stores only the changed portion of each image. This is the default. | |
# -O2 : Also uses transparency to shrink the file further. | |
# -O3 : Try several optimization methods (usually slower, sometimes better results). | |
# --careful : Write slightly larger GIFs that avoid bugs in some other GIF | |
# implementations. Some Java and Internet Explorer versions cannot display | |
# the correct, minimal GIFs that Gifsicle produces. | |
# -w : Suppress all warning messages. |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment