How do I contain feGaussianBlur within a circle? - svg

I'm trying to contain a feGaussianBlur within a circle in order make the circle's gradient smoother, but the blur expands beyond the circle.
I'm also using another gradient with a feGaussianBlur behind the top circle that will expand beyond it, so I can't use a mask over the top circle to cover up the color bleeding beyond it, else the blur of the circle below it will also be covered.

In this example I add a gradient to two circles and a rectangle.
The rectangle is made into a circle using a clip-path.
The second circle and the rectangle has the filter applied.
The filter has the feComposite/in to keep the filter inside the circle as suggested by Michael Mullany: <feComposite operator="in" in2="SourceGraphic"/>
As I see it the the one without filter looks more smooth. So, I don't know if it makes sense -- maybe in your use case?
<svg width="500" viewBox="0 0 300 110" xmlns="http://www.w3.org/2000/svg">
<defs>
<filter id="blur">
<feGaussianBlur in="SourceGraphic" stdDeviation="4" />
<feComposite operator="in" in2="SourceGraphic"/>
</filter>
<linearGradient id="gradient">
<stop offset="0" stop-color="blue" />
<stop offset="30%" stop-color="blue" />
<stop offset="70%" stop-color="lightblue" />
<stop offset="100%" stop-color="lightblue" />
</linearGradient>
<clipPath id="cp1">
<circle cx="50" cy="50" r="45" />
</clipPath>
</defs>
<circle cx="50" cy="50" r="45" fill="url(#gradient)" />
<text font-size="10" x="50" y="105" text-anchor="middle">circle no filter</text>
<circle cx="150" cy="50" r="45" fill="url(#gradient)" filter="url(#blur)" />
<text font-size="10" x="150" y="105" text-anchor="middle">circle filter</text>
<rect transform="translate(200 0)" width="100" height="100" fill="url(#gradient)" clip-path="url(#cp1)" filter="url(#blur)"/>
<text font-size="10" x="250" y="105" text-anchor="middle">rect filter</text>
</svg>

To contain a blur to the area of the original graphic, you add a feComposite/in to the end of your filter.
<feComposite operator="in" in2="SourceGraphic"/>

Related

SVG - mask feGaussianBlur with radialGradient

I want to blur an image's corners, while retaining a sharp center.
Using css backdrop-blur() is out of question, because Firefox does not support it.
Adding a sharp image on top of a blurry one and then masking the first one out is not feaseable aswell, as in the end I want to change the static image with a three.js scene.
I tried to follow this tutorial, but with a radial gradient, rather than a fixed bar.
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<filter id="blurlayer" width="100%" height="100%">
<feGaussianBlur stdDeviation="4" result="blur"/>
<radialGradient id="radialGradient" cx="50%" cy="50%" r="50%" fx="50%" fy="50%" result="mask">
<stop offset="0%" stop-color="white" stop-opacity="1" />
<stop offset="100%" stop-color="black" stop-opacity="0" />
</radialGradient>
<feComposite in="blur" in2="mask" operator="in" result="comp" />
<feMerge result="merge">
<feMergeNode in="SourceGraphic" />
<feMergeNode in="comp" />
</feMerge>
</filter>
</defs>
<image filter="url(#blurlayer)" x="0" y="0" width="100%" height="100%" xlink:href="https://www.wildtextures.com/wp-content/uploads/wildtextures-grey-felt-texture.jpg"/>
</svg>
It does not work. Could somebody please help me find out why?
I set up a codepen, if that helps anyone.
Edit:
While the answer works, now that I have implemented it, I want to warn everybody else: On Firefox this slowed down my Three.js scene to a crawl (all the other browsers I tested seem fine, though).
You have to do a little more work than that - you can't drop a radialGradient in the middle of a filter - only filter primitives are allowed inside a filter and you need to import any image/shape you want to use via feImage.
Also, when masking via feComposite/in - the "in" operator only uses the alpha channel (unlike actual masks that use luminance) - so you can use a black/black gradient with a variable opacity.
Lastly, because Firefox doesn't support fragment identifiers inside feImage, if you want FF support, you have to define your mask in the content and import the image you want to use via feImage. This makes the filter not reusable, but if this is once off content that's fine. If you do want to use this filter more generally, then you can define a gradient-filled rect and then convert it in to a full SVG image that you then inline via a data:uri inside a feImage. This is more work (and I always seem to get the escaping rules for svg+xml data URI's wrong) - so I didn't do it here.
FWIW - that tutorial is both complete and correct.
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<radialGradient id="radialGradient" cx="50%" cy="50%" r="50%" fx="50%" fy="50%" result="mask">
<stop offset="0%" stop-color="black" stop-opacity="1" />
<stop offset="100%" stop-color="black" stop-opacity="0" />
</radialGradient>
<filter id="blurlayer" x="0%" y="0%" width="100%" height="100%">
<feImage xlink:href="https://www.wildtextures.com/wp-content/uploads/wildtextures-grey-felt-texture.jpg" width="100%" height="100%" result="original-image" preserveAspectRatio="none"/>
<feComposite in="original-image" in2="SourceGraphic" operator="in" result="unblurred" />
<feGaussianBlur in="original-image" stdDeviation="4" result="blurred-image"/>
<feComponentTransfer in="SourceGraphic" result="invertlight">
<feFuncA type="table" tableValues="1 0"/>
</feComponentTransfer>
<feComposite in="blurred-image" operator="in"/>
<feComposite operator="over" in="unblurred"/>
</filter>
</defs>
<g filter="url(#blurlayer)">
<rect fill="url(#radialGradient)" x="0" y="0" width="100%" height="100%"/>
</g>
</svg>
Update: this is what a data:uri version would look like:
(note that I've had to expand the sizing of the filtered content a little so the filter will clip the edge artifacts that the inversion causes).
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="100%" height="100%">
<defs>
<filter id="blurlayer" x="0%" y="0%" width="100%" height="100%">
<feImage xlink:href='data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2ZXJzaW9uPSIxLjEiIHg9IjAiIHk9IjAiIHZpZXdCb3g9IjAgMCAxMDAgMTAwIiBoZWlnaHQ9IjEwMCUiIHdpZHRoPSIxMDAlIj48ZGVmcz4gICAgICAgIDxyYWRpYWxHcmFkaWVudCBpZD0ibXlHcmFkaWVudCIgY3g9IjUwJSIgY3k9IjUwJSIgcj0iNTAlIiBmeD0iNTAlIiBmeT0iNTAlIiByZXN1bHQ9Im1hc2siPjxzdG9wIG9mZnNldD0iMCUiIHN0b3AtY29sb3I9ImJsYWNrIiBzdG9wLW9wYWNpdHk9IjEiIC8+PHN0b3Agb2Zmc2V0PSIxMDAlIiBzdG9wLWNvbG9yPSJibGFjayIgc3RvcC1vcGFjaXR5PSIwIi8+CjwvcmFkaWFsR3JhZGllbnQ+PC9kZWZzPjxyZWN0IHg9IjAlIiB5PSIwJSIgd2lkdGg9IjEwMCUiIGhlaWdodD0iMTAwJSIgZmlsbD0idXJsKCNteUdyYWRpZW50KSIvPjwvc3ZnPg==' width="100%" height="100%" result="blur-mask" preserveAspectRatio="none"/>
<feComposite in2="blur-mask" in="SourceGraphic" operator="in" result="unblurred" />
<feGaussianBlur in="SourceGraphic" stdDeviation="4" result="blurred-image"/>
<feComponentTransfer in="blur-mask" result="invertlight">
<feFuncA type="table" tableValues="1 0"/>
</feComponentTransfer>
<feComposite in="blurred-image" operator="in"/>
<feComposite operator="over" in="unblurred"/>
</filter>
</defs>
<g filter="url(#blurlayer)">
<image xlink:href="https://www.wildtextures.com/wp-content/uploads/wildtextures-grey-felt-texture.jpg" x="-5%" y="-5%" width="110%" height="110%" preserveAspectRatio="none"/>
</g>
</svg>
The data:uri is a base64 encoded version of this SVG.
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" x="0" y="0" viewBox="0 0 100 100" height="100%" width="100%"><defs><radialGradient id="myGradient" cx="50%" cy="50%" r="50%" fx="50%" fy="50%" result="mask"><stop offset="0%" stop-color="black" stop-opacity="1" /><stop offset="100%" stop-color="black" stop-opacity="0"/>
</radialGradient></defs><rect x="0%" y="0%" width="100%" height="100%" fill="url(#myGradient)"/></svg>

Apply glow to SVG element with a gradient

I have an SVG element that's based on a circle and a mask to create a ring.
The code is as follows:
<svg width="100%" height="100%" viewBox="0 0 300 300" version="1.1">
<defs>
<linearGradient x1="97.3756325%" y1="100%" x2="0%" y2="100%" id="gradient">
<stop stop-color="#FF00AC" offset="0%"></stop>
<stop stop-color="#5D00C4" offset="100%"></stop>
</linearGradient>
<mask id="circle-mask">
<circle cx="150" cy="150" r="145" fill="white"/>
<circle cx="150" cy="150" r="140" fill="black"/>
</mask>
</defs>
<circle cx="150" cy="150" r="145" mask="url(#circle-mask)" fill="url(#gradient)"/>
</svg>
The ring got a gradient as fill color. Now I want to apply a glow effect on the ring that uses the colors of the gradient. Any ideas on how to do that?
What is a "glow"? I don't think there is a canonical definition, so I'm going with one that I have seen used before: a backdrop shadow that is colored and whose opacity values are exagerated. To define those effects, refer to the SVG <filter> spec, its support is pretty good across browsers.
I understand you need the mask for more complex situations than this one. The important thing here is the order in which different effects are applied: clip paths and masks are processed after filter effects:
First the element is styled under absence of filter effects, masking, clipping and opacity. Then the element and its descendants are drawn on a temporary canvas. In a last step the following effects are applied to the element in order: filter effects, clipping, masking and opacity.
Therefore you need to wrap the masked element in a <g> and apply the glow filter there.
Vary stdDeviation to stretch or shrink the shadow, and vary slope to change its opacity. If you set the slope to a value > 2, you will no longer get a clear border between the ring and its shadow.
<svg width="100%" height="100%" viewBox="0 0 300 300" version="1.1">
<defs>
<linearGradient x1="97.3756325%" y1="100%" x2="0%" y2="100%" id="gradient">
<stop stop-color="#FF00AC" offset="0%"></stop>
<stop stop-color="#5D00C4" offset="100%"></stop>
</linearGradient>
<mask id="circle-mask">
<circle cx="150" cy="150" r="145" fill="white"/>
<circle cx="150" cy="150" r="140" fill="black"/>
</mask>
<filter id="glow">
<feGaussianBlur stdDeviation="5"/>
<feComponentTransfer>
<feFuncA type="linear" slope="2" />
</feComponentTransfer>
<feBlend in2="SourceGraphic" />
</filter>
</defs>
<g filter="url(#glow)">
<circle cx="150" cy="150" r="145" mask="url(#circle-mask)" fill="url(#gradient)"/>
</g>
</svg>

SVG — radial gradient with a smooth edged cutout

I'm trying to create a SVG background image like this (two colors, radial gradient, S-shape cutout with smooth edges):
It's quite easy to create a radial gradient (e.g. using this tool):
<!-- SVG syntax -->
<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" viewBox="0 0 1 1" preserveAspectRatio="none">
<radialGradient id="g920" gradientUnits="userSpaceOnUse" cx="5.408560311284047%" cy="0%" r="93.04166277718278%">
<stop stop-color="#ed1c24" offset="0.1"/><stop stop-color="#003663" offset="1"/>
</radialGradient>
<rect x="-50" y="-50" width="101" height="101" fill="url(#g920)" />
</svg>
but is it possible to add the cutout too?
Lennis' answer was close. But you would get better results by combining the fill and the filter in one element, rather than try to use a blurry white shape to hide part of the gradient.
Note that the blur will affect any edge of the shape, including the top, left and right. So you need to make sure those edges are well away from (outside of) the edge of the SVG viewport.
<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" viewBox="0 0 1 1" preserveAspectRatio="none">
<defs>
<radialGradient id="g" gradientUnits="userSpaceOnUse" cx="5.4%" cy="0%" r="93%">
<stop stop-color="#ed1c24" offset="0.1"/>
<stop stop-color="#003663" offset="0.8"/>
</radialGradient>
<filter id="f1" x="0" y="0">
<feGaussianBlur in="SourceGraphic" stdDeviation=".05" />
</filter>
</defs>
<path id="svg_1" d="M -0.5,-0.5
L 1.5,-0.5
L 1.5,0.5
L 1,0.5
C 1,0 0.6,0.1 0.5,0.25
C 0.4,0.4 0.1,0.4 0,0.25
L -0.5,0.25
Z"
fill="url(#g)" filter="url(#f1)"/>
</svg>
You could use a blur on a white element to make it look like a cutout.
<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" viewBox="0 0 1 1" preserveAspectRatio="none">
<defs>
<radialGradient id="g" gradientUnits="userSpaceOnUse" cx="5.4%" cy="0%" r="93%">
<stop stop-color="#ed1c24" offset="0.1"/>
<stop stop-color="#003663" offset="1"/>
</radialGradient>
<filter id="f1" x="0" y="0">
<feGaussianBlur in="SourceGraphic" stdDeviation=".05" />
</filter>
</defs>
<rect x="0" y="0" width="100%" height="100%" fill="url(#g)" />
<path id="svg_1" fill="white" d="m-0.1,0.5 l0,0.55l1.15,0l0,-0.53495c0,0 -0.1,-0.1 -0.5,0c-0.3,0.1 -0.5,0 -0.5,0l-0.1,0z" filter="url(#f1)"/>
</svg>
You could also try a meshgradient, it's in the svg 2.0 spec. At the moment no browser supports it that I know off.

Why does a filter break the half-pixel trick for crisp 1px strokes?

I'm using the "offset everything by half a pixel" trick discussed in the second answer to this question to get a crisp 1px stroke on a shape.
(There are rounded edges involved, so the shape-rendering: crispEdges solution isn't viable here -- it makes the curved parts of strokes look terrible.)
How come adding a filter (I'm using a gaussian blur + offset filter to implement a drop shadow) breaks the half-pixel offset hack?
You can play with the jsfiddle.
<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
<defs>
<filter id="f1" x="-20%" y="-20%" width="160%" height="160%">
<feOffset result="offOut" in="SourceAlpha" dx="10" dy="10" />
<feGaussianBlur result="blurOut" in="offOut" stdDeviation="2" />
<feBlend in="SourceGraphic" in2="blurOut" mode="normal" />
</filter>
</defs>
<g transform="translate(5.5,5.5)">
<!-- This one has a blurry stroke -->
<rect width="50" height="50" rx="5" ry="5" stroke="black" stroke-width="1" fill="steelblue" filter="url(#f1)" />
<!-- This one has a crisp stroke -->
<rect x="150" width="50" height="50" rx="5" ry="5" stroke="black" stroke-width="1" fill="steelblue" />
</g>
</svg>
Apparently I can't post inline images as a new user, but you can this is an image of how the svg looks for me.
If you don't like the blurry edges, you can always experiment with adding an feComponentTransfer with feFuncA to manually manipulate the edge blur. Aka:
<filter id="fee" x="-20%" y="-20%" width="160%" height="160%">
<feComponentTransfer>
<feFuncA type="table" tableValues="0 0 1" />
</feComponentTransfer>
</filter>

Is it possible to apply a transform matrix to a SVG filter effect

I'm trying to recreate an iphone maps like push pin in SVG and I have the pin part down but I'm wondering how to tackle the shadow. I've seen a bunch of drop shadow examples but they're all just offsetting the original by a few pixels. Is it possible to apply a transform matrix to a filter so it's skewed?
Here's the pin SVG so far:
<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
<defs>
<radialGradient id="SVGID_1_" cx="29.3623" cy="31.1719" r="11.6241" gradientTransform="matrix(1.1875 0 0 1.1875 -30.8438 -30.2812)" gradientUnits="userSpaceOnUse">
<stop offset="0.2637" style="stop-color:#FF0000"/>
<stop offset="1" style="stop-color:#6D0000"/>
</radialGradient>
</defs>
<rect x="9.251" y="13.844" fill="#CCCCCC" stroke="#7C7C7C" width="2" height="24.83"/>
<circle fill="url(#SVGID_1_)" stroke="#660000" cx="10.5" cy="11.5" r="9.5"/>
<ellipse transform="matrix(0.8843 0.4669 -0.4669 0.8843 4.475 -1.6621)" fill="#FFCCCC" cx="6.591" cy="8.199" rx="1.538" ry="1.891"/>
</svg>
thanks!
Here is a simple transform and filter to rotate it. If you want to do the skewing too you will need to replace the rotate line with some matrix stuff.
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1">
<defs>
<radialGradient id="SVGID_1_" cx="29.3623" cy="31.1719" r="11.6241" gradientTransform="matrix(1.1875 0 0 1.1875 -30.8438 -30.2812)" gradientUnits="userSpaceOnUse">
<stop offset="0.2637" style="stop-color:#FF0000"/>
<stop offset="1" style="stop-color:#6D0000"/>
</radialGradient>
<filter id="drop-shadow">
<feGaussianBlur in="SourceAlpha" result="blur-out" stdDeviation="1" />
</filter>
</defs>
<g id="pin">
<rect x="9.251" y="13.844" fill="#CCCCCC" stroke="#7C7C7C" width="2" height="24.83"/>
<circle fill="url(#SVGID_1_)" stroke="#660000" cx="10.5" cy="11.5" r="9.5"/>
<ellipse transform="matrix(0.8843 0.4669 -0.4669 0.8843 4.475 -1.6621)" fill="#FFCCCC" cx="6.591" cy="8.199" rx="1.538" ry="1.891"/>
</g>
<use xlink:href="#pin" transform="rotate(60 10.251 38.674)" filter="url(#drop-shadow)"/>
</svg>

Resources