Эффективное изменение размера картинок при помощи ImageMagick

http://www.smashingmagazine.com/2015/06/25/efficient-image-resizing-with-imagemagick/
  • Перевод
В наше время всё чаще сайты сталкиваются с необходимостью введения отзывчивого дизайна и отзывчивых картинок – а в связи с этим есть необходимость эффективного изменения размера всех картинок. Система должна работать так, чтобы каждому пользователю по запросу отправлялась картинка нужного размера – маленькие для пользователей с небольшими экранами, большие – для больших экранов.

Веб таким образом работает отлично, но для доставки картинок разных размеров разным пользователям необходимо все эти картинки сначала создать.

Множество инструментов занимается изменением размера, но слишком часто они выдают большие файлы, аннулирующие выигрыш в быстродействии, который должен приходить вместе с отзывчивыми картинками. Давайте рассмотрим, как при помощи ImageMagick, инструмента командной строки, быстренько изменять размеры картинок, сохраняя превосходное качество и получая файлы небольших объёмов.

Большие картинки == большие проблемы


Средняя веб-страница весит 2 Мб, из них 2/3 – картинки. Миллионы людей ходят в интернет через 3G, или ещё хуже. 2Мб-сайты в этих случаях работают ужасно. Даже на быстром соединении такие сайты могут израсходовать лимиты трафика. Работа веб-дизайнеров и разработчиков – упростить и улучшить жизнь пользователя.

image

Очень маленькие сайты могут просто сохранить несколько вариантов всех картинок. Но что, если у вас их дофига? Например, в магазине может быть сотня тысяч картинок – не делать же их варианты вручную.

ImageMagick


Утилита командной строки с 25-летним стажем в то же время является редактором картинок с полным набором функций. В ней огромная куча функций, и среди них – быстрое и автоматическое изменение размера картинок. Но с настройками по умолчанию файлы часто получаются излишне большими – иногда по объёму больше оригинала, хотя в них и меньше пикселей. Сейчас я объясню, в чём проблема, и покажу, какие настройки необходимы для её решения.

Как работает изменение размера картинок


По определению, при изменении размера картинки меняется количество пикселей в ней. Если её увеличивают, на выходе будет больше пикселей, чем на входе; при уменьшении – наоборот. Задача состоит в том, как лучше всего сохранить содержимое исходной картинки при помощи другого количества пикселей.

Увеличение картинок изобразить проще, так что начнём с него. Рассмотрим картинку с квадратиком 4х4 пикселя, который мы хотим удвоить до 8х8. По сути, мы берём эту картинку и натягиваем на новую сетку – это называется resampling (дискретизация). Чтобы сделать дискретизацию картинки 4х4 на 8х8, нужно куда-то вставить 48 лишних пикселей. У них должен быть какой-то цвет – процесс его подбора зовётся интерполяцией. При дискретизации алгоритм, выбирающий, как работает интерполяция, называется фильтром дискретизации.

image

Фильтров таких есть множество. Проще всего добавить четыре ряда и четыре столбца любого цвета. Ну, скажем, красного. Это будет фоновая интерполяция, когда на пустых местах появляется цвет фона (красный). В фотошопе это делается через “Image” → “Canvas Size” вместо “Image” → “Image Size”.

Это, конечно, нам не подойдёт. Картинка не будет похожа на исходную. Фоновая интерполяция используется только для добавления новых пикселей, да и тогда она бесполезна при изменении размера.

image

Ещё одна простая интерполяция – сделать цвет новых пикселей такой же, как у их соседей – это интерполяция по ближайшим соседям. Для картинок, особенно для нашего квадратика, результат будет гораздо лучше.

image

При понижающей дискретизации, то есть, уменьшении картинки, не так-то просто сделать интерполяцию по ближайшим соседям. Нужно принять, что в математических целях можно оперировать дробными пикселями. Для начала новая сетка применяется к оригинальной картинке. Поскольку пикселей стало меньше, а размер их больше, некоторые из них содержат по нескольку цветов.

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

image

Но если это что-то посложнее линии и квадратов, такой метод выдаёт зазубренные и квадратные картинки. Он работает быстро, выдаёт мелкие файлы, но выглядит плохо.

