Кроссплатформенная растеризация SVG — сравниваем библиотеки и экспериментируем

15 ИЮНЯ 2022

------------------------------------------------------------------------

Наш телеграм-канал: t.me/@mailsgun_online

Youtube: youtube.com/@mailsgun_online

------------------------------------------------------------------------

Обновить

Рейтинг

2ГИС

Главные по городской навигации

На карте 2ГИС очень много картинок — те же знаки дорожного движения и логотипы компаний. Графические API, которые в наши карты предоставляют Android и iOS, обычно не могут рисовать векторную графику напрямую, поэтому нам приходится её растеризовать. А так как мы заранее не знаем нужный размер картинки и не можем её растеризовать до сборки ресурсов, используем растеризаторы.

И если для 2ГИС на Android и iOS мы можем использовать платформенные решения, то затаскивать их в Mobile SDK было бы, мягко говоря, не очень правильно.

При внедрении поддержки векторных изображений в мобильный 2ГИС мы не нашли нормальное кроссплатформенное решение, поэтому использовали разные растеризаторы для Android и iOS. На Android — это QtSvg, потому что приложение уже использует библиотеку Qt. На iOS нашли избыточную по функциональности, но подходящую нам библиотеку Macaw.

В случае SDK такой подход обычно не срабатывает: вряд ли мы сможем интегрировать Qt в нашу SDK только ради QtSvg, а написанную на Swift библиотеку Macaw невозможно использовать на Android. Да и вообще — иметь два различных растеризатора неудобно, поэтому мы начали искать единый инструмент, который сможет работать на наших платформах.

Поиски растеризатора

Стандарт SVG довольно объёмный — только официальная документация версии SVG 1.1 составляет более 800 страниц, а SVG Tiny для смартфонов — ещё 500 страниц. И создание собственного растеризатора — нетривиальная техническая задача.

Поэтому мы решили всё-таки посмотреть уже существующие решения, а уже потом, если не найдём, пытаться создать собственный растеризатор.

Сначала мы собрали пожелания к единому растеризатору:

  • умение парсить и растеризовать SVG,

  • открытость,

  • бесплатность,

  • кроссплатформенность,

  • легковесность (минимум дополнительных зависимостей),

  • простая интеграция с CMake-проектом,

  • статическая компоновка.

Под эти качества подошли три библиотеки — pathfinder, librsvg и resvg. Мы проверили работу каждой.

Pathfinder

Pathfinder — библиотека для растеризации векторных изображений с GPU-ускорением. Она является частью проекта Servo — браузерного движка, написанного на Rust.

Немного изучив документацию, мы нашли несколько моментов, которые не позволили бы полноценно перевести наши продукты на эту библиотеку.

  1. Минимальное требование к графическому API у библиотеки — OpenGL ES3. Мы сейчас кое-где ещё на OpenGL ES2 и только собираемся переезжать.

  2. Библиотека находится на начальной стадии разработки и, судя по всему, обновляется не очень регулярно.

Librsvg

Librsvg — библиотека для парсинга и растеризации SVG-изображений, используемая проектом GNOME. Её задача — растеризация SVG из популярной библиотеки Cairo. Отсюда и растут ноги у её недостатков.

  1. Сборка зависимостей. Пакетный менеджер Rust, cargo, не собирает эти зависимости сам, а на Windows их нужно собирать поштучно с помощью autoconf/make, с использованием либо MSYS, либо Cygwin. Сама сборка зависимостей — это добрая часть стека GTK (Cairo, GDK-Pixbuf, Pango, GLib).

  2. Размер библиотеки. Вместе с зависимостями она может достигать 40 МБ. А максимальный размер приложений в App Store и Google Play, которые можно скачивать не через Wi-Fi, — 100 и 200 МБ. Трата от 20 до 40% места только на растеризацию SVG выглядит как серьёзный удар по конкурентоспособности SDK.

