Search code examples
svgsvg-filters

svg filter compositing with partial transparency


I'm trying to composite one shape onto another. The top layer is a set of strokes that have opacity. The bottom is just a flood fill.

The issue is that I need the strokes to retain their original color and opacity - not be blended with the flood.

The issue I'm having is that when I "cut" the strokes with opacity from the flood, it cuts the color but not the opacity. So when I replace the cut with the strokes w/ opacity, the opacity is no longer correct.

I've got a working/clunky solution where I have to create two sets of identical strokes - one with opacity and one solid. The solid is used to perform the cut and then I can take the one with opacity and overlay it.

<svg width="120" height="120" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
      <defs>
        <!-- create duplicate strokes, one with opacity and one without -->
        <path id="strokes" d="M20,10 L60,60 M50,10 L90,90" stroke="rgb(112,48,160)" stroke-width="15" stroke-opacity="0.5" fill="none" />
        <path id="strokesSolid" d="M20,10 L60,60 M50,10 L90,90" stroke="rgb(112,48,160)" stroke-width="15" fill="none" />
        <filter id="filter" color-interpolation-filters="sRGB">
          <feFlood flood-color="blue" x="0" y="0" width="100" height="100" result="flood" />
          <feImage xlink:href="#strokesSolid" x="0" y="0" width="100" height="100" result="lines2" />
          <!-- cut the solid stroke -->
          <feComposite in="flood" in2="lines2" operator="out" result="cut" />
          <!-- composite the opacity stroke -->
          <feImage xlink:href="#strokes" x="0" y="0" width="100" height="100" result="lines" />
          <feComposite in="lines" in2="cut" operator="over" result="final" />
        </filter>
      </defs>
      <rect x="15" y="15" width="105" height="105" fill="white" stroke="red" stroke-width="2" id="backdrop" />
      <rect x="20" y="20" width="100" height="100" fill="rgb(0, 128, 0)" id="compare" />
      <rect width="100" height="100" filter="url(#filter)" id="partialTransparentShape" />
    </svg>

The red outline and green rect back are just their for testing purposes to prove that this produces the right results (save SVG as file, open in Inkscape, export to .png, open in Paint.net, use color picker to prove the color/opacity).

Is there a more straightforward way of achieving this without having to create two identical paths with and without opacity?


Solution

  • Just use an extra feColorMatrix to double the opacity of the line before you use the feComposite/out

    <svg width="120" height="120" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
          <defs>
            <path id="strokes" d="M20,10 L60,60 M50,10 L90,90" stroke="rgb(112,48,160)" stroke-width="15" stroke-opacity="0.5" fill="none" />
    
            <filter id="filter" color-interpolation-filters="sRGB">
              <feFlood flood-color="blue" x="0" y="0" width="100" height="100" result="flood" />
              <feImage xlink:href="#strokes" x="0" y="0" width="100" height="100" />
              <!-- cut the solid stroke -->
              <feColorMatrix type="matrix" values="1 0 0 0 0 
                                                   0 1 0 0 0 
                                                   0 0 1 0 0 
                                                   0 0 0 2 0"/>
              <feComposite in="flood" operator="out" result="cut" />
              <!-- composite the opacity stroke -->
              <feImage xlink:href="#strokes" x="0" y="0" width="100" height="100" result="lines" />
              <feComposite in="lines" in2="cut" operator="over" result="final" />
            </filter>
          </defs>
          <rect x="15" y="15" width="105" height="105" fill="white" stroke="red" stroke-width="2" id="backdrop" />
          <rect x="20" y="20" width="100" height="100" fill="rgb(0, 128, 0)" id="compare" />
          <rect width="100" height="100" filter="url(#filter)" id="partialTransparentShape" />
        </svg>