JT's Scribblings

a.k.a. a blog of some description.

Shiny stuff with SVG, part 3: Combining filter effects

Saturday October 27th 2012, at 8:10 pm

Here we go then, the third post in my SVG Shinies series. I apologise for the lack of examples in the previous post — this one will include a couple more. This time the focus will be on the feComposite, feBlend and feMerge filter primitives and how to wire different primitives together.

Wiring up primitives using in, in2 and result

Many filter primitives (with a few exceptions including feImage and feFlood) require some input of image data (some require two), and these are supplied via the in and in2 attributes. The output of a filter primitive can be assigned an identifier with the result attribute.

The in and in2 attributes can take the values SourceGraphic, SourceAlpha, BackgroundImage, BackgroundAlpha, FillPaint, StrokePaint, or a reference to another primitive's result. The behaviours for each are as follows:

  • SourceGraphic represents the original element to which the filter is applied (as full RGBA image data)
  • SourceAlpha represents just the alpha channel of the original element to which the filter is applied
  • BackgroundImage represents the area directly underneath the filter region
  • BackgroundAlpha represents just the alpha channel of the area underneath the filter region
  • FillPaint represents whatever fill was applied to the element in question
  • StrokePaint represents whatever stroke was applied to the element
  • Any other value should be a reference equal to the result attribute for any preceding filter primitive. In this case, the output of the referenced primitive will be used as the specified input.

NB: WebKit browsers do not (as of writing) support the BackgroundImage and BackgroundAlpha values for inputs. This has been a known bug for several years now so probably won't be fixed any time soon.

The output of the last filter primitive is what is finally rendered.

feComposite, feBlend and feMerge

These three filter primitives are essentially the core operations for combining separate filter results into one output. Each has its own different characteristic.

feComposite

The feComposite primitive combines two objects using a given Porter-Duff alpha compositing method. The primitive itself comes with three properties, in and in2 to specify the two input objects, and operator to specify the compositing operator. If either of in or in2 is omitted, the composite operation will use the output of the previous operation. Available values for operator are "over", "in", "out", "atop", "xor" and "arithmetic". The first five of these behave exactly as described by the Porter-Duff methods:

over in out atop xor 100% Opacity 50% Opacity
<svg>
  <defs>
    <filter id="f1" x="0" y="0" width="150%" height="800%">
      <feOffset dx="20" dy="20" />
      <feColorMatrix type="hueRotate" values="240" result="blue"/>

      <feOffset dx="0" dy="80" in="SourceGraphic" result="red2" />
      <feOffset dx="0" dy="80" in="blue" result="blue2" />

      <feOffset dx="0" dy="80" in="red2" result="red3" />
      <feOffset dx="0" dy="80" in="blue2" result="blue3" />

      <feOffset dx="0" dy="80" in="red3" result="red4" />
      <feOffset dx="0" dy="80" in="blue3" result="blue4" />

      <feOffset dx="0" dy="80" in="red4" result="red5" />
      <feOffset dx="0" dy="80" in="blue4" result="blue5" />

      <feComposite in="blue" in2="SourceGraphic"
                   operator="over" result="over"/>
      <feComposite in="blue2" in2="red2"
                   operator="in" result="in" />
      <feComposite in="blue3" in2="red3"
                   operator="out" result="out" />
      <feComposite in="blue4" in2="red4"
                   operator="atop" result="atop" />
      <feComposite in="blue5" in2="red5"
                   operator="xor" result="xor" />
      <feMerge>
        <feMergeNode in="over" />
        <feMergeNode in="in" />
        <feMergeNode in="out" />
        <feMergeNode in="atop" />
        <feMergeNode in="xor" />
      </feMerge>
    </filter>

    <g id="labels" text-anchor="middle"
       transform="translate(15,45)rotate(-90)">
      <text x="-35">over</text>
      <text x="-115">in</text>
      <text x="-195">out</text>
      <text x="-275">atop</text>
      <text x="-355">xor</text>
    </g>
  </defs>

  <use xlink:href="#labels" />
  <use xlink:href="#labels" transform="translate(0,460)" />
  <g text-anchor="middle" transform="translate(60,20)">
    <text>100%</text>
    <text y="20">Opacity</text>
  </g>
  <g text-anchor="middle" transform="translate(60,480)">
    <text>50%</text>
    <text y="20">Opacity</text>
  </g>

  <g fill="#f00" transform="translate(25,45)">
    <rect width="50" height="50" filter="url(#f1)" />
    <rect y="460" width="50" height="50"
          fill-opacity="0.5" filter="url(#f1)" />
  </g>
