Introduction to SVG Filters

As alluded to in this week's introduction, filters are a complex topic. There are many kinds of filters of varying complexity. One may chain filters together in rather complex ways, storing intermediate results in temporary locations and then combine those temporary results together using a variety of methods of blending and composition. Rather than thinking of single filters, or even chains of filters, think of a flow-chart of filters hooked together in a network.

As in Adobe Photoshop in which one can overlay different layers while applying different filters to each and then extract color channels from those layers and then calculate difference between the resulting layers, such work can be done in SVG. The difference is that in SVG it can be done programmatically and dynamically on a web page through script or animation.

After reading the first parts of the SVG Primer's treatement of filters and filter primitives, let's get started by building a fairly interesting example using the simplest of the filter primitives: <feColorMatrix> and <feGaussianBlur>.

Important Note: Though Firefox (3 and 4 beta) provide fairly good support for filters, and although Webkit (Safari and Chrome) are starting to implement filters, you will need either Opera or the Adobe plugin (ASV) for Internet Explorer to properly view the most complex of these examples! I have put a ========break============= in the document to indicate how far I was able to get using Firefox 4 as my viewer. Beyond that, either Opera or IE/ASV is needed.  Safari and Chrome users ... well you may want to try the recent nightly builds of Webkit and report back on your success in viewing these effects. Also, IE9's first release, Microsoft has said, will not be supporting filters. They are rather hard to implement, it appears!

Let's begin thusly by displaying an unfiltered image next to a filtered version of the same image:
<filter id="F">
<feColorMatrix type="saturate" values="0" />
</filter>
<image id="I" x="0" y="0" width="200" height="200"
preserveAspectRatio="none" xlink:href="p17.jpg" />
<use xlink:href="#I" filter="url(#F)"
transform="translate(200,0)" />
Face, with and without color
In this case we have used <feColorMatrix> to simply desaturate a color image and turn it into a greyscale image. Note: I think the Primer has an example which leaves off the values attribute. That still works in IE/ASV, and I believe it used to work in Opera, but to make it work in modern browsers the values attribute is required for this to have any effect!

Now, let's make a sort of checkerboard with these two images. (I had the idea that juxtapostion of many greyscale and color images might be intriguing. You can see if it is!)
<filter id="F">
<feColorMatrix type="saturate" values="0" />
</filter>
<g id="g" transform="scale(.5)">
<image id="I" x="0" y="0" width="200" height="200"
preserveAspectRatio="none" xlink:href="p17.jpg" />
<use xlink:href="#I" filter="url(#F)" transform="translate(200,0)" />
</g>
<use xlink:href="#g" transform=" translate(200,100) scale(-1,1)"/>
four copies of the face with two turned to grey
Our next step, above, involves duplicating the two images. Rather than just duplicating, though, I wanted to reverse their order so that the grey one ends up on the left. Rather than building two more uses to do this, I decided to simply scale the first group, using a scale factor of negative one on the x-axis to flip the group horizontally! Note that in this example, we have used only one image. In traditional HTML prior to CSS3, it would have taken four separate bitmapped images to be built and transmitted through over the Internet.

Now we may simply take the above content (the group and the use) and place it inside a <pattern> and then apply the pattern to a rectangle that fills the screen (running from 0 to 100% of its height and width).
<filter id="F">
<feColorMatrix type="saturate" values="0" />
</filter>
<pattern id="Pix" patternUnits="userSpaceOnUse"
width="200" height="200" >
<g id="g" transform="scale(.5)">
<image x="0" y="0" width="200" height="200"
preserveAspectRatio="none" xlink:href="p17.jpg" />
<use xlink:href="#I" filter="url(#F)"
transform="translate(200,0)" />

</g>
<use xlink:href="#g" transform=" translate(200,100)
scale(-1,1)"/>
</pattern>

<rect x="0" y="0" width="100%" height="100%"
fill="url(#Pix)"
/>
pattern with filtered face
Next, two things came to mind that I thought might be interesting and instructive: blurring the above pattern, and overlaying several copies of the pattern at different scales so that smaller versions might be visible "behind" the larger versions.

The first of these is quite easy to try. We simply create an <feGaussianBlur> and apply it to the rectangle:
<filter id="B" x="0%" y="0%" width="100%" height="100%">
<feGaussianBlur stdDeviation="3"/>
</filter>

...and...later...

<rect x="0" y="0" width="100%" height="100%"
fill="url(#Pix)" filter="url(#B)"/>
blurring the entire pattern
It should perhaps be noted that when we choose to blur makes a difference, as the following table should illustrate. Once the pictures have combined into a pattern and then blurred, the blur bleeds color values between previously separte cells of the pattern space.
Blurring the final result (stdDeviation="20") Blurring the image first (stdDeviation="20")
<pattern id="Pix ,,,
<g id="g" transform="scale(.5)" >
<image x="0" y="0" ...
<image x="200" y="0" ...
</g>
<use ...
</pattern>

<rect x="0" y="0" width="100%" height="100%"
fill="url(#Pix)" filter="url(#B)"/>
<pattern id="Pix" ...
<g id="g" transform="scale(.5)" filter="url(#B)">
<image x="0" y="0" ...
<image x="200" y="0" ...
</g>
<use ...
</pattern>

<rect x="0" y="0" width="100%" height="100%"
fill="url(#Pix)" />
blurring the rectangle with its pattern blurring before adding to the pattern

