digital-garden/dev/snippet/Сжатие изображений без потери качества.md
Struchkov Mark d313510395
Some checks failed
continuous-integration/drone/push Build is failing
Обновление статей про сжатие
2024-09-06 00:21:20 +03:00

7.5 KiB
Raw Blame History

tags date zero-link parents linked
maturity/🌱
2023-11-20
../../meta/zero/00 Снипеты на bash

Размер изображений составляет существенную часть от размера страницы сайта. Поэтому часто я сжимаю изображения на своих сайтах. В этой заметке рассказываю какими способами я это делаю.

#!/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 мб.

Запускаем скрипт и смотрим на результат сжатия.

** 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 мб. При этом сохранив исходный размер изображения и без потерь качества.

Note

Лучшее, на мой взгляд, приложение для сжатия jpg это JPEGmini Pro. Имеет версию cli для серверов. Но к сожалению оно платное. Его результат сжатия 2.2 мб —> 959 кб.

Дополнительные материалы