You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, intensity = "1", sepiaTone = "0.8", noiseAmount = "1") {
// Parse parameters to ensure they are treated as numbers
intensity = parseFloat(intensity);
sepiaTone = parseFloat(sepiaTone);
noiseAmount = parseFloat(noiseAmount);
// Create the main canvas
const canvas = document.createElement('canvas');
const w = originalImg.width;
const h = originalImg.height;
canvas.width = w;
canvas.height = h;
const ctx = canvas.getContext('2d');
// 1. Initial 1880s filter application
// Early film was orthochromatic & slightly soft. We use grayscale -> sepia -> contrast -> blur.
const blurAmt = 0.8 * intensity;
const contrast = 105 + (15 * intensity);
const sepiaVal = sepiaTone * 100;
// Apply standard canvas filters
ctx.filter = `grayscale(100%) sepia(${sepiaVal}%) contrast(${contrast}%) brightness(95%) blur(${blurAmt}px)`;
ctx.drawImage(originalImg, 0, 0, w, h);
ctx.filter = 'none'; // Reset filters for subsequent drawings
// 2. Film grain (Noise overlay)
if (noiseAmount > 0) {
const noiseCanvas = document.createElement('canvas');
noiseCanvas.width = 256;
noiseCanvas.height = 256;
const nCtx = noiseCanvas.getContext('2d');
const nData = nCtx.createImageData(256, 256);
for (let i = 0; i < nData.data.length; i += 4) {
// Emulate silver halide grain
const val = Math.random() * 255;
nData.data[i] = val; // R
nData.data[i+1] = val; // G
nData.data[i+2] = val; // B
nData.data[i+3] = Math.random() * 50 * noiseAmount * intensity; // Alpha
}
nCtx.putImageData(nData, 0, 0);
ctx.globalCompositeOperation = 'overlay';
ctx.fillStyle = ctx.createPattern(noiseCanvas, 'repeat');
ctx.fillRect(0, 0, w, h);
ctx.globalCompositeOperation = 'source-over';
}
// 3. Vintage Lens Vignette (Darkened, organic corners)
const cx = w / 2;
const cy = h / 2;
const maxRadius = Math.sqrt(cx * cx + cy * cy);
ctx.globalCompositeOperation = 'multiply';
const vignette = ctx.createRadialGradient(cx, cy, maxRadius * 0.4, cx, cy, maxRadius * 1.1);
vignette.addColorStop(0, 'rgba(210, 190, 160, 0)'); // Transparent warm center
vignette.addColorStop(1, `rgba(40, 20, 5, ${0.85 * intensity})`); // Dark brownish edges simulating old barrel vignette
ctx.fillStyle = vignette;
ctx.fillRect(0, 0, w, h);
ctx.globalCompositeOperation = 'source-over';
// 4. Physical imperfections (Scratches & dust from old glass plates / film)
const numScratches = Math.floor(15 * intensity);
ctx.lineWidth = 1;
for (let i = 0; i < numScratches; i++) {
// Vary scratch color between bright and dark
if (Math.random() > 0.5) {
ctx.strokeStyle = `rgba(255, 255, 255, ${Math.random() * 0.25 * intensity})`;
} else {
ctx.strokeStyle = `rgba(0, 0, 0, ${Math.random() * 0.25 * intensity})`;
}
ctx.beginPath();
const sx = Math.random() * w;
const sy = Math.random() * h;
const length = Math.random() * h * 0.4;
const angle = (Math.random() - 0.5) * 0.15; // Scratches tend to be mostly vertical
ctx.moveTo(sx, sy);
// Slightly wavy line for realistic non-linear scratch
let cxTemp = sx;
let cyTemp = sy;
for(let step = 0; step < 5; step++) {
cxTemp += Math.sin(angle) * (length / 5) + (Math.random() - 0.5) * 2;
cyTemp += Math.cos(angle) * (length / 5);
ctx.lineTo(cxTemp, cyTemp);
}
ctx.stroke();
}
// Specks of dust and plate emulsion drops
const numDust = Math.floor(60 * intensity);
for (let i = 0; i < numDust; i++) {
ctx.fillStyle = Math.random() > 0.5
? `rgba(0, 0, 0, ${Math.random() * 0.5 * intensity})`
: `rgba(255, 255, 255, ${Math.random() * 0.4 * intensity})`;
ctx.beginPath();
const r = Math.random() * 1.5 + 0.5; // Irregular sizes
ctx.arc(Math.random() * w, Math.random() * h, r, 0, Math.PI * 2);
ctx.fill();
}
// 5. Final unifying tone wash (helps harmonize scratches and grain into the sepia spectrum)
ctx.globalCompositeOperation = 'color';
ctx.fillStyle = `rgba(139, 105, 65, ${0.25 * intensity})`;
ctx.fillRect(0, 0, w, h);
ctx.globalCompositeOperation = 'source-over';
return canvas;
}
Apply Changes