=================break==================
beyond here either Opera or ASV is needed
=================break==================

In order, though, to try the second idea, in which background images might be visible underneath foreground ones, we observe that the base image we are given is a JPEG file. JPEG does not have transparency in the file format though so, how are we to allow background imagery  to shine through selected parts of foreground imagery?

Well, that is where a more advanced variant of the <feColorMatrix> might come in handy. Suppose we were to take the "white" pixels and turn them transparent. In the original image, a look inside Photoshop or the GIMP would reveal that the background is not exactly white, but near white. Suppose, then that we were to take the light pixels (those that are high on the Red, Green and Blue channels) and convert that brightness into transparency (namely high values on the alpha channel). <feColorMatrix> is good at exactly those sort of shifts on color channels.

Note the the reader -- here is just a hint of how complex filters can become. Please don't despair if what follows seems complex -- it is! Give it a read. If it makes sense to you, then great! Otherwise, please don't worry!

We begin with the basic code base developed above, but make two copies of the content of the pattern: one shrunken and the other at larger size.
<filter id="F">
<feColorMatrix type="saturate" values="0" />
</filter>
<pattern id="BigPix" patternUnits="userSpaceOnUse" width="400" height="400" >
<g id="ggg" >
<g id="gg">
<g id="g">
<image id="I" x="0" y="0" width="200" height="200"
preserveAspectRatio="none" xlink:href="p17.jpg" />
<use xlink:href="#I" filter="url(#F)" transform=" translate(200,0)" />
</g>
<use xlink:href="#g" transform=" translate(400,200) scale(-1,1)"/>
</g>
</g>
</pattern>

<pattern id="Pix" patternUnits="userSpaceOnUse" width="100" height="100" >
<use xlink:href="#gg" transform="scale(.25)"/>
</pattern>
<rect x="0" y="0" width="100%" height="100%" fill="url(#Pix)" />
<rect x="0" y="0" width="100%" height="100%" fill="url(#BigPix)" />
The above trick of developing one more layer of group nesting ("g3") so that we may reuse the content but change one aspect of it is something we've seen before. If you actually put the above code in a browser, you will see that the rectangle containing the finer grained pattern is not visible since the top layer is entirely opaque.

So the next question is how to design a filter (let's call it #F2) to be applied to the last rectangle:
 <rect x="0" y="0" width="100%" height="100%" fill="url(#BigPix)" filter="url(#F2)"/>
so that the whitish values in that image are converted to transparent.

Here's the key:
<filter id="F2">
<feColorMatrix type="matrix"
values="1 0 0 0 0
0 1 0 0 0
0 0 1 0 0
-.33 -.33 -.33 0 1
"/>
</filter>
Labeling the rows and columns of the above matrix to make some sense of it we have:
				To
Red Green Blue Alpha Constant
Red 1 0 0 0 0
From Green 0 1 0 0 0
Blue 0 0 1 0 0
Alpha -.33 -.33 -.33 0 1
In this filter, <feComposite> instructs any content to which it is applied to keep its channels R, G, and B unaffected -- to multiply their numeric values at each pixel by one. However, we'll be constructing a new Alpha channel by summing the values on each of the channels R G and B and letting positive values on that channel contribute negatively to the opacity of the image.
    alpha =  -0.33  ( R + G + B ) + constant.
We add a constant of 1 since if the constant were zero, then the top image would be everywhere transparent (alpha less than 0). If the constant were 2.0, then the top image would be everywhere opaque (alpha greater than 1).  It is in between this range and for values that have high brightness that we wish to make transparency.

The result:

<filter id="F2">
<feColorMatrix type="matrix"
values="1 0 0 0 0
0 1 0 0 0
0 0 1 0 0
-.33 -.33 -.33 0 1
"/>
</filter>

<filter id="F">
<feColorMatrix type="saturate" values="0" />
</filter>
<pattern id="BigPix" patternUnits="userSpaceOnUse"
width="400" height="400" >
<g id="ggg" >
<g id="gg">
<g id="g">
<image id="I" x="0" y="0" width="200" height="200"
preserveAspectRatio="none" xlink:href="p17.jpg" />
<use xlink:href="#I" filter="url(#F)"
transform="translate(200,0)" />
</g>
<use xlink:href="#g" transform=" translate(400,200)
scale(-1,1)"/>
</g>
</g>
</pattern>

<pattern id="Pix" patternUnits="userSpaceOnUse"
width="100" height="100" >
<use xlink:href="#gg" transform="scale(.25)"/>
</pattern>
<rect x="0" y="0" width="100%" height="100%"
fill="url(#Pix)" />
<rect x="0" y="0" width="100%" height="100%"
fill="url(#BigPix)" filter="url(#F2)"/>
</svg>
changing the opacity of the top layer

A couple of remarks about the above example:


An example in which the effect is applied to three levels, each with different sized faces,  is shown in this example. If you study that example, you'll see that only the color images are mostly opaque while the greyscale ones are mostly transparent in the bright ranges of the image. This was accomplished by recognizing that during the conversion to greyscale, all channels are made equal, although in the color image the face is far stronger in the red channel than in G or B. Hence by associating bright levels of G and B with transparency, but associating R with opacity, the greyscale image is made more transparent.
transparency applied to greyscale but not to color
For the adventurous, here is a bit more play can be seen in these examples