You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, tolerance = 40, feather = 20, mode = "contiguous", targetColor = "auto") {
// Create a canvas to process the image data
const canvas = document.createElement("canvas");
const width = originalImg.naturalWidth || originalImg.width;
const height = originalImg.naturalHeight || originalImg.height;
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext("2d", { willReadFrequently: true });
ctx.drawImage(originalImg, 0, 0, width, height);
// Extract the image data
const imgData = ctx.getImageData(0, 0, width, height);
const data = imgData.data;
let bgR = 0, bgG = 0, bgB = 0;
// Determine target background color
if (String(targetColor).trim().toLowerCase() === "auto") {
// Automatically detect the most frequent background color on the borders
const buckets = new Map();
const bucketSize = 15;
const addColorToBucket = (r, g, b) => {
const br = Math.round(r / bucketSize) * bucketSize;
const bg = Math.round(g / bucketSize) * bucketSize;
const bb = Math.round(b / bucketSize) * bucketSize;
const key = `${br},${bg},${bb}`;
if (!buckets.has(key)) {
buckets.set(key, { count: 0, r: 0, g: 0, b: 0 });
}
const bkt = buckets.get(key);
bkt.count++;
bkt.r += r;
bkt.g += g;
bkt.b += b;
};
// Sample edges
for (let x = 0; x < width; x++) {
let idxTop = x * 4;
addColorToBucket(data[idxTop], data[idxTop+1], data[idxTop+2]);
let idxBot = ((height - 1) * width + x) * 4;
addColorToBucket(data[idxBot], data[idxBot+1], data[idxBot+2]);
}
for (let y = 1; y < height - 1; y++) {
let idxLeft = (y * width) * 4;
addColorToBucket(data[idxLeft], data[idxLeft+1], data[idxLeft+2]);
let idxRight = (y * width + (width - 1)) * 4;
addColorToBucket(data[idxRight], data[idxRight+1], data[idxRight+2]);
}
let maxCount = 0;
let bestBkt = null;
for (const bkt of buckets.values()) {
if (bkt.count > maxCount) {
maxCount = bkt.count;
bestBkt = bkt;
}
}
// Exact average of the most prominent bucket color handles JPG compression noise well
bgR = Math.round(bestBkt.r / maxCount);
bgG = Math.round(bestBkt.g / maxCount);
bgB = Math.round(bestBkt.b / maxCount);
} else {
// Parse hex color if manually provided (e.g. "#FFFFFF")
let hex = String(targetColor).replace(/^#/, '');
if (hex.length === 3) {
hex = hex.split('').map(c => c + c).join('');
}
bgR = parseInt(hex.substring(0, 2), 16) || 0;
bgG = parseInt(hex.substring(2, 4), 16) || 0;
bgB = parseInt(hex.substring(4, 6), 16) || 0;
}
// Function to calculate Euclidean distance between a pixel and target color
const colorMatchDist = (r, g, b) => {
return Math.sqrt((r - bgR) ** 2 + (g - bgG) ** 2 + (b - bgB) ** 2);
};
const parsedTolerance = Number(tolerance);
const parsedFeather = Number(feather);
const maxDist = parsedTolerance + parsedFeather;
if (String(mode).trim().toLowerCase() === "global") {
// Global Replacement: Replaces all matching colors uniformly regardless of location
for (let i = 0; i < data.length; i += 4) {
const dist = colorMatchDist(data[i], data[i+1], data[i+2]);
if (dist <= parsedTolerance) {
data[i+3] = 0; // Fully transparent
} else if (dist <= maxDist && parsedFeather > 0) {
// Anti-Aliasing (Feathering) the edges for a smoother look
const ratio = (dist - parsedTolerance) / parsedFeather;
data[i+3] = Math.min(data[i+3], Math.round(ratio * 255));
}
}
} else {
// Contiguous (Flood Fill): Replaces matching background connected to the borders
const visited = new Uint8Array(width * height);
const stack = [];
const checkAndPush = (x, y) => {
const idx = y * width + x;
if (!visited[idx]) {
visited[idx] = 1;
const pIdx = idx * 4;
const dist = colorMatchDist(data[pIdx], data[pIdx+1], data[pIdx+2]);
if (dist <= parsedTolerance) {
data[pIdx+3] = 0; // Fully transparent
stack.push(x, y);
} else if (dist <= maxDist && parsedFeather > 0) {
const ratio = (dist - parsedTolerance) / parsedFeather;
data[pIdx+3] = Math.min(data[pIdx+3], Math.round(ratio * 255));
// Intentionally NOT pushing onto the stack because it acts as the soft boundary edge
}
}
};
// Initialize the stack by checking the borders
for (let x = 0; x < width; x++) {
checkAndPush(x, 0);
checkAndPush(x, height - 1);
}
for (let y = 1; y < height - 1; y++) {
checkAndPush(0, y);
checkAndPush(width - 1, y);
}
// Iterative DFS stack based fill to prevent call-stack exceeded errors
while (stack.length > 0) {
const y = stack.pop();
const x = stack.pop();
if (x > 0) checkAndPush(x - 1, y);
if (x < width - 1) checkAndPush(x + 1, y);
if (y > 0) checkAndPush(x, y - 1);
if (y < height - 1) checkAndPush(x, y + 1);
}
}
// Apply modifying data
ctx.putImageData(imgData, 0, 0);
return canvas;
}
Apply Changes