SVG Essentials/Filters

From WikiContent

Jump to: navigation, search
SVG Essentials

The preceding chapters have given you a basis for creating graphics that convey information with great precision and detail. If you're going on a spring picnic, you want a precise map. When you look in the newspaper for the graphics that describe the weather forecast, you want "just the facts."

If you're asked later to describe the day of the picnic, nobody wants a crisp recitation of meteorological statistics. Similarly, nobody wants to see a graphic of a spring flower composed of pure vectors; Figure 10-1 fails totally to convey any warmth or charm.

Figure 10-1. Flower composed of plain vectors

Flower composed of plain vectors

Graphics are often designed to evoke feelings or moods as much as they are meant to convey information. Artists who work with bitmap graphics have many tools at their disposal to add such effects; they can produce blurred shadows, selectively thicken or thin lines, add textures to part of the drawing, make an object appear to be embossed or beveled, etc.

Contents

How Filters Work

Although SVG is not a bitmap description language, it still lets you use some of these same tools. When an SVG viewer program processes a graphic object, it will render the object to some bitmapped output device; at some point the program will convert the object's description into the appropriate set of pixels that appear on the output device. Now let's say that you use the SVG <filter> element to specify a set of operations that display an object with a blurred shadow offset slightly to the side, and attach that filter to an object:

<filter id="drop-shadow">
   <!-- filter operations go here -->
</filter>

<g id="spring-flower"
    style="filter: url(#drop-shadow);"/>
    <!-- drawing of flower goes here -->
</g>

Because the flower uses a filter in its presentation style, SVG will not render the flower directly to the final graphic. Instead, SVG will render the flower's pixels into a temporary bitmap. The operations specified by the filter will be applied to that temporary area and their result will be rendered into the final graphic.

Creating a Drop Shadow

In Chapter 4, in Section 4.3.5, we created a drop shadow by offsetting a gray ellipse underneath a colored ellipse. It worked, but it wasn't elegant. Let's investigate a way to create a better-looking drop shadow with a filter.

Establishing the Filter's Bounds

The <filter> element has attributes that describe the clipping region for a filter. You specify an x, y, width, and height in terms of the percentage of the filtered object's bounding box. (That is the default.) Any portion of the resulting output that's outside the bounds will not be displayed. If you are intending to apply a filter to many objects, you may want to omit these attributes altogether and take the default values of x equal to -10%, y equal to -10%, width equal to 120%, and height equal to 120%. This gives extra space for filters — such as the drop shadow that we're constructing — that produce output larger than their input.

These attributes are in terms of the filtered object's bounding box; specifically, filterUnits has a value of objectBoundingBox by default. If you wish to specify boundaries in user units, then set the attribute's value to userSpaceOnUse.

Using feGaussianBlur for a Drop Shadow

Between the beginning and ending <filter> tags are the filter primitives that perform the operations you desire. Each primitive has one or more inputs, and exactly one output. An input can be the original graphic, specified as SourceGraphic, the alpha (opaqueness) channel of the graphic, specified as SourceAlpha, or the output of a previous filtering primitive. You will probably use the alpha source more often, since it avoids the interactions of alpha and color, as described in Chapter 9, in Section 9.2.

Example 10-1 is our first attempt to produce a drop shadow on the flower, using the <feGaussianBlur> filter primitive. We specify SourceAlpha as its input (the in attribute), and the amount of blur with the stdDeviation attribute. The larger this number, the greater the blur. If you give two numbers separated by whitespace as the value for stdDeviation, the first number is taken as the blur in the x-direction and the second as the blur in the y-direction.

Example 10-1. First attempt to produce a drop shadow

<defs>

<filter id="drop-shadow">
    <feGaussianBlur in="SourceAlpha" stdDeviation="2"/>
</filter>

</defs>
<g id="flower" filter="url(#drop-shadow)">
    <!-- drawing here -->
</g>

Figure 10-2 shows the result, which is probably not what you thought it would be.

Figure 10-2. Result of first attempt at a drop shadow

Result of first attempt at a drop shadow

Don't be surprised; remember, the filter returns the output, which is a blurred alpha channel, instead of the original source graphic. We could get the effect we want by putting the flower within the <defs> section of the document and changing our SVG to read:

<use xlink:href="#flower" filter="url(#drop-shadow)"
    transform="translate(4, 4)"/>
<use xlink:href="#flower"/>

However, that would require SVG to execute all the elements that make up the flower twice. Instead, we will add more filter primitives, so that all the work can be handled during rendering.

Storing, Chaining, and Merging Filter Results

Example 10-2 is the updated filter.

Example 10-2. Improved drop shadow filter

<filter id="drop-shadow"> 
    <feGaussianBlur in="SourceAlpha" stdDeviation="2" result="blur"/>     [1]
    <feOffset in="blur" dx="4" dy="4" result="offsetBlur"/>     [2]
    <feMerge>     [3]
        <feMergeNode in="offsetBlur"/>
        <feMergeNode in="SourceGraphic"/>
    </feMerge>
</filter>

[1] The result attribute specifies that the result of this primitive can be referenced later by the name blur. This isn't like an XML id; the name you give is a local name that's only valid for the duration of the primitives contained in the current <filter>.

[2] The <feOffset> primitive takes its input, in this case the blur result from the Gaussian blur, offsets it by the specified dx and dy values, and stores the resulting bitmap under the name offsetBlur.

[3] The <feMerge> primitive encloses a list of <feMergeNode> elements, each of which specifies an input. The inputs are stacked one on top of another in the order that they appear. In this case, we want the offsetBlur below the original sourceGraphic.

We now refer to this improved drop shadow filter sequence when drawing the flower, producing a surprisingly pleasant image in Figure 10-3.

<g id="flower" filter="url(#drop-shadow)">
    <!-- drawing here -->
</g>

