image

Генерация SVG из изображений может использоваться для Placeholder’ов.

Я занимаюсь оптимизацией изображений и картинок для их быстрой загрузки. Одна из самых интересных областей исследования это Placeholder’ы: что показывать, когда изображение еще не загружено.

В последние дни я сталкивался с некоторыми методами загрузки, которые используют SVG, и я хотел бы описать их в этом посте.

В этом посте мы рассмотрим следующие темы:

  • Обзор различных типов Placeholder’ов
  • Placeholder на основе SVG (контуры, фигуры и силуэты)
  • Автоматизация процесса.



Перевод выполнен при поддержке компании EDISON Software, которая профессионально занимается созданием корпоративных сайтов на WordPress, а так же дизайнит персонажей для фирменного стиля.


Обзор различных типов Placeholder’ов


В прошлом посте я писал о Placeholder’e и ленивой загрузке изображений, а также говорил об этом. Когда вы делаете ленивую загрузку изображений, неплохо подумать о том, что делать в качестве Placeholder’а, поскольку это может иметь большое влияние на восприятие пользователя. Недавно я описывал несколько вариантов:

image

Несколько вариантов заполнения области изображения перед его загрузкой.

Сохранение пространства для изображения пустым: в мире адаптивного дизайна это предотвращает сдвиг контента. Эти изменения компоновки плохи с точки зрения пользователя, и для представления. Браузер вынужден выполнять вычисления компоновки каждый раз, когда он извлекает размеры изображения, оставляя для него пространство.

  • Placeholder: Представьте, что мы показываем изображение профиля пользователя. Мы могли бы отобразить силуэт на месте изображения. Он отображается не только при загрузке основного изображения, но также и при неудачном выполнении запроса или когда пользователь вообще не установил изображение профиля. Эти изображения, как правило, основаны на векторах, и из-за их небольшого размера являются хорошим кандидатом, который должен быть встроен.
  • Solid colour: возьмите цвет из изображения и используйте его в качестве цвета фона для Placeholder’а. Это может быть доминирующим цветом, наиболее ярким… Идея состоит в том, что он основан на загружаемом изображении и должен помочь сделать переход между “без изображения” на изображение более плавным.
  • Размытое изображение: также называется техникой размытия. Вы создаете малую версию изображения, а затем переходите к полной. Начальное изображение маленькое как в пикселях, так и в размерах. Для удаления артефактов изображение масштабируется и размывается. Ранее я писал об этом в этих статьях How Medium does progressive image loading, Using WebP to create tiny preview images и More examples of Progressive Image Loading.
  • Оказывается, есть много других вариантов, и многие умные люди разрабатывают другие методы создания Placeholder’ов.

Один из них это использование градиентов вместо сплошных цветов. Градиенты могут создавать более точный предварительный просмотр конечного изображения с минимальными расходами (увеличение полезной нагрузки).

image

Использование градиентов в качестве фона. Скриншот от Gradify, который больше не в сети. Код на GitHub.

Другой метод — использование SVG на основе изображения.

Placeholder’ы на основе SVG


Мы знаем, что SVG идеально подходит для векторных изображений. В большинстве случаев мы хотим загрузить растровое изображение, поэтому вопрос заключается в том, как векторизировать изображение. Некоторые опции используют контуры, фигуры и области.

Контуры


В предыдущем посте я объяснил, как узнать контуры в изображении и создать анимацию. Моя первоначальная цель состояла в том, чтобы попытаться нарисовать области, векторизируя изображение, но я не знал, как это сделать. Я понял, что использование контуров также может быть инновационным, и я решил оживить их, создав эффект «рисования».

image

Codepen

Drawing images using edge detection and SVG animation

Формы


SVG также может использоваться для рисования областей из изображения без использования контуров / границ. В некотором смысле, мы будем векторизовать растровое изображение для создания Placeholder’a.

Когда-то я пытался сделать что-то подобное с треугольниками. Вы можете увидеть результат в моих беседах в CSSConf и Render Conf.

image

Codepen выше является доказательством концепции Placeholder’ов на основе SVG, состоящий из 245 треугольников. Генерация треугольников основана на триангуляции Делоне с использованием polyserver Поссана. Как и ожидалось, чем больше треугольников использует SVG, тем больше размер файла.

Primitive и SQIP, метод LQIP на основе SVG


Тобиас Балдауф работает над другим методом низкого качества изображения, используя SVG, называемый SQIP. Прежде чем копаться в SQIP, я дам обзор Primitive, библиотеки, на которой основан SQIP.

