How to blend two raster images using SVG filters

Starting withInternet Explorer 10, you can blend, or merge, two raster images using the <feImage> and <feBlend> SVG filter primitives. Here we'll show you how.

Let's start with a mystery. Consider the following example that attempts to blend slide1.jpg and slide2.jpg:

Example 1

<!DOCTYPE html>
<html>

<head>
  <meta http-equiv="X-UA-Compatible" content="IE=10" >
  <meta content="text/html; charset=utf-8" http-equiv="Content-Type">
  <title>Example 1</title>
</head>

<body>
  <h1>Note that this example requires a browser that supports SVG filters, 
      such as Internet Explorer 10 or later.</h1>
  <svg>
    <defs>
      <filter id="myFilter">
        <feImage xlink:href="slide2.jpg" result="slide2" />
        <feBlend in="SourceGraphic" in2="slide2" mode="lighten" />
      </filter>
    </defs>   
    <image href="slide1.jpg" filter="url(#myFilter)" /> 
  </svg>
</body>

</html>

This screen shot shows the unexpected output of example 1:

Because the blended image doesn't render, we know something went wrong. Before explaining what, we'll first describe how this example fundamentally works.

example 1 blends slide1.jpg and slide2.jpg by applying the myFilter SVG filter to the slide1.jpg SVG image element:

<image xlink:href="slide1.jpg" filter="url(#myFilter)" />

The myFilter SVG filter is composed of two filter primitives:

<feImage xlink:href="slide2.jpg" result="slide2" />
<feBlend in="SourceGraphic" in2="slide2" mode="lighten" />

The input of <feImage> is slide2.jpg and its output is slide2. The slide2 moniker can now be used as input to other filters (such as <feBlend>).

Because <feBlend> blends two objects, it has two inputs, in and in2. The first, in="SourceGraphic", represents the image associated with the object the filter is be applied to. That is, in="SourceGraphic" represents slide1.jpg in <image xlink:href="slide1.jpg" filter="url(#myFilter)" />.

With these fundamentals understood, we can now answer why the blended image is not rendered. It turns out that the default width and height values for SVG <image> elements is 0. Because example 1 neglected to specify <image> , width, and height values, 0 was used by default. Therefore, the blended image wasn't rendered.