Figure 10-3. Result of improved drop shadow

Result of improved drop shadow

Note

When you first start working with filters, I strongly recommend that you do things in stages, testing filters one at a time. I created large numbers of stunningly ugly results during botched attempts to discover how a filter really works. You probably will, too. We'll just keep it as our little secret.

Similarly, when you first learn about filters, you will be tempted to apply as many of them as possible to a drawing, just to see what will happen. Since your purpose is experimentation, go ahead. Once you finish experimenting and begin production work, the purpose of the filter changes. Filters should support and enhance your message, not overwhelm it. Judicious use of one or two filters is a buoy; a flotilla of filters almost always sinks the message.

Creating a Glowing Shadow

The drop shadow works well on the flower, but looks totally unimpressive when applied to text, as we see in Figure 10-4.

Figure 10-4. Drop shadow applied to text

Drop shadow applied to text

Instead, we'd like a glowing turquoise area to surround the text, and we can do this with the <feColorMatrix> primitive to change black to a different color.

The feColorMatrix Element

The <feColorMatrix> element allows you to change color values in a very generalized way. The sequence of primitives used to create a glowing turquoise shadow is shown in Example 10-3.

Example 10-3. Glow filter

 <filter id="glow">
    <feColorMatrix type="matrix"     [1]
        values=
            "0 0 0 0   0
             0 0 0 0.9 0 
             0 0 0 0.9 0 
             0 0 0 1   0"/>
    <feGaussianBlur stdDeviation="2.5"     [2]
        result="coloredBlur"/>     [3]
    <feMerge>     [4]
        <feMergeNode in="coloredBlur"/>
        <feMergeNode in="SourceGraphic"/>
    </feMerge>
</filter>

[1] The <feColorMatrix> is a very versatile primitive, allowing you to modify any of the color or alpha values of a pixel. When the type attribute equals matrix, you must set the value to a series of twenty numbers describing the transformation.

To set up a transformation that adds color to the alpha values, set up your matrix values as follows:

values=
    "0 0 0 red 0
     0 0 0 green 0 
     0 0 0 blue 0 
     0 0 0 1 0"

where the red, green, and blue values are decimal numbers which usually range from zero to 1. In this example, we've set the red to zero, and the green and blue values to 0.9, which will produce a bright cyan color.

You'll note that we didn't specify an in attribute for the input to this primitive; the default is to use the SourceGraphic. We also didn't put a result attribute into this primitive. This means that the color matrix operation's output is available only as the implicit input to the next filter primitive. If you use this shortcut, then the next filter primitive must not have an in attribute.

[2] Now that we have a cyan-colored source, we use Gaussian blur to spread it out.

[3] The resulting cyan-colored blur is stored for future reference as coloredBlur.

[4] As in the previous example, we use <feMerge> to output the glow underneath the object in question.

With these two filters, we can create the new, improved Figure 10-5 with SVG like this:

<g id="flower" style="filter: url(#drop-shadow);">
    <!-- draw the flower -->
</g>
<text x="120" y="50"
    style="filter: url(#glow); fill: #003333; font-size:18;>
Spring <tspan x="120" y="70">Flower</tspan>
</text>

Figure 10-5. Drop shadow and glowing text

Drop shadow and glowing text

More About the feColorMatrix Element

We started with the most general kind of color matrix, where you get to specify any values you wish. There are three other values for the type attribute. Each of these "built-in" color matrices accomplishes a particular visual task and has its own way of specifying values.


hueRotate
The values is a single number that tells how many degrees the color values should be rotated. The mathematics used to accomplish this are very similar to those used in the rotate transformation as described in Section 5.5 in Chapter 5. The relation between rotation and resulting color is not at all obvious, as shown in Figure 10-6.

Figure 10-6. Result of hueRotate on fully saturated colors

Result of hueRotate on fully saturated colors

saturate
The values attribute specifies a single number in the range zero to one. The smaller the number, the more "washed out" the colors will be, as you see in Figure 10-7.

Figure 10-7. Result of saturate on primary colors

Result of saturate on primary colors

luminanceToAlpha
This filter creates an alpha channel based upon a color's luminance. The luminance is the inherent "brightness" of a color, as described in Section 9.2 in Chapter 9. In Figure 10-8, the luminance of the colored squares is used as an alpha channel for solid black squares. The lighter a color, the less the transparency it confers upon the filtered object. The values attribute is ignored for this type.

Figure 10-8. Result of luminanceToAlpha

Result of luminanceToAlpha

The feImage Filter

Up to this point, we've used only the original graphic or its alpha channel as input to a filter. SVG's <feImage> element lets you use any JPG, PNG, or SVG file; or an SVG element with an id attribute as input to a filter. In Example 10-4, we import a picture of the sky with a cloud in it to use as a background in the picture of the flower.

Example 10-4. Using the feImage element

<defs>
<filter id="sky-shadow" filterUnits="objectBoundingBox">
    <feImage xlink:href="sky.jpg" result="sky"/>
    <feGaussianBlur in="SourceAlpha" stdDeviation="2" result="blur"/>
    <feOffset in="blur" dx="4" dy="4" result="offsetBlur"/>
    <feMerge>
        <feMergeNode in="sky"/>
        <feMergeNode in="offsetBlur"/>
        <feMergeNode in="SourceGraphic"/>
    </feMerge>
</filter>
</defs>

<g id="flower" style="filter: url(#sky-shadow)">
    <!-- flower graphic goes here -->
</g>

<!-- show original image -->
<image xlink:href="sky.jpg" x="170" y="10"
    width="122" height="104"/>

Since we're referencing a JPG image, it stretches to fill the bounding box of the filtered object. Figure 10-9 shows the result, with the original picture of the sky shown at right at its true size.

Figure 10-9. Result of feImage

Result of feImage

The feComponentTransfer Filter

