Оптимизация картинок для блога

В ходе знакомства с возможностями движка блога столкнулся с проблемой большого размера изображений. Большую часть фотографий я делаю на телефон, камера которого имеет разрешение 13 мегапикселей. Получаются фотографии формата JPEG и разрешением 4208×3120. Размер таких фотографий сильно зависит от содержимого, обычно — 1-5Мб. Ниже пример картинки, её размер — 4Мб, что многовато для веба.

Исходное изображение 4Мб

Исходное изображение 4Мб

Та же самая картинка после оптимизаций имеет размер 370Кб — примерно в 10 раз меньше.

Оптимизированное изображение 370Кб

Оптимизированное изображение 370Кб

Визуальной разницы нет, а польза налицо.

Как это сделать

1. Уменьшаем изображение

Я уменьшаю изображения до 2880×1800 — это разрешение экрана моего ноутбука; если картинка меньше, то ничего не делаю. Для этого использую утилиту из набора библиотеки ImageMagick. На MacOS установить её можно следующей командой (требуется наличие Homebrew):

1
$ brew install imagemagick

Команда для уменьшение изображения:

1
$ convert windmill.jpg -resize 2880x1800\> windmill.jpg

\> в значение параметра -resize предписывает не изменять размер изображения, если оно помещается в прямоугольник заданного размера. После уменьшения разрешения, размер картинки составляет 1,4Мб.

2. Понижаем качество изображения

Коэффициент качества при сжатии JPEG выбирается в диапазоне от 1 до 100. В зависимости от содержимого картинки, снижение качества со 100 до значений между 80 и 60 визуально незаметно. Для своих целей я выбрал значение 77 — я рассчитываю, что при таком значении понижение качества не будет заметно для любых картинок. Для изменения качества можно использовать ту же утилиту convert:

1
$ convert windmill.jpg -quality 77 windmill.jpg

Или можно объединить с шагом №1:

1
$ convert windmill.jpg -quality 77 -resize 2880x1800\> windmill.jpg

После понижения качества изображения, её размер уменьшился до 515Кб.

3. Изменяем тип компрессии

Библиотека mozjpeg является одной из самых эффективных для этой цели. На MacOS устанавливается через brew:

1
2
3
4
$ brew install mozjpeg
$ ln -s `brew info mozjpeg \
    | grep Cellar \
    | cut -d' ' -f1`/bin/cjpeg /usr/local/bin/mozcjpeg

Последняя команда создаёт символьную ссылку на исполняемый файл утилиты cjpeg, чтобы его можно было запускать без указания полного пути. Имя изменено на mozcjpeg, чтобы избежать конфликта с одноимённой утилитой библиотеки libjpeg.

Так как утилита mozcjpeg умеет изменять качество изображения, я использую её для этой цели, вместо convert.

1
2
$ mozcjpeg -quality 77 -outfile _windmill.jpg windmill.jpg
$ mv -f _windmill.jpg windmill.jpg

Более того, понижение качества через mozcjpeg даёт лучший результат. Если сначала понизить качество с помощью convert и потом применить сжатие mozjpeg, то получим изображение размером 482Кб. А если изменять качество с помощью mozcjpeg, то результатом будет 370Кб.

Помимо изменения качества, по-умолчанию также применяются параметры -optimize (ради чего эта библиотека и используется) и -progressive (позволяет загружать картинку более плавно — пример). Более подробно можно посмотреть в инструкции к утилите.

Экономим на спичках

Дополнительно можно удалить информацию в EXIF. Например, это можно сделать с помощью параметра -strip утилиты convert. Но я не стал этого делать.

Автоматизируем

Чтобы избавиться от рутины я написал небольшой скрипт, который выполняется перед каждым коммитом и обрабатывает новые или изменённые изображения. Если в названии картинки есть подстрока .noopt., картинка пропускается — это значит она мне нужна необработанная, как картинки в этой заметке. Также создаются бекапы каждого изображения, которые игнорируются в .gitignore. Чтобы скрипт работал, его нужно поместить в исполняемый файл .git/hooks/pre-commit.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
#!/bin/bash -e

for file in `git diff --cached --name-only | grep -Ei ".(jpg|jpeg)\$"`
do
  if [[ $file = *".noopt."* ]]
  then
    echo "Skip optimization for $file"
    continue
  fi
  echo "Optimizing image $file"
  cp $file $file.bak
  echo "Backup: $file.bak"
  echo "Before: $(identify $file)"
  filename=$(basename -- $file)
  filepath=${file%$filename}
  tmpfile="$filepath~$filename"
  convert $file -resize 2880x1800\> $tmpfile
  mozcjpeg -quality 77 -outfile $file $tmpfile
  rm -f $tmpfile
  echo "After:  $(identify $file)"
  git add $file
done

Берегите трафик!