Большинство фильтров используют вариации интерполяции по ближайшим соседям – они делают точечную дискретизацию по нескольким точкам и как-то вычисляют некий средний цвет для них. В билинейной интерполяции считается взвешенное среднее цветов.

image

Но такой фильтр влияет на размер файла, потому что он вместе с округлыми краями добавляет новые цвета. У оригинальной картинки было всего два цвета, теперь же у нас их больше. А при прочих равных, чем больше цветов, тем тяжелее файл.

И что же это означает для нас

Нам нужно как-то уменьшать количество цветов, не теряя в качестве. Больше всего на это влияет выбор фильтра, но и другие настройки тоже могут.

Оптимальные настройки для ImageMagick


Основы ImageMagick

У ImageMagick дофига настроек и функций, и найти нужную довольно тяжело. Нас интересуют две функции, convert и mogrify. Они выполняют схожие действия, но mogrify работает с несколькими файлами сразу, а convert – по одному.

Простая операция:

convert input.jpg -resize 300 output.jpg


При этом IM берёт input.jpg и изменяет размер на пикселей в ширину, сохраняя результат в output.jpg. Функция -resize 300 – пример одной из множества функций. У них у всех один формат: -functionName option.

Использовать mogrify можно также, с небольшим дополнением:

mogrify -path output/ -resize 300 *.jpg


Тут IM берёт все файлы JPEG из текущей директории (*.jpg), изменяет их размер до 300 пикселей в ширину и сохраняет их в директории output.

Функции можно комбинировать:

convert input.jpg -resize 300 -quality 75 output.jpg


Это тоже изменяет размер input.jpg до 300 пискелей, но устанавливает качество JPEG в 75 перед сохранением в output.jpg.

Тестирование и результаты

Тестируя различные настройки IM, я стремился уменьшить размер файлов, не ухудшая их качества – так, чтобы их не отличить было от опции фотошопа «Save for Web». Я использовал для проверки как субъективное мнение, так и объективное – измерял структурные различия (structural dissimilarity, DSSIM). DSSIM сравнивает две картинки и выдаёт оценку. Чем меньше оценка, тем больше они похожи. 0 означает идентичность. Я добивался оценки DSSIM не более 0,0075. А в одном из исследований в прошлом году было установлено, что обычно люди не могут на глаз отличить картинки с DSSIM менее 0,015.

Протестировав различные изображения разных размеров в форматах JPEG и PNG я пришёл к выводу, что следующие настройки IM создают наименьшие результаты, которые при этом практически неотличимы от выдачи фотошопа:

mogrify -path OUTPUT_PATH -filter Triangle -define filter:support=2 -thumbnail OUTPUT_WIDTH -unsharp 0.25x0.25+8+0.065 -dither None -posterize 136 -quality 82 -define jpeg:fancy-upsampling=off -define png:compression-filter=5 -define png:compression-level=9 -define png:compression-strategy=1 -define png:exclude-chunk=all -interlace none -colorspace sRGB -strip INPUT_PATH


Разберём их подробнее.

Mogrify или Convert

IM использует convert для обработки картинок по одной, а mogrify обычно нужен для пакетной обработки. В идеальном мире результаты их работы должны совпадать. К сожалению, в convert есть ошибка, из-за которой он игнорирует некоторые из настроек (-define jpeg:fancy-upsampling=off), поэтому пришлось использовать mogrify.

Дискретизация (resampling)

Выбор фильтра дискретизации в IM почему-то запутан. Есть три способа это сделать:

  • выбрав функцию изменения размера
  • используя опцию -filter
  • или опцию -interpolate


image
Примеры работы двенадцати различных функций изменения размера

Очевиднее всего использовать –resize, но результаты получаются слишком большими. Я проверил 11 разных функций и обнаружил, что лучше всего с уменьшением справляется –thumbnail, как по размеру, так и по качеству. Эта функция работает в три этапа:

  1. изменяет размер картинки до размера, в пять раз превышающего необходимый, используя функцию –sample, у которой есть свой встроенный фильтр
  2. затем изменяет размер до необходимого через –resize
  3. удаляет мета-данные из картинки


То есть, если мы уменьшаем картинку до 500 пикселей в ширину, -thumbnail сначала изменит её размер до 2500 при помощи –sample. Затем IM изменит размер с 2500 до 500 через –resize. И в конце удалит мета-данные.

