Пишу, по большей части, про историю, свою жизнь и немного про программирование.

Сжатый несжатый GIF

Как многие знают, браузер нативно понимает сжатие gzip (алгоритм DEFLATE). Наконец-то браузеры научились с ним правильно работать, после стольких лет мучений — сжимать можно что угодно, достаточно просто указать правильный заголовок.

Графический файлы обычно так не сжимают — внутри у них свой алгоритм сжатия, например в ПНГ используется тот же DEFLATE. Но с ГИФом ситуация хуже — у него внутри более старый алгоритм LZW, к тому же нет никаких оптимизационных техник (например, ПНГ использует так называемые «фильтры», которые позволяют эффективно сжимать градиенты.

Вот я и подумал — нельзя ли как-то заменить алгоримт сжатия ГИФа с внутреннего LZW на внешний gzip? Если было бы нельзя, я бы эту заметку не писал.

Смотрите, слева у вас на экране ГИФ, сжатый при помощи gzip (так и грузится с сервера), справа — ПНГ.

В каждом из них равное количество уникальных цветов, ПНГ пропущен через все оптимизаторы, которые я только нашёл. При этом ПНГ занимает 1374 байта, а гзипованный ГИФ — 1347, на 27 байт меньше. Дело тут конечно, не в паре десятков байт, а в принципе — если бы я выбрал картинку побольше, выигрыш, возможно, был бы существеннее.

Почему же LZW внутри ГИФа, плюс DEFLATE снаружи сделали файл меньше, чем оптимизирующие просто DEFLATE, плюс фильтры в случае ПНГ? Обычно наложение одного сжимающего алгоритма на другой делает файл больше сравнению с применением одного, более удачного алгоритам из двоих.

Дело в том, что в ГИФ я использовал несжатый, он подготовлен специальным образом, так, что его собственный алгоритм сжатия не работает, а gzip работает над несжатым потоком данных.

Самый простой, по моему мнению, способ получить такой файл — пропустить его через утилиту gifinter из набора libungif — библиотеки и набора утилит для работы с несжатыми файлами ГИФ.

Утилиты эти отлично скомпилировались под мой «Мак» и обрабатывал я ГИФ следующим образом: для начала оптимизировал его при помощи giflite (эта старая утилита под DOS, которую я запускаю из-под dosbox, творит с ГИФами чудеса), потом пропустил через утилиту, вырезающую из ГИФа комментарии (их туда вставил giflite), только после этого пропустил файл через gifinter и сжал при помощи gzip:

gifinter optimized.gif > non-compressed.gif
gzip -9n non-compressed.gif

Дальше я переименовал файл в 2012.08.08.gif-gz и добавил в конфигурацию своего «Апача» следующие строки:

AddEncoding gzip .gif-gz
AddType image/gif .gif-gz

Теперь при запросе сервер будет выдавать все необходимые заголовки:

bolk@Bolk ~  $ telnet bolknote.ru 80
Trying 91.230.61.15...
Connected to bolknote.ru.
Escape character is '^]'.
HEAD /imgs/2012.08.08.gif-gz HTTP/1.0
Host: bolknote.ru
Accept-encoding: gzip

HTTP/1.1 200 OK
Server: nginx
Date: Wed, 08 Aug 2012 06:12:14 GMT
Content-Type: image/gif
Content-Length: 1347
Connection: close
Accept-Ranges: bytes
Cache-Control: max-age=2592000
Expires: Fri, 07 Sep 2012 06:12:14 GMT
Content-Encoding: gzip

Если интересно как устроен несжатый ГИФ, об этом можно прочитать в Википедии, откуда я взял картинку, там интересная уличная магия, перепечатывать «Википедию» не очень-то интересно.

Готовой разгадки почему сжатый несжатый ГИФ обогнал ПНГ у меня нет, но есть одно возможное соображение — ГИФ чуть-чуть компактнее ПНГ, кроме того, ГИФ сжат целиком, тогда как в ПНГ сжата только картинка. Если моё предположение правда, то хорошего выигрыша ждать не сто́ит, но списывать формат ГИФ со счетов тоже не нужно.

По-настоящему опыт ценен, если удасться сделать несжатую ГИФ-анимацию (этого я ещё не пробовал) и сжать её при помощи gzip. Если удасться, это будет очень полезно, буду держать вас в курсе.

14 комментариев
Lynn «Кофеман» (alexeyten.ya.ru) 2012

/tmp$ pngout 2012.08.08.png
 In: 1374 bytes 2012.08.08.png /c3 /f0 /d8
Out: 1320 bytes 2012.08.08.png /c3 /f0 /d8, 102 colors
Chg: -54 bytes ( 96% of original)

:-)

Lynn «Кофеман» (alexeyten.ya.ru) 2012

Комментарий для alexeyten.ya.ru:

Забавно, ровно на 27 байт меньше gif-а. :-)

Lynn «Кофеман» (alexeyten.ya.ru) 2012

Но вообще идея интересная.

Евгений Степанищев (bolknote.ru) 2012

Комментарий для alexeyten.ya.ru:

Я пропускал через pngcrush, optipng, оптимизатор Yahoo и punypng. Значит надо было ещё и pngout использовать. Кошмар :)

В любом случае, ценность идеи совсем не в этом (см. последнее предложение в заметке).

Евгений Степанищев (bolknote.ru) 2012

Вот то, ради чего всё затевалось: http://bolknote.ru/all/3708

Евгений Степанищев (bolknote.ru) 2012

Я, кстати, думал, что для «Мака» pngout не существует. Оказывается, есть порт: http://www.jonof.id.au/kenutils

greli (greli.livejournal.com) 2012

В IMGO ( https://github.com/imgo/imgo ) входит pngout.

Евгений Степанищев (bolknote.ru) 2012

Комментарий для greli.livejournal.com:

Спасибо, буду пробовать!

Евгений Степанищев (bolknote.ru) 2012

Комментарий для greli.livejournal.com:

Кстати, pngout там старше, чем тот, который я пришёл выше.

Евгений Степанищев (bolknote.ru) 2012

Комментарий для alexeyten.ya.ru:

Могу, кстати, этот ГИФ ещё на 18 байт уменьшить. Где бы ещё 11 байт найти? :)

Евгений Степанищев (bolknote.ru) 2012

Комментарий для alexeyten.ya.ru:

Т. е. 9, конечно, а не 11.

Евгений Степанищев (bolknote.ru) 2012

Комментарий для alexeyten.ya.ru:

Out: 1320 bytes 2012.08.08.png /c3 /f0 /d8, 102 colors

$ ./compressgif test.gif test.gif-def

$ ll test.gif-def
-rw-r-​-​r-​-​ 1 bolk staff 1316 11 авг 17:22 test.gif-def

Это утилита из следующей статьи: http://bolknote.ru/all/3713

subzey 2013

Если прогнать оба файла через zopfli, результат ещё больше впечатляет. PNG: 1339 байт, GIF.GZ: 1241.

Евгений Степанищев (bolknote.ru) 2013

Комментарий для subzey:

Ого, спасибо! Посмотрю!