You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, overlayColor = "#0077AA", overlayOpacity = 0.25, contrastFactor = 1.15, brightnessOffset = 5, desaturation = 0.15) {
// Helper to parse hex color string to [R, G, B] array
// This helper is defined inside processImage to keep it self-contained.
function parseHexColor(hex) {
// Ensure hex is a string, trim whitespace, remove leading #
hex = String(hex).trim().replace(/^#/, '');
if (hex.length === 3) {
hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
}
// Ensure it's a 6-digit hex string containing valid characters
if (!/^[0-9A-Fa-f]{6}$/.test(hex)) {
console.warn(`Invalid hex color: "#${hex}". Using default black ([0,0,0]).`);
return [0, 0, 0]; // Default to black if invalid format
}
const bigint = parseInt(hex, 16);
const r = (bigint >> 16) & 255;
const g = (bigint >> 8) & 255;
const b = bigint & 255;
return [r, g, b];
}
const [tintR, tintG, tintB] = parseHexColor(overlayColor);
const canvas = document.createElement('canvas');
// Optimization hint for frequent readback operations
const ctx = canvas.getContext('2d', { willReadFrequently: true });
const imgWidth = originalImg.naturalWidth || originalImg.width;
const imgHeight = originalImg.naturalHeight || originalImg.height;
canvas.width = imgWidth;
canvas.height = imgHeight;
// Draw the original image onto the canvas
ctx.drawImage(originalImg, 0, 0, imgWidth, imgHeight);
// If image dimensions are invalid, return the canvas (possibly empty or 0x0)
if (imgWidth === 0 || imgHeight === 0) {
console.warn("Image has zero width or height.");
return canvas;
}
let imageData;
try {
imageData = ctx.getImageData(0, 0, imgWidth, imgHeight);
} catch (e) {
// This can happen due to tainted canvas (CORS issues) or other errors
console.error("Failed to get image data: ", e);
// Draw an error message on the canvas as feedback
ctx.clearRect(0, 0, canvas.width, canvas.height); // Clear the drawn image
ctx.fillStyle = "rgba(200, 0, 0, 0.7)"; // Semi-transparent red background
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.fillStyle = "white";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
// Adjust font size based on canvas size for better readability
const fontSize = Math.min(24, Math.max(12, canvas.width / 20));
ctx.font = `${fontSize}px Arial`;
ctx.fillText("Error: Could not process image.", canvas.width / 2, canvas.height / 2 - fontSize * 0.6);
ctx.font = `${fontSize * 0.75}px Arial`;
ctx.fillText(e.message.length > 50 ? e.message.substring(0,47) + "..." : e.message, canvas.width / 2, canvas.height / 2 + fontSize * 0.6);
return canvas;
}
const data = imageData.data;
// Clamp parameter values to sensible ranges
const currentDesaturation = Math.max(0, Math.min(1, desaturation));
const currentOverlayOpacity = Math.max(0, Math.min(1, overlayOpacity));
// Allow contrast factor to be 0 (results in grey image), but not negative.
const currentContrastFactor = Math.max(0, contrastFactor);
// brightnessOffset can be negative or positive, no specific clamping beyond data type limits
// Iterate over each pixel
// data is a flat array: [R, G, B, A, R, G, B, A, ...]
for (let i = 0; i < data.length; i += 4) {
let r = data[i];
let g = data[i + 1];
let b = data[i + 2];
// Alpha channel (data[i+3]) is preserved by default
// 1. Apply Desaturation (0.0 to 1.0)
if (currentDesaturation > 0) {
const luma = 0.299 * r + 0.587 * g + 0.114 * b; // Standard luma calculation
r = r * (1 - currentDesaturation) + luma * currentDesaturation;
g = g * (1 - currentDesaturation) + luma * currentDesaturation;
b = b * (1 - currentDesaturation) + luma * currentDesaturation;
}
// 2. Apply Brightness (-255 to 255 relative to current values)
if (brightnessOffset !== 0) {
r += brightnessOffset;
g += brightnessOffset;
b += brightnessOffset;
}
// 3. Apply Contrast (0.0 to Infinity, 1.0 is no change)
if (currentContrastFactor !== 1.0) {
// Pixel values are adjusted relative to the midpoint (128)
r = (r - 128) * currentContrastFactor + 128;
g = (g - 128) * currentContrastFactor + 128;
b = (b - 128) * currentContrastFactor + 128;
}
// 4. Apply Tint Overlay (opacity 0.0 to 1.0)
if (currentOverlayOpacity > 0) {
r = r * (1 - currentOverlayOpacity) + tintR * currentOverlayOpacity;
g = g * (1 - currentOverlayOpacity) + tintG * currentOverlayOpacity;
b = b * (1 - currentOverlayOpacity) + tintB * currentOverlayOpacity;
}
// Clamp final R, G, B values to the 0-255 range
data[i] = Math.max(0, Math.min(255, Math.round(r)));
data[i + 1] = Math.max(0, Math.min(255, Math.round(g)));
data[i + 2] = Math.max(0, Math.min(255, Math.round(b)));
// data[i+3] (alpha) remains unchanged
}
// Put the modified image data back onto the canvas
ctx.putImageData(imageData, 0, 0);
return canvas;
}
Apply Changes