Primitive довольно увлекательный, и я определенно рекомендую вам его попробовать. Он преобразует растровое изображение в SVG, состоящий из перекрывающихся фигур. Его небольшие размеры делают его подходящим для наложения его прямо на страницу. Еще один хороший вариант, а также значимый placeholder в начальной загрузки HTML.

Primitive генерирует изображение, основанное на таких фигурах, как треугольники, прямоугольники и круги (и несколько других). На каждом шаге он добавляет новую. Чем больше шагов, тем результирующее изображение выглядит ближе к исходному. Если у вас на выходе SVG, это также означает, что размер выходного кода будет больше.

Чтобы понять, как работает Primitive, я провел его через пару изображений. Я сгенерировал SVG для художественной работы, используя 10 фигур и 100 фигур:

image

к картинкам(Обработка этого изображения с помощью Primitive, используя 10 Фигур и 100 фигур.)

image

При использовании 10 фигур в изображении мы начинаем понимать исходное изображение. В контексте placeholder’a изображений существует возможность использовать этот SVG в качестве placeholder’a. На самом деле, код для SVG с 10 фигурами очень мал, около 1030 байт, который снижается до ~ 640 байт при передаче вывода через SVGO.

<path fill=”#817c70" d=”M0 0h1024v1024H0z”/><path fill=”#03020f” d=”M178 994l580 92L402–62"/><path fill=”#f2e2ba” d=”M638 894L614 6l472 440"/><path fill=”#fff8be” d=”M-62 854h300L138–62"/><path fill=”#76c2d9" d=”M410–62L154 530–62 38"/><path fill=”#62b4cf” d=”M1086–2L498–30l484 508"/><path fill=”#010412" d=”M430–2l196 52–76 356"/><path fill=”#eb7d3f” d=”M598 594l488–32–308 520"/><path fill=”#080a18" d=”M198 418l32 304 116–448"/><path fill=”#3f201d” d=”M1086 1062l-344–52 248–148"/><path fill=”#ebd29f” d=”M630 658l-60–372 516 320"/>

Изображения, сгенерированные с 100 фигурами, больше, как и ожидалось, весом ~ 5 КБ после SVGO (ранее 8КБ). Они имеют большой уровень детализации с небольшой полезной нагрузкой. Решение о том, сколько треугольников использовать, будет во многом зависеть от типа изображения (например, контраста, количества цветов, сложности) и уровня детализации.

Можно было бы создать скрипт, похожий на cpeg-dssim, который изменяет количество используемых фигур до тех пор, пока не будет достигнут порог структурного подобия (или максимального количества фигур в худшем случае).

Эти SVG также отлично подходят для использования в качестве фоновых изображений. Будучи ограниченным по размеру и основанным на векторе, они являются хорошим кандидатом для изображений героев и больших бекграундов, которые в противном случае показывали бы артефакты.

SQIP


По словам Тобиаса:
SQIP-это попытка найти баланс между этими двумя крайностями: использование Primitive для генерации SVG, состоящего из нескольких простых фигур, которые приближают основные объекты, видимые внутри изображения, оптимизирует SVG с помощью SVGO и добавляет к нему фильтр Gaussian Blur. Это производит SVG placeholder, который весит всего ~800-1000 байт, выглядит гладко на всех экранах и обеспечивает визуальную реплику содержимого изображения.

Результат аналогичен использованию крошечного изображения Placeholder’a для технологии размытия (что делают Medium и другие сайты). Разница заключается в том, что вместо использования растрового изображения, например JPG или WebP, Placeholder является SVG.

Если мы запустим SQIP против исходных изображений, мы получим следующее:

image

Выходные изображения с использованием SQIP для первого изображения и второго.

Выходной SVG составляет ~ 900 байт, и, проверяя код, мы можем обнаружить фильтр feGaussianBlur, применяемый к группе фигур:

image

SQIP также может выводить тег изображения с содержимым SVG Base 64 encoded:

<img width="640" height="640" src="example.jpg” alt="Add descriptive alt text" style="background-size: cover; background-image: url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAw…<stripped base 64>…PjwvZz48L3N2Zz4=);">

Силуэты


Мы просто посмотрели на использование SVG для контуров и примитивных фигур. Другая возможность заключается в векторизации изображений, «прорисовывая» их. Несколько дней назад Mikael Ainalem поделился codepen’он, указав, как использовать 2-цветный силуэт в качестве placeholder’a. Результат очень красивый:

image

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

  • Gatsby, статический генератор сайта, использующий React, поддерживает эти прорисованные SVG сейчас. Он использует JS PORT potrace для векторизации изображений.

    image
  • Craft 3 CMS, который также добавил поддержку силуэтов. Он использует PHP Port of potrace.

    image
  • image-trace-loader, загрузчик Webpack, который использует potrace для обработки изображений.

    image

