You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(originalImg, foilColorNameOrHex = "gold", contrast = 2.5, shininess = 0.6) {
// Helper function for parsing color string (hex or named)
// Defined inside processImage to encapsulate it.
function _parseColor(colorStrParam) {
const namedColors = {
"gold": "#FFD700",
"silver": "#C0C0C0",
"copper": "#B87333",
"rosegold": "#B76E79",
"red": "#FF0000",
"green": "#00FF00",
"blue": "#0000FF",
"black": "#000000",
"white": "#FFFFFF"
};
let colorInput = String(colorStrParam).toLowerCase().replace(/\s+/g, '');
if (namedColors[colorInput]) {
colorInput = namedColors[colorInput];
}
if (/^#([0-9a-f]{3}){1,2}$/i.test(colorInput)) {
let hex = colorInput.substring(1);
if (hex.length === 3) {
hex = hex.split('').map(char => char + char).join('');
}
const r = parseInt(hex.substring(0, 2), 16);
const g = parseInt(hex.substring(2, 4), 16);
const b = parseInt(hex.substring(4, 6), 16);
return [r, g, b];
}
// Default to gold if parsing fails.
const goldHex = namedColors["gold"].substring(1); // Gold is a safe default
return [
parseInt(goldHex.substring(0, 2), 16),
parseInt(goldHex.substring(2, 4), 16),
parseInt(goldHex.substring(4, 6), 16)
];
}
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d', { willReadFrequently: true });
canvas.width = originalImg.naturalWidth || originalImg.width;
canvas.height = originalImg.naturalHeight || originalImg.height;
ctx.drawImage(originalImg, 0, 0, canvas.width, canvas.height);
let imageData;
try {
imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
} catch (e) {
// Handle potential security errors (tainted canvas) or other issues
console.error("Metallic Foil Filter: Error getting ImageData. This might be due to cross-origin image issues.", e);
// Return canvas with original image drawn, as processing isn't possible
return canvas;
}
const data = imageData.data;
const foilRgb = _parseColor(foilColorNameOrHex);
const R_foil = foilRgb[0];
const G_foil = foilRgb[1];
const B_foil = foilRgb[2];
// Clamp and validate parameters. Contrast: 0.5 (brightening) to 5.0 (strong contrast). Shininess: 0 to 1.
const currentContrast = Math.max(0.5, Math.min(Number(contrast) || 2.5, 5.0));
const currentShininess = Math.max(0.0, Math.min(Number(shininess) || 0.6, 1.0));
// Luminance value (post-contrast, 0-1) above which shininess effect applies
const highlightThreshold = 0.7;
for (let i = 0; i < data.length; i += 4) {
const r = data[i];
const g = data[i + 1];
const b = data[i + 2];
// 1. Calculate luminance (0-255) using perceptually accurate sRGB standard weights
let luminance = 0.2126 * r + 0.7152 * g + 0.0722 * b;
// 2. Normalize luminance to 0-1 and apply contrast using a power curve
// An exponent > 1 increases contrast, typically making mid-tones darker.
// An exponent < 1 decreases contrast, typically making mid-tones brighter.
let normLuminance = luminance / 255.0;
let contrastedLuminance = Math.pow(normLuminance, currentContrast);
// 3. Colorize with foil color, scaled by the contrasted luminance
let newR = R_foil * contrastedLuminance;
let newG = G_foil * contrastedLuminance;
let newB = B_foil * contrastedLuminance;
// 4. Apply "shininess": blend highlights towards white
// This simulates specular reflection on the metallic surface.
if (contrastedLuminance > highlightThreshold) {
// Calculate blend factor: how much white to mix in.
// This factor is 0 at highlightThreshold and ramps up to 1 as contrastedLuminance approaches 1.0.
let shineBlendFactor = (contrastedLuminance - highlightThreshold) / (1.0 - highlightThreshold);
shineBlendFactor = Math.min(1.0, Math.max(0.0, shineBlendFactor)); // Clamp to [0, 1]
// Scale by the overall shininess parameter
shineBlendFactor *= currentShininess;
// Linear interpolation towards white: C_final = C_current * (1-factor) + C_white * factor
newR = newR * (1 - shineBlendFactor) + 255 * shineBlendFactor;
newG = newG * (1 - shineBlendFactor) + 255 * shineBlendFactor;
newB = newB * (1 - shineBlendFactor) + 255 * shineBlendFactor;
}
// Clamp final RGB values to the valid 0-255 range
data[i] = Math.min(255, Math.max(0, newR));
data[i + 1] = Math.min(255, Math.max(0, newG));
data[i + 2] = Math.min(255, Math.max(0, newB));
// Alpha channel (data[i+3]) remains unchanged to preserve original transparency
}
ctx.putImageData(imageData, 0, 0);
return canvas;
}
Apply Changes