How to apply feBlend and preserve source alpha - svg

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>

Related

Elliptical light source using SVG filters

I am trying to apply a Vignette effect on images with SVG filters. I am trying to implement this with the <fePointLight> primitive, but I find this a bit limited in that it is always has a circular shape. Is it possible to change the width such that the lighting effect takes on a wide elliptical shape? This is currently the filter I am using:
<filter id="vignette">
<feFlood id="flood-5" result="blackfield-6" x="0%" y="0%" width="100%" height="100%" flood-color="#000000" flood-opacity="1"/>
<feSpecularLighting id="specular-5" result="Spotlight-6" lighting-color="#FFFFFF" surfaceScale="1" specularConstant="1" specularExponent="100">
<fePointLight id="pointlight-5" x="720" y="450" z="1200"/>
</feSpecularLighting>
<feBlend id="svg-7" result="A-6" in="blackfield-6" in2="Spotlight-6" mode="lighten"/>
<feBlend id="blend-5" result="B-6" in="A-6" in2="SourceGraphic" mode="multiply"/>
</filter>
I am aware that it is possible to do this with a radial gradient effect on a separate shape, but I have a requirement that it must be done purely using SVG filters.
The values of the x=350 and y=240 attributes of the fePointLight filter are chosen so that the point is in the center of the image.
Different values for the z attribute of the fePointLight filter render the light source at different depths in relation to the drawing. The nearest position corresponds to the largest size.
Please watch in full screen
Hover over image
#img {
width:700px;
height:481px;
}
#img:hover {
filter:url(#spotlight);
}
<img id="img" src="https://i.stack.imgur.com/mBuDo.jpg" >
<svg width="700" height="481" viewBox="0 0 700 481" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<filter id="spotlight">
<feSpecularLighting result="spotlight" specularConstant="3.5"
specularExponent="70" lighting-color="grey">
<fePointLight x="350" y="240" z="520"/>
</feSpecularLighting>
<feComposite in="SourceGraphic" in2="spotlight" operator="in"/>
</filter>
</defs>
</svg>
Other image
#img {
width:700px;
height:481px;
}
#img:hover {
filter:url(#spotlight);
}
<img id="img" src="https://i.stack.imgur.com/GlhkD.jpg" >
<svg width="700" height="481" viewBox="0 0 700 481" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<filter id="spotlight">
<feSpecularLighting result="spotlight" specularConstant="3.5"
specularExponent="70" lighting-color="grey">
<fePointLight x="350" y="240" z="520"/>
</feSpecularLighting>
<feComposite in="SourceGraphic" in2="spotlight" operator="in"/>
</filter>
</defs>
If you want that sort of effect (elliptical light), you probably want to be using a spotlight rather than a point light.
If you offset the light position to one side and shine the cone down at a shallow-ish angle, you will get an elliptical spot.
<svg width="600" height="529">
<defs>
<filter id="spotlight">
<feSpecularLighting result="spotlight" specularConstant="1.5"
specularExponent="4" lighting-color="#FFF">
<feSpotLight x="-200" y="265" z="400" limitingConeAngle="10" pointsAtX="300" pointsAtY="265" />
</feSpecularLighting>
<feComposite in="SourceGraphic" in2="spotlight" operator="out" k1="0" k2="1" k3="1" k4="0"/>
</filter>
</defs>
<rect x="0" y="0" width="600" height="529" style="fill: skyblue; filter:url(#spotlight);"/>
</svg>
Note that the lighting filter components can be unreliable to use. Each browser has differences in interpretation of the standard. Not to mention some bugs.
A point in case is the above example, which looks different in Firefox and Chrome.
But good luck with your project.

how to make a svg scatter filter?

