I needed a way to add a "stroke" (outline) and drop-shadow effect to a transparent PNG image based on its alpha mask, and the only solution I could find was using custom SVG filters. (Note: The web app for which I need these effects is for my own private use, so it's ok that this solution isn't compatible with legacy browsers. Moving on...)
I had never used SVG before, but it was pretty simple to create stroke and drop-shadow filters individually. Unfortunately, I could not find a way to create a combined effect without actually copying-and-pasting the filters into a new one, as shown in the code below:
<svg width="0" height="0" xmlns="http://www.w3.org/2000/svg">
<!-- drop shadow -->
<filter id="drop-shadow">
<feGaussianBlur in="SourceAlpha" stdDeviation="4" />
<feOffset result="m_offsetBlurred" dx="12" dy="12" />
<feFlood result="m_floodTrans50" flood-color="rgba(0,0,0,0.5)" />
<feComposite result="m_offsetBlurredTrans50" in="m_floodTrans50" in2="m_offsetBlurred" operator="in" />
<feMerge>
<feMergeNode in="m_offsetBlurredTrans50" />
<feMergeNode in="SourceGraphic" />
</feMerge>
</filter>
<!-- outer stroke -->
<filter id="outer-stroke">
<!-- create rectangle of the desired color -->
<feFlood result="m_floodRect" flood-color="black" />
<!-- create copy of png's alpha mask and expand -->
<feMorphology result="m_expandedMask" in="SourceAlpha" operator="dilate" radius="1" />
<!-- "cut out" a section of the flood fill matching the expanded copy -->
<feComposite result="m_expandedColored" in="m_floodRect" in2="m_expandedMask" operator="in" />
<!-- blend it behind the original shape to create the outline effect -->
<feBlend in="SourceGraphic" in2="m_expandedColored" mode="normal" />
</filter>
<!-- drop shadow & outer stroke (must copy & paste the 2 filters above, which violates the DRY principle) -->
<filter id="outer-stroke-drop-shadow">
<!-- create rectangle of the desired color -->
<feFlood result="m_floodRect" flood-color="black" />
<!-- create copy of png's alpha mask and expand -->
<feMorphology result="m_expandedMask" in="SourceAlpha" operator="dilate" radius="1" />
<!-- "cut out" a section of the flood fill matching the expanded copy -->
<feComposite result="m_expandedColored" in="m_floodRect" in2="m_expandedMask" operator="in" />
<!-- blend it behind the original shape to create the outline effect -->
<feBlend result="m_stroked" in="SourceGraphic" in2="m_expandedColored" mode="normal" />
<!-- add drop shadow -->
<feGaussianBlur result="m_blurred" in="SourceAlpha" stdDeviation="4" />
<feOffset result="m_offsetBlurred" in="m_blurred" dx="12" dy="12" />
<feFlood result="m_floodTrans50" flood-color="rgba(0,0,0,0.5)" />
<feComposite result="m_offsetBlurredTrans50" in="m_floodTrans50" in2="m_offsetBlurred" operator="in" />
<feMerge>
<feMergeNode in="m_offsetBlurredTrans50" />
<feMergeNode in="m_stroked" />
</feMerge>
</filter>
</svg>
<style>
.fx_drop_shadow { filter: url('#drop-shadow'); }
.fx_outer_stroke { filter: url('#outer-stroke'); }
.fx_outer_stroke_drop_shadow { filter: url('#outer-stroke-drop-shadow'); }
</style>
<div>
<img src="gfx/odd_shape.png" />
<img src="gfx/odd_shape.png" class="fx_drop_shadow" />
<img src="gfx/odd_shape.png" class="fx_outer_stroke" />
<img src="gfx/odd_shape.png" class="fx_outer_stroke_drop_shadow" />
</div>
Here is how the above code will render in an HTML5 document:
And here is the original PNG graphic (odd_shape.png):
Question 1: How can I reuse the first 2 filters (drop-shadow
and outer-stroke
) so I can simply apply them in the combined filter (outer-stroke-drop-shadow
) instead of having to copy and paste them.
Question 2: Is it possible to parameterize the custom filters so that I can specify things such as the stroke color, or the transparency of the drop shadow? This would make them even more reusable.
Thanks.