You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, colorShift = 'magenta', intensity = 0.6, grainAmount = 0.15, vignetteAmount = 0.7, addLightLeak = 1) {
const canvas = document.createElement('canvas');
const width = originalImg.naturalWidth || originalImg.width;
const height = originalImg.naturalHeight || originalImg.height;
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d');
// Draw original image
ctx.drawImage(originalImg, 0, 0, width, height);
// Get pixels for manipulation
const imageData = ctx.getImageData(0, 0, width, height);
const data = imageData.data;
// Parse parameters to ensure they are numbers where needed
intensity = Math.max(0, Math.min(1, Number(intensity)));
grainAmount = Math.max(0, Math.min(1, Number(grainAmount)));
vignetteAmount = Math.max(0, Math.min(1, Number(vignetteAmount)));
const lightLeakFlag = Number(addLightLeak) === 1;
const shift = colorShift.toLowerCase();
for (let i = 0; i < data.length; i += 4) {
let r = data[i];
let g = data[i + 1];
let b = data[i + 2];
// 1. Faded / Lifted Blacks & Crushed Whites (Characteristic of chemical degradation)
// Wash out shadows by mapping 0-255 to something like 40-240 based on intensity
const lift = 45 * intensity;
const drop = 255 - (20 * intensity);
r = lift + (r / 255) * (drop - lift);
g = lift + (g / 255) * (drop - lift);
b = (lift + 15 * intensity) + (b / 255) * (drop - lift); // Shadows naturally skew slightly blue/grey in film
// 2. Color Shifts (Mimicking expired emulsion layers)
if (shift === 'magenta') {
r += 35 * intensity;
g -= 15 * intensity;
b += 20 * intensity;
} else if (shift === 'green') {
r -= 15 * intensity;
g += 30 * intensity;
b -= 15 * intensity;
} else if (shift === 'yellow') {
r += 30 * intensity;
g += 25 * intensity;
b -= 30 * intensity;
} else if (shift === 'blue') {
r -= 20 * intensity;
g -= 10 * intensity;
b += 45 * intensity;
} else {
// Default warm fade if unknown
r += 15 * intensity;
b -= 15 * intensity;
}
// 3. Film Grain / Noise
if (grainAmount > 0) {
// Random noise per pixel
const noise = (Math.random() - 0.5) * 255 * grainAmount;
r += noise;
g += noise;
b += noise;
}
// Clamp values 0 - 255
data[i] = Math.min(255, Math.max(0, r));
data[i + 1] = Math.min(255, Math.max(0, g));
data[i + 2] = Math.min(255, Math.max(0, b));
}
// Apply the manipulated pixels back to canvas
ctx.putImageData(imageData, 0, 0);
// 4. Soft Vignette (Lens falloff & dirty edges)
if (vignetteAmount > 0) {
const cx = width / 2;
const cy = height / 2;
const radius = Math.max(cx, cy) * 1.2;
const gradient = ctx.createRadialGradient(cx, cy, radius * 0.4, cx, cy, radius);
gradient.addColorStop(0, 'rgba(0, 0, 0, 0)');
// A dirty, slightly warm black/brown for the vignette
gradient.addColorStop(1, `rgba(20, 10, 0, ${0.8 * vignetteAmount})`);
ctx.globalCompositeOperation = 'multiply';
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, width, height);
}
// 5. Light Leaks (Characteristic of light seeping into old analog cameras)
if (lightLeakFlag && intensity > 0) {
ctx.globalCompositeOperation = 'screen';
// Primary leak (usually top left or right depending on film advancing)
const leakGrad1 = ctx.createLinearGradient(0, 0, width * 0.4, height * 0.4);
leakGrad1.addColorStop(0, `rgba(255, 90, 0, ${0.35 * intensity})`);
leakGrad1.addColorStop(0.4, `rgba(255, 30, 30, ${0.15 * intensity})`);
leakGrad1.addColorStop(1, 'rgba(0, 0, 0, 0)');
ctx.fillStyle = leakGrad1;
ctx.fillRect(0, 0, width, height);
// Secondary subtle leak (bottom or opposite edge)
const leakGrad2 = ctx.createLinearGradient(width, height, width * 0.6, height * 0.7);
leakGrad2.addColorStop(0, `rgba(255, 180, 50, ${0.2 * intensity})`);
leakGrad2.addColorStop(1, 'rgba(0, 0, 0, 0)');
ctx.fillStyle = leakGrad2;
ctx.fillRect(0, 0, width, height);
}
// Reset composite operation to default
ctx.globalCompositeOperation = 'source-over';
return canvas;
}
Apply Changes