To set the stage for this question, I will be pasting the definition of Vibrance
and what makes it differ from regular Saturation
.
Vibrance is a smart-tool which cleverly increases the intensity of the more muted colors and leaves the already well-saturated colors alone. It’s sort of like fill light, but for colors. Vibrance also prevents skin tones from becoming overly saturated and unnatural.
Source: https://digital-photography-school.com/vibrance-vs-saturation-in-plain-english/
Currently all CSS filters apply homogeneously and equally to every pixel color value on the image. Is there any way and/or package that can apply so called smart filters
which applies its filter depending on the current pixel color value that it is applying its filter on?
The exact formula used in Adobe's vibrance filter appears to be a bit of a mystery, but the Caman.js library actually includes a pretty simple formula for calculating vibrance.
I've built a version of Vibrance for Fabric.js using that same formula and it seems to do the trick. You should find performance quite a bit better than with Caman.js as well since Fabric.js filters support WebGL.
Update: For anyone reading this who's interesting in this feature, Vibrance
is now a built-in filter as of Fabric.js v4.6.0 (http://fabricjs.com/docs/fabric.Image.filters.Vibrance.html)
var canvas = new fabric.Canvas("canvas", {
backgroundColor: "white"
});
fabric.Image.fromURL("https://live.staticflickr.com/65535/51283215722_e949fa76c8_k.jpg", function(img) {
img.filters.push(new fabric.Image.filters.Vibrance({
vibrance: 0
}));
img.filters.push(new fabric.Image.filters.Saturation({
saturation: 0
}));
img.applyFilters()
img.scaleToWidth(350)
img.set({
left: 20,
top: 20
});
canvas.add(img)
}, {
crossOrigin: 'anonymous'
});
function setVibrance(value) {
var img = canvas.item(0);
img.filters[0].vibrance = value;
img.applyFilters();
canvas.renderAll();
}
function setSaturation(value) {
var img = canvas.item(0);
img.filters[1].saturation = value;
img.applyFilters();
canvas.renderAll();
}
//start vibrance filter code
(function(global) {
var fabric = global.fabric || (global.fabric = {}),
filters = fabric.Image.filters,
createClass = fabric.util.createClass;
filters.Vibrance = createClass(filters.BaseFilter, {
type: 'Vibrance',
fragmentSource: 'precision highp float;\n' +
'uniform sampler2D uTexture;\n' +
'uniform float uVibrance;\n' +
'varying vec2 vTexCoord;\n' +
'void main() {\n' +
'vec4 color = texture2D(uTexture, vTexCoord);\n' +
'float max = max(color.r, max(color.g, color.b));\n' +
'float avg = (color.r + color.g + color.b) / 3.0;\n' +
'float amt = (abs(max - avg) * 2.0) * uVibrance;\n' +
'color.r += max != color.r ? (max - color.r) * amt : 0.00;\n' +
'color.g += max != color.g ? (max - color.g) * amt : 0.00;\n' +
'color.b += max != color.b ? (max - color.b) * amt : 0.00;\n' +
'gl_FragColor = color;\n' +
'}',
vibrance: 0,
mainParameter: 'vibrance',
applyTo2d: function(options) {
if (this.vibrance === 0) {
return;
}
var imageData = options.imageData,
data = imageData.data,
len = data.length,
adjust = -this.vibrance,
i, max, avg, amt;
for (i = 0; i < len; i += 4) {
max = Math.max(data[i], data[i + 1], data[i + 2]);
avg = (data[i] + data[i + 1] + data[i + 2]) / 3;
amt = ((Math.abs(max - avg) * 2 / 255) * adjust);
data[i] += max !== data[i] ? (max - data[i]) * amt : 0;
data[i + 1] += max !== data[i + 1] ? (max - data[i + 1]) * amt : 0;
data[i + 2] += max !== data[i + 2] ? (max - data[i + 2]) * amt : 0;
}
},
getUniformLocations: function(gl, program) {
return {
uVibrance: gl.getUniformLocation(program, 'uVibrance'),
};
},
sendUniformData: function(gl, uniformLocations) {
gl.uniform1f(uniformLocations.uVibrance, -this.vibrance);
},
});
fabric.Image.filters.Vibrance.fromObject = fabric.Image.filters.BaseFilter.fromObject;
})(typeof exports !== 'undefined' ? exports : this);
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/4.5.0/fabric.min.js"></script>
<span>Vibrance</span>
<input type="range" value="0" min="-1" max="1" step="0.1" onchange="setVibrance(this.value)"/>
<span>Saturation</span>
<input type="range" value="0" min="-1" max="1" step="0.1" onchange="setSaturation(this.value)"/>
<canvas id="canvas" width="400" height="200"></canvas>