I try to turn a precise shapes into a series of scatted dots that have a higher density in the middle of the source image.
Example: White Circle on Black Background → Messier 92 (sorry i hive not enough reputation to embed the image)
Consequently a shape other then a circle should still be recognizable. Here is the best I was able to do:
I would call such an effect a scatter filter. Please tell me if you have a better name.
<svg height='100' width='100'>
<filter id="cluster" filterUnits="userSpaceOnUse">
<feGaussianBlur in="SourceGraphic" result='blurred' stdDeviation="10" />
<feTurbulence type="turbulence" baseFrequency="9" numOctaves="4" result="turbulence" />
<feDisplacementMap in2="turbulence" in="blurred" scale="10" xChannelSelector="R" yChannelSelector="G" />
</filter>
<rect x='0' y='0' height='100' width='100' fill='black' />
<circle cx='50' cy='50' fill='white' r='30' filter='url(#cluster)' />
</svg>
It is basically a fancy blur. The results look better with increased scale in feDisplacementMap.
<svg height='100' width='100'>
<filter id="cluster" filterUnits="userSpaceOnUse">
<feGaussianBlur in="SourceGraphic" result='blurred' stdDeviation="10" />
<feTurbulence type="turbulence" baseFrequency="9" numOctaves="4" result="turbulence" />
<feDisplacementMap in2="turbulence" in="blurred" scale="50" xChannelSelector="R" yChannelSelector="G" />
</filter>
<rect x='0' y='0' height='100' width='100' fill='black' />
<circle cx='50' cy='50' fill='white' r='30' filter='url(#cluster)' />
</svg>
But that also displaces the image. Can I undo this? Or not do it in the first place? Maybe by using something other than feDisplacementMap?
The main problem with your filter is its very high baseFrequency - you need to dial that waaay down. And fractalNoise is better than turbulence as a feTurbulence type for a scatter filter. As long as your displacement map is centered at 0.5 of the channel, your image shouldn't be displaced in x/y on average. It's only a problem if you've got a bright or dark displacement map (which is not the case with feTurbulence). Finally - a higher displacement scale will give you the pointy look you're looking for - like so.
It can take a bunch of trial and error to get the exact look that you want. This is a link to a filter editor for shadows that might give you some ideas https://codepen.io/mullany/pen/sJopz
<svg height="220px" width="260px" viewBox="0 0 800 600">
<defs>
<filter id="scatter">
<feTurbulence baseFrequency=".2" type="fractalNoise" numOctaves="3"/>
<feDisplacementMap in="SourceGraphic" xChannelSelector="G" yChannelSelector="B" scale="300"/>
<feComposite operator="in" in2="finalMask"/>
</filter>
</defs>
<g filter="url(#scatter)">
<polyline points="10,10 10,300, 300,400" transform="translate(60 60)" fill="blue"/>
<polyline points="500,10 110,300, 300,400" transform="translate(260 60)" fill="red"/>
</g>
</svg>

Generating images with non-repeating random patterns

I've been trying to create a special random pattern for some time. For example random black dots, like this:
https://picload.org/thumbnail/riogwpll/pattern2.jpg
However, I need a much larger image with about 100,000 points / circles. In principle, no problem, however, the SVG with several MB then becomes too large to open it, for example, with Inkscape, because each circle is drawn individually. Any ideas how this could be realize better resulting in a smaller file. I have already tried something with pattern. Problem is that it should be a truly random, non-repeating pattern.
It's not necessary to do this with dots it could also look like this:
[enter image description here][1]
https://picload.org/thumbnail/riogwwdr/pattern1.jpg
For ideas / suggestions, I am grateful.
Is it something like this that you are after?
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="600" height="600">
<defs>
<filter id="dots" filterUnits="objectBoundingBox" x="0%" y="0%" width="100%" height="100%" color-interpolation-filters="sRGB">
<feTurbulence baseFrequency=".1" numOctaves="1" seed="42" />
<feColorMatrix type="saturate" values="0"/>
<feGaussianBlur result="blur" stdDeviation="2" />
<feComponentTransfer>
<feFuncA type="discrete" tableValues="0 1 1 1 1"/>
</feComponentTransfer>
</filter>
</defs>
<rect x="0" y="0" width="600" height="600" style="fill:#888; stroke:#bbd; stroke-width:2px; filter: url(#dots)" />
</svg>
How this works:
<feTurbulence baseFrequency=".1" numOctaves="1" seed="42" /> generates some random noise. Remove the seed attribute if you want a different pattern each time.
<feColorMatrix type="saturate" values="0"/> converts the noise to greyscale.
<feGaussianBlur result="blur" stdDeviation="2" /> blurs the noisey pattern so that the dots merge together a little. Experiment with this value to vary the "blobbiness".
<feComponentTransfer> thresholds the grey values to either black or white.