Второй способ выбрать фильтр дискретизации – через –filter. У некоторых функций есть встроенные фильтры, а у других есть фильтры по умолчанию, которые можно поменять. На втором шаге работы –thumbnail используется –filter, потому что там используется функция –resize.

Я проверил 31 вариант настроек для –filter и добился наилучших результатов, используя Triangle. Этот фильтр также известен как билинейная интерполяция. Он вычисляет взвешенный средний цвет из соседних пикселей. Я нашёл, что лучше всего задавать область соседних пикселей как -define filter:support=2 setting.

Третий способ выбора фильтра – это –interpolate, но он игнорируется при использовании -thumbnail.

Кроме всего прочего, IM по умолчанию использует некую функцию под названием JPEG fancy upsampling, которая пытается выдать JPEG лучшего качества. Я решил, что она лишь увеличивает размер картинок, а разница в качестве получается пренебрежимо малой, поэтому рекомендую отключать её через -define jpeg:fancy-upsampling=off.

Увеличение чёткости (Sharpening)

При изменении размера картинки слегка размываются, поэтому тот же фотошоп применяет различные техники увеличения чёткости. Я рекомендую фильтр unsharp, который, несмотря на название, увеличивает чёткость картинки: -unsharp 0.25x0.25+8+0.065.

Фильтр работает так, что сначала применяется гауссово размытие. Первые два числа – это радиус и сигма (в нашем случае, по 0,25 пикселя). После размытия фильтр сравнивает размытую версию с оригиналом, и там, где яркость отличается более, чем заданный порог (0.065), применяется увеличение чёткости заданной силы (8).

Уменьшение цветности

Как я говорил ранее, основная причина увеличения объёма файлов при изменении размера картинок – добавление новых цветов. Поэтому нужно попытаться уменьшить их количество, но без потери качества.

Один из способов – это постеризация (posterization), когда градиенты заменяются на наборы из чётких цветов. Постеризация уменьшает количество уровней цвета – это то, сколько вариантов остаётся у красного, зелёного и синего каналов. Общее количество цветов картинки будет комбинацией цветов этих каналов.

Постеризация может уменьшить объём файла, но и ухудшить качество. Я предлагаю цифру 136, при которой вы получаете небольшой файл, не теряя особо в качестве.

image
Оригинал

image
Уменьшение количества цветов

Дизеринг (Dithering) – процесс, который смягчает последствия уменьшения количества цветов, добавляя шума в цветовые наборы, чтобы создать иллюзию большего количества цветов. В теории это хорошая идея.

image
После дизеринга

К сожалению, в IM есть ошибка, которая портит картинки с прозрачностью при дизериге. Поэтому его лучше отключить через -dither None. К счастью, и без него результаты постеризации выглядят неплохо.

image
Ошибка дизеринга в IM

Цветовое пространство

Цветовой пространство косвенно относится к количеству цветов на картинке. Это пространтсво определяет, какие цвета доступны. На картинке ниже показано, что пространство цветов ProPhoto RGB содержит больше цветов, чем Adobe RGB, которое, в свою очередь, содержит больше, чем sRGB. И все они содержат цветов меньше, чем видит глаз.

image

sRGB сделали королём цветовых пространств интернета. Его одобрили W3C и другие организации. Он обязателен к поддержке в CSS Color Module Level 3 и в спецификациях SVG и WebP. На него ссылаются в спецификации PNG. В фотошопе это также это цветовое пространство по умолчанию. Короче, sRGB – лучший выбор для веба, и если вы хотите, чтобы ваши картинки правильно отображались, лучше использовать его.

Качество и сжатие

В форматах с потерей качества вроде JPEG качество и сжатие связаны напрямую – чем больше сжатие, тем ниже качество и меньше объём файла. Поэтому нужно найти баланс.

В моих тестах у контрольных картинок и фотошопа было выставлено качество high, или 60. А в настройках IM я рекомендую использовать 82. Почему?

Оказывается, что количественно настройка качества не определена в формате JPEG, и поэтому она не является стандартом. Качество 60 в фотошопе может оказаться таким же, как качество 40 в одной программе, качество B+ в другой или качество «офигенское» в третьей. В моих тестах я выяснил, что фотошоповское 60 соответствует -quality 82 в ImageMagick.

