Apply a texture to an image in SVG - svg

I'm trying to apply a texture to an image
Original
Texture
Result (made with PHP GD)
But with SVG, the closest I got is this result
<svg preserveAspectRatio="none" width="500" height="500" viewBox="0 0 500 500">
<defs>
<filter id="texture">
<feImage href="https://i.imgur.com/pjWcnJs.jpg" result="texture-img"/>
<feBlend in="SourceGraphic" in2="texture-img" mode="multiply"/>
</filter>
</defs>
<g>
<g filter="url(#texture)">
<image x="0" y="0" href="https://i.imgur.com/oVEdsQt.png" opacity="1" width="500" height="500" />
</g>
</g>
</svg>
fiddle
Is there another way which won't texturize transparent pixels?

I found a solution which is to pass the texture through a composite filter which would crop it to the source image
<svg preserveAspectRatio="none" width="500" height="500" viewBox="0 0 500 500">
<defs>
<filter id="texture">
<feImage href="https://i.imgur.com/pjWcnJs.jpg" result="texture-img"/>
<feComposite in2="SourceGraphic" operator="in" in="texture-img" result="composite"/>
<feBlend in="SourceGraphic" in2="composite" mode="multiply"/>
</filter>
</defs>
<g>
<g filter="url(#texture)">
<image x="0" y="0" href="https://i.imgur.com/oVEdsQt.png" opacity="1" width="500" height="500" />
</g>
</g>
</svg>
To tile the texture, I used feTile like this
<svg preserveAspectRatio="none" width="500" height="500" viewBox="0 0 500 500">
<defs>
<filter id="texture" x="0" y="0" width="100%" height="100%">
<feImage href="https://i.imgur.com/gWH7NLm.jpg" result="texture-img" width="256" height="256"/>
<feTile in="texture-img" x="0" y="0" width="100%" height="100%" result="tile" ></feTile>
<feComposite in2="SourceGraphic" operator="in" in="tile" result="composite"/>
<feBlend in="SourceGraphic" in2="composite" mode="multiply"/>
</filter>
</defs>
<g>
<g filter="url(#texture)">
<image x="0" y="0" href="https://i.imgur.com/oVEdsQt.png" opacity="1" width="500" height="500" />
</g>
</g>
</svg>
I got the idea by checking how inkscape applies material textures

Related

How do I stop svg GaussianBlur from being clipped? [duplicate]

Why does the grid in this SVG not fill the entire 256x256 space? No matter what size I change it to, about 15% of the grid is cut off, which appears to me to be arbitrary in the context of my code.
<svg width="256" height="256" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<pattern id="grid" width="18.75" height="18.75" patternUnits="userSpaceOnUse">
<path d="M 18.75 0 L 0 0 0 18.75" fill="none" stroke="black" stroke-width="1"/>
</pattern>
<rect id="gridRect" width="100%" height="100%" fill="url(#grid)" />
<filter id="gridify" width="100%" height="100%" filterUnits = "userSpaceOnUse">
<feImage result="sourceTwo" xlink:href="#gridRect" />
<feComposite in="SourceGraphic" in2="sourceTwo" operator="in"/>
</filter>
</defs>
<g filter="url(#gridify)" >
<rect width="100%" height="100%" fill="url(#linGradient)" />
</g>
<rect width="100%" height="100%" fill="none" stroke="black" stroke-width="1"/>
</svg>
The SVG specification defaults filters to being 10% larger than the filtered object by default.
If ‘x’ or ‘y’ is not specified, the effect is as if a value of -10% were specified.
If ‘width’ or ‘height’ is not specified, the effect is as if a value of 120% were specified.
I imagine that's to stop lots of questions along the lines of "why is my Gaussian blur based drop-shadow filter cut off?"
So, looks like all I needed to do to fix it was add an x="0" and a y="0" to the filter. I don't understand why it is necessary though, as it does not make sense that it would default to "-15%".
<svg width="256" height="256" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink">
<defs>
<pattern id="grid" width="18.75" height="18.75" patternUnits="userSpaceOnUse">
<path d="M 18.75 0 L 0 0 0 18.75" fill="none" stroke="black" stroke-width="1"/>
</pattern>
<rect id="gridRect" width="100%" height="100%" fill="url(#grid)" />
<filter id="gridify" x="0" y="0" width="100%" height="100%" filterUnits = "userSpaceOnUse">
<feImage result="sourceTwo" xlink:href="#gridRect" />
<feComposite in="SourceGraphic" in2="sourceTwo" operator="in"/>
</filter>
</defs>
<g filter="url(#gridify)" >
<rect width="100%" height="100%" fill="url(#linGradient)" />
</g>
<rect width="100%" height="100%" fill="none" stroke="black" stroke-width="1"/>
</svg>

