Введение в SVG-фильтры

Введение в SVG-фильтры

Оригинал: SVG Filters 101

В CSS есть хорошее свойство filter, которое поддерживает целых 11 эффектов (насыщенность, яркость, контрастность и др.)

CSS-фильтры достаточно мощные и удобные, но в то же время их возможности ограничены цветовыми манипуляциями и размытием. Применять их в большинстве случаев можно только к изображениям.

Однако у нас есть гораздо более мощный функциональный инструмент, и уже очень давно, хотя вы возможно даже не догадывались об этом, – это SVG. В этой статье мы поговорим именно о SVG-фильтрах, известных также как «примитивы фильтров»: об их возможностях и применении.

Для начала сравним две технологии на примере размытия. В CSS за него отвечает функция blur(), которая применяет к элементу размытие по Гауссу.

blur(6px)

Изображение размывается равномерно в обоих направлениях (по осям X и Y).

На самом деле эта функция – просто упрощенный и ограниченный шорткат для blur-примитива фильтра из SVG, который также позволяет создавать однонаправленный эффект размытия (только по X или только по Y).

Отдельное размытие по оси X и по оси Y с помощью SVG-фильтра
Отдельное размытие по оси X и по оси Y с помощью SVG-фильтра

Применять SVG-фильтры можно и к SVG-элементам и к обычным элементам HTML с помощью CSS-функции url(). То есть вы можете создать нужный фильтр в SVG (об этом позже) с идентификатором myAwesomeEffect и применить его с помощью вот такой конструкции:

С помощью SVG-фильтров вы сможете создавать в браузере вполне себе «Photoshop-эффекты»! Пора раскрыть и использовать их потенциал на полную катушку.

Но что насчет браузерной поддержки..?

Поддержка браузерами

Поддержка у SVG-фильтров впечатляющая. Однако конкретный способ применения эффекта может отличаться в разных браузерах и для разных примитивов. Также могут быть различия в отображении фильтров в SVG и HTML-элементами.

Лучше всего использовать эту технологию в качестве Progressive Enhancement (прогрессивного улучшения) поверх привычных безфильтровых конструкций.

Итак, как создать эффект фильтра в SVG?

Элемент <filter>

Как и у градиентов, масок, паттернов и прочих графических эффектов, для фильтров в SVG выделен специальный элемент – <filter>.

Сам по себе он не отображается на странице, поэтому его можно даже не упаковывать в тег <defs>. Фильтр работает только на конкретном объекте при наличии явной ссылки на него.

Минимальный синтаксис определения и применения фильтра выглядит так:

Этот кусок кода не будет работать, так как тут нет ни одного реального фильтра. Чтобы что-то увидеть, нужно добавить один или несколько примитивов внутрь тега <filter>. Другими словами, здесь мы только создали контейнер для будущих фильтров.

Примитивы фильтров

В SVG элемент <filter> должен содержать набор дочерних примитивов, каждый из которых выполняет одну из основных графических операций над входящим объектом (или входящими объектами).

Каждый примитив удобно называется по той операции, которую он выполняет. Например, для эффекта размытия по Гауссу используется примитив feGaussianBlur. Префикс fe означает filter effect.

Выглядит это примерно так (размытие по Гауссу на 5 пикселей):

 

Сейчас в спецификации определено 17 примитивов, с помощью которых можно создавать очень крутые фильтры, включая генерацию шума, текстуры, световые эффекты, манипуляции с каналами цвета и многое другое.

Каждый фильтр принимает некоторые графические данные на вход и отдает на выходе результат своей работы. При этом вывод одного фильтра может быть использован в качестве входных данных для другого. Это очень важно и очень круто. Благодаря такой технике вы имеет почти бесконечное количество комбинаций различных эффектов.

У каждого примитива может быть один или два входных значения и всегда один-единственный результат. Входные данные указываются в атрибуте inin2, если необходимо), результат – в атрибуте result. То есть вы можете дать псевдоним результату одного фильтра и указать его в атрибуте in другого.

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

Аналогично, если у примитива не указаны в атрибутах входные данные, то они берутся из результата работы предыдущего фильтра.

