I'm using libvips to transform images on the backend with a css/svg preview on the frontend to save resources.
I'm struggling with implementing the contrast css/svg filter function.
The specification shows contrast as a linear transformation in form of:
out = slope * in + intercept
where intercept
should be:
intercept = - (0.5 * slope) + 0.5
This way, I can use contrast(1.25)
in css preview of image modifications.
However, implementing this linear function in libvips through JS library sharp:
sharp.linear(contrast, - (0.5 * contrast) + 0.5)
Looking deeper into contrast changes for an image, the expectat result is that highs are put even higher and lows even lower. This looks like a contradition with the specification because the spec applies linear transformation so it should always multiple and add, makes highs higher but also lows a little big higher.
Using linear in sharp (and so in libvips) to change contrast the output actually looks like a brightness filter, which in the spcecification for css/svg filter is in form linear transformation without addition
out = slope * in
This looks to me like I might be misunderstanding what intercept does in svg linear function. Also, comparing svg and css shows differences. Using contrast(2)
in css should mimic slope = 2
and intercept = -(0.5 * 2) + 0.5 = -0.5
in svg, which is not the case in this fiddle:
.svg {
filter: url(#contrast);
}
.css {
filter: contrast(2);
}
<img src="https://dev-cdn.swbpg.com/o/g/1515254671.jpeg" width="300">
<img class="svg" src="https://dev-cdn.swbpg.com/o/g/1515254671.jpeg" width="300">
<img class="css" src="https://dev-cdn.swbpg.com/o/g/1515254671.jpeg" width="300">
<svg>
<filter id="contrast">
<feComponentTransfer>
<feFuncR type="linear" slope="2" intercept="-0.5"/>
<feFuncG type="linear" slope="2" intercept="-0.5"/>
<feFuncB type="linear" slope="2" intercept="-0.5"/>
</feComponentTransfer>
</filter>
</svg>
You can clearly see that the second image with svg filter looks different than the third one using css filter.
Is my understanding of filters completely wrong? I would expect there should some treshold somewhere to invert multiplication into division for lows.
How can I implement css contrast in different evnironments as a linear function with the same result?
Your intuition is not correct :) For input values less than 0.5 - the formula decreases the brightness - why? Let's take a contrast value of 2 and an input value of 0.4
Output = 2*0.4 - (0.5 *2) + 0.5
Output = 0.8 - 1 + 0.5
Output = 0.3
As you can see, when the input is lower than 0.5, the output will be always be less than the input because the sum of the slope component and the first (negative) component of the intercept will be equal to the contrast multiplied by the difference between the input and 0.5
This is the formula results on unitized values (floored and ceilinged on 0/1).
Also CSS Filters use sRGB color space by default. SVG Filters use linearRGB. You need to set the SVG filter color space to sRGB by adding attribute: color-interpolation-filters="sRGB" to your svg element. When you do this - your images look the same.
.svg {
filter: url(#contrast);
}
.css {
filter: contrast(2);
}
<img src="https://dev-cdn.swbpg.com/o/g/1515254671.jpeg" width="300">
<img class="svg" src="https://dev-cdn.swbpg.com/o/g/1515254671.jpeg" width="300">
<img class="css" src="https://dev-cdn.swbpg.com/o/g/1515254671.jpeg" width="300">
<svg color-interpolation-filters="sRGB">
<filter id="contrast">
<feComponentTransfer>
<feFuncR type="linear" slope="2" intercept="-0.5"/>
<feFuncG type="linear" slope="2" intercept="-0.5"/>
<feFuncB type="linear" slope="2" intercept="-0.5"/>
</feComponentTransfer>
</filter>
</svg>