— Pixels Commander

[ In English, На русском ]

Растеризация HTML контента сегодня: подходы, проблемы, решения

Сталкивались ли вы когда — либо с задачей растеризации HTML контента в браузере? Вероятно нет, но это не причина прекращать чтение статьи. Интеграционное тестирование, создание миниатюр страниц, отложенная/удаленная печать содержимого, GPU рендеринг — вот небольшой список задач которые требуют конвертации HTML блока в изображение. Рано или поздно вы встретите их в своих проектах. Давайте рассмотрим имеющийся инструментарий и подходы используемые для растеризации, поймем актуальные проблемы этой области и подумаем о лучшем методе растеризации.

Как мы можем растеризовать HTML сейчас?

Стандартных API для растеризации не существует, однако есть несколько обходных путей:

  • Вставка контента в SVG тэг foreignObject с последующей растеризацией через dataURI
  • Отрисовка содержимого на Canvas на основании CSS стилей страницы, что фактически требует имплементации всей бокс модели и стилизации на JavaScript
  • Растеризация через PhantomJS

Рассмотрим плюсы и минусы каждого метода.

Экспорт контента через SVG

Эта методика впервые описана Robert`O Callahan в статье «Drawing DOM Content To Canvas«, реализована в библиотеке rasterizeHTML.js Christoph Burgmer и построена на возможности импорта HTML контента в SVG тег foreignObject и последующей растеризации SVG в dataURL.Главным недостатком техники является полное отсутствие совместимости с IE, так же проблемным является использование внешних ресурсов в растеризуемом контенте. Все содержимое должно располагаться на том же домене или быть сконвертированы в dataURL и уже затем вставлены в SVG. В целом техника весьма проста в реализации, но перечисленные недостатки делают невозможным ее использование в публичных проектах.

Отрисовка содержимого на Canvas на основании содержимого страницы

Подход несравненно сложнее в реализации так как требует JavaScript имплементации HTML лэйаут модели и CSS. К счастью это уже сделано Niklas von Hertzen и используется в его библиотеке html2canvas. При использовании этого подхода same-origin проблема остается сохраняется, так же (временно) отсутствует поддержка Retina устройств, что делает полученые изображения несколько размытыми, но в то же время есть большое приемущество — хорошая кросс-браузерность. Поддержка IE9+ делает html2canvas единственным актуальным решением для публичных проектов.

Растеризация через PhantomJS

Метод отличается от остальных тем, что требует загрузки страницы в headless браузер PhantomJS и растеризации через вызов webpage.render. Очевидно, что это не подходит для клиентских приложений и может использоваться только в связке с сервером или для интеграционного тестирования. Но даже в тестировании возникают проблемы так как PhantomJS является конкретной имплементацией рендеринга и результат растеризации может не совпадать с изображением полученным от другого браузера. Это отчасти является проблемой всех перечисленных подходов.

Общая проблема — неконсистентность

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

Общая проблема — быстродействие

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

Сравнение быстродействия библиотек и подходов

В тесте замерялось время растеризации документа средней сложности, содержащей 282 DOM узла (из них 10 небольших изображений и полноэкранное фоновое изображение) на MacBook Pro 2,3 GHz Intel Core i7 16 GB 1600 MHz DDR3.

Результаты теста:

Диаграма - тест библиотек для растеризации HTML

  • html2canvas.js 170 msec
  • rasterizeHTML.js 450 msec

Как мы видим html2canvas в несколько раз быстрее, но время работы все равно на порядок больше максимального бюджета кадра (16 msec), что в свое время приведет к задержкам при использовании в интерактивных приложениях, а на менее мощных устройствах можно ожидать критически низкого быстродействия.

Решение проблем — нативное Rasterization API

С учетом озвученных выше проблем было разработан черновик спецификации на нативное API для растеризации. В кратце, рендеринг контента предлагается выполнять методом document.rasterize(element, options), который будет возвращать объект типа ImageData, который затем может быть использован для сохранения в файл, отправки на сервер или повторного изображения через Canvas/WebGL/IMG тэг. Для обеспечения безопасности предполагается скрытие значений в полях ввода и следование same-origin политике при растеризации iframe. С черновиком спецификации вы можете ознакомиться тут.

Rasterization API нуждается в ваших комментариях и экспертизе! Присоединяйтесь!

P.S. В заключение можно сказать, что в Mozilla уже экспериметировали в области растеризации и в свое время внедрили drawWindow API, который не был принят с энтузиазмом из-за недостаточной гибкости и мотивации инициативных групп.