You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, tolerance = 30, feathering = 15, method = "floodfill", bgColorHex = "auto") {
const canvas = document.createElement('canvas');
const width = originalImg.width;
const height = originalImg.height;
canvas.width = width;
canvas.height = height;
// Return empty canvas if dimension is 0
if (width === 0 || height === 0) return canvas;
const ctx = canvas.getContext('2d');
ctx.drawImage(originalImg, 0, 0);
const imgData = ctx.getImageData(0, 0, width, height);
const data = imgData.data;
// Determine the background color to be removed
let bgR = 255, bgG = 255, bgB = 255;
if (bgColorHex !== "auto" && /^#([0-9A-F]{3}){1,2}$/i.test(bgColorHex)) {
// Parse from hex string if explicitly provided
let hex = bgColorHex.replace('#', '');
if (hex.length === 3) hex = hex.split('').map(c => c + c).join('');
bgR = parseInt(hex.substring(0, 2), 16);
bgG = parseInt(hex.substring(2, 4), 16);
bgB = parseInt(hex.substring(4, 6), 16);
} else {
// Automatically determine background color by averaging the 4 corners of the image
const corners = [
0,
(width - 1) * 4,
((height - 1) * width) * 4,
((height - 1) * width + width - 1) * 4
];
let rSum = 0, gSum = 0, bSum = 0;
for (let c of corners) {
rSum += data[c];
gSum += data[c+1];
bSum += data[c+2];
}
bgR = Math.round(rSum / 4);
bgG = Math.round(gSum / 4);
bgB = Math.round(bSum / 4);
}
const tol = Math.max(0, tolerance);
const fade = Math.max(1, feathering);
// Calculate Euclidean color distance in RGB space
function colorDist(r, g, b) {
return Math.sqrt(
(r - bgR) * (r - bgR) +
(g - bgG) * (g - bgG) +
(b - bgB) * (b - bgB)
);
}
if (method === "chroma_key") {
// Mode 1: Remove all pixels across the whole image matching the color
for (let i = 0; i < data.length; i += 4) {
const dist = colorDist(data[i], data[i+1], data[i+2]);
if (dist <= tol) {
// Perfect background match, make completely transparent
data[i+3] = 0;
} else if (dist <= tol + fade) {
// Feathering edge, make semi-transparent for anti-aliasing
const alpha = Math.round(((dist - tol) / fade) * 255);
data[i+3] = Math.min(data[i+3], alpha);
}
}
} else {
// Mode 2: Flood-fill to remove only the connected outer background (Leaves matching inner colors intact)
const visited = new Uint8Array(width * height);
// Pre-allocate a 1D array to act as a stack for maximum performance instead of JS Array .push() / .pop()
const stack = new Int32Array(width * height);
let stackPtr = 0;
// Seed the outer edges of the image as the initial background area
for (let x = 0; x < width; x++) {
const topIdx = x;
const bottomIdx = (height - 1) * width + x;
if (colorDist(data[topIdx*4], data[topIdx*4+1], data[topIdx*4+2]) <= tol) {
if (!visited[topIdx]) { stack[stackPtr++] = topIdx; visited[topIdx] = 1; }
}
if (colorDist(data[bottomIdx*4], data[bottomIdx*4+1], data[bottomIdx*4+2]) <= tol) {
if (!visited[bottomIdx]) { stack[stackPtr++] = bottomIdx; visited[bottomIdx] = 1; }
}
}
for (let y = 1; y < height - 1; y++) {
const leftIdx = y * width;
const rightIdx = y * width + width - 1;
if (colorDist(data[leftIdx*4], data[leftIdx*4+1], data[leftIdx*4+2]) <= tol) {
if (!visited[leftIdx]) { stack[stackPtr++] = leftIdx; visited[leftIdx] = 1; }
}
if (colorDist(data[rightIdx*4], data[rightIdx*4+1], data[rightIdx*4+2]) <= tol) {
if (!visited[rightIdx]) { stack[stackPtr++] = rightIdx; visited[rightIdx] = 1; }
}
}
// Neighborhood offsets for Flood-fill: Left, Right, Up, Down
const dx = [-1, 1, 0, 0];
const dy = [0, 0, -1, 1];
// Process queue
while (stackPtr > 0) {
const current = stack[--stackPtr];
const cx = current % width;
const cy = Math.floor(current / width);
// Set background pixel alpha to 0 (Transparent)
const pIdx = current * 4;
data[pIdx + 3] = 0;
for (let i = 0; i < 4; i++) {
const nx = cx + dx[i];
const ny = cy + dy[i];
if (nx >= 0 && nx < width && ny >= 0 && ny < height) {
const nPos = ny * width + nx;
if (!visited[nPos]) {
const nIdx = nPos * 4;
const dist = colorDist(data[nIdx], data[nIdx+1], data[nIdx+2]);
if (dist <= tol) {
visited[nPos] = 1;
stack[stackPtr++] = nPos;
} else if (dist <= tol + fade) {
// Hit an object's boundary pixel. Stop traversing and feather the alpha.
visited[nPos] = 1;
const alpha = Math.round(((dist - tol) / fade) * 255);
data[nIdx+3] = Math.min(data[nIdx+3], alpha);
}
}
}
}
}
}
// Apply the manipulated transparent pixels back to the Canvas
ctx.putImageData(imgData, 0, 0);
// Note: To obtain the transparent PNG out of this utility method outside the scope:
// const dataUrl = canvas.toDataURL("image/png");
return canvas;
}
Apply Changes