Также интересно посмотреть сравнение вывода между загрузчиком Webpack от Emil (основано на potrace) и отрисованными вручную SVG от Mikael.

image


Я предполагаю, что вывод, генерируемый potrace, использует параметры по умолчанию. Однако их можно настроить. Проверьте параметры для Image-trace-loader, которые в основном передаются в potrace.

Итого


Мы рассмотрели различные инструменты и методы для генерации SVG из изображений и использования их в качестве placeholder’a. Также WebP — фантастический формат для эскизов, SVG также интересный формат для использования в placeholder’ах. Мы можем контролировать уровень детализации (и, следовательно, размер), он очень сжимается и легко управляется с помощью CSS и JS.

Дополнительные ресурсы


  • Geometrize — это порт Primitive, написанный на Haxe. Существует также реализация на JS, которую вы можете попробовать прямо в своем браузере.
  • Primitive.js, который является портом Primitive в JS. Кроме того, primitive.nextgen, который представляет собой порт приложения Primitive для настольных компьютеров с использованием Primitive.js и Electron.
  • Есть несколько аккаунтов Twitter, где вы можете увидеть примеры изображений, созданных с помощью примитива и геометрии. Посмотрите @PrimitivePic и @Geometrizer.
  • imagetracerjs, который является трассировщиком растрового изображения и векторизатором, написанным на JavaScript. Есть также порты для Java и Android.

