На карте 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 — библиотека для растеризации векторных изображений с GPU-ускорением. Она является частью проекта Servo — браузерного движка, написанного на Rust. Немного изучив документацию, мы нашли несколько моментов, которые не позволили бы полноценно перевести наши продукты на эту библиотеку.  Минимальное требование к графическому API у библиотеки — OpenGL ES3. Мы сейчас кое-где ещё на OpenGL ES2 и только собираемся переезжать.  Библиотека находится на начальной стадии разработки и, судя по всему,  обновляется не очень регулярно. Librsvg — библиотека для парсинга и растеризации SVG-изображений, используемая проектом GNOME. Её задача — растеризация SVG из популярной библиотеки Cairo. Отсюда и растут ноги у её недостатков. Сборка зависимостей. Пакетный менеджер Rust, cargo, не собирает эти зависимости сам, а на Windows их нужно собирать поштучно с помощью autoconf/make, с использованием либо MSYS, либо Cygwin. Сама сборка зависимостей — это добрая часть стека GTK (Cairo, GDK-Pixbuf, Pango, GLib). Размер библиотеки. Вместе с зависимостями она может достигать 40 МБ. А максимальный размер приложений в App Store и Google Play, которые можно скачивать не через Wi-Fi, — 100 и 200 МБ. Трата от 20 до 40% места только на растеризацию SVG выглядит как серьёзный удар по конкурентоспособности SDK.  Librsvg нам точно не подходит — несмотря на качество этой библиотеки, она слишком большая для нашей задачи.  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 мы начали проводить испытания — сравнивали скорость растеризации и внешний вид изображений с QtSvg. Практически сразу оказалось, что растеризация у пропатченной resvg по сравнению с QtSvg проходит в шесть раз быстрее. Позже, после обновления resvg, заметили регресс производительности. Скорость стала уже, конечно, не в шесть раз больше, но качество не пострадало. Во время тестирования resvg мы нашли небольшую проблему — в версии библиотеки на Android не загружались системные шрифты, потому что зависимость resvg-fontdb не поддерживает Android и iOS, хотя их поддержку возможно добавить буквально парой строк кода. Несмотря на это, мы не стали отказываться от resvg — библиотека позволяет работать с собственными шрифтами, которые мы чаще всего и используем.  Перед испытаниями мы написали адаптер, чтобы использовать resvg в этих наших случаях, и небольшой бенчмарк, который тестировал отдельные функции растеризатора — инициализацию растеризатора, парсинг svg-файла, вычисление размера по умолчанию и растеризацию. Для экспериментов собрали три набора данных по пять файлов в каждом: Изображения с проблемной растеризацией. Это логотипы разных организаций, на которых можно увидеть как прогресс, так и регресс в процессе растеризации.  Несколько случайно выбранных векторных изображений из стандартных ресурсов 2ГИС. Здесь нас больше всего интересовали изменения в производительности. Случайно выбранные векторные изображения из тестовой базы resvg. Тут нам хотелось узнать, насколько плохо с ними справится QtSvg. Всё исследование разбили на шесть основных частей. 1. Скорость инициализации растеризатора Инициализация растеризатора у QtSvg происходит за 84 нс, а у resvg — за 10 000 нс. В некоторых экспериментах эти цифры могли немного отличаться, но общая тенденция все равно сохранялась. Несмотря на то, что разница огромна, она едва ли критична: инициализация выполняется редко, в идеальном случае — единственный раз за жизненный цикл приложения. А абсолютное время инициализации даже в плохом случае как resvg не выглядит огромным. 2. Парсинг SVG Во втором эксперименте библиотека resvg показала хорошие результаты — не превосходные, но вполне конкурирующие с QtSvg.  Набор/файл QtSvg, мкс resvg, мкс разница, мкс 1/0 134 134 0  1/1 225 220 -5 ​ 1/2 134 122 -12 ​ 1/3 103 109 +6 ​ 1/4 339 398 +59 ​ 2/0 109 87 -22 ​ 2/1 131 84 -47 ​ 2/2 112 82 -30 ​ 2/3 188 110 -78 ​ 2/4 100 90 -10 ​ 3/0 78 68 -10 ​ 3/1 80 146 +66 ​ 3/2 203 148 -55 ​ 3/3 75 138 +63 ​ 3/4 82 241 159 ​ 3. Вычисление размера по умолчанию Здесь resvg тоже выигрывает у QtSvg. Судя по результатам, resvg использует какой-то алгоритм с константной сложностью. Набор/файл QtSvg, мкс resvg, мкс разница, мкс 1/0 12 2 -10 ​  1/1 14 2 -12 ​ 1/2 13 2 -11 ​ 1/3 13 2 -11 ​ 1/4 12 2 -10 ​ 2/0 9 2 -7 ​ 2/1 8 2 -6 ​ 2/2 8 2 -6 ​ 2/3 9 2 -7 ​ 2/4 8 2 -6 ​ 3/0 9 2 -7 ​ 3/1 10 2 -8 ​ 3/2 9 2 -7 ​ 3/3 9 2 -7 ​ 3/4 10 2 -8 ​ 4. Растеризация Первый эксперимент, где нас не впечатлили результаты resvg. По всем показателям она была медленнее QtSvg, иногда — на порядок. Мы не знаем, с чем это может быть связано — может быть, с бóльшим набором поддерживаемых возможностей resvg или с небольшим возрастом библиотеки. Набор/файл QtSvg, мкс resvg, мкс разница, мкс 1/0 216 264 48 ​ 1/1 2041 14 788 12 747 ​ 1/2 1343 12 500 11 157 ​ 1/3 1153 14 160 13 007 ​ 1/4 365 941 576 ​ 2/0 293 703 410 ​ 2/1 276 680 404 ​ 2/2 58 93 35 ​ 2/3 110 286 176 ​ 2/4 319 732 413 ​ 3/0 106 893 787 ​ 3/1 242 351 109 ​ 3/2 234 452 218 ​ 3/3 341 371 30 ​ 3/4 594 1081 487 ​ 5. Регресс и прогресс на проблемных логотипах Какого-то сильного отличия между библиотеками в качестве обработки логотипов мы не нашли. Ошибка растеризации, хоть и разная, наблюдается только на одном из изображений. 6. Ошибки растеризации у QtSvg В этом эксперименте выяснили, что растеризация у QtSvg не идеальна и имеет слабые стороны там, где resvg нормально справляется. Спустя шесть экспериментов мы пришли к выводу, что библиотека resvg хоть и не идеальна для нашего проекта, мы всё же попробуем использовать её в работе нашей Android-версии SDK вместо QtSvg. Важная ремарка — все наши эксперименты проводились для дальнейшей интеграции resvg с Android. Когда мы добрались до iOS-версии, уже вышла новая версия resvg 0.12, в которой пакеты зависимостей были переписаны с С++ на Rust. Это упростило интеграцию с нашим кодом — количество путей к инструментам для передачи в cargo уменьшилось и появилась статическая компоновка, потому что второй экземпляр Harfbuzz исчез. Мы этим оперативно воспользовались. Сейчас мы внедрили библиотеку resvg в Mobile SDK и заканчиваем её интеграцию в Android- и iOS-приложения 2ГИС. Наши партнёры по экосистеме уже могут работать с тестовой версией приложений, в которых используется resvg, и даже оставлять свои отзывы. Пока жалоб на растеризацию не было ;) Программист C++