I tried without success to use an animated filter noise on a mask to mask an element. The mask itself works but I am unable to get the animation working in the mask, even though the animation itself works.
I've tried all possible combination of the feColorMatrix (single channel, only alpha, various combinations) and both luminance and alpha types for the mask, yet nothing gives.
Any help would be greatly appreciated!
<svg width="200" height="200" viewBox="0 0 200 200"
xmlns="http://www.w3.org/2000/svg">
<defs>
<filter id="noise" x="0" y="0" width="100%" height="100%">
<feTurbulence type="fractalNoise" baseFrequency="0.01" seed="12345" />
<feColorMatrix type="hueRotate" values="0">
<animate attributeName="values" from="0" to="360" dur="5s" repeatCount="indefinite" />
</feColorMatrix>
<feColorMatrix type="matrix" values="0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 1 0 0 0 0 " />
</filter>
<mask id="Mask">
<rect x="0" y="0" width="100%" height="100%" filter="url(#noise)" />
</mask>
</defs>
<rect x="0" y="0" width="200" height="200" fill="green" />
<rect x="0" y="0" width="200" height="200" fill="red" mask="url(#Mask)" />
</svg>
EDIT:
It almost works, thanks to Michael. This is the updated code, essentially I am trying to merge two images, bottom one is a full image and the top image has an alpha and is transparent. It works but the noise is still visible on top of the image, even though the mask itself works (though it's hard to see because of the visible noise).
<svg width="960" height="1200" viewBox="0 0 960 1200"
xmlns="http://www.w3.org/2000/svg">
<defs>
<filter id="noise" x="0" y="0" width="100%" height="100%">
<feTurbulence type="fractalNoise" baseFrequency="0.005" seed="1" />
<feColorMatrix type="hueRotate" values="0">
<animate attributeName="values" from="0" to="360" dur="5s" repeatCount="indefinite" />
</feColorMatrix>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0" result="opacity-mask"/>
<feComposite operator ="in" in2="opacity-mask"/>
<feComposite operator="over" in2="SourceGraphic"/>
</filter>
<filter id="dropshadow">
<feGaussianBlur in="SourceAlpha" stdDeviation="20" />
<feOffset dx="5" dy="5" result="offsetblur" />
<feFlood flood-color="#000" flood-opacity="1" result="offsetColor"/>
<feComposite in="offsetColor" in2="offsetblur" operator="in" result="offsetblur"/>
<feComposite operator="over" in2="SourceGraphic"/>
</filter>
</defs>
<image href="https://martinhoura.net/svg/img_bottom.jpg" width="100%" x="0%" y="0" />
<g filter="url(#noise)">
<image href="https://martinhoura.net/svg/img_top.png" width="100%" x="0" y="0" filter="url(#dropshadow)" />
</g>
</svg>
Animated filters can have problems in Chrome and Safari when they're combined with patterns, masks and clips. You can fix this by doing the mask within the filter by using feComposite/in.
<svg width="200" height="200" viewBox="0 0 200 200"
xmlns="http://www.w3.org/2000/svg">
<defs>
<filter id="noise" x="0" y="0" width="100%" height="100%">
<feTurbulence type="fractalNoise" baseFrequency="0.01" seed="12345" />
<feColorMatrix type="hueRotate" values="0">
<animate attributeName="values" from="0" to="360" dur="5s" repeatCount="indefinite" />
</feColorMatrix>
<feColorMatrix type="matrix" values="0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 1 0 0 0 0" result="opacity-mask"/>
<feFlood flood-color="green"/>
<feComposite operator ="in" in2="opacity-mask"/>
<feComposite operator="over" in2="SourceGraphic"/>
</filter>
</defs>
<rect x="0" y="0" width="200" height="200" fill="red" filter="url(#noise)" />
</svg>
Update:
Thank you for posting the relevant code because the issues are different here.
There are two problems with your implementation. The first is that you're using the SourceGraphic as the mask on the Turbulence in your feComposite/in - so the turbulence is showing up, not the SourceGraphic - you need to change that 'in2' to 'in'.
But the bigger issue is that you're overlaying a variably-transparent version of the original image on top of the fully-opaque original image. The result of this is simply the original image. You need to make your underlying image semi-opaque if you want any effect to show up. Here's an exaggerated example of what I mean.
<svg width="960" height="1200" viewBox="0 0 960 1200"
xmlns="http://www.w3.org/2000/svg">
<defs>
<filter id="noise" x="0" y="0" width="100%" height="100%">
<feTurbulence type="fractalNoise" baseFrequency="0.005" seed="1" />
<feColorMatrix type="hueRotate" values="0">
<animate attributeName="values" from="0" to="360" dur="5s" repeatCount="indefinite" />
</feColorMatrix>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0" result="opacity-mask"/>
<feComposite operator="in" in="SourceGraphic"/>
</filter>
<filter id="half-opacity">
<feColorMatrix type="matrix" values="1 0 0 0 0
0 1 0 0 0
0 0 1 0 0
0 0 0 0.25 0"/>
</filter>
</defs>
<image filter="url(#half-opacity") href="https://martinhoura.net/svg/img_bottom.jpg" width="100%" x="0%" y="0" />
<g filter="url(#noise)">
<image href="https://martinhoura.net/svg/img_top.png" width="100%" x="0" y="0" />
</g>
</svg>
I have a shape with a stroke. The fill is orange with 50% opacity (so alpha = .5 and rgb(255,112,0)) and the stroke is blue (no transparency).
Using filters, I've been trying to make a copy (similar to a drop shadow, but without the blur). I'd like the copy to be solid orange.
But I just can't seem to get this as feColorMatrix continues to use SourceGraphic values.
No idea why, but if my shape fill is no transparency, and I also use feComponentTransfer again, I can get the solid shape copy.
The shape on the right is the one I'd like to make solid orange (or any color and opacity I choose), regardless of shape/stroke fill/opacity.
<svg xmlns:xlink="http://www.w3.org/1999/xlink" width="960" height="540" class="slide" shape-rendering="geometricPrecision" fill-rule="evenodd">
<rect width="960" height="540" stroke="#385D8A" fill="white" stroke-width="3" class="testSlideBorder" />
<svg x="10" y="10" overflow="visible" stroke="#0000FF" stroke-miterlimit="8" stroke-width="4">
<defs>
<filter id="offsetColoredShape" height="500%" width="500%" x="-275%" y="-275%">
<feColorMatrix in="SourceAlpha" type="matrix" values="
0 0 0 0 1
0 0 0 0 0.439
0 0 0 0 0
0 0 0 1 0"
result="changeToOrangeFill"/>
<feComponentTransfer result="changedAgain">
<feFuncR type="linear" slope="1" />
<feFuncG type="linear" slope="0.439" />
<feFuncB type="linear" slope="0" />
<feFuncA type="linear" slope="1" />
</feComponentTransfer>
<feOffset dx="120"/>
</filter>
</defs>
<use xlink:href="#star" filter="url(#offsetColoredShape)" />
<path id="star" fill="rgb(255,112,0)" fill-opacity="0.5" d="M0,63.904L17.609,51.5L8.562,31.952L30.014,30.014L31.952,8.562L51.5,17.609L63.904,0L76.309,17.609L95.857,8.562L97.795,30.014L119.247,31.952L110.199,51.5L127.809,63.904L110.199,76.309L119.247,95.857L97.795,97.795L95.857,119.247L76.309,110.199L63.904,127.809L51.5,110.199L31.952,119.247L30.014,97.795L8.562,95.857L17.609,76.309Z" />
</svg>
</svg>
Notice in the <path/> that fill-opacity="0.5". If I change that to fill-opacity="1", it works as expected. Here's what that looks like:
<svg x="10" y="10" overflow="visible" fill="#4472C4" stroke="#0000FF" stroke-miterlimit="8" stroke-width="4">
<defs>
<filter id="offsetColoredShape" height="500%" width="500%" x="-275%" y="-275%">
<feColorMatrix in="SourceAlpha" type="matrix" values="
0 0 0 0 1
0 0 0 0 0.439
0 0 0 0 0
0 0 0 1 0"
result="changeToOrangeFill"/>
<feComponentTransfer result="changedAgain">
<feFuncR type="linear" slope="1" />
<feFuncG type="linear" slope="0.439" />
<feFuncB type="linear" slope="0" />
<feFuncA type="linear" slope="1" />
</feComponentTransfer>
<feOffset dx="120"/>
</filter>
</defs>
<use xlink:href="#star" filter="url(#offsetColoredShape)" />
<path id="star" fill="rgb(255,112,0)" fill-opacity="1" d="M0,63.904L17.609,51.5L8.562,31.952L30.014,30.014L31.952,8.562L51.5,17.609L63.904,0L76.309,17.609L95.857,8.562L97.795,30.014L119.247,31.952L110.199,51.5L127.809,63.904L110.199,76.309L119.247,95.857L97.795,97.795L95.857,119.247L76.309,110.199L63.904,127.809L51.5,110.199L31.952,119.247L30.014,97.795L8.562,95.857L17.609,76.309Z" />
</svg>
That's what I'm after no matter the shapes fill opacity.
Any thoughts as to how I can get a solid color (like black) and alpha = 100% of the SourceGraphic, and then be able to modify the color and it's opacity to any color/opacity I like?
You're not setting the alpha to 100% in your original feColorMatrix - you're multiplying the alpha by 1. If you want to set alpha to 100% - you should set the fifth column to 1 (not the fourth column).
Now the problem with that is that it sets all the background to 100% opacity as well so you get the rest of the graphic colored solid black.
But - we have a hack to get around this. Instead of using SourceAlpha - use SourceGraphic, and use the first three columns of the alpha row to boost the alpha of just the colored pixels to 100%. The result is a little crispy (because we nuke anti-aliasing) - but it does get you what you want and works for every color - including rgb(1,1,1).
If you know your colors are not going to be that close to black then, you can dial down those 255's to something more reasonable (like 5 or 10) & retain at least some of the anti-aliasing.
<svg x="10" y="10" overflow="visible" stroke="#0000FF" stroke-miterlimit="8" stroke-width="4" style="background:grey" color-interpolation-filters="sRGB">
<defs>
<filter id="offsetColoredShape" height="500%" width="500%" x="-275%" y="-275%">
<feColorMatrix in="SourceGraphic" type="matrix" values="
0 0 0 0 1
0 0 0 0 0.439
0 0 0 0 0
255 255 255 1 0"
result="changeToOrangeFill"/>
<feOffset dx="80"/>
</filter>
</defs>
<use xlink:href="#star" filter="url(#offsetColoredShape)" />
<path id="star" fill="rgb(255,112,0)" fill-opacity="0.5" d="M0,63.904L17.609,51.5L8.562,31.952L30.014,30.014L31.952,8.562L51.5,17.609L63.904,0L76.309,17.609L95.857,8.562L97.795,30.014L119.247,31.952L110.199,51.5L127.809,63.904L110.199,76.309L119.247,95.857L97.795,97.795L95.857,119.247L76.309,110.199L63.904,127.809L51.5,110.199L31.952,119.247L30.014,97.795L8.562,95.857L17.609,76.309Z" />
</svg>
You can put the #star path in the <defs> with no fill or stroke and you can use it the first time with the fill-opacity="0.5"and the blue stroke and the second time with the filter, if this is what you need.
svg{border:1px solid}
<svg xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="-10 -10 360 150" class="slide" shape-rendering="geometricPrecision" fill-rule="evenodd">
<defs>
<path id="star" stroke-miterlimit="8" d="M0,63.904L17.609,51.5L8.562,31.952L30.014,30.014L31.952,8.562L51.5,17.609L63.904,0L76.309,17.609L95.857,8.562L97.795,30.014L119.247,31.952L110.199,51.5L127.809,63.904L110.199,76.309L119.247,95.857L97.795,97.795L95.857,119.247L76.309,110.199L63.904,127.809L51.5,110.199L31.952,119.247L30.014,97.795L8.562,95.857L17.609,76.309Z" />
<filter id="offsetColoredShape" height="500%" width="500%" x="-275%" y="-275%">
<feColorMatrix in="SourceAlpha" type="matrix" values="
0 0 0 0 1
0 0 0 0 0.439
0 0 0 0 0
0 0 0 1 0"
result="changeToOrangeFill"/>
<feComponentTransfer result="changedAgain">
<feFuncR type="linear" slope="1" />
<feFuncG type="linear" slope="0.439" />
<feFuncB type="linear" slope="0" />
<feFuncA type="linear" slope="1" />
</feComponentTransfer>
<feOffset dx="120"/>
</filter>
</defs>
<use xlink:href="#star" filter="url(#offsetColoredShape)" />
<use xlink:href="#star" fill="rgb(255,112,0)" fill-opacity="0.5" stroke="#0000FF" stroke-width="4" />
</svg>
UPDATE
The OP is commenting:
I'm wondering why, despite discarding all color values and setting alpha to 100% in feColorMatrix the alpha values are retained.
It's because the used element (i.e the #star) has fill-opacity="0.5". You need to use an element without the fill-opacity attribute.
In this simple example you can see that I can not modify the fill of the <use> since the used element has a fill. However I can add a stroke since the <use> element has no stroke:
<svg viewBox="0 0 100 50">
<circle id="c" fill="deepPink" stroke-width="5" cx="20" cy="25" r="10"></circle>
<use xlink:href="#c" x="50" fill="gold" stroke="skyBlue" />
</svg>
The OP is also commenting that the previous solution
doesn't work as the shapes are generated and the filters are simply inserted after the fact.
In the next demo I'm generating the #star. Next I'm generating the filtered <use> element and it works
const SVG_NS = 'http://www.w3.org/2000/svg';
const SVG_XLINK = "http://www.w3.org/1999/xlink";
const svg = document.querySelector("svg")
let d = "M0,63.904 L17.609,51.5L8.562,31.952L30.014,30.014L31.952,8.562L51.5,17.609L63.904,0L76.309,17.609L95.857,8.562L97.795,30.014L119.247,31.952L110.199,51.5L127.809,63.904L110.199,76.309L119.247,95.857L97.795,97.795L95.857,119.247L76.309,110.199L63.904,127.809L51.5,110.199L31.952,119.247L30.014,97.795L8.562,95.857L17.609,76.309Z"
let star = drawSVGelmt({d:d,id:"star"},"path", theDefs);
let use1 = document.createElementNS(SVG_NS, 'use');
use1.setAttributeNS(SVG_XLINK, 'xlink:href', '#star');
use1.setAttribute('class', 'filtered');
svg.appendChild(use1)
function drawSVGelmt(o,tag, parent) {
let elmt = document.createElementNS(SVG_NS, tag);
for (let name in o) {
if (o.hasOwnProperty(name)) {
elmt.setAttributeNS(null, name, o[name]);
}
}
parent.appendChild(elmt);
return elmt;
}
.filtered{filter:url(#offsetColoredShape)}
<svg xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="-10 -10 360 150" class="slide" shape-rendering="geometricPrecision" fill-rule="evenodd">
<defs id="theDefs">
<filter id="offsetColoredShape" height="500%" width="500%" x="-275%" y="-275%">
<feColorMatrix in="SourceAlpha" type="matrix" values="
0 0 0 0 1
0 0 0 0 0.439
0 0 0 0 0
0 0 0 1 0"
result="changeToOrangeFill"/>
<feComponentTransfer result="changedAgain">
<feFuncR type="linear" slope="1" />
<feFuncG type="linear" slope="0.439" />
<feFuncB type="linear" slope="0" />
<feFuncA type="linear" slope="1" />
</feComponentTransfer>
<feOffset dx="120"/>
</filter>
</defs>
<use xlink:href="#star" fill="rgb(255,112,0)" fill-opacity="0.5" stroke="#0000FF" stroke-width="4" />
</svg>
I'm trying to colorize a white image using a filter but I'm getting an unexpected result
SVG Sample:
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="373" height="400"
viewBox="0 0 373 400">
<defs>
<filter id="colorize">
<feComponentTransfer>
<feFuncR type="linear" slope="0.3333333"></feFuncR>
<feFuncG type="linear" slope="0"></feFuncG>
<feFuncB type="linear" slope="0.3333333"></feFuncB>
</feComponentTransfer>
</filter>
</defs>
<g filter="url(#colorize)">
<image x="0" y="0" xlink:href="https://i.imgur.com/Df8zD8O.png" width="373" height="400"></image>
</g>
<text font-weight="bold" fill="rgb(85,0,85)" x="0" y="100" font-size="100">Surfer</text>
</svg>
The expected result is that the image becomes the same color as the text
In this case #550055 or rgb(85,0,85)
I set the slopes on the filter to 0.3333333 for R and B based on the result of 85 / 255 but as you can see the result is not correct
Maybe I'm using the wrong method of calculation to attain the desired color
One thing to note is that colors having components approaching/equaling 255 give much better results
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="373" height="400"
viewBox="0 0 373 400">
<defs>
<filter id="colorize">
<feComponentTransfer>
<feFuncR type="linear" slope="255"></feFuncR>
<feFuncG type="linear" slope="0"></feFuncG>
<feFuncB type="linear" slope="255"></feFuncB>
</feComponentTransfer>
</filter>
</defs>
<g filter="url(#colorize)">
<image x="0" y="0" xlink:href="https://i.imgur.com/Df8zD8O.png" width="373" height="400"></image>
</g>
<text font-weight="bold" fill="rgb(255,0,255)" x="0" y="100" font-size="100">Surfer</text>
</svg>
I based my calculation on this formula
C' = slope * C + intercept
What am I doing wrong?
The default colour space for most SVG filters is linearRGB. You seem to be assuming it's sRGB though.
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="373" height="400"
viewBox="0 0 373 400">
<defs>
<filter id="colorize" color-interpolation-filters="sRGB">
<feComponentTransfer>
<feFuncR type="linear" slope="0.3333333"></feFuncR>
<feFuncG type="linear" slope="0"></feFuncG>
<feFuncB type="linear" slope="0.3333333"></feFuncB>
</feComponentTransfer>
</filter>
</defs>
<g filter="url(#colorize)">
<image x="0" y="0" xlink:href="https://i.imgur.com/Df8zD8O.png" width="373" height="400"></image>
</g>
<text font-weight="bold" fill="rgb(85,0,85)" x="0" y="100" font-size="100">Surfer</text>
</svg>
You have to set the color space to sRGB - because that's the color space that the CSS rgb() color property uses. (The default color space for SVG filters is linearRGB).
Add color-interpolation-filters="sRGB" to your filter element and you'll get the result you expect.
I see how to use feComponentTransfer to adjust RGBA values using a table and tableValues.
But is there a way to adjust the R, G, or B values using alpha values as input (for example)?
Why? Say I composite many translucent shapes, resulting in different opacities where these shapes overlap. I would like to use these opacity values to adjust the RGB using a lookup table based on the opacity of each pixel.
For example, in this image the ellipse, squares, and text are drawn with alpha 0.1. Overlapping regions have higher alpha values.
How would I map every pixel in this image with alpha 0.1 to rgba(255,0,0,1) and everywhere with alpha 0.2 to rgba(0,255,0,1) (and interpolate the replacement colors between those two alpha values on the anti-aliased edges)? More colors would be assigned to all other alpha values up to 1.
Easy to make such a lookup table.
But how to apply to image with a svg filter?
You can achieve it, but it is a multistep process, and you'll have to see about performance and maintainability yourself.
My example code divides the opacity values into four equally-spaced ranges: 0.2...0.4...0.6...0.8...1. The range 0...0.2 is mapped to transparency.
For each of the four colored ranges you need to combine three filter primitives:
pick up SourceAlpha as input, and apply <feComponentTransfer> to the alpha channel with type discrete such that only one range is mapped to opacity="1"
fill the filter area with <feFlood> in the assigned color
combine the first and second result with <feComposite> and operator="in", so that only the mapped values are colored
Then, merge all the different monochromatic parts together with <feMerge>.
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="400" height="200">
<linearGradient id="sourceGradient" x1="0" x2="1" y1="0" y2="0">
<stop style="stop-color:#000000;stop-opacity:0" offset="0" />
<stop style="stop-color:#000000;stop-opacity:1" offset="1" />
</linearGradient>
<filter id="filter">
<feComponentTransfer in="SourceAlpha" result="step1">
<feFuncA type="discrete" tableValues="0 1 0 0 0"/>
</feComponentTransfer>
<feFlood flood-color="rgb(255,0,0)" />
<feComposite operator="in" in2="step1" result="color1" />
<feComponentTransfer in="SourceAlpha" result="step2">
<feFuncA type="discrete" tableValues="0 0 1 0 0"/>
</feComponentTransfer>
<feFlood flood-color="rgb(255,255,0)" />
<feComposite operator="in" in2="step2" result="color2" />
<feComponentTransfer in="SourceAlpha" result="step3">
<feFuncA type="discrete" tableValues="0 0 0 1 0"/>
</feComponentTransfer>
<feFlood flood-color="rgb(0,255,0)" />
<feComposite operator="in" in2="step3" result="color3" />
<feComponentTransfer in="SourceAlpha" result="step4">
<feFuncA type="discrete" tableValues="0 0 0 0 1"/>
</feComponentTransfer>
<feFlood flood-color="rgb(0,0,255)" />
<feComposite operator="in" in2="step4" result="color4" />
<feMerge>
<feMergeNode in="color1" />
<feMergeNode in="color2" />
<feMergeNode in="color3" />
<feMergeNode in="color4" />
</feMerge>
</filter>
<rect fill="url(#sourceGradient)" x="50" y="25" width="300" height="50" id="rect" />
<use xlink:href="#rect" y="100" filter="url(#filter)" />
</svg>
interpolate the replacement colors between those two alpha values on the anti-aliased edges
I am not sure I understand that, but type="table" for component transfer results in a continuous scale. The result after merge has opacities below 1 in most places, so you would need some final steps to ramp that up again.
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="400" height="200">
<linearGradient id="sourceGradient" x1="0" x2="1" y1="0" y2="0">
<stop style="stop-color:#000000;stop-opacity:0" offset="0" />
<stop style="stop-color:#000000;stop-opacity:1" offset="1" />
</linearGradient>
<filter id="filter" x="0" y="0" width="1" height="1">
<feComponentTransfer in="SourceAlpha" result="step1">
<feFuncA type="table" tableValues="0 1 0 0 0"/>
</feComponentTransfer>
<feFlood flood-color="rgb(255,0,0)" />
<feComposite operator="in" in2="step1" result="color1" />
<feComponentTransfer in="SourceAlpha" result="step2">
<feFuncA type="table" tableValues="0 0 1 0 0"/>
</feComponentTransfer>
<feFlood flood-color="rgb(255,255,0)" />
<feComposite operator="in" in2="step2" result="color2" />
<feComponentTransfer in="SourceAlpha" result="step3">
<feFuncA type="table" tableValues="0 0 0 1 0"/>
</feComponentTransfer>
<feFlood flood-color="rgb(0,255,0)" />
<feComposite operator="in" in2="step3" result="color3" />
<feComponentTransfer in="SourceAlpha" result="step4">
<feFuncA type="table" tableValues="0 0 0 0 1"/>
</feComponentTransfer>
<feFlood flood-color="rgb(0,0,255)" />
<feComposite operator="in" in2="step4" result="color4" />
<feMerge result="merged">
<feMergeNode in="color1" />
<feMergeNode in="color2" />
<feMergeNode in="color3" />
<feMergeNode in="color4" />
</feMerge>
<feComponentTransfer in="SourceAlpha" result="capped">
<feFuncA type="discrete" tableValues="0 1 1 1 1"/>
</feComponentTransfer>
<feComposite operator="atop" in="merged" in2="capped" />
</filter>
<rect fill="url(#sourceGradient)" x="50" y="25" width="300" height="50" id="rect" />
<use xlink:href="#rect" y="100" filter="url(#filter)" />
</svg>