You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, sepiaAmount = 80, noiseAmount = 30, vignetteAmount = 60, scratchAmount = 25) {
// Ensure parameters are numbers
const sepia = Number(sepiaAmount) || 0;
const noise = Number(noiseAmount) || 0;
const vignette = Number(vignetteAmount) || 0;
const scratches = Number(scratchAmount) || 0;
// Create the main canvas and get its context
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
// Set dimensions based on the natural dimensions of the image
const width = originalImg.naturalWidth || originalImg.width;
const height = originalImg.naturalWidth ? originalImg.naturalHeight : originalImg.height;
canvas.width = width;
canvas.height = height;
// Step 1: Draw the base image with color adjustments
// Old photos usually have lower contrast, slightly reduced brightness, less overall saturation, and a sepia tone.
ctx.filter = `sepia(${sepia}%) contrast(85%) brightness(95%) grayscale(15%)`;
ctx.drawImage(originalImg, 0, 0, width, height);
ctx.filter = 'none'; // reset filter for subsequent layers
// Step 2: Apply Film Grain / Noise
if (noise > 0) {
// Create an off-screen canvas for the noise pattern
const noiseCanvas = document.createElement('canvas');
const noiseCtx = noiseCanvas.getContext('2d');
noiseCanvas.width = 150;
noiseCanvas.height = 150;
const imgData = noiseCtx.createImageData(noiseCanvas.width, noiseCanvas.height);
const data = imgData.data;
// Max alpha for noise is set relatively low to blend nicely
const maxAlpha = (noise / 100) * 80;
for (let i = 0; i < data.length; i += 4) {
const val = Math.random() * 255; // Monochromatic noise
data[i] = val; // R
data[i + 1] = val; // G
data[i + 2] = val; // B
data[i + 3] = maxAlpha; // Alpha
}
noiseCtx.putImageData(imgData, 0, 0);
// Overlay the noise onto the main canvas
ctx.globalCompositeOperation = 'overlay';
ctx.fillStyle = ctx.createPattern(noiseCanvas, 'repeat');
ctx.fillRect(0, 0, width, height);
ctx.globalCompositeOperation = 'source-over'; // Reset
}
// Step 3: Apply vintage scratches and dust
if (scratches > 0) {
const scratchCount = (width * height / 50000) * (scratches / 100);
// Add white/light scratches
ctx.globalCompositeOperation = 'screen';
for (let i = 0; i < scratchCount; i++) {
ctx.beginPath();
const x = Math.random() * width;
const y = Math.random() * height;
const length = Math.random() * 150 + 20;
// Orient scratches mostly vertically, fitting old film feeding mechanisms
const angle = Math.PI / 2 + (Math.random() * 0.2 - 0.1);
ctx.moveTo(x, y);
ctx.lineTo(x + Math.cos(angle) * length, y + Math.sin(angle) * length);
ctx.lineWidth = Math.random() * 1 + 0.5;
ctx.strokeStyle = `rgba(255, 255, 255, ${Math.random() * 0.4 * (scratches / 100)})`;
ctx.stroke();
}
// Add dark scratches and artifact speckles
ctx.globalCompositeOperation = 'multiply';
for (let i = 0; i < scratchCount / 2; i++) {
ctx.beginPath();
const x = Math.random() * width;
const y = Math.random() * height;
const length = Math.random() * 60 + 10;
const angle = Math.PI / 2 + (Math.random() * 0.6 - 0.3);
ctx.moveTo(x, y);
ctx.lineTo(x + Math.cos(angle) * length, y + Math.sin(angle) * length);
ctx.lineWidth = Math.random() * 1.5 + 0.5;
ctx.strokeStyle = `rgba(50, 40, 30, ${Math.random() * 0.5 * (scratches / 100)})`;
ctx.stroke();
}
ctx.globalCompositeOperation = 'source-over'; // Reset
}
// Step 4: Apply Vignette Matrix (darkened edges common in vintage camera lenses)
if (vignette > 0) {
const cx = width / 2;
const cy = height / 2;
// The gradient radius needs to cover up to the corners
const maxRadius = Math.sqrt(cx * cx + cy * cy);
const radGrad = ctx.createRadialGradient(cx, cy, maxRadius * 0.4, cx, cy, maxRadius);
radGrad.addColorStop(0, 'rgba(0,0,0,0)');
radGrad.addColorStop(0.7, `rgba(0,0,0, ${vignette / 100 * 0.4})`);
radGrad.addColorStop(1, `rgba(0,0,0, ${vignette / 100 * 0.9})`);
ctx.globalCompositeOperation = 'multiply';
ctx.fillStyle = radGrad;
ctx.fillRect(0, 0, width, height);
ctx.globalCompositeOperation = 'source-over'; // Reset
}
// Step 5: Simple Light Leak (often happens when old film is exposed to light)
if (vignette > 0 || sepia > 0) {
ctx.globalCompositeOperation = 'screen';
const leakGrad = ctx.createLinearGradient(0, 0, width * 0.25, height * 0.1);
leakGrad.addColorStop(0, `rgba(255, 120, 40, 0.15)`);
leakGrad.addColorStop(1, 'rgba(255, 120, 40, 0)');
ctx.fillStyle = leakGrad;
ctx.fillRect(0, 0, width * 0.3, height * 0.2);
ctx.globalCompositeOperation = 'source-over';
}
return canvas;
}
Apply Changes