svg feComponentTransfer linear function - svg

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.

Related

Overlaying SVG image with color, gradient or another image

Is is possible to apply a color/gradient/pattern overlay to an SVG image the same way as Adobe Photoshop implements “Layer styles”?
If a pixel of the image is transparent, the resulting pixel is transparent.
If a pixel of the image is semi-transparent, alpha value of the resulting pixel is equal to or less than alpha value of that pixel.
A pixel of a mask, defined by a color, a gradient or another image, is drawn on top of the corresponding pixel of the image, based on alpha channel of the mask.
In other words, something like the fill attribute applying to an image element instead of a shape (unfortunately, it has no effect).
Of course, the images, colors and gradients are dynamically set.
The only trick I've found so far is using a large internal shadow, but I'd like to animate the overlay position also.
As pointed out in the comments, filters do what I need.
For instance, this is Photoshop-like “color overlay” with green color and opacity 95%:
<defs>
<filter id="myfilter">
<feFlood result="floodFill" x="0" y="0" width="100%" height="100%" flood-color="green" flood-opacity="0.95"/>
<feBlend result="mergedImg" in="SourceGraphic" in2="floodFill" mode="multiply" />
<feComposite in2="SourceGraphic" in="mergedImg" operator="in" />
</filter>
</defs>
To apply, set the following style:
<image xlink:href="img/my.png" x="100" y="100" width="256" height="256" style="filter:url(#myfilter);" />

SVG image element with crisp edges

I have an SVG which includes a PNG image using <image>. This included image is a pixel art and I would like it to show pixelated.
But instead it is showing blurry.
Can I change the display so it is pixelated?
Minimal test case:
<svg width="100" height="100" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<image
width="100"
height="100"
xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAABCAIAAAB7QOjdAAAAD0lEQVR4AWP48OotAwMDAA3tAsiMG69RAAAAAElFTkSuQmCC"
/>
</svg>
Inside is a PNG with dimensions two pixels wide and one pixel tall.
Here is how it displayed on macOS 12.1 (21C52) / Safari 15.2 (17612.3.6.1.6).
But I want it to look like this:
Styling the <image> element with image-rendering: pixelated achieves the desired result:
<svg width="100" height="100" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<image style="image-rendering: pixelated;" width="100" height="100" xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAABCAIAAAB7QOjdAAAAD0lEQVR4AWP48OotAwMDAA3tAsiMG69RAAAAAElFTkSuQmCC" />
</svg>
The image-rendering CSS property sets an image scaling algorithm. The property applies to an element itself, to any images set in its other properties, and to its descendants.
— MDN Web Docs: Image Rendering
Note, this doesn't appear to render correctly in Safari.
Well the right way would be to use image-rendering: pixelated - but that's not supported on Safari yet.
Until then - this filter will work on black and white pixel art - transforming all RG & B values between 0 and 127 -> 0 and all values from 128 to 255 -> 255. (If you have scaled up anti-aliasing that you want to squash, you should add another <feFuncA type="discrete" tableValues="0 1"/> to that list of feFunc's in the filter).
<svg width="100" height="100" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" color-interpolation-filters="sRGB">
<defs>
<filter id="crispen">
<feComponentTransfer>
<feFuncR type="discrete" tableValues="0 1"/>
<feFuncG type="discrete" tableValues="0 1"/>
<feFuncB type="discrete" tableValues="0 1"/>
</feComponentTransfer>
</filter>
</defs>
<image
filter="url(#crispen)"
width="100"
height="100"
xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAIAAAABCAIAAAB7QOjdAAAAD0lEQVR4AWP48OotAwMDAA3tAsiMG69RAAAAAElFTkSuQmCC"
/>
</svg>
You can also generalize this to duo-tone pixel art by specifying different arrays for tableValues. If your dark tones is rgb(40,60,70) and your light tone is rgb (250,200,180) then your feComponentTransfer would be:
<feComponentTransfer>
<feFuncR type="discrete" tableValues="0.156 .98"/>
<feFuncG type="discrete" tableValues="0.235 .784"/>
<feFuncB type="discrete" tableValues="0.274 .706"/>
</feComponentTransfer>
(e.g. - when you unitize the lower red value of 40 - you get 40/255 = .156)
Note that this only works when all the lower and upper RGB values are lower and higher than 127.5. For example, if your light tone is rgb(200,50,50) and your dark tone is (50,60,60) - then this will result in the wrong answer. In this case you have to create a longer (and more complex) tableValues array so that the source colors are converted to the right destination color.

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 can I create the material design shadows in SVG using SVG filters

I have found this SVG that creates some of the shadows but it only works in Chrome (Firefox and Safari on OS X do not display the shadows)
I'm trying to reimplement the visual look of Material Design in pure SVG for a project and I'm interested in a solution that follows as much of the design requirements from Elevation Shadows section in the Material Design Specs.
I have programatic control over the generated SVG so, if the parameters of the filters are easier to express in computations based on elevation, please specify so.
This is a comprehensive drop shadow filter structure that duplicates all the functionality of the drop shadow control in Photoshop. I wrote a mini-app to allow you to change any of these parameters and copy and paste the resulting filter: http://codepen.io/mullany/pen/sJopz
<filter id="drop-shadow" color-interpolation-filters="sRGB" x="-50%" y="-50%" height="200%" width="200%">
<!-- Take source alpha, offset it by angle/distance and blur it by size -->
<feOffset id="offset" in="SourceAlpha" dx="-5.49" dy="-5.11" result="SA-offset"/>
<feGaussianBlur id="blur" in="SA-offset" stdDeviation="4.75" result="SA-o-blur"/>
<!-- Apply a contour by using a color curve transform on the alpha and clipping the result to the input -->
<feComponentTransfer in="SA-o-blur" result="SA-o-b-contIN">
<feFuncA id="contour" type="table" tableValues="0 1"/>
</feComponentTransfer>
<feComposite operator="in" in="SA-o-blur" in2="SA-o-b-contIN" result="SA-o-b-cont"/>
<!-- Adjust the spread by multiplying alpha by a constant factor --> <feComponentTransfer in="SA-o-b-cont" result="SA-o-b-c-sprd">
<feFuncA id="spread-ctrl" type="linear" slope="2.4"/>
</feComponentTransfer>
<!-- Adjust color and opacity by adding fixed offsets and an opacity multiplier -->
<feColorMatrix id="recolor" in="SA-o-b-c-sprd" type="matrix" values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 .8 0" result="SA-o-b-c-s-recolor"/>
<!-- Generate a reasonably grainy noise input with baseFrequency between approx .5 to 2.0. And add the noise with k1 and k2 multipliers that sum to 1 -->
<feTurbulence result="fNoise" type="fractalNoise" numOctaves="6" baseFrequency="1.98"/>
<feColorMatrix in="fNoise" type="matrix" values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 7 -3" result="clipNoise"/>
<feComposite id="noisemix" operator="arithmetic" in="SA-o-b-c-s-recolor" in2="clipNoise" k1="0" k2="1" result="SA-o-b-c-s-r-mix"/>
<!-- Merge the shadow with the original -->
<feMerge>
<feMergeNode in="SA-o-b-c-s-r-mix"/>
<feMergeNode in="SourceGraphic"/>
</feMerge>
</filter>
The filter chain does not appear to be valid so you should report a bug on Chrome. Firefox is doing the right thing by not displaying anything.
For instance in the first filter chain (the one with id="filter4284") two of the feGaussianBlur elements require an input of something called "composite" e.g.
<feGaussianBlur
id="feGaussianBlur4338"
in="composite"
stdDeviation="1"
result="blur" />
but there's no result in that chain called composite so the filter chain fails. The other chains are similarly broken.
The simplest version I could find is the following:
<filter style="color-interpolation-filters:sRGB;" id="height-1" x="-100%" y="-100%" width="300%" height="400%">
<feFlood flood-opacity="0.5" flood-color="rgb(0,0,0)" result="flood"></feFlood>
<feComposite in="flood" in2="SourceGraphic" operator="in" result="comp"></feComposite>
<feOffset dx="0" dy="1" result="offset"></feOffset>
<feGaussianBlur in="offset" stdDeviation="1" result="blur"></feGaussianBlur>
<feBlend in="SourceGraphic" in2="blur" mode="normal"></feBlend>
</filter>
The only things that change from one elevation to the other are the dy in feOffset and the stdDeviation from feGaussianBlur. In both cases, the value for the elevation is the value they take.
The x and y of the filter are set to generous margins in order to avoid cutoffs in large elevation (e.g. 24)

Apply SVG filter to "fill" only

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>

Resources