Clearly, the solution is to specify width and height values for the <image> element (not to be confused with HTML's <img> element). Not knowing the precise dimensions of slide1.jpg and slide2.jpg, we first try using width="100%" and height="100%", as shown next:

Example 2

<!DOCTYPE html>
<html>

<head>
  <meta http-equiv="X-UA-Compatible" content="IE=10" >
  <meta content="text/html; charset=utf-8" http-equiv="Content-Type">
  <title>Example 2</title>
</head>

<body>
  <h1>Note that this example requires a browser that supports SVG filters, 
      such as Internet Explorer 10 or later.</h1>
  <svg>
    <defs>
      <filter id="myFilter">
        <feImage xlink:href="slide2.jpg" result="slide2" />
        <feBlend in="SourceGraphic" in2="slide2" mode="lighten" />
      </filter>
    </defs>   
    <image href="slide1.jpg" filter="url(#myFilter)" width="100%" height="100%" /> 
  </svg>
</body>

</html>

With width and height specified, the blended image is rendered (using mode="lighten"):

Clearly, something odd is still occurring. One issue is that the rendered image appears much smaller than expected. To test this hypothesis, we first display the blended image (using <image>) followed by its underlying images slide1.jpg and slide2.jpg (using <img>):

Example 3

<!DOCTYPE html>
<html>

<head>
  <meta http-equiv="X-UA-Compatible" content="IE=10" >
  <meta content="text/html; charset=utf-8" http-equiv="Content-Type">
  <title>Example 3</title>
  <style>
    svg, img {
      display: block;
    }
    
    svg {
      border: 2px red solid;
    }
    
    img {
      border: 2px green solid;
    }    
  </style>
</head>

<body>
  <h1>Note that this example requires a browser that supports SVG filters, 
      such as Internet Explorer 10 or later.</h1>
  <svg>
    <defs>
      <filter id="myFilter">
        <feImage xlink:href="slide2.jpg" result="slide2" />
        <feBlend in="SourceGraphic" in2="slide2" mode="lighten" />
      </filter>
    </defs>   
    <image href="slide1.jpg" filter="url(#myFilter)" width="100%" height="100%" /> 
  </svg>
  <img src="slide1.jpg" />
  <img src="slide2.jpg" />
</body>

</html>

Here we see the output of example 3:

Our hypothesis was true—the blended image is smaller than its underlying images, despite having a width and height value of 100%:

<image href="slide1.jpg" filter="url(#myFilter)" width="100%" height="100%" />

Now, we explain what happened:

  • In HTML, <img src="slide1.jpg" width="100%" height="100%"> requests that the HTML image be rendered in its full size.
  • In SVG, <image xlink:href="slide1.jpg" width="100%" height="100%" /> requests that the SVG image be expanded to fill its container (the parent <svg> element, in this case).

Additionally, because no width or height values were specified on the <svg> element, the <svg> element uses its default values:

  • A width equal to the current screen size.
  • A height equal to 150px.

With this in mind, the blended image is not its full size because:

  1. The current (default) height of the <svg> element is 150px.
  2. SVG <image> elements maintain the aspect ratio of their associated images by default, which in this case, is 4:3 (or 1.3333).
  3. The height of the SVG <image> element (the blended image) is set to 100%. Because its parent container has a height of 150px, the height of the SVG <image> element also becomes 150px.
  4. Assuming that the width of the screen is much larger than 150px (as shown in the prior screen shot), steps 2 and 3 imply that the width of the blended image must be 200px (that is, (150px) ∙ (1.3333) = 200px).

And if you're curious, the reason the blended image is centered is because its width value is set to 100% (which produces an effect similar to margin: 0 auto in CSS).

Earlier, we learned why the blended image is centered and smaller than its underlying images, but this doesn't explain why this oddly blended image is larger than its parent <svg> container. You can see this in the prior screen shot where the blended image flows outside of the <svg> element’s red border. This behavior is due to the fact that the default x, y, width, and height values of <filter> elements are -10%, -10%, 120%, 120%, respectively (as described in The 'filter' element).

Because the default height of the applied filter is 120%, it expands the blended image outside of its container by 20% (10% above and 10% below). To resolve this issue, we set these <filter> attributes like this:

<filter id="myFilter" x="0" y="0" width="100%" height="100%">

The effect of doing so is shown in example 4 (right-click to view source) and produces a blended image that doesn't flow outside of its parent element:

And as hinted to previously, in order to make the blended image its full size, we must provide dimensions for both the <svg> and <image> elements, as shown next:

Example 5

<!DOCTYPE html>
<html>

<head>
  <meta http-equiv="X-UA-Compatible" content="IE=10" >
  <meta content="text/html; charset=utf-8" http-equiv="Content-Type">
  <title>Example 5</title>
  <style>
    svg, img {
      display: block;
    }
    
    svg {
      border: 2px red solid;
    }
    
    img {
      border: 2px green solid;
    }  
  </style>
</head>

<body>
  <h1>Note that this example requires a browser that supports SVG filters, 
      such as Internet Explorer 10 or later.</h1>
  <svg width="400" height="300">
    <defs>
      <filter id="myFilter" x="0" y="0" width="100%" height="100%">
        <feImage href="slide2.jpg" result="slide2" />
        <feBlend in="SourceGraphic" in2="slide2" mode="lighten" />
      </filter>
    </defs>   
    <image xlink:href="slide1.jpg" filter="url(#myFilter)" width="400" height="300" /> 
  </svg>
  <img src="slide1.jpg" />
  <img src="slide2.jpg" />
</body>

</html>

Here we show a screen shot showing the output of example 5:

You can also center the blended imaged by setting the <svg> and <image> element's width values to 100%, as shown next:

Example 6

<!DOCTYPE html>
<html>

<head>
  <meta http-equiv="X-UA-Compatible" content="IE=10" >
  <meta content="text/html; charset=utf-8" http-equiv="Content-Type">
  <title>Example 6</title>
  <style>
    svg {
      display: block;
      border: 2px black solid;
    } 
  </style>
</head>

<body>
  <h1>Note that this example requires a browser that supports SVG filters, 
      such as Internet Explorer 10 or later.</h1>
  <svg width="100%" height="300">
    <defs>
      <filter id="myFilter" x="0" y="0" width="100%" height="100%">
        <feImage href="slide2.jpg" result="slide2" />
        <feBlend in="SourceGraphic" in2="slide2" mode="lighten" />
      </filter>
    </defs>   
    <image href="slide1.jpg" filter="url(#myFilter)" width="100%" height="300" /> 
  </svg>
</body>

</html>

Here's a screen shot showing the output of example 6:

All the prior code examples used mode="lighten". This leaves the remaining mode values to explore:

  • normal
  • multiply
  • screen
  • darken

The final example programmatically blends slide1.jpg and slide2.jpg using all possible mode values:

Example 7

<!DOCTYPE html>
<html>

<head>
  <meta http-equiv="X-UA-Compatible" content="IE=10" > <!-- For intranet development only. Remove prior to placing the page into production. -->
  <meta content="text/html; charset=utf-8" http-equiv="Content-Type">
  <title>Example 7</title>
</head>

<body>
  <h1>Note that this example requires a browser that supports SVG filters, such as Internet Explorer 10 or later.</h1>
  <svg width="100%" height="100%">
    <defs>
      <!-- "Missing" markup injected via JavaScript. -->
    </defs>   
  </svg>
  <script>
    var defsElement = document.getElementsByTagName('defs')[0];
    var svgElement = document.getElementsByTagName('svg')[0];
    var modeList = ['normal', 'multiply', 'screen', 'darken', 'lighten'];

    for (var i = 0; i < modeList.length; i++) {
      var filterElement = document.createElementNS('http://www.w3.org/2000/svg', 'filter');
      filterElement.setAttribute('id', "filter" + i);
      filterElement.setAttribute('x', 0);
      filterElement.setAttribute('y', 0);
      filterElement.setAttribute('width', '100%');
      filterElement.setAttribute('height', '100%');

      var feImageElement = document.createElementNS('http://www.w3.org/2000/svg', 'feImage');
      feImageElement.setAttribute('href', 'slide2.jpg');
      feImageElement.setAttribute('result', 'slide2');
      filterElement.appendChild(feImageElement);

      var feBlendElement = document.createElementNS('http://www.w3.org/2000/svg', 'feBlend');
      feBlendElement.setAttribute('in', 'SourceGraphic');
      feBlendElement.setAttribute('in2', 'slide2');
      feBlendElement.setAttribute('mode', modeList[i]);
      filterElement.appendChild(feBlendElement);

      defsElement.appendChild(filterElement);
    } // for

    svgElement.appendChild(defsElement);

    for (var i = 0; i < modeList.length; i++) {
      var svgImageElement = document.createElementNS('http://www.w3.org/2000/svg', 'image');
      svgImageElement.setAttribute('href', 'slide1.jpg');
      svgImageElement.setAttribute('x', 0);
      svgImageElement.setAttribute('y', i*304);
      svgImageElement.setAttribute('width', '400');
      svgImageElement.setAttribute('height', '300');
      svgImageElement.setAttribute('filter', 'url(#filter' + i + ')');

      svgElement.appendChild(svgImageElement);
    } // for

    for (var i = 0; i < modeList.length; i++) {
      var svgTextElement = document.createElementNS('http://www.w3.org/2000/svg', 'text');
      svgTextElement.setAttribute('x', 4);
      svgTextElement.setAttribute('y', i*304 + 26);
      svgTextElement.style.fill = "white"; // Equivalent to svgTextElement.setAttribute('fill', 'white')
      svgTextElement.style.fontSize = "2em"; // Equivalent to svgTextElement.setAttribute('font-size', '2em')
      svgTextElement.style.fontWeight = "bold"; // Equivalent to svgTextElement.setAttribute('font-weight', 'bold')
      svgTextElement.appendChild(document.createTextNode(modeList[i]));
      
      svgElement.appendChild(svgTextElement);
    }
  </script>
</body>

</html>

And the output of example 7:

All the previous examples only used two filter primitives (<feImage> and <feBlend>) to produce a number of interesting effects. However, there are many more possibilities. As of this writing, there are 16 possible filter primitives (see Filter Effects for the current list):

Because the output of one filter can be used as input to another, the number of possible effects is extremely large. To learn more about how to combine SVG filters to produce interesting results, see Filter effects.

Filter effects

How to use canvas, SVG, and multi-touch to create a tiled puzzle game

HTML5 Graphics

Internet Explorer 9 HTML5 Graphics