Это сайт — моя персональная записная книжка. Интересна мне, по большей части, история, своя жизнь и немного программирование.

Сжатие GIF через gzip

Эксперимент мой сжатию несжатых ГИФ, по сути, провалился — если статические изображения иногда оказываются меньше (или сравнимы) с PNG, то с анимированными изображениями, ради которых всё и затевалось, всё плохо, я не сумел подобрать условия, при которых новоявленный метод обгоняет обычные соптимизированные ГИФы.

Зато я попутно выяснил, что ГИФ, вопреки моим ожиданиям, иногда жмётся gzip, на первой попавшейся анимации я получил выигрыш порядка 9%. Так что вот он метод ещё больше утоптать файлы ГИФ:

#!/bin/sh

which -s defluff
if [ $? -eq 0 ]; then
    gifsicle -O3 "$1" |  gzip -9nc | defluff 2>&- > "$2".temp
else
    gifsicle -O3 "$1" |  gzip -9nc > "$2".temp
fi

size=( $(ls -l "$2".temp) )

# срезаем заголовок gzip и контрольную сумму
let 'size=size[4]-10-8'

dd if="$2".temp of="$2" bs=1 skip=10 count=$size 2>&-
rm -f "$2".temp

В настройки Апача надо добавить новое расширение файла:

AddEncoding deflate .gif-def
AddType image/gif .gif-def

Для браузера вместо gzip я указываю метод deflate — по факту это тот же gzip, но без заголовка и контрольной суммы, что позволяет сэкономить 18 байт, не бог весть сколько, конечно, но почему бы их не убрать, спрашивается.

Утилита defluff , которая у меня упоминается — это оптимизатор кодов Хаффмана для gzip, PNG и zip, иногда даёт выигрыш в размере.

25 комментариев
Евгений Степанищев (bolknote.ru) 2012

Иногда такой ГИФ занимает меньше ПНГ, например, вот результат по одной из картинок с моего сайта ( http://bolknote.ru/imgs/2011.11.06.png ):

$ ll gifs-def/2011.11.06.png.gif-def
-rw-r-​-​r-​-​ 1 bolk staff 21270 11 авг 17:33 gifs-def/2011.11.06.png.gif-def
ll pngs/2011.11.06.png
-rw-r-​-​r-​-​ 1 bolk staff 24749 11 авг 17:36 pngs/2011.11.06.png

ПНГ сжат при помощи imgo ( https://github.com/imgo/imgo ).

TATAPuH 2012

забавно
экономим на байтах переданных по сети, но напрягаем броузер на распаковку

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

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

Он быстрее распаковывает, чем информация по сети приходит.

<wj4 (hayk.livejournal.com) 2012

Комментарий для Евгения Степанищева:

что он метод

Видимо пропущено «вот».

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

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

Спасибо!

masterspammer (masterspammer.livejournal.com) 2012

Оптимизация в самом формате (GIF) хороша для многих типичных случаев анимации — часто или передаётся разница между кадрами или даже маленький кадр размером значительно меньше всего изображения (а в нём опять-таки может быть лишь только разница с предыдущим состоянием).

Такая оптимизация чем-то похожа на сжатие JPEG, MP3 — она явно подразумевает определённую специфику данных (картинка, 2d, часто изменяется не всё изображение, а его часть) и алгоритм сжатия общего назначения как правило сможет тягаться со специализированным.

Но это лишь «как правило» — есть несколько типичных случаев, с которыми GIF справляется из рук вон плохо. Приведу примеры:

  1. «Проявление»/«Угасание», FadeIn/FadeOut — в предельном варианте когда данные не меняются, а меняеться лишь палитра, GIF даст размер_кадра*число_кадров, а zip — примерно размер_кадра+малое_значение*число кадров. Тут даже сжатый GIF сожмётся ZIPом хорошо (если данные о пикселях реально совпадают).
  1. Сдвиг картинки. Пиксели те же, но чуть сдвинуты в сторону (MPEG тут умнее).
  1. Чередование (как я писал) нескольких повторяющихся или похожих последовательностей картинок; пример: машина едет, рука тянется к рычагу, снова машина едет, рука тянет рычаг, едет машина, дверь (гаражная) начинает опускаться, стредка спидометра движется вправа... и так далее.
  1. Последовательность из 3-х и более одинаковых картинок (например, фазы движения чего-то). Часто (например, когда после десяти повторений пяти фаз бега (50 картинок) следует прыжок) нельзя оставить всего 3 картинки.
Евгений Степанищев (bolknote.ru) 2012

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

Я детально знаю как устроена анимация в GIF. А описанные случаи попробую, может получится что-то.

Последовательность из 3-х и более одинаковых картинок (например, фазы движения чего-то).

GIF одинаковые кадры хорошо жмёт — он просто оставит прозрачным одинаковые места.

masterspammer (masterspammer.livejournal.com) 2012

Комментарий для Евгения Степанищева:

GIF одинаковые кадры хорошо жмёт — он просто оставит прозрачным одинаковые места.

Имел в виду три картинки, которые повторяются так, что одинаковые подряд не идут — например, 1-2-3-1-2-3-1-2-3-1-2-3-4-5-6, при этом все «1» — совершенно одинаковые, равно как и все «2» и «3».

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

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

А, ну попробую. От безнадёги я сейчас что угодно попробую :)

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

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

Ну, первый выигрыш :)

