I have a flood fill I'm using from another post on here. I removed all the alpha channels but the colors are off. I'v racked my brain for days changing things around and using different fill methods but this one has gotten me closest. I'll be having users draw and fill as they please. Sorry if this is something simple. Only color that works is red and white.
<canvas id="myCanvas" width="300" height="150" style="border:1px solid grey"></canvas>
<script>
const c = document.getElementById("myCanvas");
const ctx = c.getContext("2d");
ctx.strokeStyle = "rgb(252, 53, 3)";
ctx.strokeRect(10, 10, 50, 50);
var red = [252, 53, 3];
floodFill(ctx, 15, 15, red);
ctx.strokeStyle = "rgb(3, 252, 3)";
ctx.strokeRect(75, 10, 50, 50);
var green = [3, 252, 3];
floodFill(ctx, 80, 15, green);
ctx.strokeStyle = "rgb(3, 119, 252)";
ctx.strokeRect(150, 10, 50, 50);
var blue = [3, 119, 252];
floodFill(ctx, 155, 15, blue);
ctx.strokeStyle = "rgb(0, 0, 0)";
ctx.strokeRect(225, 10, 50, 50);
var black = [0, 0, 0];
//floodFill(ctx, 230, 15, black);
//black get's stackoverflow and crashes page
function getPixel(imageData, x, y) {
if (x < 0 || y < 0 || x >= imageData.width || y >= imageData.height) {
return [-1, -1, -1, -1]; // impossible color
} else {
const offset = (y * imageData.width + x) * 4;
var pixel = imageData.data.slice(offset, offset + 4);
pixel[3] = 1; // ignore alpha
return pixel;
}
}
function setPixel(imageData, x, y, color) {
const offset = (y * imageData.width + x) * 4;
imageData.data[offset + 0] = color[0];
imageData.data[offset + 1] = color[1];
imageData.data[offset + 2] = color[2];
imageData.data[offset + 3] = color[0];
}
function colorsMatch(a, b, rSq) {
if (a === b) return true;
if (a == null || b == null) return false;
if (a.length !== b.length) return false;
for (var i = 0; i < a.length; ++i) {
if (a[i] !== b[i]) return false;
}
return true;
}
function floodFill(ctx, x, y, fillColor, range = 1) {
// read the pixels in the canvas
const imageData = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height);
// flags for if we visited a pixel already
const visited = new Uint8Array(imageData.width, imageData.height);
// get the color we're filling
const targetColor = getPixel(imageData, x, y);
console.log("target: " + targetColor + "Fill: " + fillColor);
// check we are actually filling a different color
if (!colorsMatch(targetColor, fillColor)) {
const rangeSq = range * range;
const pixelsToCheck = [x, y];
while (pixelsToCheck.length > 0) {
const y = pixelsToCheck.pop();
const x = pixelsToCheck.pop();
const currentColor = getPixel(imageData, x, y);
if (!visited[y * imageData.width + x] && colorsMatch(currentColor, targetColor, rangeSq)) {
setPixel(imageData, x, y, fillColor);
visited[y * imageData.width + x] = 1; // mark we were here already
pixelsToCheck.push(x + 1, y);
pixelsToCheck.push(x - 1, y);
pixelsToCheck.push(x, y + 1);
pixelsToCheck.push(x, y - 1);
}
}
// put the data back
ctx.putImageData(imageData, 0, 0);
}
}
</script>
I don't know what is causing the issue, if I try to pass in rgb value black (0,0,0) it crashes.
To get full alpha you need to set the 4th items to 255
instead of 1
(in getPixel
), nor to the red channel value (in setPixel
).
However, if you do ignore the alpha value in getPixel
but still allow transparency, then the original black transparent pixel (rgba(0, 0, 0, 0)
) will be seen as being the same color as a fully opaque black pixel (rgb(0, 0, 0)
), and your code won't change it. So either disable transparency entirely on the context (e.g. draw a fully opaque white rectangle),
const c = document.getElementById("myCanvas");
const ctx = c.getContext("2d");
/**
* Convert transparent black pixels
* to opaque white ones.
*/
ctx.fillStyle = "white";
ctx.fillRect(0, 0, c.width, c.height);
ctx.strokeStyle = "rgb(252, 53, 3)";
ctx.strokeRect(10, 10, 50, 50);
var red = [252, 53, 3];
floodFill(ctx, 15, 15, red);
ctx.strokeStyle = "rgb(3, 252, 3)";
ctx.strokeRect(75, 10, 50, 50);
var green = [3, 252, 3];
floodFill(ctx, 80, 15, green);
ctx.strokeStyle = "rgb(3, 119, 252)";
ctx.strokeRect(150, 10, 50, 50);
var blue = [3, 119, 252];
floodFill(ctx, 155, 15, blue);
ctx.strokeStyle = "rgb(0, 0, 0)";
ctx.strokeRect(225, 10, 50, 50);
var black = [0, 0, 0];
floodFill(ctx, 230, 15, black);
function getPixel(imageData, x, y) {
if (x < 0 || y < 0 || x >= imageData.width || y >= imageData.height) {
return [-1, -1, -1, -1]; // impossible color
} else {
const offset = (y * imageData.width + x) * 4;
var pixel = imageData.data.slice(offset, offset + 4);
pixel[3] = 1; // ignore alpha
return pixel;
}
}
function setPixel(imageData, x, y, color) {
const offset = (y * imageData.width + x) * 4;
imageData.data[offset + 0] = color[0];
imageData.data[offset + 1] = color[1];
imageData.data[offset + 2] = color[2];
imageData.data[offset + 3] = 255; // 255 is fully opaque
}
function colorsMatch(a, b, rSq) {
if (a === b) return true;
if (a == null || b == null) return false;
if (a.length !== b.length) return false;
for (var i = 0; i < a.length; ++i) {
if (a[i] !== b[i]) return false;
}
return true;
}
function floodFill(ctx, x, y, fillColor, range = 1) {
// read the pixels in the canvas
const imageData = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height);
// flags for if we visited a pixel already
const visited = new Uint8Array(imageData.width, imageData.height);
// get the color we're filling
const targetColor = getPixel(imageData, x, y);
console.log("target: " + targetColor + "Fill: " + fillColor);
// check we are actually filling a different color
if (!colorsMatch(targetColor, fillColor)) {
const rangeSq = range * range;
const pixelsToCheck = [x, y];
while (pixelsToCheck.length > 0) {
const y = pixelsToCheck.pop();
const x = pixelsToCheck.pop();
const currentColor = getPixel(imageData, x, y);
if (!visited[y * imageData.width + x] && colorsMatch(currentColor, targetColor, rangeSq)) {
setPixel(imageData, x, y, fillColor);
visited[y * imageData.width + x] = 1; // mark we were here already
pixelsToCheck.push(x + 1, y);
pixelsToCheck.push(x - 1, y);
pixelsToCheck.push(x, y + 1);
pixelsToCheck.push(x, y - 1);
}
}
// put the data back
ctx.putImageData(imageData, 0, 0);
}
}
<canvas id="myCanvas" width="300" height="150" style="border:1px solid grey"></canvas>
or let the alpha in the getPixel
function.
const c = document.getElementById("myCanvas");
const ctx = c.getContext("2d");
ctx.strokeStyle = "rgb(252, 53, 3)";
ctx.strokeRect(10, 10, 50, 50);
var red = [252, 53, 3];
floodFill(ctx, 15, 15, red);
ctx.strokeStyle = "rgb(3, 252, 3)";
ctx.strokeRect(75, 10, 50, 50);
var green = [3, 252, 3];
floodFill(ctx, 80, 15, green);
ctx.strokeStyle = "rgb(3, 119, 252)";
ctx.strokeRect(150, 10, 50, 50);
var blue = [3, 119, 252];
floodFill(ctx, 155, 15, blue);
ctx.strokeStyle = "rgb(0, 0, 0)";
ctx.strokeRect(225, 10, 50, 50);
var black = [0, 0, 0];
floodFill(ctx, 230, 15, black);
//black get's stackoverflow and crashes page
function getPixel(imageData, x, y) {
if (x < 0 || y < 0 || x >= imageData.width || y >= imageData.height) {
return [-1, -1, -1, -1]; // impossible color
} else {
const offset = (y * imageData.width + x) * 4;
var pixel = imageData.data.slice(offset, offset + 4);
// Do not ignore alpha
return pixel;
}
}
function setPixel(imageData, x, y, color) {
const offset = (y * imageData.width + x) * 4;
imageData.data[offset + 0] = color[0];
imageData.data[offset + 1] = color[1];
imageData.data[offset + 2] = color[2];
imageData.data[offset + 3] = 255; // 255 is fully opaque
}
function colorsMatch(a, b, rSq) {
if (a === b) return true;
if (a == null || b == null) return false;
if (a.length !== b.length) return false;
for (var i = 0; i < a.length; ++i) {
if (a[i] !== b[i]) return false;
}
return true;
}
function floodFill(ctx, x, y, fillColor, range = 1) {
// read the pixels in the canvas
const imageData = ctx.getImageData(0, 0, ctx.canvas.width, ctx.canvas.height);
// flags for if we visited a pixel already
const visited = new Uint8Array(imageData.width, imageData.height);
// get the color we're filling
const targetColor = getPixel(imageData, x, y);
console.log("target: " + targetColor + "Fill: " + fillColor);
// check we are actually filling a different color
if (!colorsMatch(targetColor, fillColor)) {
const rangeSq = range * range;
const pixelsToCheck = [x, y];
while (pixelsToCheck.length > 0) {
const y = pixelsToCheck.pop();
const x = pixelsToCheck.pop();
const currentColor = getPixel(imageData, x, y);
if (!visited[y * imageData.width + x] && colorsMatch(currentColor, targetColor, rangeSq)) {
setPixel(imageData, x, y, fillColor);
visited[y * imageData.width + x] = 1; // mark we were here already
pixelsToCheck.push(x + 1, y);
pixelsToCheck.push(x - 1, y);
pixelsToCheck.push(x, y + 1);
pixelsToCheck.push(x, y - 1);
}
}
// put the data back
ctx.putImageData(imageData, 0, 0);
}
}
<canvas id="myCanvas" width="300" height="150" style="border:1px solid grey"></canvas>
Though, note that I didn't check your algorithm at all, so it may contain other issues.