You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(originalImg, sepiaStrength = 0.7, contrastLevel = 25, grainAmount = 20, vignetteStrength = 0.7, vignetteStartPoint = 0.4) {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d', { alpha: true }); // ensure alpha for transparent parts if any
// Ensure image is loaded to get correct dimensions
if (!originalImg.complete || originalImg.naturalWidth === 0) {
await new Promise(resolve => {
originalImg.onload = resolve;
originalImg.onerror = () => {
// Handle image loading error if necessary, e.g. draw a placeholder
console.error("Image could not be loaded for processing.");
resolve(); // Resolve anyway to avoid unhandled promise rejection
};
// If src is set and it's already failing, onerror might have been missed.
// This shouldn't happen if originalImg is a valid Image object passed in.
});
}
const width = originalImg.naturalWidth || originalImg.width;
const height = originalImg.naturalHeight || originalImg.height;
canvas.width = width;
canvas.height = height;
// Draw the original image onto the canvas
ctx.drawImage(originalImg, 0, 0, width, height);
// Get image data
const imageData = ctx.getImageData(0, 0, width, height);
const data = imageData.data;
// Normalize parameters
sepiaStrength = Math.max(0, Math.min(1, Number(sepiaStrength)));
contrastLevel = Math.max(-100, Math.min(100, Number(contrastLevel)));
grainAmount = Math.max(0, Math.min(50, Number(grainAmount)));
vignetteStrength = Math.max(0, Math.min(1, Number(vignetteStrength)));
vignetteStartPoint = Math.max(0, Math.min(0.99, Number(vignetteStartPoint))); // 0.99 to ensure gradient has space
// Process each pixel
for (let i = 0; i < data.length; i += 4) {
let r = data[i];
let g = data[i + 1];
let b = data[i + 2];
// 1. Sepia effect
if (sepiaStrength > 0) {
const originalR = r, originalG = g, originalB = b;
const sepiaR = (originalR * 0.393) + (originalG * 0.769) + (originalB * 0.189);
const sepiaG = (originalR * 0.349) + (originalG * 0.686) + (originalB * 0.168);
const sepiaB = (originalR * 0.272) + (originalG * 0.534) + (originalB * 0.131);
r = (1 - sepiaStrength) * originalR + sepiaStrength * sepiaR;
g = (1 - sepiaStrength) * originalG + sepiaStrength * sepiaG;
b = (1 - sepiaStrength) * originalB + sepiaStrength * sepiaB;
}
// 2. Contrast adjustment
if (contrastLevel !== 0) {
// Formula: factor * (color - 128) + 128
// C is contrast value from -100 to 100 (clamped to avoid math errors)
const C = Math.max(-258.9, Math.min(258.9, contrastLevel)); // Clamp to avoid 259-C = 0
const factor = (259 * (C + 255)) / (255 * (259 - C));
r = factor * (r - 128) + 128;
g = factor * (g - 128) + 128;
b = factor * (b - 128) + 128;
}
// 3. Film Grain
if (grainAmount > 0) {
const noise = (Math.random() - 0.5) * grainAmount * 2; // Generates noise in [-grainAmount, +grainAmount]
r += noise;
g += noise;
b += noise;
}
// Clamp 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));
}
// Put the modified image data back
ctx.putImageData(imageData, 0, 0);
// 4. Vignette effect
if (vignetteStrength > 0) {
const centerX = width / 2;
const centerY = height / 2;
// Outer radius is the distance to the furthest corner
const outerRadius = Math.sqrt(centerX * centerX + centerY * centerY);
// Inner radius is where the vignette effect starts to become visible (fully transparent inside)
const innerRadius = outerRadius * vignetteStartPoint;
const gradient = ctx.createRadialGradient(
centerX, centerY, innerRadius,
centerX, centerY, outerRadius
);
// Vignette color is black. Strength determines its opacity at the edges.
gradient.addColorStop(0, 'rgba(0,0,0,0)'); // Transparent at the inner radius
gradient.addColorStop(1, `rgba(0,0,0,${vignetteStrength})`); // Opaque (by vignetteStrength) black at the outer radius
ctx.fillStyle = gradient;
// Using source-over to overlay the semi-transparent black gradient
ctx.globalCompositeOperation = 'source-over';
ctx.fillRect(0, 0, width, height);
}
return canvas;
}
Apply Changes