А у форматов без потери качества вроде PNG, качество и сжатие не связаны. Высокое сжатие не меняет вид картинки, а зависит только от уровня загрузки процессора при её обработке. Если не жалеть компьютеры, то нет причин не выставлять максимальное сжатие PNG.

Сжатие PNG в IM можно установить тремя настройками: -define png:compression-filter, -define png:compression-level и -define png:compression-strategy. Фильтр сжатия – это дополнительный шаг перед сжатием, который так сортирует данные, что сжатие становится более эффективным. Я добился лучших результатов при использовании адаптивной фильтрации (-define png:compression-filter=5). Уровень сжатия я рекомендую ставить на максимум, 9 (-define png:compression-level=9). А стратегия определяет сам алгоритм. Мне больше понравилась стратегия по умолчанию (-define png:compression-strategy=1).

Мета данные

Кроме самой картинки, файлы могут содержать мета данные – информацию о картинке, когда она была создана, об устройстве, которое её создало. Эта информация занимает место, но не улучшает восприятие картинки, и её лучше удалить. И хотя я указал, что –thumbnail удаляет мета данные, всё-таки она удаляет их не все. Есть возможность удалить все, используя -strip и -define png:exclude-chunk=all. На качество это не влияет.

Прогрессивный рендер

JPEGs и PNGs можно сохранять при помощи прогрессивного (progressive) или последовательно (sequential) рендера. По умолчанию выполняется второй, когда пиксели грузятся по рядам, сверху вниз. Прогрессивный означает, что картинка передаётся и выводится постепенно.

У JPEG прогрессивный рендер может произойти в любое количество шагов – это определяется при сохранении файла. Первый шаг – версия низкого разрешения полной картинки; на каждом последующем появляется версия более высокого разрешения, пока не будет выдана вся картинка в высшем разрешении.

image

У PNG есть вид прогрессивного рендера под названием Adam7 interlacing, когда пиксели выводятся в семь шагов на основе сетки 8х8.

image

Оба вида рендера можно настроить в IM через –interlace. Но нужно ли?

Такой рендер увеличивает объём файлов. Долгое время считалось, что его нужно включать, поскольку он улучшает пользовательское восприятие. Даже если идеальная картинка не загрузится сразу, он что-нибудь распознает, и это будет лучше, чем ничего.

В прошлом году по результатам исследования стало видно, что пользователи предпочитают последовательный рендер. Это только одно исследование, но всё равно интересное. Так что я решил рекомендовать использовать последовательный рендер через "-interlace none".

Оптимизация картинок

Я упоминал оптимизацию. Все описанные до этого настройки я рекомендую, если вы не оптимизируете свои картинки. Если их можно оптимизировать, то я их поменяю: небольшие изменения в настройках -unsharp лучше работают (-unsharp 0.25x0.08+8.3+0.045 супротив -unsharp 0.25x0.25+8+0.065 без оптимизации) и не нужно использовать -strip.

mogrify -path OUTPUT_PATH -filter Triangle -define filter:support=2 -thumbnail OUTPUT_WIDTH -unsharp 0.25x0.08+8.3+0.045 -dither None -posterize 136 -quality 82 -define jpeg:fancy-upsampling=off -define png:compression-filter=5 -define png:compression-level=9 -define png:compression-strategy=1 -define png:exclude-chunk=all -interlace none -colorspace sRGB INPUT_PATH


Оптимизаторов есть множество. Я тестировал image_optim, picopt и ImageOptim, и они все прогоняют картинки через серию разных шагов. Я проверял их по очереди и пришёл к выводу, что лучше прогнать файлы через все три в том порядке, в каком они приведены. Правда, после использования image_optim выгода от picopt и ImageOptim становится минимальной. Если у вас нет лишнего времени и процессорной мощности, то использование более одной оптимизации будет чрезмерным.

Результаты (и что, стоило так мучаться?)


Конечно, настройки мои сложны, но для улучшения пользовательского восприятия они нужны. С радостью сообщаю, что потратив время на тесты, я сумел кардинально уменьшить объёмы файлов, не проиграв в качестве.

