You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(originalImg, bleachLevel = 128, tintColorStr = "224488") {
// Helper function for hex to RGB conversion (nested for self-containment)
function _hexToRgb(hexStr) {
// Remove '#' if present
let hex = hexStr.startsWith('#') ? hexStr.slice(1) : hexStr;
// Handle 3-digit hex codes by expanding them
if (hex.length === 3) {
hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
}
// Check if hex is valid 6-digit format
if (!/^[0-9A-F]{6}$/i.test(hex)) {
return null; // Invalid format
}
const bigint = parseInt(hex, 16);
// Check if parsing resulted in a valid number
if (isNaN(bigint)) {
return null;
}
const r = (bigint >> 16) & 255;
const g = (bigint >> 8) & 255;
const b = bigint & 255;
return { r, g, b };
}
const canvas = document.createElement('canvas');
// Use naturalWidth/Height for reliability, fallback to width/height
const imgWidth = originalImg.naturalWidth > 0 ? originalImg.naturalWidth : originalImg.width;
const imgHeight = originalImg.naturalHeight > 0 ? originalImg.naturalHeight : originalImg.height;
if (imgWidth === 0 || imgHeight === 0) {
// Handle cases where image dimensions are not valid
console.error("Image has zero width or height.");
return canvas; // Return empty canvas
}
canvas.width = imgWidth;
canvas.height = imgHeight;
// Add optimization hint for frequent readback
const ctx = canvas.getContext('2d', { willReadFrequently: true });
ctx.drawImage(originalImg, 0, 0, imgWidth, imgHeight);
let imageData;
try {
imageData = ctx.getImageData(0, 0, imgWidth, imgHeight);
} catch (e) {
// Handle potential cross-origin/tainted canvas issues
console.error("Error getting ImageData (canvas may be tainted):", e);
// Draw an X on the canvas to indicate error and return it
ctx.strokeStyle = 'red';
ctx.lineWidth = Math.min(imgWidth, imgHeight) * 0.1; // Responsive line width
ctx.beginPath();
ctx.moveTo(0,0);
ctx.lineTo(imgWidth, imgHeight);
ctx.moveTo(imgWidth,0);
ctx.lineTo(0, imgHeight);
ctx.stroke();
return canvas; // Return the canvas with the error indicator
}
const data = imageData.data;
// Parse user-provided tint color, with fallback to default, then hardcoded default
let tintRGB = _hexToRgb(tintColorStr);
if (!tintRGB) {
console.warn(`Invalid tintColor string "${tintColorStr}", using default parameter #224488.`);
tintRGB = _hexToRgb("224488"); // Attempt to parse the default parameter string
if (!tintRGB) { // Absolute fallback if default string itself is somehow bad (should not happen)
console.warn(`Default tintColor "#224488" also failed parsing, using hardcoded fallback.`);
tintRGB = {r: 34, g: 68, b: 136}; // {r:0x22, g:0x44, b:0x88}
}
}
const { r: tintR, g: tintG, b: tintB } = tintRGB;
// Ensure bleachLevel is within [0, 255] range
bleachLevel = Math.max(0, Math.min(255, bleachLevel));
// Process each pixel
for (let i = 0; i < data.length; i += 4) {
const r = data[i];
const g = data[i + 1];
const b = data[i + 2];
// Calculate luminance (using Rec. 709 coefficients, common for sRGB)
const lum = 0.2126 * r + 0.7152 * g + 0.0722 * b;
let finalR, finalG, finalB;
if (lum > bleachLevel) {
// This pixel is in the "bleached" range.
// The denominator (255.0 - bleachLevel) is safe because:
// - If bleachLevel == 255.0, then (lum > 255.0) is false (since lum <= 255.0), so this branch is not taken.
// - Thus, bleachLevel < 255.0 here, making (255.0 - bleachLevel) > 0.
let bleachFactor = (lum - bleachLevel) / (255.0 - bleachLevel);
bleachFactor = Math.max(0, Math.min(1, bleachFactor)); // Clamp, though should be ~[0,1]
bleachFactor = Math.pow(bleachFactor, 1.5); // Apply curve for bleaching effect
// Mix tint color with white based on bleachFactor
finalR = tintR * (1 - bleachFactor) + 255 * bleachFactor;
finalG = tintG * (1 - bleachFactor) + 255 * bleachFactor;
finalB = tintB * (1 - bleachFactor) + 255 * bleachFactor;
} else { // lum <= bleachLevel
// This pixel is in the "unbleached" or darker range.
let darkenFactor;
if (bleachLevel < 1e-9) { // Effectively, bleachLevel is 0.
// Since lum <= bleachLevel, lum must also be ~0.
darkenFactor = 0.0; // Make a black pixel stay black.
} else {
darkenFactor = lum / bleachLevel; // Scale luminance relative to bleachLevel.
}
darkenFactor = Math.max(0, Math.min(1, darkenFactor)); // Clamp, though should be ~[0,1]
darkenFactor = Math.pow(darkenFactor, 0.8); // Apply curve for color intensity
// Apply tint color scaled by darkenFactor
finalR = tintR * darkenFactor;
finalG = tintG * darkenFactor;
finalB = tintB * darkenFactor;
}
// Assign new pixel values. Uint8ClampedArray handles clamping to [0,255].
data[i] = Math.floor(finalR);
data[i+1] = Math.floor(finalG);
data[i+2] = Math.floor(finalB);
// Alpha channel (data[i+3]) remains unchanged
}
// Put the modified image data back onto the canvas
ctx.putImageData(imageData, 0, 0);
return canvas;
}
Apply Changes