В чём проблема GIF?
Допустим, вы ради шутки хотите создать дико трясущийся GIF (https://knowyourmeme.com/memes/vibrating-gifs). Редактор GIF позволяет задать длительность/задержку кадра, поэтому для максимальной тряски вы указываете самое маленькое значение. Но при просмотре получившегося GIF оказывается, что она проигрывается гораздо медленнее, чем задумано, и вы точно видели более быстрые GIF. Что же происходит?
Если вы читаете эту статью, чтобы исправить свой GIF и вам нужен чёткий ответ, то вот решение: установите задержку кадра не на 10 мс, а на 20 мс. Если вы хотите чуть больше узнать о GIF и о том, почему возникает этот пограничный случай, а также о том, как улучшить ситуацию, то продолжайте чтение!
(Пояснение: если вы читаете статью из далёкого утопического будущего, где это перестало быть проблемой, то некоторые из примеров GIF будут не особо понятными. В противном случае, мои соболезнования, и можете не обращать на это пояснение внимания.)
Я, когда мои GIF слишком медленные
Особенности GIF
Мы не будем вдаваться в подробности структуры файла GIF. Хороший анализ байтов, из которых состоит GIF, можно прочитать в проекте «What's in a GIF» Мэттью Фликингера.
В первой версии формата GIF (87a) несколько кадров изображения (разного размера и расположения) накладывались друг поверх друга, создавая одно готовое изображение. Каждый кадр мог ссылаться на собственную палитру из 256 цветов, то есть в изображении могло быть больше, чем 256 уникальных цветов.
В текущей версии формата GIF (89a) имеются функции анимации и прозрачности. Теперь перед каждым кадром изображения можно задать необязательное значение задержки, определяющее, как долго должен отображаться этот кадр до перехода к следующему. Если конкретнее, это количество сотых секунды, указывающее, сколько нужно ждать до перехода к следующему кадру. Значение задаётся в интервале от 0 (без задержки) до 0xffff (приблизительно десять минут).
GIF с задержкой кадра в 5 (50 мс).
GIF с задержкой кадра в 50 (500 мс).
Без задержки
Как будет выглядеть задержка, равная 0? Спецификация не отвечает на этот вопрос напрямую, но в ней упоминается два аспекта:
- При декодировании файла GIF каждый кадр изображения должен обрабатываться "без задержек, за исключением тех, которые указаны в контрольной информации".
- Значения задержки используются, только «если они не равны 0».
Я понимаю это так, что все кадры изображения с задержкой 0 должны комбинироваться с данными предыдущего кадра изображения, аналогично тому, как работал первый формат GIF 87a. Если все кадры изображения в GIF имеют задержку 0, то в результате получится статическое изображение с комбинированными данными всех кадров изображения.
Вот пара примеров того, как бы выглядели GIF, если исполнялась спецификация:
Статический файл GIF из более чем 256 цветов, комбинирующий несколько кадров.
Анимация с красными квадратами, задержка установлена на 0. Этих красных квадратов не видно.
Для понимания контекста покажем, как на самом деле рендерятся показанные выше GIF:
Многоцветный GIF за раз рендерит только отдельные части.
Красные квадраты с задержкой 0 видны при рендеринге.
(Если вы видите одинаковые изображения, то напишите мне! Это означает, что существует браузер, обрабатывающий GIF не так, как популярные браузеры.)
Источник проблемы
Проблема в том, что никто не поддерживает задержку 0. То есть ни одна из программ, которыми вы обычно просматриваете GIF, её не поддерживает (Firefox, Edge, IE, Chrome, Windows Explorer, приложения Electron, приложения Qt...). Всё с задержкой меньше 2 (20 мс) будет считаться более высоким значением. Изучив исходный код разных программ, мы сможем создать картину того, почему так получилось.
Qt (версия 6.2.2)
Исходный код Qt. «IE и mozilla используют минимальную задержку 10. При минимальной задержке 10 мы совместимы с ними и избегаем огромных нагрузок на приложение и xserver».
Chromium (версия 98.0.4754)
Исходный код Chromium. «Во многих раздражающих баннерах установлена задержка 0, чтобы изображение мерцало максимально быстро. Мы копируем поведение Firefox и используем длительность 100 мс для всех кадров, для которых длительность задана <= 10 мс».
Firefox (версии 97 и около 38)
Исходный код Firefox. «Очень маленькие значения таймаута проблематичны по двум причинам: мы не хотим тратить энергию на чрезвычайно быструю перерисовку анимированных изображений; некачественные инструменты генерируют эти значения, когда им на самом деле нужно значение „по умолчанию“, поэтому такие изображения не воспроизводятся без нормализации… Исторически поведение IE было таким: задержки в 10 – 50 мс нормализовались до 100 мс. >50 мс используются без нормализации. Opera нормализует 10 мс до 100 мс. >10 мс используются без нормализации».
Исходный код Firefox (из старой версии, с другими формулировками). «Обеспечиваем минимальное время между обновлениями, чтобы не вызывать троттлинг потока UI. Считаем 0 == неуказанному значению и делаем анимацию быстрой, но не очень быстрой… Похоже, существуют некачественные инструменты, которые устанавливают таймаут в 0 мс или 10 мс, хотя на самом деле им нужно значение „по умолчанию“. Поэтому мы изменяем значения в этом интервале».
Internet Explorer 5
Исходный код Internet Explorer 5 закрыт, но гипотетически он мог бы выглядеть примерно так:
Гипотетический исходный код Internet Explorer 5. «Грубый хак для обработки „вырожденных анимаций“, задержки которых установлены на какое-то маленькое значение из-за задержек, вызванных процессом анимирования Netscape… предполагается, что эти маленькие значения подразумевают задержку кодирования Netscape… ложное [значение задержки], чтобы GIF использовали для воспроизведения анимаций только задержку Netscape»
Подведём итог показанным выше комментариям:
- Qt делает это, чтобы соответствовать поведению IE и Firefox (и чтобы избежать перегрузки ЦП)
- Chromium делает это, чтобы соответствовать поведению Firefox (и для борьбы с раздражающими мерцающими баннерами)
- Firefox делает это, чтобы соответствовать поведению IE и Opera (и для поддержки не отвечающих спецификации GIF, а также чтобы избежать перегрузки ЦП)
- IE 5 делает это, потому что Netscape был медленным (из-за чего многие люди создавали плохо отформатированные GIF)
Во всех этих кодовых базах есть одна странность: вместо того, чтобы поднять значение 0 или 1 до 2, все они поднимают его аж до 10 (100 мс). То есть если сделать задержку слишком маленькой, то получишь медленный GIF. Если сделать задержку чуть-чуть повыше, то GIF гораздо быстрее.
Задержка 10 мс
Задержка 20 мс
Задержка 50 мс
Задержка 100 мс
Если в вашем браузере декодирование GIF реализовано неверно, то один из показанных выше GIF будет иметь неправильную скорость.
Обратите внимание, что маленькие значения задержки приводят к странным результатам. В оригинале статьи этот пример интерактивен.
Мысли
Похоже, причина увеличения значений в окрестности 10 мс до 100 мс изначально была вызвана требованием эмуляции медленности Netscape. В комментариях к исходному коду Qt и Firefox говорится, что это нужно для снижения использования ЦП, но поскольку значение 20 мс поддерживается, то разработчики должны были бы просто ограничить значение снизу 20 мс. Или вообще не ограничивать это значение! Современные браузеры и так отлично рендерят кадры GIF с задержкой 20 мс, и я не уверен, что аргумент «компьютеры слишком медленные» справедлив тридцать лет спустя.
Кроме того, стоит оставить и кадры с задержкой 0. Пусть они комбинируются с содержимым предыдущего кадра, как это и сказано в спецификации GIF. Если у всех кадров выставлена задержка 0, то должно отображаться статическое изображение, созданное комбинированием всех кадров.
Лично я хотел изменить две небольшие, но отдельные области в одном кадре большого GIF, чтобы уменьшить его размер. Сейчас для этого нужно найти прямоугольную область, охватывающую все изменения на экране в этом кадре, и добавить её в GIF, но если бы мы могли задавать несколько кадров и устанавливать всем, кроме одного, задержку 0, то можно было бы создать тот же эффект, но с меньшим количеством данных изображения:
Одну из этих областей можно задать как кадр с задержкой 0, при этом можно было бы создать более оптимизированный GIF, чем файл с одним большим кадром.
Недостаток внесения любых изменений заключается в том, что плохо сформатированные GIF больше не рендерятся. Я думаю, нам нужно быть смелыми, как релиз Netscape 2.0:
«Теперь Netscape отвечает стандартам GIF: некоторые утилиты для создания GIF создают изображения GIF, не отвечающие стандартам. Такие изображения будут показывать пустой контур вместо изображения. Этот контур гораздо больше реальных изображений. Изображения не будут видимы вообще. Чтобы починить эти изображения GIF, создатели контента могут прочитать неправильное изображение GIF в другом редакторе GIF, соответствующем спецификациям GIF89a, и снова сохранить изображение».
Если уж Netscape позволялось ломать некачественно сделанные GIF, то нам тоже стоит так поступать!
Давайте проверим Netscape
Я захотел узнать, как выглядел рендеринг GIF в Netscape 2.0. Вот тестовый GIF с красными квадратами из примера выше, отрендеренный (правильно!) в Netscape 2.0 и Windows 95:
Красные квадраты невидимы!
Так как у нас есть Netscape, почему бы не посмотреть, как в нём выглядит файл с задержкой 1? Вот для сравнения GIF с задержкой = 2 (20 мс). Однако результаты оказались неожиданными. Если не двигать мышью, он работает очень медленно (о причинах этого рассказано на странице Stack Overflow):
А вот как выглядит файл с задержкой 0, показанный ради драматического эффекта до интересующего нас файла с задержкой 10 мс. Посмотрите на скорость! Похоже, Netscape не обрабатывал GIF, где все кадры имели задержку 0, как единый статический кадр, несмотря на требование спецификации. И я понятия не имею, почему в этом случае для ускорения не требуется двигать мышью:
И, наконец, GIF с задержкой 10 мс, которого мы так долго ждали…
*барабанная дробь*
Ха. Сначала тормозит, потом браузер вылетает. Думаю, это было до появления 144-герцовых мониторов, способных рендерить такое количество кадров в секунду. Так что даже если Netscape 2.0 не отображает настолько быстрые GIF… значит ли это, что никто за всю историю не видел GIF на 10 мс? Вылет Netscape — довольно зловещий знак, кто знает, что могло бы произойти, если бы кто-то слишком упорно пытался бы увидеть самый быстрый GIF. Возможно, нам вообще не стоит обновлять браузеры…
Подведём итог
Никто не рендерит GIF по спецификации, хотя и нужно (по моему мнению). Ну а пока, чтобы получить самый быстрый GIF, устанавливайте задержку 2 (20 мс), а не 1 (10 мс). Если бы все обновили свой код для соответствия спецификации, мы бы получили следующие плюсы:
- Поддержка более 256 цветов в одном кадре GIF-анимации
- Поддержка быстрых GIF (с задержкой 10 мс)
- Никакого сбивающего с толку поведения при низкой задержке = медленных GIF
- Улучшенное сжатие для GIF, в которых за кадр обновляется несколько мелких областей
Приложения
Ссылки
- Спецификация GIF 87a. https://www.w3.org/Graphics/GIF/spec-gif87.txt
- Спецификация GIF 89a. https://www.w3.org/Graphics/GIF/spec-gif89a.txt
- What's In A GIF. http://www.matthewflickinger.com/lab/whatsinagif/
- Netscape 2.0 Release Notes. https://web.archive.org/web/19990422100600/http://home.netscape.com/eng/mozilla/2.0/relnotes/windows-2.0.html
- Информация об анимированных GIF. https://web.archive.org/web/20001212090800/http://help.netscape.com/kb/consumer/19970619-18.html
- GIF-анимации. https://web.archive.org/web/20010222034721/http://home.netscape.com/navigator/v2.0/gifanimation.html
- «Соответствующие спецификации» GIF, генерируемые на cooltext. https://cooltext.com/Logo-Design-Burning
- Несколько GIF, взятых с Bugzilla: устранение минимальной задержки кадров из файлов GIF89a. https://bugzilla.mozilla.org/show_bug.cgi?id=1511298
- GIF с танцующей собакой с tenor.com. https://tenor.com/view/dance-dancing-dog-dog-look-up-happy-gif-14211308
Ссылки на исходный код
- Chromium: https://source.chromium.org/chromium/chromium/src/+/refs/tags/98.0.4754.1:third_party/blink/renderer/platform/graphics/deferred_image_decoder.cc;l=353
- Firefox: https://searchfox.org/mozilla-central/source/image/FrameTimeout.h#54 and https://hg.mozilla.org/mozilla-central/file/0b122f0b6fcfda45606c4ee6166436201578f167/image/FrameAnimator.cpp#l314
- Qt: https://github.com/qt/qtbase/blob/6.2.2/src/plugins/imageformats/gif/qgifhandler.cpp#L661
- Opera: https://github.com/proninyaroslav/opera-presto/blob/master/modules/img/src/imagedecodergif.cpp#L282
Любопытные дополнения
Я нашёл страницу Netscape «About GIFs» с маленькой ящерицей на воздушном шаре:
Вот попытки Netscape отрендерить очень сложный GIF Opus Magnum:
А вот Internet Explorer 5, которому не удаётся правильно отрендерить тестовый GIF с красными квадратами. Это показывает, насколько давно программы начали отклоняться от спецификации:
Красные квадраты видны...
А вот программа просмотра изображений Windows XP, которой не удаётся воссоздать многоцветный GIF… за исключением режим миниатюры:
Ошибка отрисовки. Или нет?
Наконец, вот отважные попытки Internet Explorer 5 отрендерить многоцветный GIF:
Скриншот Internet Explorer 5, пытающегося отрендерить многоцветный GIF (ради справедливости нужно сказать, что после завершения рендеринга он устранил проблему).
Автор:
PatientZero