why is size of text used as source in an SVG feComposite filter affected by the svg element size?

I'm trying to learn how to use feComposite in SVG, and in particular want to use text as one of the composition sources. Here's an initial sample of what I'm trying to do.
<svg width="100" height="100">
<defs>
<circle id="circ" cx="50" cy="50" r="40" stroke-width="0" fill="black" />
<text id="A" x="35" y="70" fill="black" style="font-size:60; font-family:Arial; font-weight:700">8</text>
<filter id="myfilter" width="120%">
<feImage xlink:href="#circ" result="lay1"/>
<feImage xlink:href="#A" result="lay2"/>
<feComposite operator="out" in="lay1" in2="lay2" result="COMP"/>
</filter>
</defs>
<g filter="url(#myfilter)" >
<use href="#circ"/>
<use href="#A"/>
</g>
</svg>
It gives me this result, as expected:
But, then I wanted to make everything bigger. So, I was going to need to increase the width and height on the svg element. However, when I do that, it causes the text to get smaller. Here's modified SVG, only increasing the height attribute on the svg element:
<svg width="100" height="150">
<defs>
<circle id="circ" cx="50" cy="50" r="40" stroke-width="0" fill="black" />
<text id="A" x="35" y="70" fill="black" style="font-size:60; font-family:Arial; font-weight:700">8</text>
<filter id="myfilter" width="120%">
<feImage xlink:href="#circ" result="lay1"/>
<feImage xlink:href="#A" result="lay2"/>
<feComposite operator="out" in="lay1" in2="lay2" result="COMP"/>
</filter>
</defs>
<g filter="url(#myfilter)" >
<use href="#circ"/>
<use href="#A"/>
</g>
</svg>
That caused the text content to scale smaller vertically.
If I increase the width on the svg element, then the text will scale smaller horizontally:
<svg width="150" height="150">
<defs>
<circle id="circ" cx="50" cy="50" r="40" stroke-width="0" fill="black" />
<text id="A" x="35" y="70" fill="black" style="font-size:60; font-family:Arial; font-weight:700">8</text>
<filter id="myfilter" width="120%">
<feImage xlink:href="#circ" result="lay1"/>
<feImage xlink:href="#A" result="lay2"/>
<feComposite operator="out" in="lay1" in2="lay2" result="COMP"/>
</filter>
</defs>
<g filter="url(#myfilter)" >
<use href="#circ"/>
<use href="#A"/>
</g>
</svg>
If instead of increasing the height or width on the svg element, I decrease the values, then the text will scale larger in the corresponding direction.
This only happens for text used as the filter source. If I use the same text element without the filter, it's not affected by changes in width/height on the svg root element. For example, in the following, I've modified the previous example by adding a <use> element to add another instance of the text (wrapped in a <g> with a translation lower on the page):
<svg width="150" height="150">
<defs>
<circle id="circ" cx="50" cy="50" r="40" stroke-width="0" fill="black" />
<text id="A" x="35" y="70" fill="black" style="font-size:60; font-family:Arial; font-weight:700">8</text>
<filter id="myfilter" width="120%">
<feImage xlink:href="#circ" result="lay1"/>
<feImage xlink:href="#A" result="lay2"/>
<feComposite operator="out" in="lay1" in2="lay2" result="COMP"/>
</filter>
</defs>
<rect x="0" y="40" width="100" height="20" fill="none"/>
<g filter="url(#myfilter)" >
<use href="#circ"/>
<use href="#A"/>
</g>
<g transform="translate(0,70)">
<use href="#A"/>
</g>
</svg>
What is going on here? Why is the text that's an feComposite source getting scaled based on the svg width/height?
In general, SVG filters are prone to screw ups when dimensions and units are unspecified. In this case, the g element is passing bad dimensions to the filter. For example, if you add a "y" coordinate to the first use element (the circle) inside the g element, and adjust its value, the text will shrink and expand.
Everything works fine if you explicitly add dimensions to everything and specify via preserveAspectRatio whether you want the aspect ratio of the input preserved or not. And if you want it preserved, whether you want it to size to the greater (meet) or the smaller (slice) dimension of the input.
Your filter actually discards the contents it was invoked with (its SourceGraphic) - so it doesn't really matter what you have in the g element - it's only using the contents to size the filter region (inconsistently as it turns out). So you may as well just apply a filter to a 100%/100% rect element - which you should size explicitly.
So, if you explicitly specify dimensions - this filter works just fine. I don't know what behavior you're trying to achieve. If you want the content to stay fixed as you expand the SVG element width/height - this is the filter you want.
<svg x="0" y="0" width="100px" height="150px" >
<defs>
<circle id="circ" cx="50" cy="50" r="40" stroke-width="0" fill="black" />
<text id="A" x="35" y="70" fill="black" style="font-size:60; font-family:Arial; font-weight:700">8</text>
<filter id="myfilter" filterUnits="userSpaceOnUse" primitiveUnits="objectBoundingBox" x="0" y="0" width="100" height="150">
<feImage x="0" y="0" height="1" width="1" xlink:href="#circ" result="lay1"/>
<feImage x="0" y="0" height="1" width="1" xlink:href="#A" result="lay2"/>
<feComposite operator="out" in="lay1" in2="lay2" result="COMP"/>
</filter>
</defs>
<rect filter="url(#myfilter)" x="0%" y="0%" width="100%" height="100%"/>
</svg>
<svg x="0" y="0" width="200px" height="250px" >
<defs>
<circle id="circ" cx="50" cy="50" r="40" stroke-width="0" fill="black" />
<text id="A" x="35" y="70" fill="black" style="font-size:60; font-family:Arial; font-weight:700">8</text>
<filter id="myfilter" filterUnits="userSpaceOnUse" primitiveUnits="objectBoundingBox" x="0" y="0" width="100" height="150">
<feImage x="0" y="0" height="1" width="1" xlink:href="#circ" result="lay1"/>
<feImage x="0" y="0" height="1" width="1" xlink:href="#A" result="lay2"/>
<feComposite operator="out" in="lay1" in2="lay2" result="COMP"/>
</filter>
</defs>
<rect filter="url(#myfilter)" x="0%" y="0%" width="100%" height="100%"/>
</svg>
On the other hand, if you want the content to scale, but keep its aspect ratio, you'll need a filter like this.
<svg x="0" y="0" width="100px" height="100px" viewBox="0 0 100 100" >
<defs>
<circle id="circ" cx="50" cy="50" r="40" stroke-width="0" fill="black" />
<text id="A" x="35" y="70" fill="black" style="font-size:60; font-family:Arial; font-weight:700">8</text>
<filter id="myfilter" primitiveUnits="objectBoundingBox" x="0" y="0" width="1" height="1">
<feImage x="0" y="0" height="1" width="1" xlink:href="#circ" result="lay1" preserveAspectRatio="xMidYMid slice"/>
<feImage x="0" y="0" height=".65 " width="1" xlink:href="#A" result="lay2" preserveAspectRatio="xMidYMid slice"/>
<feComposite operator="out" in="lay1" in2="lay2" result="COMP"/>
</filter>
</defs>
<rect filter="url(#myfilter)" x="0" y="0" width="100" height="150"/>
</svg>
<svg x="0" y="0" width="200px" height="250px" viewBox="0 0 100 100" >
<defs>
<circle id="circ" cx="50" cy="50" r="40" stroke-width="0" fill="black" />
<text id="A" x="35" y="70" fill="black" style="font-size:60; font-family:Arial; font-weight:700">8</text>
<filter id="myfilter" primitiveUnits="objectBoundingBox" x="0" y="0" width="1" height="1">
<feImage x="0" y="0" height="1" width="1" xlink:href="#circ" result="lay1" preserveAspectRatio="xMidYMid slice"/>
<feImage x="0" y="0" height=".65 " width="1" xlink:href="#A" result="lay2" preserveAspectRatio="xMidYMid slice"/>
<feComposite operator="out" in="lay1" in2="lay2" result="COMP"/>
</filter>
</defs>
<rect filter="url(#myfilter)" x="0" y="0" width="100" height="150"/>
</svg>

