You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, targetColorStr = "#FF0000", tolerance = 50) {
// Helper function to parse color string to RGB object {r, g, b}
function _parseColorToRgb(colorString) {
// Ensure input is a string and normalize it (lowercase, trim whitespace)
const s = String(colorString).trim().toLowerCase();
// Standard named colors (CSS Level 2 and some common ones)
const namedColors = {
"transparent": { r: 0, g: 0, b: 0, a: 0 }, // Special case, treat as black for target
"aliceblue": { r: 240, g: 248, b: 255 }, "antiquewhite": { r: 250, g: 235, b: 215 },
"aqua": { r: 0, g: 255, b: 255 }, "aquamarine": { r: 127, g: 255, b: 212 },
"azure": { r: 240, g: 255, b: 255 }, "beige": { r: 245, g: 245, b: 220 },
"bisque": { r: 255, g: 228, b: 196 }, "black": { r: 0, g: 0, b: 0 },
"blanchedalmond": { r: 255, g: 235, b: 205 }, "blue": { r: 0, g: 0, b: 255 },
"blueviolet": { r: 138, g: 43, b: 226 }, "brown": { r: 165, g: 42, b: 42 },
"burlywood": { r: 222, g: 184, b: 135 }, "cadetblue": { r: 95, g: 158, b: 160 },
"chartreuse": { r: 127, g: 255, b: 0 }, "chocolate": { r: 210, g: 105, b: 30 },
"coral": { r: 255, g: 127, b: 80 }, "cornflowerblue": { r: 100, g: 149, b: 237 },
"cornsilk": { r: 255, g: 248, b: 220 }, "crimson": { r: 220, g: 20, b: 60 },
"cyan": { r: 0, g: 255, b: 255 }, "darkblue": { r: 0, g: 0, b: 139 },
"darkcyan": { r: 0, g: 139, b: 139 }, "darkgoldenrod": { r: 184, g: 134, b: 11 },
"darkgray": { r: 169, g: 169, b: 169 }, "darkgreen": { r: 0, g: 100, b: 0 },
"darkgrey": { r: 169, g: 169, b: 169 }, "darkkhaki": { r: 189, g: 183, b: 107 },
"darkmagenta": { r: 139, g: 0, b: 139 }, "darkolivegreen": { r: 85, g: 107, b: 47 },
"darkorange": { r: 255, g: 140, b: 0 }, "darkorchid": { r: 153, g: 50, b: 204 },
"darkred": { r: 139, g: 0, b: 0 }, "darksalmon": { r: 233, g: 150, b: 122 },
"darkseagreen": { r: 143, g: 188, b: 143 }, "darkslateblue": { r: 72, g: 61, b: 139 },
"darkslategray": { r: 47, g: 79, b: 79 }, "darkslategrey": { r: 47, g: 79, b: 79 },
"darkturquoise": { r: 0, g: 206, b: 209 }, "darkviolet": { r: 148, g: 0, b: 211 },
"deeppink": { r: 255, g: 20, b: 147 }, "deepskyblue": { r: 0, g: 191, b: 255 },
"dimgray": { r: 105, g: 105, b: 105 }, "dimgrey": { r: 105, g: 105, b: 105 },
"dodgerblue": { r: 30, g: 144, b: 255 }, "firebrick": { r: 178, g: 34, b: 34 },
"floralwhite": { r: 255, g: 250, b: 240 }, "forestgreen": { r: 34, g: 139, b: 34 },
"fuchsia": { r: 255, g: 0, b: 255 }, "gainsboro": { r: 220, g: 220, b: 220 },
"ghostwhite": { r: 248, g: 248, b: 255 }, "gold": { r: 255, g: 215, b: 0 },
"goldenrod": { r: 218, g: 165, b: 32 }, "gray": { r: 128, g: 128, b: 128 },
"green": { r: 0, g: 128, b: 0 }, "greenyellow": { r: 173, g: 255, b: 47 },
"grey": { r: 128, g: 128, b: 128 }, "honeydew": { r: 240, g: 255, b: 240 },
"hotpink": { r: 255, g: 105, b: 180 }, "indianred": { r: 205, g: 92, b: 92 },
"indigo": { r: 75, g: 0, b: 130 }, "ivory": { r: 255, g: 255, b: 240 },
"khaki": { r: 240, g: 230, b: 140 }, "lavender": { r: 230, g: 230, b: 250 },
"lavenderblush": { r: 255, g: 240, b: 245 }, "lawngreen": { r: 124, g: 252, b: 0 },
"lemonchiffon": { r: 255, g: 250, b: 205 }, "lightblue": { r: 173, g: 216, b: 230 },
"lightcoral": { r: 240, g: 128, b: 128 }, "lightcyan": { r: 224, g: 255, b: 255 },
"lightgoldenrodyellow": { r: 250, g: 250, b: 210 }, "lightgray": { r: 211, g: 211, b: 211 },
"lightgreen": { r: 144, g: 238, b: 144 }, "lightgrey": { r: 211, g: 211, b: 211 },
"lightpink": { r: 255, g: 182, b: 193 }, "lightsalmon": { r: 255, g: 160, b: 122 },
"lightseagreen": { r: 32, g: 178, b: 170 }, "lightskyblue": { r: 135, g: 206, b: 250 },
"lightslategray": { r: 119, g: 136, b: 153 }, "lightslategrey": { r: 119, g: 136, b: 153 },
"lightsteelblue": { r: 176, g: 196, b: 222 }, "lightyellow": { r: 255, g: 255, b: 224 },
"lime": { r: 0, g: 255, b: 0 }, "limegreen": { r: 50, g: 205, b: 50 },
"linen": { r: 250, g: 240, b: 230 }, "magenta": { r: 255, g: 0, b: 255 },
"maroon": { r: 128, g: 0, b: 0 }, "mediumaquamarine": { r: 102, g: 205, b: 170 },
"mediumblue": { r: 0, g: 0, b: 205 }, "mediumorchid": { r: 186, g: 85, b: 211 },
"mediumpurple": { r: 147, g: 112, b: 219 }, "mediumseagreen": { r: 60, g: 179, b: 113 },
"mediumslateblue": { r: 123, g: 104, b: 238 }, "mediumspringgreen": { r: 0, g: 250, b: 154 },
"mediumturquoise": { r: 72, g: 209, b: 204 }, "mediumvioletred": { r: 199, g: 21, b: 133 },
"midnightblue": { r: 25, g: 25, b: 112 }, "mintcream": { r: 245, g: 255, b: 250 },
"mistyrose": { r: 255, g: 228, b: 225 }, "moccasin": { r: 255, g: 228, b: 181 },
"navajowhite": { r: 255, g: 222, b: 173 }, "navy": { r: 0, g: 0, b: 128 },
"oldlace": { r: 253, g: 245, b: 230 }, "olive": { r: 128, g: 128, b: 0 },
"olivedrab": { r: 107, g: 142, b: 35 }, "orange": { r: 255, g: 165, b: 0 },
"orangered": { r: 255, g: 69, b: 0 }, "orchid": { r: 218, g: 112, b: 214 },
"palegoldenrod": { r: 238, g: 232, b: 170 }, "palegreen": { r: 152, g: 251, b: 152 },
"paleturquoise": { r: 175, g: 238, b: 238 }, "palevioletred": { r: 219, g: 112, b: 147 },
"papayawhip": { r: 255, g: 239, b: 213 }, "peachpuff": { r: 255, g: 218, b: 185 },
"peru": { r: 205, g: 133, b: 63 }, "pink": { r: 255, g: 192, b: 203 },
"plum": { r: 221, g: 160, b: 221 }, "powderblue": { r: 176, g: 224, b: 230 },
"purple": { r: 128, g: 0, b: 128 }, "rebeccapurple": { r: 102, g: 51, b: 153 },
"red": { r: 255, g: 0, b: 0 }, "rosybrown": { r: 188, g: 143, b: 143 },
"royalblue": { r: 65, g: 105, b: 225 }, "saddlebrown": { r: 139, g: 69, b: 19 },
"salmon": { r: 250, g: 128, b: 114 }, "sandybrown": { r: 244, g: 164, b: 96 },
"seagreen": { r: 46, g: 139, b: 87 }, "seashell": { r: 255, g: 245, b: 238 },
"sienna": { r: 160, g: 82, b: 45 }, "silver": { r: 192, g: 192, b: 192 },
"skyblue": { r: 135, g: 206, b: 235 }, "slateblue": { r: 106, g: 90, b: 205 },
"slategray": { r: 112, g: 128, b: 144 }, "slategrey": { r: 112, g: 128, b: 144 },
"snow": { r: 255, g: 250, b: 250 }, "springgreen": { r: 0, g: 255, b: 127 },
"steelblue": { r: 70, g: 130, b: 180 }, "tan": { r: 210, g: 180, b: 140 },
"teal": { r: 0, g: 128, b: 128 }, "thistle": { r: 216, g: 191, b: 216 },
"tomato": { r: 255, g: 99, b: 71 }, "turquoise": { r: 64, g: 224, b: 208 },
"violet": { r: 238, g: 130, b: 238 }, "wheat": { r: 245, g: 222, b: 179 },
"white": { r: 255, g: 255, b: 255 }, "whitesmoke": { r: 245, g: 245, b: 245 },
"yellow": { r: 255, g: 255, b: 0 }, "yellowgreen": { r: 154, g: 205, b: 50 }
};
if (namedColors[s]) {
// Alpha is ignored for target color logic, so we don't return it here.
const {r,g,b} = namedColors[s];
return {r,g,b};
}
// Hex color: #RGB, #RRGGBB, #RGBA, #RRGGBBAA (Alpha component is ignored for target color)
if (s.startsWith('#')) {
let hex = s.slice(1);
if (hex.length === 3 || hex.length === 4) { // #RGB or #RGBA
hex = hex[0]+hex[0] + hex[1]+hex[1] + hex[2]+hex[2]; // Extract RGB, ignore A
}
// Now hex should be 6 chars (from #RGB, #RRGGBB) or 8 chars (from #RGBA, #RRGGBBAA)
if (hex.length === 6 || hex.length === 8) { // #RRGGBB or #RRGGBBAA
const num = parseInt(hex.substring(0,6), 16); // Parse only the RGB part
if (!isNaN(num)) {
return { r: (num >> 16) & 255, g: (num >> 8) & 255, b: num & 255 };
}
}
}
// rgb(r,g,b) or rgba(r,g,b,a) (alpha in original string ignored for target color)
const rgbMatch = s.match(/^rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*[\d.]+)?\)$/);
if (rgbMatch) {
return {
r: parseInt(rgbMatch[1]),
g: parseInt(rgbMatch[2]),
b: parseInt(rgbMatch[3])
};
}
// Fallback for other CSS color names using a temporary element in DOM
if (typeof document !== 'undefined' && document.createElement) {
try {
const tempEl = document.createElement('div');
tempEl.style.color = colorString; // Use original, unnormalized string
tempEl.style.display = 'none';
let parentToAppend = document.body;
if (!parentToAppend) parentToAppend = document.documentElement; // Fallback if body not ready
if (parentToAppend && typeof parentToAppend.appendChild === 'function' && typeof parentToAppend.removeChild === 'function') {
parentToAppend.appendChild(tempEl);
const computedColor = window.getComputedStyle(tempEl).color;
parentToAppend.removeChild(tempEl);
const computedMatch = computedColor.match(/^rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*[\d.]+)?\)$/);
if (computedMatch) {
return {
r: parseInt(computedMatch[1]),
g: parseInt(computedMatch[2]),
b: parseInt(computedMatch[3])
};
}
}
} catch (e) {
console.warn(`Error parsing color "${colorString}" with DOM method: ${e.message}. This may happen if DOM is not fully available or in restricted environments.`);
}
}
console.warn(`Could not parse color string: "${colorString}". Defaulting to red (#FF0000).`);
return { r: 255, g: 0, b: 0 }; // Fallback default color
}
const imgWidth = originalImg.naturalWidth;
const imgHeight = originalImg.naturalHeight;
const canvas = document.createElement('canvas');
canvas.width = imgWidth;
canvas.height = imgHeight;
if (imgWidth === 0 || imgHeight === 0) {
// The canvas is 0x0, which is a valid element to return.
// console.warn("Image has zero dimensions. Returning an empty 0x0 canvas.");
return canvas;
}
// Optimization hint for frequent readbacks (getImageData)
const ctx = canvas.getContext('2d', { willReadFrequently: true });
if (!ctx) {
console.error("Could not get 2D context from canvas. Canvas support may be missing or disabled.");
// Return the empty canvas as per requirements (single element)
return canvas;
}
try {
ctx.drawImage(originalImg, 0, 0, imgWidth, imgHeight);
} catch (e) {
console.error("Error drawing image onto canvas:", e);
// Attempt to draw an error message on the canvas
ctx.clearRect(0, 0, canvas.width, canvas.height); // Clear previous attempts
ctx.font = "bold 14px Arial";
ctx.fillStyle = "red";
ctx.textAlign = "center";
const messages = ["Error drawing image.", e.message.length > 50 ? e.message.substring(0,50)+"..." : e.message];
messages.forEach((line, index) => {
ctx.fillText(line, canvas.width / 2, canvas.height / 2 - (messages.length-1)*9 + (index * 18));
});
return canvas;
}
const targetRgb = _parseColorToRgb(targetColorStr);
const validTolerance = Math.max(0, Number(tolerance));
const toleranceSq = validTolerance * validTolerance; // Use squared tolerance for efficiency
let imageData;
try {
imageData = ctx.getImageData(0, 0, imgWidth, imgHeight);
} catch (e) {
console.error("Could not get ImageData from canvas. This often occurs due to cross-origin security restrictions (CORS) if the image is from a different domain without appropriate headers.", e);
// Draw an error message on the canvas (original image is already drawn)
ctx.font = "bold 14px Arial";
ctx.fillStyle = "rgba(255,0,0,0.7)"; // Semi-transparent red box for message
ctx.fillRect(0, canvas.height/2 - 25, canvas.width, 50);
ctx.fillStyle = "white";
ctx.textAlign = "center";
const messages = [
"Pixel processing failed.",
"Image might be cross-origin (CORS issue)."
];
messages.forEach((line, index) => {
ctx.fillText(line, canvas.width / 2, canvas.height / 2 - (messages.length-1)*9 + (index * 18) - 5); // Shift text up a bit
});
return canvas;
}
const data = imageData.data; // Uint8ClampedArray: [R,G,B,A, R,G,B,A, ...]
for (let i = 0; i < data.length; i += 4) {
const r = data[i];
const g = data[i + 1];
const b = data[i + 2];
// Alpha channel (data[i+3]) is preserved.
// Calculate squared Euclidean distance to the target color
const dr = r - targetRgb.r;
const dg = g - targetRgb.g;
const db = b - targetRgb.b;
const distSq = dr * dr + dg * dg + db * db;
if (distSq <= toleranceSq) {
// Pixel's color is within the tolerance range of the target_color.
// Keep the original color (do nothing, as it's already in `data`).
} else {
// Pixel's color is outside the tolerance. Convert it to grayscale.
// Using the luminosity method for perceived brightness:
const gray = Math.round(0.299 * r + 0.587 * g + 0.114 * b);
data[i] = gray; // Red channel
data[i + 1] = gray; // Green channel
data[i + 2] = gray; // Blue channel
}
}
ctx.putImageData(imageData, 0, 0);
return canvas;
}
Apply Changes