В среднем объём файлов уменьшился на 35% по сравнению с опцией фотошопа “Save for Web”.

Сравнение с Photoshop Creative Cloud


Мои настройки без оптимизации оказались даже лучше, чем у фотошопа с оптимизацией!

По сравнению с настройками по умолчанию при изменении размера картинок у IM, мои рекомендации выиграли в среднем 82%.


По сравнению с настройками по умолчанию в WordPress, который «под капотом» использует ImageMagick, мои настройки в среднем выиграли 77%.


По сравнению с другими CMS и инструментами, которые используют ImageMagick, мои настройки выигрывали аж до 144%.


Напоминаю, что все картинки получились неотличимыми от вывода фотошопа.

Как реализовать это в своих проектах


bash shell

Здесь можно добавить функцию-макрос в файл .bash_aliases (или .bashrc), которая будет заменять мою рекомендуемую команду:

smartresize() {
   mogrify -path $3 -filter Triangle -define filter:support=2 -thumbnail $2 -unsharp 0.25x0.08+8.3+0.045 -dither None -posterize 136 -quality 82 -define jpeg:fancy-upsampling=off -define png:compression-filter=5 -define png:compression-level=9 -define png:compression-strategy=1 -define png:exclude-chunk=all -interlace none -colorspace sRGB $1
}


А вызывать её нужно так:

smartresize inputfile.png 300 outputdir/


Node.js

У npm package под названием imagemagick позволяет использовать ImageMagick. При его использовании можно добавить функцию изменения размера следующим образом:

var im = require('imagemagick');

var inputPath = 'path/to/input';
var outputPath = 'path/to/output';
var width = 300; // output width in pixels

var args = [
   inputPath,
   '-filter',
   'Triangle',
   '-define',
   'filter:support=2',
   '-thumbnail',
   width,
   '-unsharp 0.25x0.25+8+0.065',
   '-dither None',
   '-posterize 136',
   '-quality 82',
   '-define jpeg:fancy-upsampling=off',
   '-define png:compression-filter=5',
   '-define png:compression-level=9',
   '-define png:compression-strategy=1',
   '-define png:exclude-chunk=all',
   '-interlace none',
   '-colorspace sRGB',
   '-strip',
   outputPath
];

im.convert(args, function(err, stdout, stderr) {
   // do stuff
});


Grunt

Если вы используете Grunt для запуска задач, то специально для вас я сделал задачу по имени grunt-respimg (npm), которая делает всё, что я описал. В свои проекты её можно включать вот так:

npm install grunt-respimg --save-dev


А затем её можно выполнить в файле Grunt:

grunt.initConfig({
respimg: {
default: {
options: {
widths: [200, 400]
},
files: [{
expand: true,
cwd: 'src/img/',
src: ['**.{gif,jpg,png,svg}'],
dest: 'build/img/'
}]
}
},
});
grunt.loadNpmTasks('grunt-respimg');


PHP

В PHP ImageMagick интегрирован под именем Imagick. К сожалению, там он ограничен и не умеет выполнять всё то, что я рекомендовал – в частности, настраивать фильтр дискретизации на использование функции thumbnail.

Но вам повезло – я создал модуль php-respimg (packagist), который делает всё нужное. Его можно включить в проект при помощи Composer:

composer require nwtn/php-respimg


А затем изменять размер картинок вот так:

require_once('vendor/autoload.php');
use nwtn\Respimg as Respimg;
$image = new Respimg($input_filename);
$image->smartResize($output_width, 0, false);
$image->writeImage($output_filename);


Content Management Systems

Если ваша CMS работает на PHP, берите секцию «PHP» и делайте из неё плагин. Если вы используете WordPress, то вы можете использовать плагин RICG Responsive Images. После его установки для активации надо будет добавить следующее в файл functions.php:

function custom_theme_setup() {
   add_theme_support( 'advanced-image-compression' );
}
add_action( 'after_setup_theme', 'custom_theme_setup' );


Другие CMS так или иначе дают доступ к функциям по работе с картинками – обратитесь к их документации.

Быстродействие


В своих тестах я обнаружил, что по сравнению с –resize по умолчанию IM занимал в 2.25 раз больше времени на обработку картинок.

Заключение