SVG Filter with a mask

There is a filter
<filter id="filter">
<feGaussianBlur stdDeviation="4">
<feColorMatrix type="matrix" values="">
</filter>
applied to image
<image filter="url('#filter')">
I have a certain shape:
<polygon points="">
How can I use the polygon as a mask where the filter is not applied?
You could also apply the filter to the polygon and then use feImage to import your image into the filter. Using feComposite "in" will do the masking.
<svg height="210" width="500">
<defs>
<filter id="cutout" x="-300%" y="-300%" height="700%" width="700%">
<feImage result="photo" xlink:href="http://upload.wikimedia.org/wikipedia/commons/thumb/2/2d/%D0%9D%D1%96%D0%B6%D0%BD%D0%B8%D0%B9_%D1%80%D0%B0%D0%BD%D0%BA%D0%BE%D0%B2%D0%B8%D0%B9_%D1%81%D0%B2%D1%96%D1%82%D0%BB%D0%BE.jpg/1280px-%D0%9D%D1%96%D0%B6%D0%BD%D0%B8%D0%B9_%D1%80%D0%B0%D0%BD%D0%BA%D0%BE%D0%B2%D0%B8%D0%B9_%D1%81%D0%B2%D1%96%D1%82%D0%BB%D0%BE.jpg" preserveAspectRatio="xMidYMid meet"/>
<feGaussianBlur stdDeviation="10" result="blurphoto"/>
<feComposite operator="in" in="photo" in2="SourceGraphic" result="photomask"/>
<feComposite operator="over" in2="blurphoto" in="photomask"/>
</filter>
</defs>
<polygon filter="url(#cutout)" points="20,10 250,190 200,30 160,34 160,210" style="fill:lime;stroke:purple;stroke-width:1" />
</svg>
Use two <image> elements at the same location.
The first image would have a filter (as you have it now).
The second image would not have a filter, instead it would have a clip.
The clipPath used by the second image will need to have the polygon as its contents.
If you really want one image, you could probably merge the images using canvas drawImage and then write out the composite image to a new <image> element.

SVG filter feGaussianBlur needs boosting

I want to have a black text with a white outer glow in order to be readable on a colored map. This is what I used to do:
<defs>
<filter id="label-glow">
<feGaussianBlur stdDeviation="1" />
</filter>
</defs>
<text stroke="white" stroke-width="5" filter="url(#label-glow)">Harald's Repose</text>
<text>Harald's Repose</text>
I'd like to avoid duplicating the text element, so I decided to use feFlood to create a white rectangle, feComposite to create a white copy of the text, feGaussianBlur to create the blur, and then another feComposite to add the original text on top of it all. Unfortunately, the resulting outer glow is very weak. I found that repeating the feComposite a few times helps. I'm sure there's a better solution. What am I doing wrong?
<defs>
<filter id="label-glow">
<feFlood flood-color="white"/>
<feComposite in2="SourceGraphic" operator="in"/>
<feGaussianBlur stdDeviation="2"/>
<feComposite operator="over"/>
<feComposite operator="over"/>
<feComposite operator="over"/>
<feComposite operator="over"/>
<feComposite in="SourceGraphic"/>
</filter>
</defs>
<text filter="url(#label-glow)">Harald's Repose</text>
A slightly more elegant way is to dial up the opacity on your glow using a feComponentTransfer on the alpha channel.
<filter id="label-glow">
<feFlood flood-color="white"/>
<feComposite in2="SourceGraphic" operator="in"/>
<feGaussianBlur stdDeviation="2"/>
<feComponentTransfer>
<feFuncA type="gamma" exponent=".5" amplitude="2"/>
</feComponentTransfer>
<feComposite in="SourceGraphic"/>
</filter>
You can adjust the average intensity of the white by changing amplitude and you can adjust the intensity falloff by changing exponent.

Resources