- PVSM.RU - https://www.pvsm.ru -
Вместо тысячи слов...
xZibit тоже рад, ведь здесь GIF вставлены в стикеры, чтобы быть вставлеными в GIF для КДПВ!
А теперь о подробностях реализации.
Всё началось с дискуссии в чатике Telegram-разработчиков о грядущей фиче:
Мужик сказал — мужик сделал! Первый прототип на Pillow и svgwrite, разбирающий GIF'ку на пиксели и преобразующий их в векторные квадратики с предпросмотром в SVG, был написан за один выходной.
Веселье началось дальше…
Доселе с форматами в Telegram то и дело хитрили. Сделали поддержку GIF-анимаций — на самом деле они конвертируются в MP4-видео. Сделали поддержку стикеров — выгружаются они в PNG, но преобразуются в WebP. В этот раз всё честнее: что на входе, то и на выходе.
Для анимированных стикеров в Telegram используется не GIF, не видео, и даже не какой-нибудь устоявшийся формат векторной графики типа SVG или — упаси Ктулху! — Flash. В нём задействован новомодный формат, вышедший из-под крыла Airbnb — Lottie. Доселе он имел некоторую известность в среде мобильных разработчиков, но благодаря Telegram, возможно, обретёт бо́льшую популярность.
По сути своей, файлы Lottie являются сериализованными в JSON проектами Adobe After Effects, по максимуму реализующими все возможности этой программы. С отображением, увы, всё не так радужно [1]. Хотя готовых «официальных» реализаций библиотеки для рендеринга Lottie и много, как раз под покрываемые Telegram платформы: Android, iOS, Qt и Web — лишь часть из возможностей формата реализована во всех из них. В Telegram пошли ещё дальше и ограничили [2] перечень поддерживаемых возможностей, а также «придумали» свой формат, который отличается от обычного Lottie всего лишь упаковкой в GZip и параметром "tgs": 1
. Кажется, я знаю, где сейчас работает Денис Попов! :-)
И если с документацией на библиотеки для разных платформ всё довольно неплохо, то найти хоть какое-то описание устройства формата, увы, не удалось — только JSON-схему [3] в исходниках lottie-web. Пришлось попутно ковыряться в существующих анимациях, дабы понять общие концепции формата. Также обнаружились расхождения реальных файлов со схемой: в частности, в слоях типа 4 [4], согласно схеме, вложенные объекты хранятся в свойстве "it"
— однако в реальных файлах ключ называется "shapes"
, а "it"
не работает.
Выясненные нюансы формата:
"sh"
— кривые Безье; конвертер пока использует только тип "rc"
— прямоугольники), заливка (тип "fl"
) и трансформация (тип "tr"
).Отсюда прямо следует первая проблема: избыточность. Хотя в JSON-схему недавно добавлены значения по умолчанию для параметров трансформаций — в библиотеках они не реализованы. Так что задавать их в явном виде всё равно нужно.
Казалось бы, это и не проблема вовсе? Даже простенький GZip неплохо справляется со сжатием вопиюще повторяющихся данных, и 1 МБ сырого JSON магическим образом превращается в пару десятков килобайт, которые спокойно пролезают в заявленное ограничение в 64 кБ. Не тут-то было!
Загружаю я, значит, пухленькую анимацию, которая спокойно отображается lottie-web, в Telegram — и тут вместо условно красивого пиксель-арта на меня смотрит статическое размазанное вот это:
Что такое?! А оказалось, на разжатые данные тоже есть явно не указанное ограничение в 1 МБ. Представитель команды Telegram оперативно подтвердил [6] его и сообщил о грядущем поднятии лимита до 2 МБ.
Даже если эти проблемы решат — стикеры, выходящие за пределы 1 МБ несжатых данных и не содержащие трансформаций, окажутся недоступными для пользователей старых версий Telegram. Так что придётся, видимо, соблюдать ограничения и впредь.
Pillow, наряду с OpenCV, можно назвать индустриальным стандартом для обработки изображений в Python. Мало того, он неплохо заточен и под особенности GIF: поддерживает индексированные цвета, даёт доступ к палитре. Поддерживает преобразование пиксельной карты в NumPy-массив, что важно для продуктивной обработки. Даже статистику по цветам собирает! Но обнаружились и минусы:
Посему пришлось искать замену. В качестве неё выступил модуль gif2numpy [7]. Он «заточен» под особенности GIF и предоставляет доступ ко всем техническим свойствам как изображения, так и отдельных кадров, в том числе GCE. Таким образом, проблему считывания задержек он решает.
Прозрачность, как оказалось, gif2numpy не поддерживает вообще: цвета сразу преобразуются в три канала с разрядностью в байт, без учёта разрядности и сохранения индексов цветов. Благо, модуль состоит из одного файла, так что не составило труда включить его в проект и доработать, зарезервировав под прозрачность цвет #FE00FE
.
Проблему с частичными кадрами решить оказалось нетривиально. gif2numpy пытается накладывать [8] такие кадры на предыдущий, однако не проверяет параметры наложения, из-за чего также не всегда выходит правильный результат. Дабы не возиться с флагами, добавлена предварительная обработка изображений с помощью gifsicle
с ключом --unoptimize
— он преобразует частичные кадры в полные. А заодно приводит их к использованию глобальной палитры, что устранило необходимость отдельным образом обрабатывать прозрачный цвет при использовании собственной палитры кадра.
Квадратики — это хорошо, но с такими ограничениями нужно проявить больше фантазии, иначе в Telegram не «пролезают» даже миниатюрные GIF'ки.
Первым в ход пошло нечто похоже на RLE: соседние по горизонтали квадратики одного цвета объединяются в один прямоугольник.
Далее — черёд эксплуатации особенностей Lottie. Поскольку каждый слой имеет произвольное время начала и конца — можно применить технику, которая давным-давно используется видеокодеками, и отчасти в самом GIF: квадратики, которые остаются на одном месте в течение нескольких кадров, можно слить в один слой, во время отображения которого сменяется несколько других. Что и реализовано, пока только для пар соседних слоёв.
Идей, которые здесь можно применить, навалом:
......
.O..O.
......
.OOOO.
......
Автор: bodqhrohro
Источник [12]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/python/324364
Ссылки в тексте:
[1] всё не так радужно: https://airbnb.io/lottie/#/supported-features
[2] ограничили: https://core.telegram.org/animated_stickers
[3] JSON-схему: https://github.com/airbnb/lottie-web/tree/master/docs/json
[4] слоях типа 4: https://github.com/airbnb/lottie-web/blob/master/docs/json/layers/shape.json
[5] много: https://github.com/airbnb/lottie-web/tree/master/docs/json/shapes
[6] подтвердил: https://github.com/telegramdesktop/tdesktop/issues/6254
[7] gif2numpy: https://github.com/bunkahle/gif2numpy
[8] пытается накладывать: https://github.com/bodqhrohro/giftolottie/blob/5131a368607ebb177f2d61d6564c61264dbfbcd0/vendor/gif2numpy.py#L663
[9] неплохой алгоритм: https://github.com/mikolalysenko/rectangle-decomposition/blob/master/decomp.js
[10] Исходники: https://github.com/bodqhrohro/giftolottie/
[11] Канал: https://t.me/retrostickers
[12] Источник: https://habr.com/ru/post/460411/?utm_campaign=460411&utm_source=habrahabr&utm_medium=rss
Нажмите здесь для печати.