You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, intensity = "60", addGrain = "yes", addVignette = "yes", colorTone = "warm") {
// Parse parameters
const intVal = Math.max(0, Math.min(100, Number(intensity))) / 100; // Normalizes 0-100 to 0.0-1.0
const applyGrain = String(addGrain).toLowerCase() === "yes" || String(addGrain) === "true" || String(addGrain) === "1";
const applyVignette = String(addVignette).toLowerCase() === "yes" || String(addVignette) === "true" || String(addVignette) === "1";
const canvas = document.createElement('canvas');
const w = canvas.width = originalImg.width;
const h = canvas.height = originalImg.height;
const ctx = canvas.getContext('2d');
// 1. Base 16mm Softness and Image Filtering
// 16mm film has intrinsic softness, slightly faded saturation, and warm/aged contrast
const blurRadius = Math.min(3, Math.max(0.3, (w / 1500))) * intVal;
const contrast = 100 + (15 * intVal); // Boost contrast slightly (100% to 115%)
const sepia = 20 * intVal; // Up to 20% sepia
const saturate = 100 - (15 * intVal); // Desaturate slightly (100% to 85%)
ctx.filter = `blur(${blurRadius}px) contrast(${contrast}%) sepia(${sepia}%) saturate(${saturate}%)`;
ctx.drawImage(originalImg, 0, 0);
ctx.filter = 'none'; // Reset filter for subsequent operations
// 2. Cinematic Split Toning (Faded film emulation)
// Screen dark teal/green to lift shadows and simulate film base
ctx.globalCompositeOperation = 'screen';
ctx.fillStyle = `rgba(15, 25, 20, ${0.4 * intVal})`;
ctx.fillRect(0, 0, w, h);
// Multiply to set the overall tone and bring down highlights smoothly
ctx.globalCompositeOperation = 'multiply';
let mR = 255, mG = 255, mB = 255;
const tone = String(colorTone).toLowerCase();
if (tone === "warm") {
mG = Math.floor(255 - (15 * intVal)); // Slightly less green
mB = Math.floor(255 - (35 * intVal)); // Much less blue => warm yellow/orange
} else if (tone === "cool") {
mR = Math.floor(255 - (30 * intVal)); // Less red
mG = Math.floor(255 - (10 * intVal)); // Less green => cool teal/cyan
} else {
// Neutral fade
mR = Math.floor(255 - (10 * intVal));
mG = Math.floor(255 - (10 * intVal));
mB = Math.floor(255 - (10 * intVal));
}
ctx.fillStyle = `rgb(${mR}, ${mG}, ${mB})`;
ctx.fillRect(0, 0, w, h);
// 3. High Contour Softness (Midtone pop)
ctx.globalCompositeOperation = 'overlay';
ctx.fillStyle = `rgba(128, 128, 128, ${0.15 * intVal})`;
ctx.fillRect(0, 0, w, h);
// 4. Optical Vignette (Classic lens light falloff)
if (applyVignette) {
ctx.globalCompositeOperation = 'multiply';
const cx = w / 2;
const cy = h / 2;
const radius = Math.sqrt(cx * cx + cy * cy);
const gradient = ctx.createRadialGradient(cx, cy, radius * 0.4, cx, cy, radius);
gradient.addColorStop(0, 'rgba(0,0,0,0)');
// Edge darkness intensifies with effect strength
gradient.addColorStop(1, `rgba(0,0,0,${0.65 * intVal})`);
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, w, h);
}
// 5. 16mm Film Grain Emulation
if (applyGrain) {
// Create an offscreen canvas for noise to avoid slow pixel-by-pixel operations on a massive image
const tileSize = 512;
const noiseCanvas = document.createElement('canvas');
noiseCanvas.width = tileSize;
noiseCanvas.height = tileSize;
const nCtx = noiseCanvas.getContext('2d');
const nData = nCtx.createImageData(tileSize, tileSize);
const dataArr = nData.data;
// 16mm grain is relatively coarse
const grainIntensity = 45 * intVal;
for (let i = 0; i < dataArr.length; i += 4) {
// Generate neutral monochromatic noise centered around 128 (Neutral Gray)
const noise = (Math.random() - 0.5) * grainIntensity + 128;
dataArr[i] = noise; // R
dataArr[i + 1] = noise; // G
dataArr[i + 2] = noise; // B
dataArr[i + 3] = 255; // A
}
nCtx.putImageData(nData, 0, 0);
// Overlay the generated noise seamlessly
ctx.globalCompositeOperation = 'overlay';
ctx.fillStyle = ctx.createPattern(noiseCanvas, 'repeat');
// Slightly lower opacity of grain to keep it organic looking
ctx.globalAlpha = 0.85;
ctx.fillRect(0, 0, w, h);
// Reset Alpha
ctx.globalAlpha = 1.0;
}
// Reset composite mode safely before returning
ctx.globalCompositeOperation = 'source-over';
return canvas;
}
Apply Changes