Дизайнеры и разработчики очень сильно влияют на то, как работает веб. Мы можем сделать сайты более шустрыми, улучшить их восприятие пользователями, и даже вывести наш контент на новые рынки. Урезание объёма картинок делается довольно просто и сильно влияет на увеличение быстродействия сайта – надеюсь, что вся эта информация окажется полезной для вас и позволит вам улучшить ваш сайт для ваших пользователей.

Ссылки


grunt-respimg
php-respimg
RICG Responsive Images plugin
Поделиться публикацией
Комментарии 11
    +5
    Особенно хорошо вышли ссылки в скриншотах таблицы. Неужели так сложно было их перевести и сделать живым текстом? :(
      0
      Желающие могут ознакомиться с оригиналом и воспользоваться ссылками оттуда. Довольно муторно переводить таблицы и ссылки внутри — в редакторе хабра нет редактора таблиц.
      –9
      Правильно, EXIF придумали идиоты, поэтому давай ради двух килобайт безжалостно вымараем из картинки дату, гео и прочие незначительные детали. Эмпирические данные внезапно показали, что thumbnail лучше resize.

      Я бы как минимум добавил бы уточнение, что после такой обработки картинка станет хуже распознаваема поисками.
        +4
        Проблема EXIF данных заключается в том что они пишутся не только фотоаппаратами, но и различными редакторами и конвертерами. 2 КБ это не много для картинки в 100 КБ, но для иконки 0.5 КБ лишний багаж в 2 КБ просто ни к чему.

        Что до поиска, то сомнительно прямое использование EXIF данных в поиске, если только вы туда специально ключевые слова не пишете. В отдельно взятых фото- задачах это действительно полезная вещь, но в большинстве случаев нет.

        Статья интересная, подобным приемом с пастеризацией и последующим дизером раньше (в начале 2000-х) пользовались верстальщики газет и журналов, чтобы фотографии в 300dpi хоть как-то вмещались на 100 Mb Iomega Zip :)
          –2
          Я говорил строго про использование thumbnail вместо resize потому… ну, просто потому, что автор не понимает, чем изображение отличается от превьюшки.

          Не знаю, что там сомнительного, поиски по картинкам всех крупных игроков явно используют дату и гео.

          «Я покрутил ручки настройки и получил картинку меньшего размера» — это годный твит, код на баше как раз прекрасно помещается в 140 символов. От статьи я ожидаю, как минимум, что ресайз не поломает мне нафиг всю привязку к картам и календарю.

        +1
        >-colorspace sRGB
        И сразу получаем неправильную цветопередачу если у нас в оригинале CMYK (независимо от того, есть вшитый профиль или нет. К тому же судя по порядку аргументов встроенный профиль будет сначала удален)
          0
          Как использовать php imagick на обычном хостинге?
            0

            на обычном он обычно есть.

            +1
            Куча вещей вообще не рассмотрены.
            Например, ничего не сказано о параметре -sampling-factor для JPEG, который будучи установлен в 1x1 (вместо стандартного 2x1), позволяет на изображениях некоторого типа применять более сильную компрессию (параметр -quality) с визуально менее различимыми артефактами.
            Писать просто "-resize 300" — зло, если речь идет именно о ширине, нужно писать «300x», плюс есть еще модификаторы, которые позволяют не увеличивать картинки меньшего размера, чем указан, и наоборот.
            Как-то вообще ничего не сказано о том, что изображения многократно большего разрешения могут быть уменьшены уже на этапе чтения (не для всех форматов это имеет смысл). Также, если нужно сразу несколько экземпляров разного разрешения, те, что максимально отличаются между собой (в разы) можно генерировать одной командой используя -write, экономя на операции чтения исходного изображения и на последовательном уменьшении. Например, для подготовки нескольких изображений под обычные и high dpi экраны, чье разрешение различается более чем в два раза, это как раз идеально подходит.
              0
              Я не увидел считает ли он DSSIM и чем, в таблице этих данных нет.
              И не понимаю, что значит «Photoshop CC, with optimization». Он сохраняет из фотошопа в jpeg, а затем ещё раз прогоняет через imagemagick?
                0
                «Я упоминал оптимизацию» — выше по тексту нет упоминаний про оптимизацию.

                Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                Самое читаемое