Размер изображений составляет значительную часть от размера всей страницы. Поэтому важно сжимать изображения и использовать правильный формат.
Сжатие PNG/JPG
Я подготовил специальный sh-скрипт, который автоматизирует процесс сжатия.
#!/bin/bash
file=comp.flag
if [ -f "$file" ]; then
option="-newer $file"
fi
find ./images/ -type f -not -path "./images/comp/*" ! -name "*-no-comp.*" $option -iname "*.png" -exec sh -c '
png_file="${1/\/images\//\/images\/comp\/}"
png_dir="$(dirname "$png_file")"
mkdir -p "$png_dir"
cp "$1" "${png_file}"
optipng -o7 "${png_file}"
advpng -z4 "${png_file}"
pngcrush -rem gAMA -rem alla -rem cHRM -rem iCCP -rem sRGB -rem time -ow "${png_file}"
' _ {} \;
find ./images/ -type f-not -path "./images/comp/*" ! -name "*-no-comp.*" $option -iregex '.*\.\(jpg\|jpeg\)' -exec sh -c '
jpg_file="${1/\/images\//\/images\/comp\/}"
jpg_dir="$(dirname "$jpg_file")"
mkdir -p "$jpg_dir"
cp "$1" "${jpg_file}"
jpegoptim --all-progressive "${jpg_file}"
' _ {} \;
touch $file
echo "$(date)" > $file
Этот скрипт сжимает изображения без потери качества. Он размещается рядом с каталогом images
, в котором находятся ваши изображения. Разберемся, как он работает.
Сначала скрипт проверяет, существует ли в текущем каталоге файл comp.flag
. Если файл существует, он устанавливает значение -newer $file
в переменную option
, которая будет использоваться в качестве фильтра для поиска только тех файлов, которые были изменены после даты создания файла optimg.flag
. Если файл не существует, переменная option
будет пустой.
Затем скрипт использует команду find
для рекурсивного поиска файлов в каталоге images
и его подкаталогах, которые:
-type f
. Являются обычными файлами.- Имеют расширение имени файла
.png
,.jpeg
или.jpg
. -not -path
. Не находятся в указанных каталогах. В данном случае это каталоги./images/comp
. Мы будем складывать туда сжатые изображения, и мы не хотим заново по ним проходить поиском и сжимать снова.! -name "-no-comp."
. Оставим возможность не сжимать изображение, если его имя заканчивается на-no-comp
.- Для каждого из этих файлов сценарий использует свою команду оптимизации.
Мы не будем затирать оригиналы изображений. Вместо этого мы создадим дополнительную папку comp
в каталоге images
, в которую и сложим преобразованные изображения. Создаваемая структура подкаталогов в comp
будет повторять структуру подкатологов в images
.
Для файлов PNG сначала используется optipng
для сжатия с самым высоким уровнем оптимизации (-o7
). Далее используем advpng
для дальнейшего сжатия с уровнем сжатия 4 (-z4
). И наконец pngcrush
для удаления из файла определенных фрагментов, которые можно безопасно удалить для уменьшения размера файла.
Для файлов JPEG используется jpegoptim
для их оптимизации со сжатием без потерь (--all-progressive --strip-all
).
После оптимизации всех подходящих изображений сценарий пересоздает файл comp.flag
. Это гарантирует, что скрипт будет оптимизировать только те файлы, которые были изменены с момента последнего запуска.
Тесты на сжатия
Возьмем два одинаковых изображения (3456 x 2234): одно форматом jpg и размером 2.2 мб, второе форматом png и размером 2,7 мб. Прикрепляю архив с тестовыми данными, чтобы вы могли повторить тесты у себя.
Запускаем скрипт и смотрим на результат сжатия.
optipng
занимает очень много времени, наберитесь терпения.Вывод в консоль
** Processing: ./images/comp/image-two.png
3456x2234 pixels, 4x8 bits/pixel, RGB+alpha
Input IDAT size = 2664231 bytes
Input file size = 2666952 bytes
Trying:
zc = 9 zm = 9 zs = 0 f = 0 IDAT size = 2374591
zc = 9 zm = 8 zs = 0 f = 0 IDAT size = 2368803
zc = 9 zm = 9 zs = 0 f = 1 IDAT size = 2142264
zc = 9 zm = 8 zs = 0 f = 1 IDAT size = 2137390
zc = 9 zm = 9 zs = 1 f = 1 IDAT size = 2134232
zc = 9 zm = 8 zs = 1 f = 1 IDAT size = 2128765
zc = 9 zm = 9 zs = 0 f = 4 IDAT size = 2074935
zc = 9 zm = 8 zs = 0 f = 4 IDAT size = 2071117
zc = 9 zm = 9 zs = 1 f = 4 IDAT size = 2055799
zc = 9 zm = 8 zs = 1 f = 4 IDAT size = 2054390
zc = 9 zm = 9 zs = 0 f = 5 IDAT size = 2046026
zc = 9 zm = 8 zs = 0 f = 5 IDAT size = 2040193
zc = 9 zm = 9 zs = 1 f = 5 IDAT size = 2031130
zc = 9 zm = 8 zs = 1 f = 5 IDAT size = 2024568
Selecting parameters:
zc = 9 zm = 8 zs = 1 f = 5 IDAT size = 2024568
Output IDAT size = 2024568 bytes (639663 bytes decrease)
Output file size = 2025345 bytes (641607 bytes = 24.06% decrease)
2025345 2025345 100% ./images/comp/image-two.png (Bigger 2208631)
2025345 2025345 100%
Warning: versions are different between png.h and png.c
png.h version: 1.6.34
png.c version: 1.6.37
Recompressing IDAT chunks in ./images/comp/image-two.png
Total length of data found in critical chunks = 2024625
Best pngcrush method = 10 (ws 15 fm 6 zl 9 zs 1) = 2055326
CPU time decode 1.074758, encode 16.759768, other 0.018592, total 17.899076 sec
./images/comp/image-one.jpg 3456x2234 24bit N Exif XMP JFIF [OK] 2158567 --> 1947377 bytes (9.78%), optimized.
В результате мы смогли сжать png до 2.1 мб, а jpg до 1.9 мб. При этом сохранив исходный размер изображения и без потерь качества.
Преобразование в WebP
PNG и JPG являются хорошими форматами изображений, которые можно сжать без потери качества. Однако, на сегодняшний день существует более современный формат WebP, который может показать еще более эффективные результаты при сжатии изображений, но с едва заметным ухудшением качества.
В документации WebP перечислено множество параметров, которые повлияют на качество получаемого изображения. Вы можете провести экспериментальный подбор параметров онлайн на сайте https://squoosh.app.
Я вы выбрал для себя следующий набор параметров:
cwebp -mt -af -progress -m 6 -q 80 -pass 10 input.jpg -o output.webp
Это команда преобразует JPG файл input.jpg
в файл изображения WebP с именем output.webp
. Команда включает несколько опций, которые управляют процессом кодирования:
-mt
включает многопоточность, что может улучшить производительность на многоядерных процессорах.-af
включает автоматическую фильтрацию, которая применяет алгоритм фильтрации для повышения эффективности сжатия.-progress
выводит показывает процент обработки файла.-m 6
устанавливает максимальное количество сегментов, используемых в процессе кодирования, равным 6, что может повысить эффективность сжатия за счет увеличения времени кодирования.-q 80
устанавливает коэффициент качества на80
, который контролирует степень сжатия и влияет на визуальное качество выходного изображения.-pass 10
устанавливает число проходов кодирования равным 10, что может повысить эффективность сжатия за счет увеличения времени кодирования.
-lossless
позволяет использовать сжатие без потерь. Но тогда ваше новое изображение может оказаться существенно тяжелее исходного.Добавим в наш скрипт преобразование в WebP.
find ./images/comp -type f -iregex '.*\.\(jpg\|jpeg\|png\)' -not -iregex '.*no-comp\.\(jpg\|jpeg\|png\)' $option -exec sh -c '
webp_file="${1/\/images\/comp\//\/images\/webp\/}"
webp_dir="$(dirname "$webp_file")"
mkdir -p "$webp_dir"
cwebp -mt -af -progress -m 6 -q 75 -pass 10 "$1" -o "${webp_file%.*}.webp"
' _ {} \;
Мы берем сжатые изображения из папки comp
и преобразуем их в WebP, складывая в отдельную папку webp
. Если вы захотите использовать другие параметры сжатия, вы всегда сможете пересоздать изображения с новыми параметрами.
Тесты преобразования
Продолжим наши эксперементы со сжатием. Теперь сожмем наши файл размером в 2,7 мб и 2.2 в формат WebP с разными параметрами качества:
- -q 90: Размер 325 кб.
- -q 85: Размер 267 кб.
- -q 80: Размер 229 кб.
- -q 75: Размер 200 кб.
- -q 70: Размер 191 кб.
- -q 60: Размер 176 кб.
- -q 50: Размер 163 кб.
- -q 40: Размер 147 кб.
- -q 30: Размер 129 кб.
- -q 20: Размер 115 кб.
- -q 10: Размер 90 кб.
- -q 1: Размер 70 кб. Для экстренных случаев. Например, вы заблудились в лесу и надо отправить фото по спутниковой сети 😅
Nginx
Теперь мы научим nginx при запросе изображений сначала пытаться найти WebP файл, и только потом отдавать сжатый PNG/JPG, а если и сжатого нет, то отдавать обычный файл.
Для этого напишем следующий location
:
location ~* ^(/images/)(.+)\.(png|jpe?g)$ {
add_header Cache-Control 'public,max-age=31536000';
alias /images;
set $webp_image_subdir "/webp/";
set $comp_image_subdir "/comp/";
set $basename $2;
try_files $webp_image_subdir$basename$webp_suffix $comp_image_subdir$basename.$3 $uri;
}
Данный location
обрабатывает все запросы к адресам, которые начинаются с /images/
, за которым следует любое количество символов, затем точка, а затем формат файла png/jpeg/jpg.
Для запросов, соответствующих этому шаблону, выполняются следующие действия:
- Директива
alias
указывает путь к локальной директории, из которой будут отдаваться файлы. В данном случае путь к директории/images
. - Директивы
set
назначают переменные, которые будут использоваться в последующих директивах. Переменная$webp_image_subdir
устанавливается в/webp/
, а переменная$basename
устанавливается в захваченную подстроку шаблона регулярного выражения (т.е. имя файла без расширения). - Директива
try_files
пытается отдать Webp-версию запрошенного файла изображения, добавляя к переменным$webp_image_subdir
и$basename
суффикс.webp
. Если NGINX сможет найти Webp-версию запрашиваемого файла изображения, он отдаст его. Если он не может найти WebP-версию, то отдаст сжатый JPG/PNG, если и сжатого файла не будет, то отдаст не сжатое.
Заключение
Вот к такому скрипту оптимизации изображений я пришел. Если вы знаете как его улучшить, то обязательно оставляйте свои комментарии.