</svg>

The above example shows the result of a feComposite with the red square as in and the blue square as in2, with each of the first five operator values.

The arithmetic operator introduces extra parameters, k1, k2, k3 and k4 to the feComposite primitive, and calculates the output image data on a pixel-by-pixel, channel-by-channel basis, according to the formula:

out = k1*i1*i2 + k2*i1 + k3*i2 + k4

Where i1 and i2 correspond respectively to the pixel values of in and in2, measured between 0 and 1.

For more information on the various alpha compositing methods, take a look at the Wikipedia page, which has a more detailed description of how the different methods operate.

feBlend

The feBlend primitive behaves similarly to feComposite in that it takes input on in and in2 and combines them into an output, but differs in that it uses one of the five standard blend modes normal, multiply, screen, darken and lighten, as specified by the mode attribute. The following example demonstrates all of these modes:

Normal Multiply Screen Darken Lighten
<svg>
  <defs>
    <linearGradient id="grad">
      <stop offset="10%" stop-color="#fff" />
      <stop offset="30%" stop-color="#000" />
      <stop offset="50%" stop-color="#f00" />
      <stop offset="60%" stop-color="#ff0" />
      <stop offset="70%" stop-color="#0f0" />
      <stop offset="80%" stop-color="#0ff" />
      <stop offset="90%" stop-color="#00f" />
      <stop offset="100%" stop-color="#f0f" />
    </linearGradient>
    <filter id="f_normal">
      <feMorphology operator="erode" radius="10" result="morph" />
      <feFlood flood-color="#777" />
      <feComposite operator="in" in2="morph" />
      <feBlend mode="normal" in2="SourceGraphic"/>
    </filter>
    <filter id="f_multiply">
      <feMorphology operator="erode" radius="10" result="morph" />
      <feFlood flood-color="#777" />
      <feComposite operator="in" in2="morph" />
      <feBlend mode="multiply" in2="SourceGraphic"/>
    </filter>
    <filter id="f_screen">
      <feMorphology operator="erode" radius="10" result="morph" />
      <feFlood flood-color="#777" />
      <feComposite operator="in" in2="morph" />
      <feBlend mode="screen" in2="SourceGraphic"/>
    </filter>
    <filter id="f_darken">
      <feMorphology operator="erode" radius="10" result="morph" />
      <feFlood flood-color="#777" />
      <feComposite operator="in" in2="morph" />
      <feBlend mode="darken" in2="SourceGraphic"/>
    </filter>
    <filter id="f_lighten">
      <feMorphology operator="erode" radius="10" result="morph" />
      <feFlood flood-color="#777" />
      <feComposite operator="in" in2="morph" />
      <feBlend mode="lighten" in2="SourceGraphic"/>
    </filter>
  </defs>
  <g text-anchor="middle" transform="translate(50)">
    <text y="20">Normal</text>
    <text y="90">Multiply</text>
    <text y="160">Screen</text>
    <text y="230">Darken</text>
    <text y="300">Lighten</text>
  </g>
  <g fill="url(#grad)">
    <rect y="25" width="100" height="40" filter="url(#f_normal)" />
    <rect y="95" width="100" height="40" filter="url(#f_multiply)" />
    <rect y="165" width="100" height="40" filter="url(#f_screen)" />
    <rect y="235" width="100" height="40" filter="url(#f_darken)" />
    <rect y="305" width="100" height="40" filter="url(#f_lighten)" />
  </g>
</svg>

(Unfortunately the example above is a little more convoluted than I'd have liked, but WebKit's lack of support for in="BackgroundImage" necessitated the use of a few extra filters to create the overlaid rectangle.)

feMerge

Once you've got your head around how feComposite and feBlend work, feMerge is much simpler. It's effectively equivalent to feComposite's over and feBlend's normal operators, but with an arbitrary number of inputs.

An <feMerge> element should be specified with any number of <feMergeNode> elements as children, each of which takes a single in attribute specifying the output of a filter primitive to draw. The results are then drawn sequentially from first to last. An example of feMerge can be seen in the feComposite example above.

Other Posts in This Series

Useful Linkage