Displacement map filter not working on rotated object

I am trying to make mirrored pattern, but when I add displacement filter, it seems it only works on first and fourth quandrant. Am I overlooking some beginner mistake or is this behavior expected?
<svg width="500px" height="500px">
<defs>
<clipPath id="clip">
<rect x="0" y="0" width="50%" height="50%"/>
</clipPath>
<pattern id="diagonalHatch" patternUnits="userSpaceOnUse" width="4" height="4" patternTransform="scale(20) rotate(0)">
<path d="M-1,1 l2,-2
M0,4 l4,-4
M3,5 l2,-2"
style="stroke:red; stroke-width:1" />
</pattern>
<symbol id="quarter">
<rect x="0" y="0" width="100%" height="100%" fill="url(#diagonalHatch)" clip-path="url(#clip)"/>
</symbol>
<filter id="noise" x="0%" y="0%" width="100%" height="100%">
<feTurbulence baseFrequency="0.02" numOctaves="3" result="noise" seed="3" />
<feDisplacementMap in="SourceGraphic" in2="noise" scale="8" />
</filter>
</defs>
<g filter="url(#noise)">
<use xlink:href="#quarter"/>
<use xlink:href="#quarter" style="transform-origin: 50% 50%;" transform="rotate(90)"/>
<use xlink:href="#quarter" style="transform-origin: 50% 50%;" transform="rotate(180)"/>
<use xlink:href="#quarter" style="transform-origin: 50% 50%;" transform="rotate(270)"/>
</g>
</svg>
In your filter I've added xChannelSelector="R" for the feDisplacementMap. I'm using R for red since your shapes are red.
<filter id="noise" x="0%" y="0%" width="100%" height="100%">
<feTurbulence baseFrequency="0.02" numOctaves="3" result="noise" seed="3" />
<feDisplacementMap in="SourceGraphic" in2="noise" scale="8" xChannelSelector="R" />
</filter>
<svg width="500px" height="500px">
<defs>
<clipPath id="clip">
<rect x="0" y="0" width="50%" height="50%"/>
</clipPath>
<pattern id="diagonalHatch" patternUnits="userSpaceOnUse" width="4" height="4" patternTransform="scale(20) rotate(0)">
<path d="M-1,1 l2,-2
M0,4 l4,-4
M3,5 l2,-2"
style="stroke:red; stroke-width:1" />
</pattern>
<symbol id="quarter">
<rect x="0" y="0" width="100%" height="100%" fill="url(#diagonalHatch)" clip-path="url(#clip)"/>
</symbol>
<filter id="noise" x="0%" y="0%" width="100%" height="100%">
<feTurbulence baseFrequency="0.02" numOctaves="3" result="noise" seed="3" />
<feDisplacementMap in="SourceGraphic" in2="noise" scale="8" xChannelSelector="R" />
</filter>
</defs>
<g filter="url(#noise)">
<use xlink:href="#quarter"/>
<use xlink:href="#quarter" style="transform-origin: 50% 50%;" transform="rotate(90)"/>
<use xlink:href="#quarter" style="transform-origin: 50% 50%;" transform="rotate(180)"/>
<use xlink:href="#quarter" style="transform-origin: 50% 50%;" transform="rotate(270)"/>
</g>
</svg>
Also I would have applied the filter to the <rect> inside <symbol> instead of applying it to the group.
You're just missing an xChannelSelector or yChannelSelector - one of which is required.
(Update - as you mention in the comment, when you leave this out, it defaults to using the alpha channel in both X and Y - so it will shift everything up and down the top/left bottom/right diagonal axis - so solid color diagonal lines with this orientation will only show distortions at their starting and finishing edges).
If you're not going to process the feTurbulence in some way after you generate it, then any one of the channels (RGBA) works as a displacement source - since Perlin noise is equally noisy in all four channels.
(You don't need to use the R channel because your shapes are red - that's backwards - the channel selector applies to your noise input, not your the shape you want to apply it to.)
<svg width="500px" height="500px">
<defs>
<clipPath id="clip">
<rect x="0" y="0" width="50%" height="50%"/>
</clipPath>
<pattern id="diagonalHatch" patternUnits="userSpaceOnUse" width="4" height="4" patternTransform="scale(20) rotate(0)">
<path d="M-1,1 l2,-2
M0,4 l4,-4
M3,5 l2,-2"
style="stroke:red; stroke-width:1" />
</pattern>
<symbol id="quarter">
<rect x="0" y="0" width="100%" height="100%" fill="url(#diagonalHatch)" clip-path="url(#clip)"/>
</symbol>
<filter id="noise" x="0%" y="0%" width="100%" height="100%">
<feTurbulence baseFrequency="0.02" numOctaves="3" result="noise" seed="3" />
<feDisplacementMap in="SourceGraphic" in2="noise" scale="8" xChannelSelector="R" yChannelSelector="G"/>
</filter>
</defs>
<g filter="url(#noise)">
<use xlink:href="#quarter"/>
<use xlink:href="#quarter" style="transform-origin: 50% 50%;" transform="rotate(90)"/>
<use xlink:href="#quarter" style="transform-origin: 50% 50%;" transform="rotate(180)"/>
<use xlink:href="#quarter" style="transform-origin: 50% 50%;" transform="rotate(270)"/>
</g>
</svg>

Using filters inside a external symbol

I'm trying to use a external symbol with a filter. Unfortunately it looks like the browsers don't support this feature.
Am I declaring it wrong or hasn't it been implemented?
Example page: http://www.lisa.ink/svg/
Chrome only shows the last svg
Firefox shows the first and second svg as expected
Safari only shows the last svg as expected
1. Using external symbol with only a fragment identifier
html:
<svg>
<use xlink:href="/svg/rect.svg#rect" />
</svg>
external svg:
<svg>
<defs>
<filter id="hueRotate">
<feColorMatrix in="SourceGraphic" type="hueRotate" values="90" result="A"/>
</filter>
<symbol id="rect" viewBox="0 0 640 550">
<rect x="0" y="0" width="200" height="200" fill="red" filter="url(#hueRotate)" />
</symbol>
</defs>
</svg>
2. Using a external symbol with a full IRI
html:
<svg xmlns="http://www.w3.org/2000/svg">
<use xlink:href="/svg/rect.svg#rectWithFullUrl" />
</svg>
external svg:
<svg>
<defs>
<filter id="hueRotate">
<feColorMatrix in="SourceGraphic" type="hueRotate" values="90" result="A"/>
</filter>
<symbol id="rectWithFullUrl" viewBox="0 0 640 550">
<rect x="0" y="0" width="200" height="200" fill="red" filter="url(/svg/rect.svg#hueRotate)" />
</symbol>
</defs>
</svg>
3. Using a external symbol with local
html:
<svg xmlns="http://www.w3.org/2000/svg">
<use xlink:href="/svg/rect.svg#rectWithMissingFilter" />
</svg>
<svg xmlns="http://www.w3.org/2000/svg">
<defs>
<filter id="missingHueRotate">
<feColorMatrix in="SourceGraphic" type="hueRotate" values="90" result="A"/>
</filter>
</defs>
</svg>
external svg:
<svg xmlns="http://www.w3.org/2000/svg">
<defs>
<symbol id="rectWithMissingFilter" viewBox="0 0 640 550">
<rect x="0" y="0" width="200" height="200" fill="red" filter="url(#missingHueRotate)" />
</symbol>
</def>
</svg>

Apply texture tile tint to SVG

I'm trying to apply a texture tint to the SVG.
It's working almost fine but I need to make the texture as a tile for a better image quality
<filter id="composite" x="0" y="0" width="100%" height="100%">
<feImage result="sourceTwo" xlink:href="_link_" />
<feComposite in="SourceGraphic" in2="sourceTwo" operator="arithmetic" k1="1"/>
</filter>
How to replace feImage with a tile of the same image
jsFiddle
This is what the feTile primitive is for - but there is a regression bug in Chrome that clips the content - this is reported and a fix has been checked in (but still - buggy for now).
<svg width="500px" height="600px" viewBox="0 0 500 600">
<defs>
<filter id="composite" x="0%" y="0%" width="100%" height="100%">
<feImage result="sourceTwo" xlink:href="http://demo.prestalife.net/media/wood.jpg" width="34" height="34"/>
<feTile result="tiledImage"/>
<feComposite in="SourceGraphic" in2="tiledImage" operator="arithmetic" k1="1" k2="0" k3="0" k4="0"/>
</filter>
</defs>
<text x="0" y="20">Original image</text>
<image width="34" height="34" x="0" y="30" xlink:href="http://demo.prestalife.net/media/wood.jpg" />
<g filter="url(#composite)">
<rect fill="red" width="100%" height="100%" x="0" y="80" />
</g>
</svg>

Resources