Сейчас это выглядит довольно запутано, но как только мы перейдем к примерам, станет понятнее.

В качестве входных данных могут использоваться не только результаты работы предыдущих фильтров. Например, можно передать в атрибут in (или in2) следующее:

  • SourceGraphic – исходный элемент, к которому изначально применяется весь фильтр, например, изображение;
  • SourceAlpha – то же самое с небольшим уточнением: тут имеет значение только альфа-канал элемента. Например, для jpeg-изображения это будет черный прямоугольник размером с само изображение.

Это полезные значения, которые точно вам пригодятся. Зачастую для какого-нибудь фильтра в центре цепочки требуется исходная графика или только ее альфа-канал.

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

Прежде чем перейти к реальному примеру, рассмотрим еще одну концепцию – область фильтра (filter region).

Область Фильтра

Важно понимать, на какую область распространяется действие фильтра. Например, у вас есть большой SVG-объект, состоящий из множества элементов, а фильтр вы применяется только к одному элементу (или небольшой группе).

Элементы в SVG имеют «регионы». Каждый регион – это минимальный прямоугольник, ограничивающий элемент (Bounding Box, bbox). Например, для текста этот контейнер выглядит вот так (розовый прямоугольник на рисунке):

Обратите внимание, что этот прямоугольник немного отступает от самого текста, так как он учитывает еще и высоту строки.

Область действия фильтра ограничивается этой самой рамкой. Если применить к этому тексту какой-нибудь фильтр, он будет обрезан по контуру этого прямоугольника. Это разумно, но не очень практично, ведь большинство фильтров выходят за этот контур:

Эффект размытия, применяемый к тексту, обрезается как с правой, так и с левой стороны области ограничивающего прямоугольника текста.
Эффект размытия, применяемый к тексту, обрезается как с правой, так и с левой стороны области ограничивающего прямоугольника текста.

Чтобы предотвратить это обрезание, нужно расширить область фильтра, изменив атрибуты x, y, width, height самого фильтра (элемента <filter>).

На самом деле по умолчанию так и происходит, дефолтные значения выглядят вот так:

При необходимости вы можете их переопределить, увеличив или уменьшив область фильтра.

Единицы измерения в атрибутах x, y, width, height зависят от значения атрибута filterUnits, который определяет систему отсчета. Всего их два:

  • objectBoundingBox – значение по умолчанию. При этом координаты и размеры рассчитываются как проценты (или доли) от размера bounding box элемента.
  • userSpaceOnUse – отсчитывает все значения (обычно в пикселях) относительно системы координат svg-элемента.

Быстрый совет: визуализация текущей области фильтра с помощью feFlood

Если вы захотите визуализировать область действия фильтра, можете использовать примитив feFlood, который заполнит ее цветом, указанным в атрибуте flood-color.

 

feFlood может принимать не только цвет заливки, но и ее прозрачность (flood-opacity).

Этот фильтр зальет всю свою область действия розовым цветом. Проблема только в том, что он зальет буквально все, включая все созданные ранее эффекты, а также сам текст. Это логично, но чаще всего требуется нечто другое.

 

До и после заливки области фильтра текста цветом.
До и после заливки области фильтра текста цветом.

Чтобы поправить это, нужно переместить цветной слой назад, «за» текст.

В SVG есть примитив фильтра feMerge, который как раз и отвечает за взаимное расположение нескольких слоев. У него нет атрибута in, слои указываются как дочерние элементы feMergeNode. А вот у каждого слоя уже есть атрибут in, как у обычного примитива.

Слои отображаются именно в порядке перечисления: самый первый слой в коде окажется самым «нижним» (или «дальним») на экране.

Обратите внимание, результат работы первого примитива имеет псевдоним FLOOD и используется в одном из слоев примитива feMerge в качестве входных данных. А SouceGraphic у второго слоя – это ссылка на исходное изображение (текст).

После этого небольшого введения в мир SVG-фильтров, можно перейти к практике и создать простой эффект drop-shadow (падающей тени).

Применение тени к изображению