Создал анимацию, у которой вот именно так кадры идут (кадры отличаются несильно), в итоге:
просто GIF — 8,6КБ
несжатый, сжатый через gzip — 4КБ
GIF, сжатый gzip — 3,3КБ

т. е. несжатый, но сжатый через gzip всё-таки проигрывает сжатому просто GIF, но явно выигрывает у обычного GIF.

Сейчас попробую ещё сильно отличающиеся кадры.

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

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

На сильно отличающихся соседних кадрах сжатие сжатого ГИФа — 67КБ, сжатие несжатого — 626КБ, исходный — 626КБ.

Т. е. сжатый несжатый ГИФ можно хоронить в пользу сжатого обычного, т. е. вот этой утилитой: http://bolknote.ru/all/3713

masterspammer (masterspammer.livejournal.com) 2012

Комментарий для Евгения Степанищева:

Чтоб разница была мало чтоб были сильно отличающиеся соседние кадры (от этого просто GIF жмётся плохо), нужно ещё чтоб были сильно похожие НЕсоседние кадры (это будет жать ZIP).

Ну и более общий случай варианта 1. — кадры, совпадающиеся по пикселям, но отличающиеся палитрой (только нужно проследить чтоб конвертор не привёл бы их к общей палитре).

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

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

Чтоб разница была мало чтоб были сильно отличающиеся соседние кадры (от этого просто GIF жмётся плохо), нужно ещё чтоб были сильно похожие НЕсоседние кадры (это будет жать ZIP).

Я так и сделал. Взял четыре кадра. Все сильно отличаются и сделал 1+2+1+2+1+2+1+2+1+2+1+2+1+2+3+4.

Ну и более общий случай варианта 1. — кадры, совпадающиеся по пикселям, но отличающиеся палитрой (только нужно проследить чтоб конвертор не привёл бы их к общей палитре).

Это сложнее, надо будет «вручную» сделать.

masterspammer (masterspammer.livejournal.com) 2012

Я так и сделал. Взял четыре кадра. Все сильно отличаются и сделал 1+2+1+2+1+2+1+2+1+2+1+2+1+2+3+4.

1+2+1+2+1+2+1+2+1+2+1+2+1+2 может быть относительно хорошо сжато самим GIF — у кадра есть параметр — что с ним делать после показа — а можно его как оставлять, так и удалять и если я правильно помню эту особенность, то последовательность превратится в
1+2+2+2+2+2+2+2, причём все «2» кроме последнего после показа удаляются, а последний — оставляется.

А что присходит в случае (из тех-же 4-х кадров)
1+2+3+1+2+3+1+2+3+1+2+3+1+2+3+4?

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

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

1+2+3+1+2+3+1+2+3+1+2+3+1+2+3+4?

Я уж не помню где я взял те кадры, взял другие в этом порядке.
В случае gzipped uncompressed GIF проигрыш 172КБ, в случае DEFLATEd GIF выигрыш в 600 байт.

