SVG Filters - Putting Pieces Together

The construction of SVG Filters is one of those subjects that's great to study, because it never seems to end. There are dozens of primitives, attributes and features to delve into, and even after that I find I need to review my linear algebra skills and remember how to multiply matrices to take full advantage of the wondrous possibilities available.

Of course, you don't need to understand all the details to make good use of them. There are a lot of examples that may be tweaked with relative ease to get a desired result, but there are still some basics that are helpful.

The last time I talked about filters, I provided some simple examples using blur filters. If you look closely at the code, there is one suboptimal aspect of that third example. I'm using a colored rounded rectangle and trying to add a drop-shadow effect:

    <rect x="10" y="70" rx="3" ry="3" width="40" height="20" fill="red" filter="url(#AlphaBlur)"/>
    <rect x="10" y="70" rx="4" ry="4" width="40" height="20" fill="red"/>

I guess it's pretty obvious when I isolate this part of it. Because filters replace the original element, I've duplicated the rectangle, once for the shadow, then again to have the original rectangle that's casting the shadow. As easy as it is to copy and paste code, duplication like this is something to avoid if possible, if for no other reason that it makes subsequent changes more complicated and error prone. If I want to change the size of the rectangle, or the curvature of the rounded corners, I have to do it in two places. (I know the rx and ry are different, that's just because it looked right). Of course, if you're familiar with the DRY principle this is no surprise. And I wouldn't bring this up if there weren't a better way.

Most filter primitives have the concept of an input and output. That makes it easy to create a pipeline of filters, but in this case, to avoid duplication of the original element, you need a slightly more complex topology (a DAG for you CS geeks). Specifically we need to combine two inputs into one output, which is done with the primitive "feComposite":

<defs>
  <filter id="shadow" filterUnits="userSpaceOnUse" x="0" y="0" width="500" height="500">
    <feGaussianBlur in="SourceAlpha" stdDeviation="10" result="blur"/>
    <feComposite in="SourceGraphic" in2="blur" operator="over"/>
  </filter>
</defs>

<rect x="30" y="30" rx="10" ry="10" width="400" height="200" fill="red" filter="url(#shadow)" />

Here we're using "feComposite" to combine the "SourceGraphic" (the original element) with a blur of the "SourceAlpha" (which is black where the original is opaque), laying the red rectangle on top of a blurred shadow (operator="over") giving the resulting drop shadow effect (check it out at JSFiddle). The "operator" attribute has lots of interesting possibilities but for now we'll just consider "over" which means that the "in" input lies on top of the "in2" input. But, as they say on infomercials, that's not all.

I recently came across a picture of Tux that I wanted to include in a context with a black background. Unfortunately the bulk of Tux's outer body disappeared in the background. What I needed was a shadow glow, i.e. a blur that's got a light color to contrast with the black background. Fortunately there is a way to modify colors. Actually there are a couple of ways, but for this purpose "feColorMatrix" will work.

Not surprisingly, "feColorMatrix" takes an actual matrix that transforms the rgba values for each pixel. This provides a lot of power and flexibility in exactly how the colors (and opacity) are modified, hence my earlier comment about linear algebra, which I really intend to look up some time soon. Fortunately, there are a lot of examples to use as starting points. That's how I arrived at the matrix below:

    <feColorMatrix in="SourceAlpha" type="Matrix" values="
         0  0  0  1  0
         0  0  0  1  0
         0  0  0  1  0
         0  0  0  1  0" result="white" />
    <feGaussianBlur in="white" stdDeviation="22" result="whiteblur" />

Without going into too much detail, this matrix will transform the black bits into white bits. Trust me on that. Adjusting the first three '1's (RGB), you can adjust the color of the resulting glow. Modifying the last '1' will change the opacity of the result. And of course we need the black background to illustrate it properly, so we can do that with "feFlood", which floods a rectangular area with a specific color, and can be fed into other filter elements:

<filter id="WhiteGlow" filterUnits="userSpaceOnUse" x="-20" y="-20" width="500" height="500" color-interpolation-filters="sRGB">
    <feFlood x="0" y="0" width="100%" height="100%" flood-color="black" result="background" />
    <feColorMatrix in="SourceAlpha" type="Matrix" values="
         0  0  0  1  0
         0  0  0  1  0
         0  0  0  1  0
         0  0  0  1  0" result="whiteshade" />
    <feGaussianBlur in="whiteshade" stdDeviation="22" result="whiteblur" />
    <feComposite in="whiteblur" in2="whiteshade" operator="over" result="glow" />
    <feComposite in="SourceGraphic" in2="glow" operator="over" />
</filter>

I've put another example up on JSFiddle, but with a circle in place of Tux. With the right opacity and blur adjustments, it looks a little like a solar eclipse.

Catégories:
Reportez-vous à notre Notice d'optimisation pour plus d'informations sur les choix et l'optimisation des performances dans les produits logiciels Intel.