The problem with the background is that it is too dark. Using saturate isn't the answer; it raises or lowers all the color levels. What we need to do is increase the level of green and red more than the blue level, and the <feComponentTransfer> element lets us do just that.

You adjust the levels of red, green, blue, and alpha by placing a <feFuncR>, <feFuncG>, <feFuncB>, and <feFuncA> element inside the <feComponentTransfer>. Each of these sub-elements may independently specify a type attribute that tells how that particular channel is to be modified.

To simulate the effect of a brightness control, you specify the linear function, which places the current color value C into the formula: slope  *  C +  intercept. The intercept provides a "base value" for the result; the slope is a simple scaling factor. Example 10-5 uses a filter that adds a brightened sky to the flower with the drop shadow. Note that the red and green channels are adjusted differently than the blue channel. This dramatically brightens the sky in Figure 10-10.

Example 10-5. Changing brightness with feComponentTransfer

<filter id="brightness-shadow" filterUnits="objectBoundingBox">
    <feImage xlink:href="sky.jpg" result="sky"/>
    <feComponentTransfer in="sky" result="sky">
        <feFuncB type="linear" slope="3" intercept="0"/>
        <feFuncR type="linear" slope="1.5" intercept="0.2"/>
        <feFuncG type="linear" slope="1.5" intercept="0.2"/>
    </feComponentTransfer>
    <feGaussianBlur in="SourceAlpha" stdDeviation="2" result="blur"/>
    <feOffset in="blur" dx="4" dy="4" result="offsetBlur"/>
    <feMerge>
        <feMergeNode in="sky"/>
        <feMergeNode in="offsetBlur"/>
        <feMergeNode in="SourceGraphic"/>
    </feMerge>
</filter>

Figure 10-10. Result of linear component transfer

Result of linear component transfer

A simple linear adjustment will add and multiply the same amount to every color value within a channel. This is not the case with the gamma function, which places the current color value C into the formula: amplitude  *  C exponent  +  offset. The offset provides a "base value" for the result; the amplitude is a simple scaling factor, and exponent makes the result a curved line rather than a straight line. Since the color value is always between zero and one, the larger your exponent, the smaller the modified value will be. Figure 10-11 shows the curves generated with exponent values of 0.6 (the solid line) and 0.3 (the dashed line). Looking at the dashed line, you can see that a low original color value such as 0.1 will be boosted to 0.5, a 400% increase. An original value of 0.5, on the other hand, will increase only 80% to 0.9.

Figure 10-11. Gamma curve functions

Gamma curve functions

When you specify a gamma filter, you set the amplitude, exponent, and offset attributes to correspond to the values in the preceding formula. Example 10-6 uses gamma correction to adjust the sky. In this particular case, the differences between Figure 10-12 and Figure 10-10 are minor, but there are some images which can be improved much more by one method than by the other.

Example 10-6. Gamma adjustment with feComponentTransfer

<feImage xlink:href="sky.jpg" result="sky"/>
<feComponentTransfer in="sky" result="sky">
    <feFuncB type="gamma"
        amplitude="1" exponent="0.2" offset="0"/>
    <feFuncR type="gamma"
        amplitude="1" exponent="0.707" offset="0"/>
    <feFuncG type="gamma"
        amplitude="1" exponent="0.707" offset="0"/>
</feComponentTransfer>

Figure 10-12. Result of using gamma correction

Result of using gamma correction

Note