masterspammer (masterspammer.livejournal.com) 2012

Комментарий для Евгения Степанищева:

Уже ничего не понимаю. Можно их увидеть?

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

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

Вот: http://zalil.ru/33674961

masterspammer (masterspammer.livejournal.com) 2012

Комментарий для Евгения Степанищева:

Да... весело оно всё. Взял я GIMP, который с GIF работает честно (не делает ничего неявного).

Сначала я открыл файл и не увидел там явного криминала (чего-то такого, что жмёт сам GIF), но файл не жмётся. Мало ли что — может кадры одинаковы на вид, но, например, если два цвета в палитре местами поменять, то бинарно всё будет уже неравно.

Потом я сделал почти точную копию файла — удалил все слои кроме 4-х уникальных, а потом снова сделал как было, скопировав их. Не жмётся. Решил что как-то неявно файлы оптимизировал GIMP (такого не замечал, но мало ли какая там проруха на старуху).

Наконец, взял GIF с 3-мя кадрами и просто сделал склейку его 5 раз подряд (он конечно уже не GIF, но отличия с точки зрения архиватора минимальны). Не жмётся, ага! Дело значит в самом GZIPе.

Беру RAR — жмётся всё примерно в 5 раз (как и должно было быть).

Возникает догадка — может просто расстояние между схожими фрагментами слишком большое, чтоб GZ заметил и/или сами фрагменты слишком велики, чтоб войти в его словарь.

Уменьшаю ту же картинку что на втором шаге до 80x60 (3 слоя), размножаю слои по 6 раз (чтоб не бояться, что разные слои сожмутся по-разному) — жмётся GZIPом в 5 раз.

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

Для небольших картинок GZIP несжатого GIFа (а иногда — как в этом примере — и сжатого) работает и так.

masterspammer (masterspammer.livejournal.com) 2012

Комментарий для Евгения Степанищева:

P.S. Вот http://zalil.ru/33675627 обычный (сжатый) GIF дополнительно сжатый GZIPом.

FadeIn/FadeOut тоже можно использовать так (оба сжатия).

Картинки со сдвигами — имеет смысл только GZIP (сжатие LZW скроет схожесть кадров).

Может и этот файл лучше сжимать только GZIPом — не проверял.

masterspammer (masterspammer.livejournal.com) 2012

Комментарий для Евгения Степанищева:

Да, а сжатый обычный получается лучше сжатого несжатого когда несжатый кадр слишком велик (в словарь не лезет), а сжатый уже не слишком. То есть когда сжатый 500к, то не лезет никак, а когда

На сильно отличающихся соседних кадрах сжатие сжатого ГИФа — 67КБ, сжатие несжатого — 626КБ, исходный — 626КБ.

то скорее всего сжатый кадр в словарь ещё лезет, а несжатый — нет.

Хм... а есть ли у GZIPа вообще словарь? У LZW он точно есть...

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

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

Я не очень-то разбираюсь в методе DEFLATE, но вот что, похоже, значит параметр 1…9 у gzip:
http://pastebin.com/ajxdkwGr

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

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

Попробовал пережать один из файлов Bzip2 (алгоритм Барроуза—Уилера), со словарём 9МБ и 10 проходами:
исходный — 259КБ ( http://zalil.ru/33676213 )
исходный, сжатый bzip2 — 126КБ
несжатый — 851КБ
несжатый, сжатый bzip2 — 129КБ

таки да, размер словаря имеет значение, но у DEFLATE этот параметр отсутствует.

masterspammer (masterspammer.livejournal.com) 2012

Комментарий для Евгения Степанищева:

А при уменьшении размеров картинки где-то до 50*50 что происходит?

Как я понимаю, тогда должен начать работать и GZIP?

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

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

Не знаю, в обед попробую, вчера уже спать хотелось.

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

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

Забыл написать, что попробовал. Вот что вышло:

исходный (кадры из предыдущего моего комментария, ужаты до 50×38) — 17КБ
он же, сжатый DEFLATE — 6,6КБ
сжатый gzip неупакованный GIF — 7,2КБ