Сжатый несжатый 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. Если удасться, это будет очень полезно, буду держать вас в курсе.
8 августа 2012 08:43

Lynn «Кофеман» (alexeyten.ya.ru)
8 августа 2012, 09:30

/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)
8 августа 2012, 09:34

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

Lynn «Кофеман» (alexeyten.ya.ru)
8 августа 2012, 09:38

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

bolk (bolknote.ru)
8 августа 2012, 09:51, ответ предназначен Lynn «Кофеман» (alexeyten.ya.ru):

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

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

bolk (bolknote.ru)
8 августа 2012, 10:25

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

bolk (bolknote.ru)
8 августа 2012, 10:37

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

greli (greli.livejournal.com)
8 августа 2012, 10:56

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

bolk (bolknote.ru)
8 августа 2012, 11:14, ответ предназначен greli (greli.livejournal.com):

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

bolk (bolknote.ru)
8 августа 2012, 12:51, ответ предназначен greli (greli.livejournal.com):

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

bolk (bolknote.ru)
11 августа 2012, 14:24, ответ предназначен Lynn «Кофеман» (alexeyten.ya.ru):

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

bolk (bolknote.ru)
11 августа 2012, 14:36, ответ предназначен Lynn «Кофеман» (alexeyten.ya.ru):

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

bolk (bolknote.ru)
11 августа 2012, 16:23, ответ предназначен Lynn «Кофеман» (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/2012/08/11/~3713

subzey (инкогнито)
26 июня 2013, 15:43

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

Евгений Степанищев (bolknote.ru)
26 июня 2013, 16:24, ответ предназначен subzey

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

Ваше имя или адрес блога (можно OpenID):

Текст вашего комментария, не HTML:

Кому бы вы хотели ответить (или кликните на его аватару)