Librsvg нам точно не подходит — несмотря на качество этой библиотеки, она слишком большая для нашей задачи.

Resvg

Resvg оказалась наиболее подходящим для нас вариантом. Она легко компилируется на настольных платформах, без проблем интегрируется с существующим Qt-кодом благодаря совместимым интерфейсам и имеет не такой большой список зависимостей, как предыдущие варианты. При этом она стабильно развивается — это видно по вкладке releases на Гитхабе.

Один минус мы всё же нашли — конфликт версий зависимостей между нашим кодом и кодом resvg. В зависимостях и у resvg, и у нашей SDK есть библиотека поддержки двунаправленного текста и диакритических знаков Harfbuzz — она нужна для корректного отображения текста при растеризации SVG. Но версии Harfbuzz у нас и в resvg были разные, и это мешало нам интегрировать библиотеку статически: не получалось ни избавиться от второй сборки harfbuzz, ни скомпановаться с двумя версиями сразу.

Оказалось, что проблема в итоге не очень большая и такой конфликт всё-таки можно решить, используя одну из версий зависимости. Мы решили тестово интегрировать resvg в Android-версию SDK и посмотреть, как она покажет себя в деле — пусть даже и с динамической компоновкой.

Вооружившись статьёй об интеграции Rust-библиотек в Android-приложение и описанием системы сборки Rust, взялись за работу.

Первые сложности

Мы достаточно быстро нашли у resvg в зависимостях пакеты, для сборки которых необходим C++. Более того, по коду сборочных скриптов стало понятно, что разработчики библиотеки не планируют собирать её под мобильные ОС. Поэтому нам пришлось самостоятельно дописать код для сборочных скриптов — например, для интеграции с CMake мы создали скрипт на Python, который для сборки библиотеки вызывает пакетный менеджер Rust.

Написанный нами скрипт решал достаточно сложную техническую задачу. Для кросс-компиляции Rust-кода раньше требовалось передавать через окружение пути к платформенным инструментам — компилятору C++, архиватору и компоновщику, — а CMake сам по себе с этим не справлялся. Про возможность прописывания путей в ~/.cargo/config мы знали, но подкидывание путей через окружение казалось нам самым удобным решением: это позволяет пробросить пути напрямую из CMake, упростив таким образом конфигурацию машин CI и разработчиков.

Resvg vs QtSvg: начало

После модификации resvg мы начали проводить испытания — сравнивали скорость растеризации и внешний вид изображений с QtSvg. Практически сразу оказалось, что растеризация у пропатченной resvg по сравнению с QtSvg проходит в шесть раз быстрее.

Позже, после обновления resvg, заметили регресс производительности. Скорость стала уже, конечно, не в шесть раз больше, но качество не пострадало.

Во время тестирования resvg мы нашли небольшую проблему — в версии библиотеки на Android не загружались системные шрифты, потому что зависимость resvg-fontdb не поддерживает Android и iOS, хотя их поддержку возможно добавить буквально парой строк кода. Несмотря на это, мы не стали отказываться от resvg — библиотека позволяет работать с собственными шрифтами, которые мы чаще всего и используем.

Эксперименты и сравнения

Перед испытаниями мы написали адаптер, чтобы использовать resvg в этих наших случаях, и небольшой бенчмарк, который тестировал отдельные функции растеризатора — инициализацию растеризатора, парсинг svg-файла, вычисление размера по умолчанию и растеризацию.

Для экспериментов собрали три набора данных по пять файлов в каждом:

  1. Изображения с проблемной растеризацией. Это логотипы разных организаций, на которых можно увидеть как прогресс, так и регресс в процессе растеризации.

  2. Несколько случайно выбранных векторных изображений из стандартных ресурсов 2ГИС. Здесь нас больше всего интересовали изменения в производительности.

  3. Случайно выбранные векторные изображения из тестовой базы resvg. Тут нам хотелось узнать, насколько плохо с ними справится QtSvg.