The astute reader (that's you) may have observed that both linear and gamma functions can produce color values greater than 1.0. The SVG specification says that this is not an error; after each filter primitive, the SVG processor will clamp the values to a valid range. Thus, any value greater than 1.0 is reduced to 1.0 and any value less than zero is set to zero.

<feComponentTransfer> has other options for the type attribute. Please note that you may mix and match any of these; you can gamma correct the red values while brightening the green values with a linear function.

identity
A "do-nothing" function. This lets you explicitly state that a color channel should remain unaffected. (This is the default if you don't provide an <feFunc X > element for a particular channel.)

table
Lets you divide the color values into a series of equal intervals, each of which will be proportionately scaled. Consider the following remapping, which doubles the value of the lowest quarter of the color range, squeezes the next quarter into a range of one tenth, keeps the third quarter in exact proportion, then squeezes the last quarter of the values into the remaining 15% of the color range:

Original value range Modified value range
0.00 -- 0.25 0.00 -- 0.50
0.25 -- 0.50 0.50 -- 0.60
0.50 -- 0.75 0.60 -- 0.85
0.75 -- 1.00 0.85 -- 0.100


You would specify this mapping for the green channel by listing the endpoints of the remapped range in the tableValues attribute.

<feFuncG type="table"
    tableValues ="0.0, 0.5, 0.6, 0.85, 1.0"/>

If you are dividing the input spectrum into n different sections, you must provide n+1 items in tableValues, separated by whitespace or commas.

discrete
Lets you divide the color values into a series of equal intervals, each of which will be mapped to a single discrete color value. Consider the following remapping, which maps the value of the lowest quarter of the color range to 0.125, sets the next quarter to 0.375, the third quarter to 0.625, and remaining quarter to 0.875. (That is, each quarter of the range is mapped to its center point.)

Original value range Modified value
0.00 — 0.25 0.125
0.25 — 0.50 0.375
0.50 — 0.75 0.625
0.75 — 1.00 0.875


You would specify this mapping for the green channel by listing the discrete values, separated by commas or whitespace, in the tableValues attribute.

<feFuncG type="discrete"
    tableValues ="0.125 0.375 0.625 0.875"/>

Dividing the input channel into n sections requires n entries in the tableValues attribute. Exception: If you want to remap all the input values to a single output value, you must place that entry into tableValues twice; thus, to set any input value of the blue channel to 0.5, you would say: <feFuncB type="discrete" tableValues="0.5 0.5"/>.

Note

If you want to invert the range of color values for a channel (that is, change increasing values from a minimum to maximum into decreasing values from the maximum to the minimum), use this:

<feFuncX type="table" 
    tableValues="maximum 
                  minimum"/>

Figure 10-13 shows the results of using discrete and table transfers as well as inversion via a table transfer.

Figure 10-13. Result of using table and discrete transfers

Result of using table and discrete transfers

Warning

Ordinarily, the values for red, green, or blue run in a straight line from zero to one, with zero being none of the color and one being 100% of the color. This is called a linear color space. However, when SVG calculates the color values between gradient stops (as described in Chapter 7, in Section 7.2), it uses a special way of representing color such that the values do not follow a straight line from zero to one. This representation is called the standard RGB or sRGB color space, and its use can make gradients much more natural-looking. Figure 10-14 shows a comparison. The first gradient goes from black to green, the second from red to green to blue, and the third from black to white.

By default, filter arithmetic calculates any interpolated ("in-between") values in the linear RGB space, so if you apply a filter to an object that has been filled with a gradient, you will get results that aren't at all what you expect. In order to get the correct result, you must tell the filter to do its calculations in sRGB space by adding a color-interpolation-filters="sRGB" attribute to your <filter> element. Alternatively, you may leave the filter alone and apply color-interpolation="linearRGB" to the <gradient> element, so that it uses the same color space as the default for filters.

Figure 10-14. Comparsion of linearRGB and sRGB

Comparsion of linearRGB and sRGB

The feComposite Filter

So far we have combined the results of filters by using <feMerge> to layer the intermediate results one over another. We will now investigate the much more general <feComposite> element. This element takes two inputs, specified with the in and in2 attributes, and an operator that tells how the two are to be combined. In the following explanation, we'll presume that you've specified in="A" and in2="B".

<feComposite operator="over" in="A" in2="B"/>
Produces the result of layering A over B, exactly as <feMergeNode> does. In fact, <feMergeNode> is really just a convenient shortcut for a <feComposite> element that specifies an over operation.
<feComposite operator="in" in="A" in2="B"/>
The result is the part of A that is within the boundaries of B. Don't confuse the name of this attribute value with the in attribute.
<feComposite operator="out" in="A" in2="B"/>
The result is the part of A that is outside the boundaries of B.
<feComposite operator="atop" in="A" in2="B"/>
The result is the part of A that is inside B, as well as the part of B outside A. To quote the article in which these operators were first defined: "...paper atop table includes paper where it is on top of table, and table otherwise; area beyond the edge of the table is out of the picture."[1]
<feComposite operator="xor" in="A" in2="B"/>
The result is the part of A that is outside B together with the part of B that is outside A.

<feComposite in="A" in2="B" operator="arithmetic" .../>
The ultimate in flexibility. You provide four coefficients, k1, k2, k3, and k4. The result for each pixel is calculated as:

k1 * A * B + k2 * A + k3 * B + k4

Note

The arithmetic operator is useful for doing a "dissolve" effect. If you want to have a resulting image that is a% of image A and b% of image B, set k1 and k4 to zero, k2 to a/100, and k3 to b/100. So, to make a blend with 30% of A and 70% of B, you'd use this:

<feComposite in="A" in2="B" result="combined"
    k1="0" k2="0.30" k3="0.70" k4="0"/>

Figure 10-15 shows the combinations that we've described; the arithmetic blend is 50% of A and 50% of B.

Figure 10-15. Result of using feComposite operators

Result of using feComposite operators

Example 10-7 uses the in and out operators to do "cut-outs." The drop shadow has been eliminated from this example to produce a more visually pleasing result in Figure 10-16.

Example 10-7. Use of feComposite in and out

<defs>
<filter id="sky-in" filterUnits="objectBoundingBox">
    <feImage xlink:href="sky.jpg" result="sky"/>
    <feComposite in="sky" in2="SourceGraphic"
        operator="in"/>
</filter>

<filter id="sky-out" filterUnits="objectBoundingBox">
    <feImage xlink:href="sky.jpg" result="sky"/>
    <feComposite in="sky" in2="SourceGraphic"
        operator="out"/>
</filter>

<g id="flower">
    <!-- flower graphic goes here -->
</g>
</defs>

<use xlink:href="#flower" transform="translate(10,10)"
    style="filter: url(#sky-in);"/>

<use xlink:href="#flower" transform="translate(170,10)"
    style="filter: url(#sky-out);"/>

Figure 10-16. Result of feComposite in and out

Result of feComposite in and out

The feBlend Filter

But wait, there's more! Yes, filters provide yet another way to combine images. The <feBlend> element requires two inputs, specified with the in and in2 attributes, and a mode that tells how the inputs are to be blended. The possible values are: normal, multiply, screen, lighten, and darken. Given opaque inputs <feBlend in="A" in2="B" mode=" m "/>, the following table shows the color of the resulting pixel for each mode:

Mode Effect
normal B only; this is the same as the over operator in <feMerge>.
multiply As the name suggests, the resulting color value is the product of A's color value and B's color value. This tends to dramatically weaken light colors.
screen Adds the color values together, then subtracts their product. This tends to strengthen light colors more than dark colors.
darken Takes the minimum of A and B. This is the darker color, hence the name.
lighten Takes the maximum of A and B. This is the lighter color, hence the name.


Note that the appropriate calculation is done independently for each of the red, green, and blue values. So, if you were to darken a pure red square with RGB values of (100%, 0%, 0%) and a gray square with RGB values of (50%, 50%, 50%), the resulting color would be (50%, 0%, 0%). If the inputs are not opaque, then all the modes except for screen factor in the transparencies when making the calculations.

Finally, once the color value is calculated, the opacity of the result is determined by the formula 1 - (1 - opacity of A ) * (1 - opacity of B ). Using this formula, two opaque items will still be opaque; two items that are 50% opaque will combine to one that is 75% opaque.

Figure 10-17 shows the result of blending an opaque solid gray (50%, 50%, 50%) bar with opaque and 50% opaque color squares that have RGB values of black (#000), yellow (#ff0), red (#f00), medium-bright green (#0c0), and dark blue (#009).

Figure 10-17. Result of feBlend in and out

Result of feBlend in and out

The feFlood and feTile Filters

The <feFlood> and <feTile> elements are "utility filters." Much like <feOffset>, they allow you to carry out certain common operations within a series of filter primitives rather than having to create extra SVG elements in your main graphic.

<feFlood> provides a solid colored area for use in compositing or merging. You provide the flood-fill-color and flood-fill-opacity, and the filter does the rest.

<feTile> takes its input and tiles it horizontally and vertically to fill the area that is specified in the filter. The size of the tile itself is specified by the <feImage> element used as the input to <feTile>.

Example 10-8 uses <feComposite> to cut out the flooded and tiled area to the shape of a flower. The image used as a tile is shown for reference at the upper right of Figure 10-18.

Example 10-8. Example of feFlood and feTile

<defs>
<filter id="flood-filter" x="0" y="0" width="100%" height="100%">
    <feFlood flood-color="#993300" flood-opacity="0.8" result="tint"/>
    <feComposite in="tint" in2="SourceGraphic"
        operator="in"/>
</filter>

<filter id="tile-filter" x="0" y="0" width="100%" height="100%">
    <feImage xlink:href="cloth.jpg" width="32" height="32"
        result="cloth"/>
    <feTile in="cloth" result="cloth"/>
    <feComposite in="cloth" in2="SourceGraphic"
        operator="in"/>
</filter>

<g id="flower">
    <!-- flower graphic goes here -->
</g>

</defs>
<use xlink:href="#flower" transform="translate(0, 0)"
    style="filter: url(#flood-filter);"/>
<use xlink:href="#flower" transform="translate(110,0)"
    style="filter: url(#tile-filter);"/>
<image xlink:href="cloth.jpg" x="220" y="10"
    width="32" height="32"/>

Figure 10-18. Result of feFlood and feTile elements

Result of feFlood and    feTile elements

Lighting Effects

If you draw a bright green circle with SVG, it looks like a refugee from a traffic signal, glowing by its own light and otherwise lying flat on the screen. If you look at a circle cut out of green construction paper, it looks more "real" because it is lit from an outside source and has some texture. A circle cut from green plastic not only is lit from outside, it also has reflected highlights. We call light from an outside source diffuse lighting, and the highlights that reflect off a surface specular lighting, from the Latin speculum, meaning mirror.

In order to achieve these effects, you must specify:

  • The type of lighting you want (<feDiffuseLighting> or <feSpecularLighting>)
  • The object you want to light
  • The color of light you are using
  • The type of light source you want (<fePointLight>, <feDistantLight>, or <feSpotLight>) and its location

You specify the location of a light source in three dimensions; this means you will need a z-value in addition to x- and y-values. The relationship of the x-, y-, and z-axes is shown in Figure 10-19; the positive z axis is "coming out of the screen" and pointing at you.

Both these lighting effects use the alpha channel of the object they are illuminating as a bump map; higher alpha values are presumed to be "raised" above the surface of the object.

Figure 10-19. Relationship of x-, y,- and z-axes

Relationship of x-, y,- and z-axes

Diffuse Lighting

The best way to show how the <feDiffuseLighting> element works is to jump right into Example 10-9. We will shine a pale yellow light on a green circle, textured with the curve pattern that we used in Example 7-1.

Example 10-9. Diffuse lighting with a point light source

<path id="curve" d="M 0 0 Q 5 20 10 10 T 20 20"     [1]
    style="stroke: black; fill: none;"/>

<circle id="green-light" cx="50" cy="50" r="50"     [2]
    style="fill: #060;"/>

<filter id="diff-light" color-interpolation-filters="sRGB"     [3]
    x="0" y="0" width="100%" height="100%">

    <feImage xlink:href="#curve" result="tile"     [4]
        width="20" height="20"/>

    <feTile in="tile" result="tile"/>

    <feColorMatrix type="luminanceToAlpha" in="tile"     [5]
        result="alphaTile"/>

    <feDiffuseLighting in="alphaTile"     [6]
        lighting-color="#ffffcc"
        surfaceScale="1"      [7]
        diffuseConstant="0.5"      [8]
        result="diffuseOutput">     [9]
        <fePointLight x="0" y="50" z="50"/>     [10]
    </feDiffuseLighting>     [11]

    <feComposite in="diffuseOutput" in2="SourceGraphic"     [12]
        operator="in" result="diffuseOutput"/>

    <feBlend in="diffuseOutput" in2="SourceGraphic"     [13]
        mode="screen"/>
</filter>

[1] Define the curve that will be used as the tile.

[2] Define the object that we want to illuminate.

[3] Set the color interpolation method and the boundaries for the filter.

[4] Tile the area of the filter with the curve image. This will become our bump map...

[5] ...so convert it to a pure alpha map, named alphaTile.

[6] This tiled area is the input to the <feDiffuseLighting> element, which we will illuminate with a pale yellow light,as specified by the lighting-color attribute.

[7] The surfaceScale attribute tells the height of the surface for an alpha value of 1. (Specifically, it's the factor by which the alpha value is multiplied.)

[8] diffuseConstant is a multiplicative factor that is used in determining the final RGB values of a pixel. It must have a value greater than or equal to zero; its default value is one. The brighter your lighting-color, the smaller this number should be. Unless you like having your picture washed out.

[9] The result of this filter will be named diffuseOutput.

[10] In this example, we are using a point light source, which means a source that radiates light in all directions. We will position it at the left center of the area we wish to illuminate, and set it 50 units in front of the screen. The farther you set it away from the object, the more evenly the object is illuminated. In this example, we've moved the light up close and personal to get the greatest possible effect.

[11] The end of the <feDiffuseLighting> element.

Note

The input to this filter was an alpha channel; the output is a totally opaque RGB bitmap; its alpha channel is equal to 1.0 at every point.

[12] We use <feComposite>'s in operator to clip the filter's output to the boundaries of the source graphic (the circle).

[13] Finally, we use <feBlend> in screen mode, which tends to lighten the input, to create the final image.

Once this is all defined, the following statement activates the filter on the desired object to produce Figure 10-20:

<use xlink:href="#green-light" style="filter: url(#diff-light);"/>

Figure 10-20. Result of applying diffuse lighting filter

Result of applying diffuse lighting filter

Specular Lighting

Specular lighting, on the other hand, gives highlights rather than illumination. Example 10-10 shows how this works.

Example 10-10. Specular lighting with a distant light

<path id="curve" d="M 0 0 Q 5 20 10 10 T 20 20"     [1]
   style="stroke: black; fill: none;"/>
<circle id="green-light" cx="50" cy="50" r="50" 
    style="fill: #060;"/>

<filter id="spec-light" color-interpolation-filters="sRGB"     [2]
    x="0" y="0" width="100%" height="100%">

    <feImage xlink:href="#curve" result="tile"     [3]
        width="20" height="20"/>
         
    <feTile in="tile" result="tile"/>

    <feColorMatrix type="luminanceToAlpha" in="tile"
        result="alphaTile"/>

    <feSpecularLighting in="alphaTile"     [4]
        lighting-color="#ffffcc"
          surfaceScale="1"      [5]
          specularConstant="1"     [6]
          specularExponent="4"      [7]
          result="specularOutput">     [8]
          <feDistantLight elevation="25" azimuth="0"/>     [9]
    </feSpecularLighting>     [10]

    <feComposite in="specularOutput" in2="SourceGraphic"     [11]
        operator="in" result="specularOutput"/>

    <feComposite in="specularOutput" in2="SourceGraphic"     [12]
        operator="arithmetic" k1="0" k2="1" k3="1" k4="0"/>
</filter>

[1] As in the previous example, the first six lines define the curve and circle.

[2] The only difference between this and the previous example is the filter name.

[3] As in the previous example, this section tiles the curve into an alpha channel.

[4] Starts the definition of the <feSpecularLighting> filter and specifies the lighting-color to be a pale yellow light.

[5] The surfaceScale attribute tells the height of the surface for an alpha value of 1. (Specifically, it's the factor by which the alpha value is multiplied.)

[6] specularConstant is a multiplicative factor that is used in determining the final RGB values of a pixel. It must have a value greater than or equal to zero; its default value is one. The brighter your lighting-color, the smaller this number should be.

[7] specularExponent is another factor that is used in determining the final RGB values of a pixel. This attribute must have a value from 1 to 128; the default value is 1. The larger this number, the more "shiny" the result.

[8] The result of this filter will be named specularOutput.

[9] In this example, we are using a distant light source, which means that is z-value is effectively infinity. The elevation tells how far up or down the light is above the horizon, and the azimuth specifies the angle of the light in the plane of the screen — whether it's to the left, right, top, or bottom of the object being illuminated.

[10] The end of the <feSpecularLighting> element.

Note

The input to this filter was an alpha channel; the output contains both alpha and color information (unlike <feDiffuseLighting>, which always produces an opaque result).

[11] We use <feComposite>'s in operator to clip the filter's output to the boundaries of the source graphic (the circle).

[12] Finally, we use <feComposite> with the arithmetic operator to do a straight addition of the lighting and the source graphic.

Once this is all defined, the following statement activates the filter on the desired object, producing the highlighting relief effect in Figure 10-21.

<use xlink:href="#green-light" style="filter: url(#spec-light);"/>

Figure 10-21. Result of applying specular lighting filter

Result of applying specular lighting filter

Note

An excellent tutorial on lighting effects in three dimensions is available at http://www.webreference.com/3d/lesson12/. We're working in only two dimensions, but much of the information is applicable.

A third type of light source, <feSpotLight>, is specified with these attributes: x, y, and z, the location of the spotlight (default value is zero); pointsAtX, pointsAtY, and pointsAtZ, the place that the spotlight is pointing at (default value is zero); specularExponent, a value that controls the focus for the light source (default value is one); and limitingConeAngle, which restricts the region where the light is projected. This is the angle between the spot light axis and the cone. Thus, if you want a 30 degree spread for the entire cone, specify the angle as 15. (The default value is to allow unlimited spread.)

Accessing the Background

In addition to the SourceGraphic and SourceAlpha filter inputs, a filtered object may access the part of the image that has already been rendered onto the canvas when you invoke a filter. These parts are called BackgroundImage (not BackgroundGraphic) and BackgroundAlpha. In order to access these inputs, the filtered object must be within a container element that has set the enable-background attribute to the value new. Example 10-11 performs a Gaussian blur on the background alpha channel.

Example 10-11. Accessing the background

<defs>
<filter id="blur-background">
    <feGaussianBlur in="BackgroundAlpha"     [1]
        stdDeviation="2" result="blur"/>
    <feOffset in="blur" dx="4" dy="4" result="offsetBlur"/>
    <feMerge>
        <feMergeNode in="offsetBlur"/>
        <feMergeNode in="SourceGraphic"/>
    </feMerge>
</filter>
</defs>

<g enable-background="new">     [2]
    <circle cx="30" cy="30" r="30" style="fill: #fff;"/>     [3]
    <rect x="0" y="0" width="60" height="60"
        style="filter: url(#blur-background);      [4]
        fill: none; stroke: blue;" />
</g>

[1] This is similar to the blur filter used for drop shadows, except that the input is now the BackgroundAlpha rather than the SourceAlpha.

[2] Since <g> is a container element, it is a perfect candidate for placing the enable-background. All the children of this element will have access to the background image and alpha.

[3] We render a white circle onto the canvas; this makes it "invisible" against a white background.

[4] We now draw a rectangle and use the filter. The background alpha that it picks up will be circular, so Figure 10-22 shows a square with a circular shadow. (Strange, but true!)

Figure 10-22. Result of accessing background alpha

Result of accessing background alpha

The feMorphology Element

The <feMorphology> element lets you "thin" or "thicken" a graphic. You specify an operator with a value of erode to thin or dilate to thicken a graphic. The radius attribute tells us how much the lines are to be thickened or thinned. It's ordinarily applied to alpha channels; in Example 10-12 we erode and dilate a simple line drawing. As you see in Figure 10-23, erosion can wreak havoc on a drawing that has thin lines to begin with.

Example 10-12. Thickening and thinning with feMorphology

<defs>
<g id="cat" stroke-width="2">
    <!-- drawing of a cat -->
</g>

<filter id="erode1">
    <feMorphology operator="erode" radius="1"/>
</filter>

<filter id="dilate2">
    <feMorphology operator="dilate" radius="2"/>
</filter>
</defs>

<use xlink:href="#cat"/>
<text x="75" y="170" style="text-anchor: middle;">Normal</text>

<use xlink:href="#cat" transform="translate(150,0)"
    style="filter: url(#erode1);"/>
<text x="225" y="170" style="text-anchor: middle;">Erode 1</text>

<use xlink:href="#cat" transform="translate(300,0)"
    style="filter: url(#dilate2);"/>
<text x="375" y="170" style="text-anchor: middle;">Dilate 2</text>

Figure 10-23. Result of using feMorphology

Result of using feMorphology

The feConvolveMatrix Element

The <feConvolveMatrix> element lets you calculate a pixel's new value in terms of the values of its neighboring pixels. This filter lets you do effects such as blurring, sharpening, embossing, and beveling. It works by combining a pixel with its neighboring pixels to produce a resulting pixel value. Imagine a pixel P and its eight neighboring pixels (the usual case that is used with this filter):

A  B  C
D  P  E
F  G  H

You then specify a list of nine numbers in the kernelMatrix attribute. These numbers tell how much to multiply each pixel by. These products will be added up. The sum could well come out to be greater than one (if all the factors are positive, for example), so, to even the intensity, the result is divided by the total of the factors. Let's say you specify these nine numbers:

<feConvolveMatrix kernelMatrix="
     0  1  2
     3  4  5
     6  7  8"/>

The new value of pixel P will then be:

P' = ((0*A) + (1*B) + (2*C) + 
     (3*D) + (4*P) + (5*E) +
     (6*F) + (7*G) + (8*H)) / ( 0 + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8) 

Example 10-13 achieves the embossing effect shown in Figure 10-24 by taking the upper left neighbor minus the lower right neighbor of each pixel.

Example 10-13. Embossing with feConvolveMatrix

<defs>
<filter id="emboss">
    <feConvolveMatrix
        kernelMatrix="
         1 0 0
         0 0 0
         0 0 -1"/>
</filter>

<g id="flower">
   <!-- flower graphic goes here -->
</g>
</defs>

<use xlink:href="#flower" style="filter: url(#emboss);"/>

Figure 10-24. Result of using feConvolveMatrix

Result of using feConvolveMatrix

Although the default matrix size is three columns by three rows, you can specify any size you want with the order attribute. If you specify order="4", then the matrix will require sixteen numbers (4 by 4) in the kernelMatrix attribute. A matrix with three columns and two rows would be specified by order="3 2" and would require six numbers. The larger your kernel matrix, the more computation is required to produce the result.

For a pixel in the middle of a graphic, the neighbors are easy to identify. What do you do with the pixels on the edges of the graphic? Who are their neighbors? This decision is made by the setting that you give the edgeMode attribute. If you set its value to be duplicate, then <feConvolveMatrix> duplicates the edge values in the required direction to produce a neighbor. The value wrap wraps around to the opposite side to find a neighbor. For example, the neighbor above a pixel at the top is the pixel at the bottom, and the neighbor to the left of a pixel at the left edge is the corresponding pixel at the right edge. The value of none will provide a transparent black pixel (red, green, blue, and alpha values of zero) for any missing neighbors.

The default behavior of <feConvolveMatrix> is to apply the calculations to all the channels, including alpha. If you want to apply calculations only to the red, green, and blue values, specify preserveAlpha as true (the default value is false).

You may also add a fixed offset to the result of each calculation by specifying a value for the bias attribute, although this particular feature does not work in the current 1.0 release of Batik. Some sample convolve effects (which were not designed specifically for SVG) are available at http://www.nebulus.org/tutorials/2d/pictpub/udf/.

The feDisplacementMap Element

This fascinating filter uses the color values of its second input to decide how far to move the pixels in the first input. You specify which color channel should be used to affect the x-coordinate of a pixel with the xChannelSelector attribute; the yChannelSelector attribute specifies the color channel used to affect the y-coordinate. The legal values for these selectors are "R", "G", "B", and "A" (for the alpha channel). You must specify how far to displace pixels; the scale attribute gives the appropriate scaling factor. If you don't specify this attribute, the filter won't do anything.

Example 10-14 creates a gradient rectangle as the second input. The displacement factor will be set to ten, the red channel will be used as an x offset, and the green channel will be used as a y offset. Figure 10-25 shows the result of applying this displacement to the flower.

Example 10-14. Using a gradient as a displacement map

<defs>
<linearGradient id="gradient">
    <stop offset="0" style="stop-color: #ff0000;" />
    <stop offset="0.5" style="stop-color: #00ff00;"/>
    <stop offset="1" style="stop-color: #000000;"/>
</linearGradient>

<rect id="rectangle" x="0" y="0" width="100" height="200"
    style="fill: url(#gradient);"/>

<filter id="displace">
    <feImage xlink:href="#rectangle" result="grad"/>

    <feDisplacementMap
        scale="10"
        xChannelSelector="R"
        yChannelSelector="G"
        in="SourceGraphic" in2="grad"/>
</filter>


<g id="flower">
   <!-- flower graphic goes here -->
</g>
</defs>

<use xlink:href="#flower" style="filter: url(#displace);"/>

Figure 10-25. Result of using feDisplacementMap

Result of using feDisplacementMap

It's possible to use the same graphic for both inputs. This means that a graphic's displacement is controlled by its own coloration. This effect, as written in Example 10-15 and displayed in Figure 10-26, can be quite eccentric.

Example 10-15. Using a Graphic as Its Own Displacement Map

<defs>
<filter id="self-displace">
    <feDisplacementMap
        scale="10"
        xChannelSelector="R"
        yChannelSelector="G"
        in="SourceGraphic" in2="SourceGraphic"/>
</filter>

<g id="flower">
   <!-- flower graphic goes here -->
</g>
</defs>

<use xlink:href="#flower" style="filter: url(#self-displace);"/>

Figure 10-26. Same graphic used as both inputs to feDisplacementMap

Same graphic used as both inputs to feDisplacementMap

The feTurbulence Element

The <feTurbulence> elements lets you produce artificial textures for effects like marble, clouds, etc. by using equations developed by Ken Perlin. An excellent summary is available at http://freespace.virgin.net/hugo.elias/models/m_perlin.htm. You specify these attributes:

type
One of turbulence or fractalNoise. Fractal noise is smoother in appearance.
baseFrequency
The larger the number you give as the value for this attribute, the more quickly colors change in the result. This number must be greater than zero and should be less than one. You may also give two numbers for this attribute; the first will be the frequency in the x direction and the second will be the frequency in the y direction.
numOctaves
This is the number of noise functions that should be added together when generating the final result. The larger this number, the more fine-grained the texture. The default value is one.
seed
The starting value for the random number generator that this filter uses. The default value is zero; change it to get some variety in the result.

Figure 10-27 is a screenshot of an SVG file that shows various values of the first three of these attributes.

Figure 10-27. Various values of feTurbulence attributes

Various values of feTurbulence attributes

Filter Reference Summary

The <filter> element contains a series of filter primitives, each of which takes one or more inputs and provides a single result for use with other filters. The result of the last filter in the series is rendered into the final graphic. You specify the dimensions of the canvas to which the filter applies with the x, y, width, and height attributes.

Table 10-1 presents a filter reference summary. Each of the filter primitive elements has an in attribute that gives the source for the primitive, and may also specify an x, y, width, and height. Default attributes are in boldface.

Table 10-1. Filter reference summary

Element Attributes
<feBlend> in2="second source"mode="normal" | "multiply" | "screen" | "darken" | "lighten"
<feColorMatrix> type="matrix" | "saturate" | "hueRotate" | "luminanceToAlpha"values="matrix values" | "saturation value (0-1)" | "rotate degrees"
<feComponentTransfer> container for <feFuncR>, <feFuncG>, <feFuncB>, and <feFuncA> elements.
<feFuncX> type="identity" | "table" | "discrete" | "linear" | "gamma"tableValues="intervals for table, steps for discrete"slope="linear slope"intercept="linear intercept"amplitude="gamma amplitude"exponent="gamma exponent"offset="gamma offset"
<feComposite> in2="second source"operator="over" | "in" | "out" | "atop" | "xor" | "arithmetic"
<feComposite>(continued) The following attributes are used with arithmetic:k1="factor for in1*in2"k2="factor for in1"k3="factor for in2"k4="additive offset"
<feConvolveMatrix> order="columns rows" (default 3 by 3)kernel="values"bias="offset value"
<feDiffuseLighting> Container for a light source element.surfaceScale="height" (default 1)diffuseConstant="factor" (must be non-negative; default 1)
<feDisplacementMap> scale="displacement factor" (default 0)xChannelSelector="R" | "G" | "B" | "A"yChannelSelector="R" | "G" | "B" | "A"in2="second input"
<feFlood> flood-fill-color="color specification"flood-fill-opacity="value (0-1)"
<feGaussianBlur> stdDeviation="blur spread" (larger is blurrier; default 0)
<feImage> xlink:href="image source"
<feMerge> container for <feMergeNode> elements
<feMergeNode> in="intermediate result
<feMorphology> operator="erode" | "dilate"radius="x-radius y-radius"radius="radius"
<feOffset> dx="x offset" (default 0)dy="y offset" (default 0)
<feSpecularLighting> Container for a light source element.surfaceScale="height" (default 1)specularConstant="factor" (must be non-negative; default 1)specularExponent="exponent" (range 1-128; default 1)
<feTile> tiles the in layer
<feTurbulence> type="turbulence" | "fractalNoise"baseFrequency="x-frequency y-frequency"baseFrequency="frequency"numOctaves="integer"seed="number"
<feDistantLight> azimuth="degrees" (default 0)elevation="degrees" (default 0)
<fePointLight> x="coordinate" (default 0)y="coordinate" (default 0)z="coordinate" (default 0)
<feSpotLight> x="coordinate" (default 0)y="coordinate" (default 0)z="coordinate" (default 0)pointsAtX="coordinate" (default 0)pointsAtY="coordinate" (default 0)pointsAtZ="coordinate" (default 0)specularConstant="focus control" (default 1)limitingConeAngle="degrees"

Notes

  1. "Compositing Digital Images," T. Porter, T. Duff, SIGGRAPH '84 Conference Proceedings, Association for Computing Machinery, Volume 18, Number 3, July 1984.
Personal tools