Комментарии (40)


  1. sutarmin
    21.11.2017 16:16

    Статья отличная, за перевод спасибо. Но твиты картинками… За что вы так с нами? :)


  1. evnuh
    21.11.2017 16:29

    Как по мне, так вариант с простым векторным svg без гаусса лучше всего смотрится в роли плейсхолдера.
    А что-нибудь известно по ресурсам, требуемым для работы primitive и генерации простейшего svg из 10 фигур? Быстрее оно, чем генерация миниатюр через imagemagick?


    1. stychos
      21.11.2017 17:18

      Тут вопрос скорее об объёмах хранилища. Не думаю, что быстрее, это ведь надо не только прочесть картинку, но и выполнить ряд триангуляций.


      1. evnuh
        21.11.2017 17:30
        +1

        Вот потому и вопрос. По ощущениям Делоне явно сложнее, чем свёртка по пикселям, но всё может решить реализация. Primitive на Go vs imagemagick на C.
        А по размеру SVGO явно получше будет с <1kb против нынешних прейсхолдеров в 2kb. Не бесплатно же нам даётся эта экономия места, скорее всего в обмен на ЦПУ.


  1. ExplosiveZ
    21.11.2017 16:42
    +1

    Я уж думал, что у меня бразуер сломался. Твиты в виде картинок оказались слишком жестокими для меня.


    1. stychos
      21.11.2017 17:20

      Я вот всё никак не пойму - как этим твиттером пользоваться?


      1. Stalker_RED
        21.11.2017 18:56
        +2

        Если очень коротко, там можно писать сообщения и читать их. Подписываетесь на людей или темы которые вам интересны, и читаете.


  1. JTG
    21.11.2017 17:32
    +4

    +1 в копилку способов нагрузить браузер бесполезной работой.


    1. edge790
      21.11.2017 18:13
      +1

      можно же и на бэке сделать. Поставить чтобы аватарку нельзя было менять чаще чем раз в 5(10,20, 100500) раз в минуту или делать плейсхолдеры, только когда она "продержалась" 1(2,3,7, 14) дней.
      Отправлять svg гораздо дешевле


      1. TimsTims
        21.11.2017 18:54
        +5

        Он имел ввиду про то, что отрисовка SVG относительно трудозатратная задача, в отличии от тупого вывода jpeg (как ни странно).


        1. edge790
          21.11.2017 19:16
          +1

          Действительно, я какую-то глупость написал. Думал что под бесполезной работой подразумевается "генерация svg на фронте", но только сейчас понял что для этого нам нужно само изображение "=.=


          По поводу отображения svg vs jpeg — это стандартная дилемма: память vs вычисления.
          С одной стороны у большинства пользователей уже есть быстрый домашний и (иногда) мобильный интернет, в кафе и торговых центрах есть Wi-Fi и загрузить лишнюю маленькую jpeg не составит труда, но с другой стороны браузеру итак есть что грузить, а тут ещё "лишние" jpeg файлы, пускай и небольшого размера.
          С другой стороны вычислительные мощности бОльшей части устройств смогут справиться с выводом 10 фигур, что будет очень кстати для пользователей мобильных устройств.


          Да это решение — не панацея, но оно имеет свои плюсы (например стилизация соцсети/сервиса). Думаю особенно красиво это смотрелось бы с специальной задержкой перед показом картинки и последовательным превращением фигур в фотографию.


          В остальных случаях, как и сказал TimsTims можно использовать прогрессивный JPEG


          1. DistortNeo
            21.11.2017 23:19
            +1

            А что мешает в качестве плейсхолдеров использовать те же инлайнутые JPG или PNG?


  1. TimsTims
    21.11.2017 18:55
    +4

    Кажется, автор заново изобретает велосипед: есть ведь прогрессивная загрузка JPEG, которой почти никто не пользуется…


    1. SHVV
      21.11.2017 22:12

      На сколько я понял, основной упор делается на то, что плейсхолдеры можно встроить непосредственно в саму страницу, и они отобразятся сразу, до того как браузер начнёт грузить реальные картинки. В случае с Progressive JPEG, лаг между пустым местом и хоть чем-то напоминающим картинку будет несколько выше.


      1. SPAHI4
        21.11.2017 23:08

        Думаю, при использовании server push, лаг будет незаметен


        1. Aingis
          22.11.2017 13:37
          +1

          Server push для картинок? Месье знает толк в извращениях!


      1. TimsTims
        22.11.2017 09:37

        В случае с Progressive JPEG, лаг между пустым местом и хоть чем-то напоминающим картинку будет несколько выше.
        Отнимаем несколько десятых секунды на отрисовку SVG, иэ тот лаг уже компенсируется при «нормальном» интернете. Даже более того, небольшое зависание браузера и компьютера (100% CPU) перевешивает чашу весов в пользу JPEG.

        SVG из статьи выигрывает только когда у пользователя очень плохой канал интернета(или ваш сайт очень жирный), и ему загрузить 100% CPU на пару секунд выгоднее, чем ждать +60 секунд на полную прогрузку.


  1. amarao
    21.11.2017 19:21
    +2

    1. 1-4к — этого вполне достаточно для jpeg-превьюшки терпимого качества.
    2. Большая часть этого svg — текст, который можно было бы просто закодировать как координаты, прозрачности и цвета для треугольников. Если каждый треугольник описывается 6 u16 и цветом, то это 16 байт на треугольник. 100 треугольников — 1600 байт. Если же обнаглеть и разрешить огрублять позции треугольников до байта, то это 10 байт на треугольник, 100 треугольников — 1кб без сжатия.

    Но у меня вопрос: если на странице 30 превьюшек, то сколько мегафлопов будет сожрано у пользовательской батарейки в процессе «предрендеринга»?


    1. edge790
      21.11.2017 20:17
      +2

      Меня беспокоил такой же вопрос.
      Решил проверить, сделал примитивный тест на codepen, который просто создает svg с n количеством треугольников.
      Браузер на компьютере подвисает на 3-4 секунды от 100 000 треугольников, после чего функционирует нормально.
      У телефона такие же проблемы начинаются с 10 000 треугольников (Xiaomi Redmi 3S)


      1. amarao
        21.11.2017 20:19
        +4

        Итого: 30 превьюшек по 100 треугольников делают нам 3000 треугольников. То есть примерно секунду «подвисания» браузера мобильного телефона или 0.3с десктопа (100% CPU, насколько я понимаю).

        Я бы сказал, что дофига.


      1. ad1Dima
        22.11.2017 09:34

        Ламера вопрос: а если ту же свгшку готовым файлом вставить, а не добавлять треугольники в цикле по одному, разве быстрее не будет?


        1. edge790
          24.11.2017 14:54

          Хороший вопрос.
          Первая моя мысль мысль: выгода была бы такой, что ей можно было бы пренебречь, потому что цикл из 10 000 элементов проходится относительно легко.
          Вторая что JS страдает при каждом добавлении нового элемента в DOM, поэтому выгода могла быть большой.
          Третья: готовым файлом — это как?


          1. ad1Dima
            24.11.2017 14:57

            Третья: готовым файлом — это как?
            placeholder.svg
            Не знаю я, как это в вебе правильно делать. Вероятно как-то так habrahabr.ru/company/edison/blog/342848/?reply_to=10536428#comment_10531650


          1. DistortNeo
            24.11.2017 15:06

            Вторая что JS страдает при каждом добавлении нового элемента в DOM, поэтому выгода могла быть большой.

            По-моему, это было актуально 2-3 года назад.
            Сейчас что добавление нового элемента в DOM, что генерация одним большим куском работают одинаково быстро.


    1. MrShoor
      22.11.2017 02:35

      Но у меня вопрос: если на странице 30 превьюшек, то сколько мегафлопов будет сожрано у пользовательской батарейки в процессе «предрендеринга»?

      Тут кстати есть интересный момент. Если мы знаем, что наша svg содержит только одноцветные треугольники, то отрисовать это с помощью GPU расплюнуть. Поэтому если заморочится и написать JS рисующий через WebGL запросто можно получить ощутимый профит, и не сильно жечь батарею.


  1. radist2s
    21.11.2017 20:16
    +2

    Кстати, для вывода data-uri SVG изображений можно не кодировать в Base64, достаточно закодировать SVG в виде Url, то есть прогнать через какой-нибудь httpencode


    <img style="background: url(data:image/svg+xml;utf8,[svg_http_encoded]">

    Это позволит во-первых, немного уменьшить размер текста, во-вторых, браузеру не нужно будет выполнять декодирование base64, что может быть медленным, в-третьих, это лучше сжимается gzip'ом. Если кодировка не важна, можно ее опустить: data:image/svg+xml,[svg_http_encoded]


    1. lenar
      21.11.2017 21:33

      Насколько я помню, в IE11 есть странный глюк: если не кодировать в base64, это не будет работать.


      1. radist2s
        22.11.2017 14:26

        Работает вплоть до IE9, было бы круто ели бы вы показали пример если что-то действительно не работает, чтобы не наступить на грабли, если что.


      1. Aingis
        22.11.2017 14:47

        Для IE11 надо экранировать больше символов, включая „<“ и „>“. (Но меньше чем стандартная url-кодировка.) Например, так делает URL-encoder for SVG авторства @yoksel. См. мой пулл-реквест в кодировщик с перечнем экранируемых символов. Там же используется трюк с тем, что можно не экранировать пробелы, если заключить URL в кавычки.

        P.S. Кусок «;utf8», кстати, лишний. Я вообще не нашёл способа изменить кодировку в data-url'е.


    1. Psychosynthesis
      22.11.2017 02:48

      А разве нет ограничения на размер URL?


      1. radist2s
        22.11.2017 14:35
        +1

        А какая разница, мы httpencoded засунем внутрь url() или base64? Но, в целом, ограничение на размер data:URL было у IE8(32кб) и какой-то старой Opera.


        1. Psychosynthesis
          22.11.2017 16:41

          Извините, я сейчас не так пристально слежу за темой веб-разработки, не знал что это уже не актуально.


  1. theonlymirage
    21.11.2017 22:29

    Статья интересная, но на превьюшке для затравки алгоритм отработал странно. Вместо ожидаемого одного треугольника, видим два.
    image
    Кого это заинтересует + найдётся время изучить предложенные алгоритмы генерации (с текущими исходниками) — думаю там есть что улучшить и написать хорошую статью.


    1. TheShock
      22.11.2017 00:04

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


  1. Psychosynthesis
    22.11.2017 02:51

    Я правильно понимаю что вот этот пример: codepen.io/jmperez/pen/oogqdp

    На нативном JS написан?


    1. Stalker_RED
      22.11.2017 08:56

      А что, есть много других js-ов?


      1. TimsTims
        22.11.2017 09:41

      1. Psychosynthesis
        22.11.2017 16:39

        Вы прям как эталонный хабровчанин отвечаете.

        Я таки думал очевидно, что вопрос про фреймворки и библиотеки, ибо я не знаток Codepen, ведь вполне было возможно что там где-то в настройках используемые библиотеки подключаются. Я бегло посмотрел код, не нашёл ничего из того же jQuery, но всё же решил уточнить.


  1. dom1n1k
    22.11.2017 12:52
    +1

    Заливка доминирующим цветом — абсолютно бесполезная ерунда.

    SVG-картинка из треугольников — теоретически интересно, практически смысла ноль. Они пишут, что 100 треугольников после оптимизации весят около 5 кб. Я прикинул: аналогичный вес имеет PNG-картинка размером порядка 100-120px по каждой стороне, которая дает отличное представление об изображении и намного меньше ест ресурсы. Вообще говоря, 100px для LQIP — это сильно избыточно, обычно берут что-то в пределах 20-50px.

    SVG-силуэты — с сугубо утилитарной точки зрения тоже очень спорно, но тут можно совместить практичность и эстетику, создав интересный дизайнерский эффект. Но опять же не универсально.

    Мой выбор — банальный растровый LQIP весом не более 1 кб, причем даже без блура.


    1. Ganster41
      24.11.2017 18:00
      +1

      SVG еще отлично жмется в GZIP