You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(originalImg, saturationBoostParam = 1.4, contrastBoostParam = 1.2) {
// Ensure parameters are numbers and non-negative
const saturationBoost = Number(saturationBoostParam);
const contrastBoost = Number(contrastBoostParam);
if (isNaN(saturationBoost) || isNaN(contrastBoost) || saturationBoost < 0 || contrastBoost < 0) {
console.error("Invalid parameters: saturationBoost and contrastBoost must be non-negative numbers.");
const errorCanvas = document.createElement('canvas');
errorCanvas.width = 450;
errorCanvas.height = 50;
const errCtx = errorCanvas.getContext('2d');
errCtx.font = '14px Arial';
errCtx.fillStyle = 'red';
errCtx.fillText('Error: Invalid filter parameters. Boosts must be non-negative numbers.', 10, 30);
return errorCanvas;
}
// Helper function: Clamp value between min and max
function clamp(value, min, max) {
return Math.max(min, Math.min(value, max));
}
// Helper function: Convert RGB to HSL
// Inputs r,g,b are 0-255. Outputs h (0-360), s (0-1), l (0-1).
function rgbToHsl(r, g, b) {
r /= 255;
g /= 255;
b /= 255;
const max = Math.max(r, g, b);
const min = Math.min(r, g, b);
let h, s;
const l = (max + min) / 2;
if (max === min) {
h = s = 0; // Achromatic (grayscale)
} else {
const d = max - min;
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
switch (max) {
case r: h = (g - b) / d + (g < b ? 6 : 0); break;
case g: h = (b - r) / d + 2; break;
case b: h = (r - g) / d + 4; break;
}
h /= 6;
}
return [h * 360, s, l];
}
// Helper function: Convert HSL to RGB
// Inputs h (0-360), s (0-1), l (0-1). Outputs r,g,b are 0-255.
function hslToRgb(h, s, l) {
let r_out, g_out, b_out;
if (s === 0) {
r_out = g_out = b_out = l; // Achromatic
} else {
const hueToRgbComponent = (p, q, t_in) => {
let t = t_in;
if (t < 0) t += 1;
if (t > 1) t -= 1;
if (t < 1 / 6) return p + (q - p) * 6 * t;
if (t < 1 / 2) return q;
if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
return p;
};
const q_channel = l < 0.5 ? l * (1 + s) : l + s - l * s;
const p_channel = 2 * l - q_channel;
const h_normalized = h / 360; // Normalize h to 0-1 range
r_out = hueToRgbComponent(p_channel, q_channel, h_normalized + 1 / 3);
g_out = hueToRgbComponent(p_channel, q_channel, h_normalized);
b_out = hueToRgbComponent(p_channel, q_channel, h_normalized - 1 / 3);
}
return [Math.round(r_out * 255), Math.round(g_out * 255), Math.round(b_out * 255)];
}
try {
// Validate the image object
if (!originalImg || typeof originalImg.naturalWidth === 'undefined') {
throw new Error('The provided input is not a valid Image object.');
}
// Wait for the image to load if it's not already loaded
if (!originalImg.complete || originalImg.naturalWidth === 0) {
await new Promise((resolve, reject) => {
// Double-check in case it completed synchronously
if (originalImg.complete && originalImg.naturalWidth !== 0) {
resolve();
return;
}
// If src is not set, it might never load.
if (!originalImg.src) {
reject(new Error("Image source is not set. Cannot load."));
return;
}
originalImg.onload = resolve;
originalImg.onerror = () => reject(new Error('Image failed to load (onerror event).'));
});
}
// Final check on dimensions after attempting to load
if (originalImg.naturalWidth === 0 || originalImg.naturalHeight === 0) {
throw new Error('Image has invalid dimensions (0x0) after loading. Cannot process.');
}
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d', { willReadFrequently: true }); // Performance hint for frequent getImageData/putImageData
canvas.width = originalImg.naturalWidth;
canvas.height = originalImg.naturalHeight;
// Draw the original image onto the canvas
ctx.drawImage(originalImg, 0, 0, canvas.width, canvas.height);
// Get pixel data
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
const data = imageData.data; // This is a Uint8ClampedArray
// Process each pixel
for (let i = 0; i < data.length; i += 4) {
let r_val = data[i];
let g_val = data[i + 1];
let b_val = data[i + 2];
// Alpha channel (data[i+3]) is preserved
// 1. Adjust Saturation using HSL color space
let [h, s, l] = rgbToHsl(r_val, g_val, b_val);
s *= saturationBoost;
s = clamp(s, 0, 1); // Ensure saturation stays within [0, 1]
[r_val, g_val, b_val] = hslToRgb(h, s, l);
// 2. Adjust Contrast
// Formula: NewValue = Factor * (OldValue - MidPoint) + MidPoint
// MidPoint for 0-255 range is 128.
r_val = clamp(contrastBoost * (r_val - 128) + 128, 0, 255);
g_val = clamp(contrastBoost * (g_val - 128) + 128, 0, 255);
b_val = clamp(contrastBoost * (b_val - 128) + 128, 0, 255);
// Update pixel data
data[i] = r_val;
data[i + 1] = g_val;
data[i + 2] = b_val;
}
// Put the modified pixel data back onto the canvas
ctx.putImageData(imageData, 0, 0);
return canvas;
} catch (error) {
console.error("Error processing image:", error.message);
const errorCanvas = document.createElement('canvas');
// Adjust width based on message length slightly, or use a fixed sensible width
const estimatedCharWidth = 8; // Approximate width of a character in 14px Arial
errorCanvas.width = Math.max(400, error.message.length * estimatedCharWidth);
errorCanvas.height = 50;
const errCtx = errorCanvas.getContext('2d');
errCtx.font = '14px Arial';
errCtx.fillStyle = 'red';
errCtx.fillText(error.message, 10, 30);
return errorCanvas;
}
}
Apply Changes