floodFill = {
function getPixel(pixelData, x, y) {
if (x < 0 || y < 0 || x >= pixelData.width || y >= pixelData.height) {
return -1;
} else {
return pixelData.data[y * pixelData.width + x];
}
}
function floodFill(ctx, x, y, fillColor) {
const imageData = ctx.getImageData(
0,
0,
ctx.canvas.width,
ctx.canvas.height
);
const pixelData = {
width: imageData.width,
height: imageData.height,
data: new Uint32Array(imageData.data.buffer)
};
const targetColor = getPixel(pixelData, x, y);
let colorMatch;
if (alpha(targetColor) <= 128 && alpha(fillColor) == 255) {
colorMatch = (pixel) => alpha(pixel) <= 128;
} else {
// Filling pixels equal to targetColor
colorMatch = (pixel) => pixel === targetColor;
}
// check we are actually filling a different color
if (targetColor !== fillColor) {
const spansToCheck = [];
function addSpan(left, right, y, direction) {
spansToCheck.push({ left, right, y, direction });
}
function checkSpan(left, right, y, direction) {
let inSpan = false;
let start;
let x;
for (x = left; x < right; ++x) {
const color = getPixel(pixelData, x, y);
if (colorMatch(color)) {
if (!inSpan) {
inSpan = true;
start = x;
}
} else {
if (inSpan) {
inSpan = false;
addSpan(start, x - 1, y, direction);
}
}
}
if (inSpan) {
inSpan = false;
addSpan(start, x - 1, y, direction);
}
}
addSpan(x, x, y, 0);
while (spansToCheck.length > 0) {
const { left, right, y, direction } = spansToCheck.pop();
// do left until we hit something, while we do this check above and below and add
let l = left;
for (;;) {
--l;
const color = getPixel(pixelData, l, y);
if (color !== targetColor) {
break;
}
}
++l;
let r = right;
for (;;) {
++r;
const color = getPixel(pixelData, r, y);
if (color !== targetColor) {
break;
}
}
const lineOffset = y * pixelData.width;
pixelData.data.fill(fillColor, lineOffset + l, lineOffset + r);
if (direction <= 0) {
checkSpan(l, r, y - 1, -1);
} else {
checkSpan(l, left, y - 1, -1);
checkSpan(right, r, y - 1, -1);
}
if (direction >= 0) {
checkSpan(l, r, y + 1, +1);
} else {
checkSpan(l, left, y + 1, +1);
checkSpan(right, r, y + 1, +1);
}
}
// put the data back
ctx.putImageData(imageData, 0, 0);
}
}
return floodFill;
}