svgsvg-filters

How to apply filters to shapes multiple times in SVG?


I am trying to apply multiple operations to shapes. (Eventually, my end goal is to blur 2 different shapes using a different color matrix and then blend them together. This is only a reproducible example where I am stuck.)

I cannot understand why I am seeing a difference between the two cases mentioned here.

<svg width="400" height="300">
  <rect width="400" height="300" fill="#5d48c7"/>
  
  <defs>
    <filter
      id="my-blur"
      x="-50%" y="-50%" width="200%" height="200%"
      >
      <feGaussianBlur stdDeviation="15"/>
    </filter>

    <rect id="rect1" width="200" height="150" x="50" y="50" fill="white" filter="url(#my-blur)"/>
    
  </defs>

  <!-- Case 1: Blurred and works as expected  -->
  <use href="#rect1" />
</svg>

But when I am using the output of the second-filter, which is just a passthrough, I am getting this:

<svg width="400" height="300">
  <rect width="400" height="300" fill="#5d48c7"/>
  
  <defs>
    <filter
      id="my-blur"
      x="-50%" y="-50%" width="200%" height="200%"
      >
      <feGaussianBlur stdDeviation="15"/>
    </filter>

    <rect id="rect1" width="200" height="150" x="50" y="50" fill="white" filter="url(#my-blur)"/>

    <filter id="second-filter"
      x="0" y="0" width="400" height="300"
      >
      <feImage href="#rect1"/>
    </filter>
    
  </defs>

  <!-- Case 1: Blurred and works as expected  -->
  <!-- <use href="#rect1" /> -->

  <!-- Case 2: maybe blurred?, positioned differently, looks to be cut off  -->  
  <rect width="400" height="300" filter="url(#second-filter)" />
</svg>

In the second example, I am just using an feImage to reference the shape (which is drawn on a full-screen rectangle), but for some reason it gets cut off and the positioning is off. I have no idea how to configure the #second-filter to just use the blurred rectangle as an input image so that I will be able to apply multiple operations on it later on.


Solution

  • The problem is that when you reference the rect1 - it's grabbing whatever content is within the rectangle defined by the rect's x,y,width and height, so it's clipping out the blur that is outside the rect's dimensions.

    If you want the rect + its blur, then you need to put it inside another bigger rect within a group and import that into the feImage.

    Also - this won't work in Firefox since it's doesn't accept fragment identifiers as input to feImage - you'll need to inline the content as a complete svg/xml data URI for cross-browser.

    (A higher level question is why you're trying to chain separate filters together using feImage rather than doing everything inside a single filter where you can use "result" and "in/in2" to create processing trees)

    <svg width="400" height="300">
      <rect width="400" height="300" fill="#5d48c7"/>
      
      <defs>
        <filter
          id="my-blur"
          x="-50%" y="-50%" width="200%" height="200%"
          >
          <feGaussianBlur stdDeviation="15"/>
        </filter>
    
        <g id="rectandblur">
        <rect id="rect1" width="200" height="150" x="50" y="50" fill="white" filter="url(#my-blur)"/>
        
        <rect id = "rect2" width="270" height="220" x="15" y="15" fill="none"/>
        </g>
    
        <filter id="second-filter"
          x="0" y="0" width="400" height="300" filterUnits="userSpaceOnUse">
          <feImage href="#rectandblur"/>
        </filter>
        
      </defs>
    
      <!-- Case 1: Blurred and works as expected  -->
      <!-- <use href="#rect1" /> -->
    
      <!-- Case 2: maybe blurred?, positioned differently, looks to be cut off  -->  
      <rect width="400" height="300" filter="url(#second-filter)" />
    </svg>