Для решения этой конкретной задачи было бы проще и разумнее использовать CSS-фильтр drop-shadow(). SVG-решение получится гораздо многословнее, но тем не менее мы рассмотрим именно его, как простую точку входа в реализацию более сложных эффектов.

Итак, что вообще такое тень?

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

Чтобы создать SVG-фильтр, нужно думать поэтапно.

  • Создадим обычную черную копию и применим к ней размытие;
  • Раскрасим черную копию в серый цвет;
  • Немного сместим ее относительно исходного положения элемента;
  • Разместим тень позади элемента.

Для создания черной копии используем альфа-канал исходного изображения (это мы уже умеем – SourceAlpha в качестве входных данных фильтра).

Размытие по Гауссу обеспечит примитив feGaussianBlur. Нужное значение размытия указывается в атрибуте stdDeviation (стандартное отклонение). Если указано одно значение, размытие будет равномерным. Можно указать два значения – одно для горизонтального размытия, второе для вертикального. Мы используем обычный равномерный эффект.

 

Теперь у нас есть размытый альфа-канал исходного изображения. Это просто черный квадрат Малевича прямоугольник, так как изображение не имеет прозрачных фрагментов. Вот он:

Чтобы перекрасить тень, используем уже знакомый примитив feFlood для заливки нужным цветом и композицию (комбинацию слоев), чтобы залить только нужную область.

Композиция – это способ сочетания графического элемента и его фона (всего, что лежит под/за ним). У нас фон – это черный размытый прямоугольник, а элемент – сплошная серая заливка примитива feFlood. Именно в таком порядке, ведь заливка должна оказаться сверху. Если вы не очень хорошо понимаете, о чем идет речь, загляните сюда – здесь есть большая хорошая вводная статья от автора этой оригинальной статьи.

Примитив feComposite имеет атрибут operator, в нем указывается, по какому алгоритму будет производиться наложение слоев. Мы воспользуемся алгоритмом in – при этому верхний слой (серая заливка) будет отображаться только поверх черного размытого прямоугольника, не выходя за его пределы.

Для работы feComposite требуется два входящих графических элемента. В атрибут in (не путать с режимом наложения in) передаем верхний слой (заливку), в in2 – размытый теневой фон.

Здесь мы используем результаты работы примитивов feGaussianBlur и feFlood в качестве входных данных для feComposite.

Теперь нужно немного сместить тень немного по горизонтали и/или вертикали от исходного положения. Предположим, что источник света находится в верхнем левом углу экрана.

Для смещения слоя используем примитив фильтра feOffset. У него есть уже известные нам атрибуты in и result. Кроме них есть еще dx и dy, которые определяют величину смещения.

Далее необходимо объединить созданную тень и исходное изображение. С этим фокусом мы тоже уже знакомы – нужно использовать feMerge. Первый слой примет тень, второй – само изображение (SourceGraphic).

 

Вот так это выглядит вживую.

Вот мы и создали наш первый полноценный SVG-фильтр – ничего сложного. И работает он во всех основных браузерах.

Есть другой способ…

Существует другой, более распространенный способ создания тени. Вместо того, чтобы перекрашивать черную тень в серый для осветления, можно сделать ее полупрозрачной.

Разумеется, если тень должна быть цветной, без feFlood не обойтись, и в принципе это очень полезный метод, поэтому мы к нему и обратились с самого начала. Теперь посмотрим другой вариант.

Для изменения непрозрачности слоя можно использовать либо примитив feColorMatrix, либо примитив feComponentTransfer. Второй будет подробно разобран в следующих статьях серии, поэтому используем feColorMatrix.

Это сложный и мощный примитив, который заслуживает отдельной подробной статьи – и такая статья есть – вот она, от Una Kravets.

feColorMatrix применяет матричное преобразование к каналам цвета каждого пикселя:  R(Красный), G(зеленый), B(синий) и A(Альфа). Базовая матрица выглядит так:

Каналы R, G, B в матрице мы трогать не будем, изменим только альфа-канал:

 

Выглядит это вот так:

Заключение

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

Другие статьи этой серии:

Оставить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *