In browsers that do support it, you could make use of svg filters to do it:
Here is an other Q/A that shows an interesting way of doing this for a fixed color.
Here I made a simple helper function that will set up for us the required tableValues
with a bit of tolerance, and I removed the <feFill>
so the selected color become transparent (<feFill>
would taint the canvas in Chrome).
If you wish to replace the color, you can still achieve it with the canvas' compositing options (commented code in below snippet).
const ctx = canvas.getContext('2d');
const img = new Image();
img.onload = e => {
canvas.width = img.width;
canvas.height = img.height;
// update our filter
updateChroma([76, 237, 0], 8);
// if you wish to replace the color, uncomment followings
// ctx.fillStyle = "your_replaceColor";
// ctx.fillRect(0,0,img.width,img.height);
ctx.filter = 'url(#chroma)';
ctx.drawImage(img, 0, 0);
ctx.filter = 'none';
// ctx.globalCompositeOperation = 'destination-in';
// ctx.drawImage(img, 0,0);
};
img.src = "https://i.stack.imgur.com/hZm8o.png";
function updateChroma(rgb, tolerance) {
const sels = ['R', 'G', 'B'];
rgb.forEach((value, ind) => {
const fe = document.querySelector('#chroma feFunc' + sels[ind]);
let vals = '';
if (!value) {
vals = '0'
} else {
for (let i = 0; i < 256; i++) {
vals += (Math.abs(value - i) <= tolerance) ? '1 ' : '0 ';
}
}
fe.setAttribute('tableValues', vals);
});
}
canvas {
background: ivory
}
<svg width="0" height="0" style="position:absolute;visibility:hidden">
<filter id="chroma" color-interpolation-filters="sRGB"x="0" y="0" height="100%" width="100%">
<feComponentTransfer>
<feFuncR type="discrete"/>
<feFuncG type="discrete"/>
<feFuncB type="discrete"/>
</feComponentTransfer>
<feColorMatrix type="matrix" values="1 0 0 0 0
0 1 0 0 0
0 0 1 0 0
1 1 1 1 -1" result="selected"/>
<feComposite in="SourceGraphic" in2="selected" operator="out"/>
</filter>
</svg>
<canvas id="canvas"></canvas>
I didn't do extensive tests on many devices, but where Hardware Acceleration is enabled, this might perform better than any pixel loop, since it should be all done on GPU.
But browser support is still not that great...
So you may need to fallback to pixel manips anyway.
Here, depending on what it is you are doing the chroma on, you may want to sacrifice a bit of quality for speed.
For instance, on video, you can perform the chroma on a downsized canvas, then draw it back with compositing on the main one, winning a few iterations per frames. See this previous Q/A for an example.