You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(
originalImg,
desaturation = 0.8, // 0.0 (original color) to 1.0 (full grayscale)
contrast = 1.3, // 1.0 is no change. Recommend range: 0.5 (less contrast) to 2.5 (more contrast)
brightness = -30, // Adjusts brightness. E.g., -100 (darker) to 100 (brighter)
noise = 20, // Amount of noise. E.g., 0 (none) to 50 (heavy noise)
vignetteDarkness = 0.7, // Darkness of vignette edges. 0.0 (none) to 1.0 (fully black)
vignetteSpread = 0.3, // How far from center (as a fraction of max radius) the vignette effect starts. 0.0 (starts at center) to 1.0 (no vignette)
tintColorStr = "40,50,70", // RGB color for tint as a string "R,G,B". E.g., "40,50,70" (cool dark gray). Set to "" or null for no tint.
tintOpacity = 0.2 // Opacity of the tint. 0.0 (no tint) to 1.0 (full tint color)
) {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d', { willReadFrequently: true }); // willReadFrequently for performance with getImageData/putImageData
const imgWidth = originalImg.naturalWidth || originalImg.width;
const imgHeight = originalImg.naturalHeight || originalImg.height;
canvas.width = imgWidth;
canvas.height = imgHeight;
// If the image has no dimensions, return an empty canvas
if (imgWidth === 0 || imgHeight === 0) {
console.warn("Original image has zero width or height.");
return canvas;
}
// Draw the original image onto the canvas
try {
ctx.drawImage(originalImg, 0, 0, imgWidth, imgHeight);
} catch (e) {
console.error("Error drawing image to canvas:", e);
return canvas; // Return empty canvas on error
}
const imageData = ctx.getImageData(0, 0, imgWidth, imgHeight);
const data = imageData.data;
let parsedTintColor = null;
if (tintColorStr && typeof tintColorStr === 'string' && tintColorStr.trim() !== "" && tintOpacity > 0) {
const parts = tintColorStr.split(',').map(s => parseFloat(s.trim()));
if (parts.length === 3 && parts.every(p => !isNaN(p) && p >= 0 && p <= 255)) {
parsedTintColor = { r: parts[0], g: parts[1], b: parts[2] };
} else {
console.warn("Invalid tintColorStr format. Expected 'R,G,B' with numbers 0-255. Tint disabled.", tintColorStr);
}
}
for (let i = 0; i < data.length; i += 4) {
let r = data[i];
let g = data[i + 1];
let b = data[i + 2];
// 1. Desaturation
if (desaturation > 0) {
const gray = 0.299 * r + 0.587 * g + 0.114 * b; // Luminosity method
const desat = Math.max(0, Math.min(1, desaturation)); // Clamp desaturation 0-1
r = r * (1 - desat) + gray * desat;
g = g * (1 - desat) + gray * desat;
b = b * (1 - desat) + gray * desat;
}
// 2. Brightness
if (brightness !== 0) {
r += brightness;
g += brightness;
b += brightness;
}
// 3. Contrast
if (contrast !== 1.0) {
const clampedContrast = Math.max(0, contrast); // Ensure contrast factor is not negative
r = (r - 128) * clampedContrast + 128;
g = (g - 128) * clampedContrast + 128;
b = (b - 128) * clampedContrast + 128;
}
// 4. Tint
if (parsedTintColor && tintOpacity > 0) {
const tOp = Math.max(0, Math.min(1, tintOpacity)); // Clamp opacity 0-1
r = r * (1 - tOp) + parsedTintColor.r * tOp;
g = g * (1 - tOp) + parsedTintColor.g * tOp;
b = b * (1 - tOp) + parsedTintColor.b * tOp;
}
// 5. Noise
if (noise > 0) {
const noiseVal = Math.max(0, noise); // Ensure noise intensity is not negative
const randomNoise = (Math.random() - 0.5) * 2 * noiseVal; // Noise in range [-noiseVal, noiseVal]
r += randomNoise;
g += randomNoise;
b += randomNoise;
}
// Clamp final pixel values to [0, 255]
data[i] = Math.max(0, Math.min(255, r));
data[i + 1] = Math.max(0, Math.min(255, g));
data[i + 2] = Math.max(0, Math.min(255, b));
// Alpha channel (data[i+3]) remains unchanged
}
// Put the modified image data back onto the canvas
ctx.putImageData(imageData, 0, 0);
// 6. Vignette
const cVignetteDarkness = Math.max(0, Math.min(1, vignetteDarkness));
const cVignetteSpread = Math.max(0, Math.min(1, vignetteSpread));
if (cVignetteDarkness > 0 && cVignetteSpread < 1.0) { // vignetteSpread = 1 means no vignette visible
const centerX = imgWidth / 2;
const centerY = imgHeight / 2;
const maxRadius = Math.sqrt(centerX * centerX + centerY * centerY); // Radius to the furthest corner
// innerRadius: where vignette is still fully transparent
// outerRadius: where vignette reaches its specified darkness
const innerRadius = maxRadius * cVignetteSpread;
const outerRadius = maxRadius;
if (outerRadius > innerRadius) { // Ensure gradient is valid (outer must be > inner)
const gradient = ctx.createRadialGradient(
centerX, centerY, innerRadius,
centerX, centerY, outerRadius
);
gradient.addColorStop(0, 'rgba(0,0,0,0)'); // Transparent towards the center
gradient.addColorStop(1, `rgba(0,0,0,${cVignetteDarkness})`); // Darker towards the edges
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, imgWidth, imgHeight);
}
}
return canvas;
}
Apply Changes