You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, targetColorHex_str = "#0000FF", tolerance_num = 50) {
// Helper function to convert hex color string to an {r, g, b} object
function hexToRgb(hex_str) {
if (typeof hex_str !== 'string') return null;
// Expand shorthand form (e.g. "03F") to full form (e.g. "0033FF")
const shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
hex_str = hex_str.replace(shorthandRegex, (m, r_char, g_char, b_char) => {
return r_char + r_char + g_char + g_char + b_char + b_char;
});
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex_str);
return result ? {
r: parseInt(result[1], 16),
g: parseInt(result[2], 16),
b: parseInt(result[3], 16)
} : null;
}
// Helper function to calculate Euclidean distance between two RGB colors
function colorDistance(rgb1, rgb2) {
// Check if rgb1 or rgb2 are null or undefined, which might happen if targetRgb is not set.
if (!rgb1 || !rgb2) return Infinity;
const dr = rgb1.r - rgb2.r;
const dg = rgb1.g - rgb2.g;
const db = rgb1.b - rgb2.b;
return Math.sqrt(dr * dr + dg * dg + db * db);
}
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d', { willReadFrequently: true }); // Optimization hint for frequent getImageData/putImageData
// Ensure the image object exists and is loaded, valid, and has dimensions
if (!originalImg || typeof originalImg.complete !== 'boolean' || !originalImg.complete ||
typeof originalImg.naturalWidth !== 'number' || originalImg.naturalWidth === 0 ||
typeof originalImg.naturalHeight !== 'number' || originalImg.naturalHeight === 0) {
console.error("Provided image is not loaded, is invalid, or has no dimensions.");
canvas.width = 250;
canvas.height = 100;
ctx.fillStyle = "lightgray";
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "red";
ctx.font = "14px Arial";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillText("Error: Image not loaded or invalid.", canvas.width / 2, canvas.height / 2);
return canvas;
}
canvas.width = originalImg.naturalWidth;
canvas.height = originalImg.naturalHeight;
// Draw the original image onto the canvas
try {
ctx.drawImage(originalImg, 0, 0, canvas.width, canvas.height);
} catch (e) {
console.error("Error drawing image onto canvas: ", e);
// Clear canvas and draw error message
ctx.fillStyle = "lightgray";
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "red";
ctx.font = "14px Arial"; // Ensure font is appropriate for potentially small canvas
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.fillText("Error drawing image.", canvas.width / 2, canvas.height / 2);
return canvas;
}
let targetRgb = hexToRgb(targetColorHex_str);
if (!targetRgb) {
const defaultHex = "#0000FF"; // Default to Blue
console.warn(`Invalid targetColorHex_str: "${targetColorHex_str}". Using default ${defaultHex}.`);
targetRgb = hexToRgb(defaultHex);
if (!targetRgb) { // This should ideally not happen if defaultHex is valid
console.error("Default hex color is also invalid. Critical error in color parsing logic.");
// Fallback to a hardcoded known safe RGB to prevent further errors
targetRgb = { r: 0, g: 0, b: 255 };
}
}
// Ensure tolerance is a valid number
let numericTolerance = Number(tolerance_num);
if (isNaN(numericTolerance) || numericTolerance < 0) {
console.warn(`Invalid tolerance value: "${tolerance_num}". Using default 50.`);
numericTolerance = 50;
}
// Get image data from the canvas
let imageData;
try {
imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
} catch (e) {
// This typically happens due to cross-origin issues if the image source is external and not CORS-enabled
console.error("Error getting image data (likely cross-origin restrictions):", e);
ctx.save(); // Save current context state (font, fillStyle, etc.)
ctx.fillStyle = "rgba(200, 200, 200, 0.8)"; // Semi-transparent overlay to make text readable
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "red";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
const message = "Error: Cannot process image pixels.";
const subMessage = "(Possibly cross-origin restrictions)";
// Dynamically adjust font size based on canvas dimensions
let fontSize = Math.max(10, Math.min(canvas.width / (message.length * 0.6), canvas.height / 5));
ctx.font = `${fontSize}px Arial`;
ctx.fillText(message, canvas.width / 2, canvas.height / 2 - fontSize * 0.7);
let subFontSize = Math.max(8, fontSize * 0.8);
ctx.font = `${subFontSize}px Arial`;
ctx.fillText(subMessage, canvas.width / 2, canvas.height / 2 + fontSize * 0.7);
ctx.restore(); // Restore context state
return canvas;
}
const data = imageData.data;
// Index constants for accessing RGBA components in the flat data array
const R_IDX = 0, G_IDX = 1, B_IDX = 2; // A_IDX = 3 (Alpha is preserved)
for (let i = 0; i < data.length; i += 4) {
const r = data[i + R_IDX];
const g = data[i + G_IDX];
const b = data[i + B_IDX];
const pixelRgb = { r, g, b };
if (colorDistance(pixelRgb, targetRgb) > numericTolerance) {
// Convert to grayscale using the luminosity method (standard ITU-R BT.709 coefficients)
const gray = 0.2126 * r + 0.7152 * g + 0.0722 * b; // More perceptually accurate grayscale
// const gray = 0.299 * r + 0.587 * g + 0.114 * b; // Older standard, also common
data[i + R_IDX] = gray;
data[i + G_IDX] = gray;
data[i + B_IDX] = gray;
}
// Else: pixel color is within tolerance of targetColor, so keep its original color.
}
// Put the modified image data back onto the canvas
ctx.putImageData(imageData, 0, 0);
return canvas;
}
Apply Changes