Effects of filtering SVG. Part 1. SVG filters 101

https://tympanus.net/codrops/2019/01/15/svg-filters-101/
  • Transfer

This series of articles " SVG filter effects " Sara Soueidan, freelance developer UI / UX interface, and the author of many technical papers, who lives in Lebanon, is devoted to the work of 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. Input filter primitive defined in an attribute in . 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 operation may be used as input data for any other operation, but if the input operation is not specified in the attribute in , then 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:


  • SourceGraphic : the element to which the entire filter is applied; for example, an image or a piece of text.
  • SourceAlpha : this is the same as SourceGraphic , except that this graphic contains only the element's alpha channel. For example, for a JPEG image, this is a black rectangle the size of the image itself.

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 side 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 may 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:


  • objectBoundingBox . This is the default value. When filterUnits is set to objectBoundingBox , the values ​​of the x , y , width, and height attributes are percentages or fractions of the size of the bounding rectangle of the element. It also means that you can use fractional values ​​instead of percentages, if you prefer.
  • userSpaceOnUse If filterUnits is set to userSpaceOnUse , the coordinates of the x, y, width and height attributes are relative to the current coordinate system used by the user. In other words, this is relative to the current coordinate system used in the SVG, which uses pixels as units of measurement and, as a rule, relative to the size of the SVG itself, assuming that the viewBox values ​​correspond to the values ​​of the original coordinate system.

Everything you need to know about coordinate systems in SVG you can find out in this article that 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 an attribute in . 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 color area that matches our shadow layer will be displayed. Both layers will be mixed where they intersect, i.e. gray will be used to color our black shadow.


Primitive feComposite requires two inputs for operation, specified in the attributes in and in2 . The first entrance is our color layer, and the second is a blurred shadow background. Using the composite operation specified in the operator attribute , our code now looks like this:


<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>

Notice how the results of the feGaussian Blur and feFlood primitives are used as input to the composite. Now our demo looks like this:


Now the shadow is gray
Fig_7. Now the shadow is gray.


Before we superimpose our original image over the shadow, we want to shift it vertically and / or horizontally. How much and in which direction is completely up to you. For this demo, I assume that we have a light source coming from the upper left corner of our screen, so I will shift the shadow a few pixels down and to the right.


To offset the layer in the SVG, the feOffset primitive is used . In addition to the in and result attributes, this primitive accepts two main attributes: dx and dy , which define the distance by which you want to displace the layer along the X and Y axes, respectively.


After offsetting the shadow, we will merge it with the original image using feMerge , just as we combined text and color fills in the previous step - one mergeNode will take our shadows as input, and the other mergeNode will be the source image layer using SourceGraphic as input. Our code now looks like this:


<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>

And the following demo for the code above:



And this is how you apply the filter effect in SVG using SVG filters. You will find that this effect works in all major browsers.


There is another way ...


There is another, more common way to create a shadow. Instead of creating a black shadow and applying a color to it to make it lighter, you can apply transparency to it, making it translucent and therefore lighter.


In the previous demo, we learned how to apply color to shadows using feFlood , a coloring technique that you will probably find necessary and use often. That's why I thought it needed to be considered. This is also useful to know, because this is the way you want to create a shadow, which, for some reason, will be variegated, for example, instead of black or gray.


To change the transparency of a layer, you can use either the primitive feColorMatrix or feComponentTransfer . I will talk about the primitive feComponentTransfer in more detail in the following articles, so now we use feColorMatrix to reduce the opacity of our shadow.


Primitive feColorMatrix deserves a separate article. At this point, I strongly recommend reading the article by Una Kravet , which will be a great introduction with really good examples.


In short, this filter applies a matrix transformation to the R (Red), G (green), B (blue) and A (Alpha) channels of each pixel in the input graphic to produce a result with a new set of colors and Alpha values. In other words, a matrix operation is used to control the colors of the object. The basis of the color matrix is ​​as follows:


<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>

Once again, I recommend checking Una's article to learn more about its syntax.


Since we only want to reduce the opacity of our shadow, we will use the same matrix, which does not change the RGB channels, but we will decrease the alpha channel value in this matrix:


<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>

And this is our live demo:



Conclusion


In this series, I will try to move away from very technical definitions of filter operations and stick to simplified and understandable definitions. Often you do not need to go into the small details of what is happening under the hood. Therefore, the entry into these little things will only add complexity to the articles, perhaps make them less understandable and bring little benefit. Understanding what the filter does and how to use it, in my opinion, is more than enough to take advantage of the advantages it has to offer. If you want to get more information, I recommend to start with the specification.. However, it may be of little use, so you will ultimately do your own research on the side. I will provide a list of excellent resources for further study in the final article of this series.


Now that we have covered the basics of SVG filters and how to create and use them, we will look at additional examples of the effects of using additional filter primitives in future articles. Stay with us.


Also popular now: