Apply SVG filter to "fill" only - svg

How do I apply an SVG filter on an SVG element, but not on its stroke?
Let's say I have this SVG filter (it puts the red component to 100%):
<filter id="testStroke">
<feComponentTransfer>
<feFuncR type="linear" slope="0" intercept="1"/>
</feComponentTransfer>
</filter>
If I apply this filter on that text node:
<text x="450" y="210" fill="black" stroke="blue" filter="url('#testStroke')">
Result
</text>
Then the filled part (originally black) turns red (because of the filter), and the blue stroke turns purple (same reason).
I would like the stroke to stay blue (not filtered), but the fill to turn red (filtered).
I'm not looking for the "don't stroke the shape, apply the filter on it and create a clone of that shape to apply the stroke on".
Is there a way to apply the filter only on the filled part on a shape, and not on its stroke?

There is no configuration or attribute to select the fill directly, but you can use a "green-screen" technique to select out the stroke, filter the fill and then reapply the stroke. You have to know the colors of the stroke and fill ahead of time, which is a downside (because the pseudo inputs for doing this are not supported in Chrome/Safari (although they are in Firefox and IE10)). So here is a working example:
<filter id="testStroke">
<feComponentTransfer in="SourceGraphic" result="recolored">
<feFuncR type="linear" slope="0" intercept="1"/>
</feComponentTransfer>
<!-- We're using the fact that the black fill has zero luminance to create a selection mask for the stroke -->
<feColorMatrix in="SourceGraphic" type="luminanceToAlpha" result="lumMask"/>
<feComponentTransfer in="lumMask" result="lumMaskCeil">
<!-- a blue fill doesn't have a high luminance, so we're going to dial it way up using a gamma transform with a high exponent-->
<feFuncA type="gamma" exponent=".01"/>
</feComponentTransfer>
<!-- this selects just the part of the input image that overlaps with our stroke mask-->
<feComposite operator="in" in="SourceGraphic" in2="lumMaskCeil" result="stroke"/>
<!-- and composite it over our recolored content from the original filter-->
<feComposite operator="over" in="stroke" in2="recolored"/>
</filter>

Related

Illustrator - "real" inner shadow to SVG object

Wanted: An inset shadow (NOT inner glow) for a complex svg path, supporting following options:
shadow/light direction
shadow size
blur amount
opacity
Example Pic: Inset Shadow for SVG-Paths (SVG Effect)
select the svg object, open appearance toolbar, use this icon at the bottom to add svg-filter
use "+" to add a new filter and copy following code
<filter id="inset-shadow">
<feOffset dx="25" dy="25"></feOffset>
<feGaussianBlur result="offset-blur" stdDeviation="10"></feGaussianBlur>
<feComposite in="SourceGraphic" in2="offset-blur" operator="out" result="inverse"></feComposite>
<feFlood flood-color="black" flood-opacity=".75" result="color"></feFlood>
<feComposite in="color" in2="inverse" operator="in" result="shadow"></feComposite>
<feComposite in="shadow" in2="SourceGraphic" operator="over"></feComposite>
</filter>
dx="25" dy="25": shadow width
stdDeviation="10": blur width
flood-opacity=".75": opacity

SVG glow filter showing wrong color

I took pretty much exactly the filter code from here (just playing with the flood color, radius and std. deviation to get the exact effect I wanted):
<defs>
<filter id="sofGlow" height="300%" width="300%" x="-75%" y="-75%">
<feMorphology operator="dilate" radius="4" in="SourceAlpha" result="thicken" />
<feGaussianBlur in="thicken" stdDeviation="10" result="blurred" />
<feFlood flood-color="rgb(0,186,255)" result="glowColor" />
<feComposite in="glowColor" in2="blurred" operator="in" result="softGlow_colored" />
<feMerge>
<feMergeNode in="softGlow_colored"/>
<feMergeNode in="SourceGraphic"/>
</feMerge>
</filter>
</defs>
I applied it to one SVG I was working on, and it turned out as expected - a light blue ish glow.
I then tried applying it to another SVG, and it is turning out like a black glow. I even tried changing the flood color to rgb(255, 0, 0) and it still looks black.
Why would it be doing that, and what can I do to fix it?
Here is the Demo.
https://codepen.io/vccodepen/pen/qyjoNr
You can set two filters and give different ID name in same <svg>. In other <svg>, simply assigned the filter's ID you want.

How to apply feBlend and preserve source alpha

I'm trying to modify colors of an image by applying feFlood and feBlend to it in "lighten" and "darken" modes. How do I preserve the alpha channel?
<svg>
<filter id="filter">
<feFlood result="flood" flood-color="blue" />
<feBlend in="SourceGraphic" in2="flood" mode="lighten" />
</filter>
<image filter="url(#filter)" href="https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png" />
</svg>
https://jsfiddle.net/utqghr0o/
The way lighten works is that it takes the maximum colour value from each channel of each of the two inputs (pre-multiplied with the alpha). So if a pixel has zero opacity, it will not ever count as the maximum colour for any channel, and the value from the other input will be used.
What you need to do is first mask the flood with the alpha from the source image ("SourceAlpha"), then blend the masked flood with the original image.
<svg width="544" height="184">
<filter id="filter">
<feFlood result="flood" flood-color="blue" />
<feComposite in="flood" in2="SourceAlpha" operator="atop" result="maskedflood"/>
<feBlend in="SourceGraphic" in2="maskedflood" mode="lighten" />
</filter>
<image filter="url(#filter)" width="544" height="184" href="https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png" />
</svg>
I think Paul's answer is close but not exactly correct.
The feBlend formula is:
Opacity of Final Pixel = 1 - (1- Opacity of Image A Pixel)*(1- Opacity of Image B Pixel)
If you do the mask first and then the blend, the final opacity will be a bit off. For example for a pixel of 0.6 opacity in image A, you'll end up with a final pixel opacity = 1 - (.6 * .6) = .64
That's close to, but not the same as 0.6.
If you want to retain the exact opacity of Image A in the final image - you need to do the blending first and the masking second. That's assuming that you want the lighten to be done on the pre-multiplied, "100%-opaque-equivalent" colors, which is normally the case.
<svg width="544" height="184">
<filter id="filter">
<feFlood result="flood" flood-color="blue" />
<feBlend in="SourceGraphic" in2="flood" mode="lighten" result="blend"/>
<feComposite in="blend" in2="SourceAlpha" operator="atop" result="maskedflood"/>
</filter>
<image filter="url(#filter)" width="544" height="184" href="https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_272x92dp.png" />
</svg>

How to get "crispEdges" for SVG text?

Svg shapes other than text are affected by the shape-rendering attribute which can be set to the crispEdges value (https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/shape-rendering). This value seems to turn anti-aliasing off.
But text is only affected by text-rendering. However, this does not provide the crispEdges value (https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/text-rendering). Why? Is there another way to get non-anti-alias?
For really crisp edges, you can use a filter to posterize your text.
<svg width="400px" height="400px">
<defs>
<filter id="crispify">
<feComponentTransfer>
<feFuncA type="discrete" tableValues="0 1"/>
</feComponentTransfer>
</filter>
</defs>
<text filter="url(#crispify)" font-size="60" y="60" >Some crispy text</text>
</svg>

svg feComponentTransfer linear function

The SVG feComponentTransfer linear function isn't working as I expect it to. A slope of -1 and intercept of 1 applied to a grayscale image should invert the image: black -> white, white-> black, 25% gray -> 75% gray, 50% gray unchanged, and so on.
My expectation is based on http://www.w3.org/TR/filter-effects/#feComponentTransferElement, which says "C' = slope * C + intercept" where "C is the initial component (e.g., ‘feFuncR’), C' is the remapped component; both in the closed interval [0,1].".
The following filter
<filter id="linear">
<feComponentTransfer>
<feFuncR type="linear" slope="-1" intercept="1" />
<feFuncG type="linear" slope="-1" intercept="1" />
<feFuncB type="linear" slope="-1" intercept="1" />
</feComponentTransfer>
</filter>
maps black to white and white to black, but intermediate values are off, e.g. 50% gray maps to 90% gray and 75% gray maps to 98% gray. See http://jsfiddle.net/Rpjs2/ for a simple example. I get the same results in Firefox and Safari.
This is my first attempt at SVG filters, so I suspect I'm misunderstanding the specs. Can someone correct me?
Filters generally work in the linearRGB colour space. This use case wants sRGB so you just need to set color-interpolation-filters="sRGB" on the filter element
You can mostly correct for this using another component transfer after your original.
<feComponentTransfer>
<feFuncR type="gamma" offset="0" amplitude="1" exponent="4.84"/>
<feFuncG type="gamma" offset="0" amplitude="1" exponent="4.84"/>
<feFuncB type="gamma" offset="0" amplitude="1" exponent="4.84"/>
</feComponentTransfer>
This will spread out the color range more appropriately using a "double" gamma correction of 2.2 ^ 2 = 4.84.

Resources