📜 ⬆️ ⬇️

Effects of filtering SVG. Part 1. SVG filters 101

Sara Soueidan, a freelance UI / UX developer of the interface and author of many technical articles living in Lebanon, offers a series of articles on the SVG filters and consists of the following articles:


Effects of filtering SVG


  1. SVG filters 101.
  2. Effects of SVG filters: outline text using feMorphology .


This is the first article in the series about SVG filters. This guide will help you understand what it is and show you how to use them to create your own visual effects.



CSS currently provides us with a way to apply color effects to images such as saturation, brightness, and contrast, among other effects, using the filter property and its functions that come with it.


We now have 11 filter functions in CSS that perform a range of effects from blurring to changing color contrast and saturation and much more. Detailed information on this can be found in the CSS reference .


Although powerful and very convenient, CSS filters are also very limited. The effects that we can create with their help are often applicable to images and are limited to color manipulations and simple blurring. Thus, to create more powerful effects that we can apply to a wider range of elements, we will need a wider range of functions. These features are available today, and have been available for more than a decade in SVG. In this article, which is the first in a series about SVG filters, you will learn about the functions of SVG filters, known as “primitives,” and how to use them.


CSS filters imported from SVG. They are fairly well optimized versions of a subset of filtering effects presented in SVG and existing in the SVG specification for many years.


SVG has more filtering effects than CSS, and SVG versions are more powerful and perform much more complex effects than their CSS shortcuts. For example, you can now blur an element using the blur () CSS filtering feature. Applying a blur effect using this function will create a uniform Gaussian blur of the element to which it is applied. The following image shows the result of applying a 6px blur to an image in CSS:


Effect of using the function ** blur () ** - Gaussian blur
Fig_1. The effect of applying the CSS function blur () - Gaussian blur.


The blur () function creates a blur effect that is uniformly applied in both directions (X and Y) in the image. But this function is just a simplified and limited reduction of the blur filter primitive available in SVG, which allows us to blur the image either evenly or apply a unidirectional blur effect along the X or Y axes.


The effect of applying the SVG function ** blur () ** separately along the axes
Pic_2. The effect of applying the SVG-function blur () separately along the axes.


SVG filters can be applied to both HTML elements and SVG elements. The SVG filtering effect can be applied to an HTML element in CSS using the url () filter function. For example, if you have a filter effect with the identifier myAwesomeEffect defined in SVG (we’ll talk about defining filter effects in SVG soon), you can apply this effect to an HTML element or image like this:


.el { filter: url(#myAwesomeEffect); } 

Best of all, as you will see in this article series, SVG filters are capable of creating Photoshop-level effects in a browser using a few lines of code. I hope that this series will help dispel the aura of secrets and unleash some of the potential of SVG filters that will inspire you to use them in your own projects.


But what about browser support, you ask ..?


Browser Support


Browser support for most SVG filters is impressive. However, the method of applying the effect may differ for some browsers depending on their support for certain filter primitives used in the SVG filtering effect, as well as on possible browser errors. Browser support may also differ when the SVG filter is applied to SVG elements or to HTML elements.


I would recommend that you consider filtering effects as an extension: you can almost always apply an effect as an improvement over a completely useful non-filter experience. Many people know that I support the progressive approach of creating a UI, when it is possible. Thus, we will not worry too much about browser support in this series of articles.


Finally, despite the fact that support for SVG filters is generally good, keep in mind that some of the effects that we will consider later can be considered experimental. I will point out any major problems or errors if and when they will.


So, how to define and create a filtering effect in SVG?


<Filter> element


Like linear gradients, masks, patterns and other graphic effects in SVG, filters have a conveniently-named specialized element: <filter> .


It is never displayed directly, but is used only as an object that can be referenced using the filter attribute in SVG or the url () function in CSS. Elements that are not displayed without an explicit link are usually defined as templates within the <defs> elements in the SVG. But SVG <filter> does not need to be packaged in a defs element. Whether you wrap the filter in the defs element or not, it will still not be displayed.


The reason for this is that the filter requires the original image to be processed . And if you do not explicitly define this source image by calling a filter on it, the filter will not have anything to render, and therefore it will not work.


A very simple, minimal code example defining an SVG filter and applying it to an original image in an SVG will look like this:


 <svg width="600" height="450" viewBox="0 0 600 450"> <filter id="myFilter"> <!-- filter effects go in here --> </filter> <image xlink:href="..." width="100%" height="100%" x="0" y="0" filter="url(#myFilter)"></image> </svg> 

The filter in the above code example does nothing at the moment, as it is empty. To create a filter effect, you need to define a series of one or more filtering operations that create this effect inside the filter. In other words, the <filter> element is a container for a series of filtering operations that together create a filter effect. These filtering operations in SVG are called “ filter primitives ”.


Filter primitives


Thus, in SVG, each <filter> element contains a set of filter primitives as child elements. Each filter primitive performs one elementary graphic operation on one or several inputs, creating a graphic result.


Filter primitives are conveniently named after the graphic operations they perform. For example, a primitive that applies a Gaussian Blur effect to a graphics source is called feGaussian Blur . All primitives have the same prefix: fe, abbreviated from the “ filter effect ”. Again, the names in the SVG are convenient to choose in order to understand what this element is or what it does.


The following code snippet shows how a simple filter will look like if you apply a 5px Gaussian blur to an image:


 <svg width="600" height="450" viewBox="0 0 600 450"> <filter id="myFilter"> <feGaussianBlur stDeviation="5"></feGaussianBlur> </filter> <image xlink:href="..." width="100%" height="100%" x="0" y="0" filter="url(#myFilter)"></image> </svg> 

Currently, the SVG Filter specification defines 17 filter primitives that are capable of creating extremely powerful graphic effects, including, but not limited to, noise and texture generation, lighting effects, color manipulations (from channel to channel) and much more.


The filter primitive works by taking a graphic source to input and outputting to another. And the output of one filter effect can be used as an input to another. This is very important and very effective, because with almost a countless number of combinations of filtering effects, you can create an almost countless number of graphic effects.


Each filter primitive can take one or two inputs and output only one result. The input to the filter primitive is specified in the in attribute. The result of the operation is defined in the result attribute. If the filter effect requires a second entry, it is indicated in the in2 attribute. The result of the operation can be used as input for any other operation, but if the input data of the operation is not specified in the in attribute, the result of the previous operation is automatically used as input data. If you do not specify a result primitive, then its result will automatically be used as input for the next primitive. This will become clearer when we start exploring code samples.


As an input, the filter primitive can use other data types, the most important of which are:



You will find that sometimes you want to use a graphics source as input, and sometimes only its alpha channel. Examples that we will look at in this and the following articles will provide a clear understanding of when and what to use.


This code snippet is an example of how a filter with a package of filter primitives as child elements may look. Do not worry about primitives and what they do. At this stage, just pay attention to how the inputs and outputs of certain primitives are defined and used. I added some comments for help.


 <svg width="600" height="400" viewBox="0 0 850 650"> <filter id="filter"> <feOffset in="SourceAlpha" dx="20" dy="20"></feOffset> <!-- since the previous filter did not have a result defined and this following one does not have the input set, the result of the above primitive is automatically used as input to the following filter --> <feGaussianBlur stdDeviation="10" result="DROP"></feGaussianBlur> <!-- setting/defining the result names in all caps is a good way to make them more distinguishable and the overall code more readable --> <feFlood flood-color="#000" result="COLOR"></feFlood> <!-- This primitive is using the outputs of the previous two primitives as input, and outputting a new effect --> <feComposite in="DROP" in2="COLOR" operator="in" result="SHADOW1"></feComposite> <feComponentTransfer in="SHADOW1" result="SHADOW"> <feFuncA type="table" tableValues="0 0.5"></feFuncA> </feComponentTransfer> <!-- You can use ANY two results as inputs to any primitive, regardless of their order in the DOM.--> <feMerge> <feMergeNode in="SHADOW"></feMergeNode> <feMergeNode in="SourceGraphic"></feMergeNode> </feMerge> </filter> <image xlink:href="..." x="0" y="0" width="100%" height="100%" filter="url(#filter)"></image> </svg> 

Now the last concept, which I want to briefly explain before moving on to our first filter example, is the concept of a Filter Region .


Filtration area


A set of filtering operations require an area for processing, an area to which they can be applied. For example, you may have a complex SVG with many elements, and you want to apply the filtering effect only to a specific area or to one or a group of elements within that SVG itself.


In SVG, elements have “areas” whose boundaries are defined by the edges of the rectangle bounding the element. The bounding box (also abbreviated as “bbox”) is the smallest enclosing rectangle around an element. For example, in the following image for a fragment of text, such a rectangle is highlighted in pink.


The smallest enclosing rectangle around a piece of text.
Fig_3. The smallest enclosing rectangle around a piece of text.


Note that this rectangle may contain several more vertical spaces, since the height of the text line is taken into account when calculating the height of the bounding rectangle.


By default, an element's filter area is the bordering box of the element. Therefore, if you apply a filter effect to our text fragment, the effect will be limited to this rectangle, and any filtering result that is outside of it will be clipped. Although this is reasonable, it is not very practical, because many filters will affect some pixels outside the bounding box, and by default these pixels will eventually be cut off.


For example, if you apply a blur effect to our text fragment, you can see that it is clipped along the left and right edges of the rectangle bounding the text:


The blur effect applied to text is clipped from both the right and left side of the area of ​​the rectangle bounding the text.
Fig_4. The blur effect applied to text is clipped from both the right and left sides of the area of ​​the rectangle bounding the text.


So how do we prevent this? The answer is: by expanding the filter area. We can expand the area to which the filter is applied by changing the x , y , width and height attributes of the <filter> element.


According to specification,


It is often necessary to provide fields in the filter area, since the filter effect can affect some bits outside the bounding box for a given object. To do this, you can give negative percent values ​​to the X and Y properties, and values ​​greater than 100% to the width and height properties.

By default, filters have areas that extend the width and height of the bounding box in all four directions by 10%. In other words, the default values ​​for the x , y , width, and height attributes are as follows:


 <filter x="-10%" y="-10%" width="120%" height="120%" filterUnits="objectBoundingBox"> <!-- filter operations here --> </filter> 

If you do not include these attributes in the <filter> element, the default values ​​will be used. You can also redefine them to expand or shrink the area as needed.


Keep in mind that the units used in the x , y , width, and height attributes depend on which filterUnits value is used. It defines the coordinate system of the attributes x , y , width and height and can take one of two values:



Everything you need to know about coordinate systems in SVG You can find out in this article, which I wrote a few years ago.

 <!-- Using objectBoundingBox units --> <filter id="filter" x="5%" y="5%" width="100%" height="100%"> <!-- Using userSpaceOnUse units --> <filter id="filter" filterUnits="userSpaceOnUse" x="5px" y="5px" width="500px" height="350px"> 

Quick tip: visualize the current filter area with feFlood


If you ever need to see the limits of the filter area, you can visualize it, filling it with some kind of color. Conveniently, there is a filter primitive called feFlood , the only purpose of which is exactly this: fill the current filter area with the color specified in the flood-color attribute.


So, if we assume that we have a piece of text, the filter area of ​​which we want to see, then the code might look something like this:


 <svg width="600px" height="400px" viewBox="0 0 600 400"> <filter id="flooder" x="0" y="0" width="100%" height="100%"> <feFlood flood-color="#EB0066" flood-opacity=".9"></feFlood> </filter> <text dx="100" dy="200" font-size="150" font-weight="bold" filter="url(#flooder)">Effect!</text> </svg> 

As can be seen from the code snippet above, the feFlood primitive also accepts the flood-opacity attribute, which can be used to create transparency of the color fill layer.


The above snippet fills the filter area with pink. But here's the thing: when you fill the area with color, you literally fill it with color, which means that the color will cover everything in the filter area, including any elements and effects you created before, as well as the text itself. In the end, this is the concept of fill, right?


Before and after the color of the filter text area
Pic_5. Before and after filling the color of the filter text area.


To change this, we need to move the color layer below and show the original text layer on top.


If you have several context layers that need to be displayed in the SVG filter over each other, you can use the filter primitive <feMerge> . As the name implies, the primitive feMerge is used to combine layers of elements or effects together.


Primitive has no in attribute. To merge the layers inside <feMerge> , two or more <feMergeNode> are used , each of which has its own attribute in , representing the layer that we want to add.

Laying a layer (or “node”) depends on the order of the source <feMergeNode> - the first <feMergeNode> is displayed “in” or “below” the second. The last <feMergeNode> represents the topmost layer. And so on.


So, in our text example, the color fill is a layer, and the text source (graphics source) is another layer, and we want to place the text on top of the color fill. Our code will look like this:


 <svg width="600px" height="400px" viewBox="0 0 600 400"> <filter id="flooder"> <feFlood flood-color="#EB0066" flood-opacity=".9" result="FLOOD"></feFlood> <feMerge> <feMergeNode in="FLOOD" /> <feMergeNode in="SourceGraphic" /> </feMerge> </filter> <text dx="100" dy="200" font-size="150" font-weight="bold" filter="url(#flooder)">Effect!</text> </svg> 

Notice how I called the result of feFlood in the result attribute so that I could use this name in the <feMergeNode> layer as input. Because we want to display the source text on top of the stream color, we reference this text using SourceGraphic . The following demo shows the result:



Apply a shadow to an image


Let me start with a brief warning: you had better create a simple shadow using the CSS drop-shadow () filtering feature. The SVG filter path is much more detailed. After all, as we mentioned earlier, the CSS filter functions are convenient shortcuts. But I still want to see this example as a simple entry point to more complex filter effects, which we will look at in future articles.


So how is shadow created?


A shadow is usually a light gray layer behind or below an element that has the same shape (or shape) as the element itself. In other words, you can think of it as a blurred gray copy of the item.


When creating SVG filters, you need to reason in stages. What steps are needed to achieve this or that effect? For a shadow, a blurred gray copy of an element can be created by blurring the black copy of the element, and then coloring this black copy, i.e. make it gray. This newly created blurred gray copy is then placed behind the original element and slightly shifted in both directions.


So, we ’ll start by getting a black copy of our element and blur it . A black copy can be created using the element's alpha channel, using SourceAlpha as the input to the filter.


The primitive feGaussian Blur will be used to apply a Gaussian blur to this Source Alpha layer. The required amount of blur is set in the stdDeviation attribute (short for Standard Deviation). If you specify a single value for the stdDeviation attribute, this value will be used to apply a uniform blurring of the input data. You can also specify two numerical values, then the first will be used to blur the element in the horizontal direction, and the second - for vertical blur. For the shadow, we need to apply a uniform blur, so our code will start with this:


 <svg width="600" height="400" viewBox="0 0 850 650"> <filter id="drop-shadow"> <-- Grab a blakc copy of the source image and blur it by 10 --> <feGaussianBlur in="SourceAlpha" stdDeviation="10" result="DROP"></feGaussianBlur> </filter> <image xlink:href="..." x="0" y="0" width="100%" height="100%" filter="url(#drop-shadow)"></image> </svg> 

The above code snippet leads to the following effect, where at the moment only the blurred alpha channel of the image is displayed:


Created black copy of image with blur (shadow)
Fig_6. Created black copy of image with blur (shadow).


Then we want to change the color of the shadow and make it gray . We do this by applying a color fill to the filter area, and then we merge this color fill layer with the shadow layer we created.


Combination is the connection of a graphic element with a blurred background. The blurred background is the content behind the element with which the element is combined. In our filter, the fill color is the top layer, and the blurred shadow is its background because it lies behind it. We will look at the primitive feComposite in more detail in the following articles, so if you are not familiar with the composition and how it works, then I recommend reading the detailed article on this topic in my blog.


The primitive feComposite has an operator attribute, which is used to indicate which composite operation we want to use.


Using the in composite operator, the color fill layer will be “cropped”, and only the area of ​​color that matches our shadow layer will be displayed. Both layers will be mixed where they intersect, i.e. серый цвет будет использоваться для окрашивания нашей черной тени.


Примитив feComposite требует двух входных данных для работы, указанных в атрибутах in и in2 . Первый вход — это наш цветной слой, а второй — размытый теневой фон. С помощью составной операции, указанной в атрибуте operator , наш код теперь выглядит следующим образом:


 <svg width="600" height="400" viewBox="0 0 850 650"> <filter id="drop-shadow"> <feGaussianBlur in="SourceAlpha" stdDeviation="10" result="DROP"></feGaussianBlur> <feFlood flood-color="#bbb" result="COLOR"></feFlood> <feComposite in="COLOR" in2="DROP" operator="in" result="SHADOW"></feComposite> </filter> <image xlink:href="..." x="0" y="0" width="100%" height="100%" filter="url(#drop-shadow)"></image> </svg> 

Обратите внимание, как результаты feGaussianBlur и примитивов feFlood используются в качестве входных данных для композита. Теперь наше демо выглядит так:


Теперь тень серая
Рис_7. Теперь тень серая.


Перед тем, как мы наложим наше исходное изображение поверх тени, мы хотим сместить ее по вертикали и/или горизонтали. На сколько и в каком направлении полностью зависит от вас. Для этой демо я предположу, что у нас есть источник света, исходящего из верхнего левого угла нашего экрана, поэтому я смещу тень на несколько пикселей вниз и вправо.


Для смещения слоя в SVG используется примитив feOffset . Дополнительно к атрибутам in и result этот примитив принимает два главных атрибута: dx и dy , которые определяют расстояние, на которое вы хотите сместить слой вдоль осей X и Y соответственно.


После смещения тени, мы будет объединить ее с исходным изображением, используя feMerge , подобно тому, как мы объединяли текст и цветную заливку на предыдущем шаге — один mergeNode примет наши тени в качестве входных данных, а на другом mergeNode будет слой исходного изображения, используя SourceGraphic в качестве входных данных. Наш код теперь выглядит так:


 <svg width="600" height="400" viewBox="0 0 850 650"> <filter id="drop-shadow"> <!-- Get the source alpha and blur it; we'll name the result "DROP" --> <feGaussianBlur in="SourceAlpha" stdDeviation="10" result="DROP"></feGaussianBlur> <!-- flood the region with a ligh grey color; we'll name this layer "COLOR" --> <feFlood flood-color="#bbb" result="COLOR"></feFlood> <!-- Composite the DROP and COLOR layers together to colorize the shadow. The result is named "SHADOW" --> <feComposite in="COLOR" in2="DROP" operator="in" result="SHADOW"></feComposite> <!-- Move the SHADOW layer 20 pixels down and to the right. The new layer is now called "DROPSHADOW" --> <feOffset in="SHADOW" dx="20" dy="20" result="DROPSHADOW"></feOffset> <!-- Layer the DROPSHADOW and the Source Image, ensuring the image is positioned on top (remember: MergeNode order matters) --> <feMerge> <feMergeNode in="DROPSHADOW"></feMergeNode> <feMergeNode in="SourceGraphic"></feMergeNode> </feMerge> </filter> <!-- Apply the filter to the source image in the `filter` attribute --> <image xlink:href="..." x="0" y="0" width="100%" height="100%" filter="url(#drop-shadow)"></image> </svg> 

И следующее демо для кода выше:



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


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


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


В предыдущем демо мы узнали, как применить цвет к тени, используя feFlood , технику окраски, которую вы, вероятно, посчитаете нужной и станете часто пользоваться. Вот почему я подумала, что ее необходимо рассмотреть. Это также полезно узнать, потому что это путь, которым вы захотите создать тень, которая, по какой-то причине, будет пестрой, например, вместо черной или серой.


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


Примитив feColorMatrix заслуживает отдельной статьи. На данный момент я настоятельно рекомендую прочитать статью Una Kravet , которая будет отличным введением с действительно хорошими примерами.


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


 <filter id="myFilter"> <feColorMatrix type="matrix" values="R 0 0 0 0 0 G 0 0 0 0 0 B 0 0 0 0 0 A 0 "/> </feColorMatrix> </filter> 

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


Поскольку мы хотим только уменьшить непрозрачность нашей тени, мы будем использовать тождественную матрицу, которая не изменяет каналы RGB, но мы уменьшим значение альфа-канала в этой матрице:


 <filter id="filter"> <!-- Get the source alpha and blur it, --> <feGaussianBlur in="SourceAlpha" stdDeviation="10" result="DROP"></feGaussianBlur> <!-- offset the drop shadow --> <feOffset in="SHADOW" dx="20" dy="20" result="DROPSHADOW"></feOffset> <!-- make the shadow translucent by reducing the alpha channel value to 0.3 --> <feColorMatrix type="matrix" in="DROPSHADOW" result="FINALSHADOW" values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 0.3 0"> </feColorMatrix> <!-- Merge the shadow and the source image --> <feMerge> <feMergeNode in="FINALHADOW"></feMergeNode> <feMergeNode in="SourceGraphic"></feMergeNode> </feMerge> </filter> 

А это наше живое демо:



Conclusion


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


Теперь, когда мы рассмотрели основы SVG-фильтров и способы их создания и применения, мы рассмотрим дополнительные примеры эффектов использования дополнительных примитивов фильтров в следующих статьях. Stay with us.



Source: https://habr.com/ru/post/439282/