Reputation: 61
What is the fastest way to create copy this image without green color or making green color transparent or removing green color from 100X100 px top left
In this case do I have to check every pixel value? This process is too slow, eg: for 100X100px it takes 40000 loops for checking all rgba values
Upvotes: 1
Views: 2823
Reputation: 33072
If you check every pixel value and remove the green you'll be left with a ugly image with holes. An easier way to do it and with better results is planning in advance. This is produced with canvas. When you draw the image you may save every circle in an array, and then redraw everything after excluding the green circles.
In the next example click the color to choose it or click the D to remove the green circles.
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var cw = canvas.width = 300,
cx = cw / 2;
var ch = canvas.height = 180,
cy = ch / 2;
var color = "blue";
var drawing = false;
var points= [];
class Point{
constructor(color,x,y){
this.color = color;
this.x = x;
this.y = y
}
draw(){
ctx.fillStyle = this.color;
ctx.beginPath();
ctx.arc(this.x,this.y,5,0,2*Math.PI);
ctx.fill()
}
}
canvas.addEventListener('mousedown', function(evt) {
drawing = true;
}, false);
canvas.addEventListener('mouseup', function(evt) {
drawing = false;
}, false);
canvas.addEventListener("mouseout", function(evt) {
drawing = false;
}, false);
canvas.addEventListener("mousemove", function(evt) {
if (drawing) {
ctx.clearRect(0, 0, cw, ch);
m = oMousePos(canvas, evt);
var point = new Point(color,m.x,m.y);
//point.draw();
points.push(point);
points.forEach((p) =>{
p.draw()
})
}
}, false);
function oMousePos(canvas, evt) {
var ClientRect = canvas.getBoundingClientRect();
return {
x: Math.round(evt.clientX - ClientRect.left),
y: Math.round(evt.clientY - ClientRect.top)
}
}
colors.addEventListener("click", (e)=>{
if(e.target.tagName == "SPAN"){color = e.target.id;
}else if(e.target.id == "deleteGreen"){
ctx.clearRect(0,0,cw,ch);
points.forEach( p => {
if(p.color !== "green"){p.draw()}
})
}
})
body {
background-color: #eee;
}
#app {
display: block;
margin: auto;
position: absolute;
top: 0;
bottom: 0;
left: 0;
right: 0;
width: 300px;
height: 300px;
}
canvas {
background: #fff;
border-radius: 3px;
box-shadow: 0px 0px 15px 3px #ccc;
cursor: pointer;
}
#colors {
display: flex;
margin-top: 1em;
justify-content: space-between;
}
#colors span, #deleteGreen {
display: block;
width: 50px;
height: 50px;
border: 1px solid #d9d9d9;
}
#green {
background-color: green;
}
#gold {
background-color: gold;
}
#tomato {
background-color: tomato;
}
#blue {
background-color: blue;
}
#deleteGreen {
text-align: center;
line-height: 50px;
}
<div id="app">
<canvas id="canvas">:( </canvas>
<div id="colors" >
<span id="green"></span>
<span id="gold"></span>
<span id="tomato"></span>
<span id="blue"></span>
<div id="deleteGreen">D</div>
</div>
</div>
Upvotes: 0
Reputation: 137171
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.sstatic.net/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.
Upvotes: 1