Скорость загрузки любого сайта во многом зависит от количества и качества используемых изображений. Поэтому очень важно уметь их оптимизировать. Существует множество веб сервисов для этого, но большинство из них обладает недостатками:
- Нет возможности оптимизировать автоматически много файлов
- Сложно и неудобно использовать в рабочем процессе
Но прежде всего следует отметить, что описанный ниже способ нельзя причислить к самым лучшим хотя бы потому, что в идеале каждое изображение следует оптимизировать индивидуально.
Оптимизация изображений с помощью командой строки
Для каждого png файла используются optipng и pngcrush, а для jpg — jpegtran. Для начала опробуем optipng:
Примечание: С параметр -o7 optipng работает в самом медленном режиме. Для быстрого используется -o0.
Затем pngcrush:
Оптимизация JPG с помощью jpegtran:
Написание скрипта
Готовый скрипт можно посмотреть на GitHub'е. Ниже подробно представлен процесс написания.
Прежде всего необходимо задать основные параметры:
- -i или --input для исходной папки
- -o или --output для папки с результатом
- -q или --quiet для отключения вывода процесса выполнения
- -s или --no-stats для отключения вывода статистики
- -h или --help для вызова справки
Две переменные для коротких и полных имен параметров:
SHORTOPTS="h,i:,o:,q,s"
LONGOPTS="help,input:,output:,quiet,no-stats"
Используем getopt для передаваемых в скрипт параметров, цикл для вызова функция или определения переменных для хранения:
ARGS=$(getopt -s bash --options $SHORTOPTS --longoptions $LONGOPTS --name $PROGNAME -- "$@")
eval set -- "$ARGS"
while true; do
case $1 in
-h|--help)
usage
exit 0
;;
-i|--input)
shift
INPUT=$1
;;
-o|--output)
shift
OUTPUT=$1
;;
-q|--quiet)
QUIET='1'
;;
-s|--no-stats)
NOSTATS='1'
;;
--)
shift
break
;;
*)
shift
break
;;
esac
shift
done
HELP
Создаем две функции:
- usage(), в цикле, для вызова справки
- main() для оптимизации изображений
Они должны быть объявлены до цикла.
PROGNAME=${0##*/}
usage()
{
cat <<EO
Usage: $PROGNAME [options]
Script to optimize JPG and PNG images in a directory.
Options:
EO
cat <<EO | column -s& -t
-h, --help & shows this help
-q, --quiet & disables output
-i, --input [dir] & specify input directory (current directory by default)
-o, --output [dir] & specify output directory ("output" by default)
-ns, --no-stats & no stats at the end
EO
}
SHORTOPTS="h,i:,o:,q,s"
LONGOPTS="help,input:,output:,quiet,no-stats"
ARGS=$(getopt -s bash --options $SHORTOPTS --longoptions $LONGOPTS --name $PROGNAME -- "$@")
Проверим, что получилось.
Примечание: если возникают ошибки, вроде "./optimize.sh: line 2: $'r': command not found", то необходимо открыть скрипт в Sublime Text 2 и включить Unix Mode в View > Line endings > Unix.
Главная функция (main)
main()
{
# If $INPUT is empty, then we use current directory
if [[ "$INPUT" == "" ]]; then
INPUT=$(pwd)
fi
# If $OUTPUT is empty, then we use the directory "output" in the current directory
if [[ "$OUTPUT" == "" ]]; then
OUTPUT=$(pwd)/output
fi
# We create the output directory
mkdir -p $OUTPUT
# To avoid some troubles with filename with spaces, we store the current IFS (Internal File Separator)...
SAVEIFS=$IFS
# ...and we set a new one
IFS=$(echo -en "nb")
max_filelength=`get_max_file_length`
pad=$(printf '%0.1s' "."{1..600})
sDone=' [ DONE ]'
linelength=$(expr $max_filelength + ${#sDone} + 5)
# Search of all jpg/jpeg/png in $INPUT
# We remove images from $OUTPUT if $OUTPUT is a subdirectory of $INPUT
IMAGES=$(find $INPUT -regextype posix-extended -regex '.*.(jpg|jpeg|png)' | grep -v $OUTPUT)
if [ "$QUIET" == "0" ]; then
echo --- Optimizing $INPUT ---
echo
fi
for CURRENT_IMAGE in $IMAGES; do
filename=$(basename $CURRENT_IMAGE)
if [ "$QUIET" == "0" ]; then
printf '%s ' "$filename"
printf '%*.*s' 0 $((linelength - ${#filename} - ${#sDone} )) "$pad"
fi
optimize_image $CURRENT_IMAGE $OUTPUT/$filename
if [ "$QUIET" == "0" ]; then
printf '%sn' "$sDone"
fi
done
# we restore the saved IFS
IFS=$SAVEIFS
if [ "$NOSTATS" == "0" -a "$QUIET" == "0" ]; then
echo
echo "Input: " $(human_readable_filesize $max_input_size)
echo "Output: " $(human_readable_filesize $max_output_size)
space_saved=$(expr $max_input_size - $max_output_size)
echo "Space save: " $(human_readable_filesize $space_saved)
fi
}
Необходимо дать возможность задать директории, либо выполнять скрипт в текущей, используя команду mkdir. Далее необходимо заставить скрипт корректно работать с файлами, в названиях которых есть пробелы. Для этого используем IFS (Internal File Separator). Функция optimize_image, оптимизирующая изображения, имеет два параметра — для исходной и финальной директорий.
optimize_image:
# $1: input image
# $2: output image
optimize_image()
{
input_file_size=$(stat -c%s "$1")
max_input_size=$(expr $max_input_size + $input_file_size)
if [ "${1##*.}" = "png" ]; then
optipng -o1 -clobber -quiet $1 -out $2
pngcrush -q -rem alla -reduce $1 $2 >/dev/null
fi
if [ "${1##*.}" = "jpg" -o "${1##*.}" = "jpeg" ]; then
jpegtran -copy none -progressive $1 > $2
fi
output_file_size=$(stat -c%s "$2")
max_output_size=$(expr $max_output_size + $output_file_size)
}
Выходная информация
Результат выполнения скрипта должен наглядно отображаться, например так:
file1 ...................... [ DONE ]
file2 ...................... [ DONE ]
file_with_a_long_name ...... [ DONE ]
...
Сначала необходимо сделать следующие шаги:
- Определить длины названий файлов
- Заменить промежутки точками
- Задать максимальную длина названия и текста " [ DONE ]"
В итоге строки должны содержать название файла, точки и DONE и должны быть одинаковой длины.
max_filelength=`get_max_file_length`
pad=$(printf '%0.1s' "."{1..600})
sDone=' [ DONE ]'
linelength=$(expr $max_filelength + ${#sDone} + 5)
# Search of all jpg/jpeg/png in $INPUT
# We remove images from $OUTPUT if $OUTPUT is a subdirectory of $INPUT
IMAGES=$(find $INPUT -regextype posix-extended -regex '.*.(jpg|jpeg|png)' | grep -v $OUTPUT)
if [ "$QUIET" == "0" ]; then
echo --- Optimizing $INPUT ---
echo
fi
for CURRENT_IMAGE in $IMAGES; do
filename=$(basename $CURRENT_IMAGE)
if [ "$QUIET" == "0" ]; then
printf '%s ' "$filename"
printf '%*.*s' 0 $((linelength - ${#filename} - ${#sDone} )) "$pad"
fi
optimize_image $CURRENT_IMAGE $OUTPUT/$filename
if [ "$QUIET" == "0" ]; then
printf '%sn' "$sDone"
fi
done
Проверим скрипт, запустив с параметрами:
# All parameters to default
./optimize.sh
# Or with custom options
./optimize.sh --input images --output optimized-images
# Or with custom options and shorthand
./optimize.sh -i images -o optimized-images
Статистика
Для отображения статистики работы скрипта используем input_file_size и output_file_size, которые возвращают исходный и конечный размер изображения. Для удобства чтения информации используем human_readable_filesize().
Запускаем скрипт еще раз и видим статистику:
Осталось только отображать процесс выполнения оптимизации:
if [ "$QUIET" == "0" ]; then
echo --- Optimizing $INPUT ---
echo
fi
for CURRENT_IMAGE in $IMAGES; do
filename=$(basename $CURRENT_IMAGE)
if [ "$QUIET" == "0" ]; then
printf '%s ' "$filename"
printf '%*.*s' 0 $((linelength - ${#filename} - ${#sDone} )) "$pad"
fi
optimize_image $CURRENT_IMAGE $OUTPUT/$filename
if [ "$QUIET" == "0" ]; then
printf '%sn' "$sDone"
fi
done
Все! В результате получился скрипт, который умеет автоматически оптимизировать изображения. Скачать на GitHub'е.
Автор: grokru