Repeat mode for SVG displacement filter - svg

I am trying to apply a continuous 'flowing' turbulence on an image through SVG filter. This is the svg (Edited after a suggestion in the comments):
<svg>
<filter id="filter" >
<feTurbulence
id="turb"
type="turbulence"
baseFrequency="0.5 0"
numOctaves="3"
stitchTiles="noStitch"
result="turbulence"/>
<!-- Below code inserted from stackoverflow response -->
<feColorMatrix in="turbulence" type="hueRotate" values="0" result="cloud">
<animate attributeName="values" from="0" to="360" dur="10s" repeatCount="indefinite"/>
</feColorMatrix>
<feDisplacementMap
id="disp"
in="SourceGraphic"
in2="cloud"
xChannelSelector="R"
yChannelSelector="G"
scale="50"
result="displacementMap"/>
</filter>
</svg>
Applying it with
img{
width: 100%;
filter: url(#filter);
}
And this is how I am updating it with JS
let turb = document.getElementById('turb');
turb.setAttribute('baseFrequency', `0 ${current.toFixed(6)}`)
Here current is a self-incrementing value in my requestAnimationFrame function, but it keeps piling up the displacement texture over the image.
Take a look:
I want it to stick to a max baseFrequency value and then flow smoothly on the chosen axis like Pixi.js allows in its Displacement Filter when it is put to repeat mode. Any idea?

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.

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>

Resources