You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, saturationAdjustment = -0.4, contrastFactor = 1.3, brightnessOffset = -10, tintColorStr = "20,30,50", tintOpacity = 0.2) {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d', { willReadFrequently: true });
const width = originalImg.naturalWidth || originalImg.width;
const height = originalImg.naturalHeight || originalImg.height;
if (!width || !height) {
console.error("Image has zero width or height. Ensure the image is loaded properly before calling processImage.");
// Return a small canvas with an error message
const errorCanvas = document.createElement('canvas');
errorCanvas.width = 300;
errorCanvas.height = 100;
const errorCtx = errorCanvas.getContext('2d');
errorCtx.fillStyle = '#f0f0f0';
errorCtx.fillRect(0, 0, errorCanvas.width, errorCanvas.height);
errorCtx.fillStyle = 'red';
errorCtx.font = '14px Arial';
errorCtx.textAlign = 'center';
errorCtx.fillText('Error: Invalid image dimensions.', errorCanvas.width / 2, errorCanvas.height / 2 - 10);
errorCtx.fillText('Please ensure image is loaded.', errorCanvas.width / 2, errorCanvas.height / 2 + 10);
return errorCanvas;
}
canvas.width = width;
canvas.height = height;
ctx.drawImage(originalImg, 0, 0, width, height);
let imageData;
try {
imageData = ctx.getImageData(0, 0, width, height);
} catch (e) {
console.error("Error getting image data: ", e);
// This can happen due to CORS issues if the image is from another domain
const errorCanvas = document.createElement('canvas');
errorCanvas.width = width;
errorCanvas.height = height;
const errorCtx = errorCanvas.getContext('2d');
errorCtx.fillStyle = '#f0f0f0';
errorCtx.fillRect(0, 0, width, height);
errorCtx.font = '16px Arial';
errorCtx.fillStyle = 'red';
errorCtx.textAlign = 'center';
const errorMessage = `Error processing image.`;
const corsMessage = `(Possibly CORS policy issue if image is cross-origin)`;
errorCtx.fillText(errorMessage, width / 2, height / 2 - 10);
if (height > 40) { // Only show second line if there's space
errorCtx.fillText(corsMessage, width / 2, height / 2 + 10);
}
console.error("Failed to get ImageData, likely due to CORS policy. Image source: ", originalImg.src);
return errorCanvas;
}
const data = imageData.data;
const [tintR_str, tintG_str, tintB_str] = tintColorStr.split(',');
const tr = parseInt(tintR_str, 10);
const tg = parseInt(tintG_str, 10);
const tb = parseInt(tintB_str, 10);
const useTint = tintOpacity > 0 && !isNaN(tr) && !isNaN(tg) && !isNaN(tb);
// Helper for clamping values to 0-255 range.
// Uint8ClampedArray handles this automatically on assignment, but explicit clamping
// is good practice if intermediate results are used in formulas expecting valid ranges.
const clamp = (value) => Math.max(0, Math.min(255, value));
for (let i = 0; i < data.length; i += 4) {
let r = data[i];
let g = data[i + 1];
let b = data[i + 2];
// 1. Brightness adjustment (offset)
if (brightnessOffset !== 0) {
r += brightnessOffset;
g += brightnessOffset;
b += brightnessOffset;
}
// Clamp after brightness
r = clamp(r);
g = clamp(g);
b = clamp(b);
// 2. Contrast adjustment (factor)
// Adjusts values relative to mid-gray (127.5)
if (contrastFactor !== 1.0) {
// Normalize to 0-1, shift center to 0, apply contrast, shift back, scale to 0-255
r = ((r / 255.0 - 0.5) * contrastFactor + 0.5) * 255.0;
g = ((g / 255.0 - 0.5) * contrastFactor + 0.5) * 255.0;
b = ((b / 255.0 - 0.5) * contrastFactor + 0.5) * 255.0;
}
// Clamp after contrast
r = clamp(r);
g = clamp(g);
b = clamp(b);
// 3. Saturation adjustment
// saturationAdjustment: -1 (grayscale) to 1 (super-saturated), 0 is original.
// Convert to satFactor: 0 (grayscale) to 2 (super-saturated), 1 is original.
if (saturationAdjustment !== 0) {
const satFactor = saturationAdjustment + 1.0;
// Luminance weights for grayscale conversion (Rec. 709, for sRGB/HDTV)
const lumR = 0.2126;
const lumG = 0.7152;
const lumB = 0.0722;
const gray = r * lumR + g * lumG + b * lumB;
r = gray + satFactor * (r - gray);
g = gray + satFactor * (g - gray);
b = gray + satFactor * (b - gray);
}
// Clamp after saturation
r = clamp(r);
g = clamp(g);
b = clamp(b);
// 4. Tint
if (useTint) {
r = r * (1.0 - tintOpacity) + tr * tintOpacity;
g = g * (1.0 - tintOpacity) + tg * tintOpacity;
b = b * (1.0 - tintOpacity) + tb * tintOpacity;
}
// Final assignment to ImageData. Uint8ClampedArray handles clamping and rounding.
data[i] = r;
data[i + 1] = g;
data[i + 2] = b;
// Alpha channel (data[i + 3]) remains unchanged
}
ctx.putImageData(imageData, 0, 0);
return canvas;
}
Apply Changes