Всё исследование разбили на шесть основных частей.

1. Скорость инициализации растеризатора

Инициализация растеризатора у QtSvg происходит за 84 нс, а у resvg — за 10 000 нс. В некоторых экспериментах эти цифры могли немного отличаться, но общая тенденция все равно сохранялась.

Несмотря на то, что разница огромна, она едва ли критична: инициализация выполняется редко, в идеальном случае — единственный раз за жизненный цикл приложения. А абсолютное время инициализации даже в плохом случае как resvg не выглядит огромным.

2. Парсинг SVG

Во втором эксперименте библиотека resvg показала хорошие результаты — не превосходные, но вполне конкурирующие с QtSvg.

Набор/файл

QtSvg, мкс

resvg, мкс

разница, мкс

3. Вычисление размера по умолчанию

Здесь resvg тоже выигрывает у QtSvg. Судя по результатам, resvg использует какой-то алгоритм с константной сложностью.

Набор/файл

QtSvg, мкс

resvg, мкс

разница, мкс

4. Растеризация

Первый эксперимент, где нас не впечатлили результаты resvg. По всем показателям она была медленнее QtSvg, иногда — на порядок. Мы не знаем, с чем это может быть связано — может быть, с бóльшим набором поддерживаемых возможностей resvg или с небольшим возрастом библиотеки.

Набор/файл

QtSvg, мкс

resvg, мкс

разница, мкс

5. Регресс и прогресс на проблемных логотипах

Какого-то сильного отличия между библиотеками в качестве обработки логотипов мы не нашли. Ошибка растеризации, хоть и разная, наблюдается только на одном из изображений.

6. Ошибки растеризации у QtSvg

В этом эксперименте выяснили, что растеризация у QtSvg не идеальна и имеет слабые стороны там, где resvg нормально справляется.

Результаты битвы resvg с QtSvg

Спустя шесть экспериментов мы пришли к выводу, что библиотека resvg хоть и не идеальна для нашего проекта, мы всё же попробуем использовать её в работе нашей Android-версии SDK вместо QtSvg.

Важная ремарка — все наши эксперименты проводились для дальнейшей интеграции resvg с Android. Когда мы добрались до iOS-версии, уже вышла новая версия resvg 0.12, в которой пакеты зависимостей были переписаны с С++ на Rust. Это упростило интеграцию с нашим кодом — количество путей к инструментам для передачи в cargo уменьшилось и появилась статическая компоновка, потому что второй экземпляр Harfbuzz исчез. Мы этим оперативно воспользовались.

Сейчас мы внедрили библиотеку resvg в Mobile SDK и заканчиваем её интеграцию в Android- и iOS-приложения 2ГИС. Наши партнёры по экосистеме уже могут работать с тестовой версией приложений, в которых используется resvg, и даже оставлять свои отзывы. Пока жалоб на растеризацию не было 😉

Всего голосов 7: ↑7 и ↓0+7

Просмотры275

  • 7 июня 2016 в 09:13
    Всего голосов 21: ↑21 и ↓0+21

    Просмотры7.5K

  • вчера в 19:49
    Всего голосов 64: ↑60 и ↓4+56

    Просмотры14K

  • сегодня в 10:11
    Всего голосов 53: ↑50 и ↓3+47

    Просмотры7.8K

  • сегодня в 10:01
    Всего голосов 42: ↑39 и ↓3+36

    Просмотры3.6K

  • вчера в 15:07
    Всего голосов 33: ↑33 и ↓0+33

    Просмотры4.3K

  • сегодня в 13:31
    Всего голосов 30: ↑30 и ↓0+30

    Просмотры3K

  • сегодня в 14:09
  • 20 июля в 10:22
  • 6 июля в 15:18
  • 22 июня в 11:04
  • 27 мая в 07:00

Source: habr1

Ну и напоследок напоминаю вам о нашем новом проекте vkserfing bot.