You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, style = "vintage", intensity = 75, addScannerLine = 1) {
// Parse parameters
const val = Number(intensity) / 100;
const showScanner = Number(addScannerLine) === 1;
// Scale down image if it's too large to ensure fast processing
const maxDim = 1200;
let w = originalImg.width;
let h = originalImg.height;
if (w > maxDim || h > maxDim) {
const ratio = Math.min(maxDim / w, maxDim / h);
w = Math.floor(w * ratio);
h = Math.floor(h * ratio);
}
// Create canvas
// We add top and bottom black borders to simulate actual film strips
const borderH = Math.max(50, Math.floor(h * 0.12));
const canvas = document.createElement('canvas');
canvas.width = w;
canvas.height = h + borderH * 2;
const ctx = canvas.getContext('2d');
// Draw background (black borders)
ctx.fillStyle = '#111';
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Draw the image in the center
ctx.drawImage(originalImg, 0, borderH, w, h);
// Get pixel data for color manipulation
const imgData = ctx.getImageData(0, borderH, w, h);
const data = imgData.data;
// Helper functions for image processing
const clamp = (v) => Math.min(255, Math.max(0, v));
// Contrast factor for specific styles
const contrast = val * 100;
const factor = (259 * (contrast + 255)) / (255 * (259 - contrast));
for (let i = 0; i < data.length; i += 4) {
let r = data[i];
let g = data[i+1];
let b = data[i+2];
// 1. Color Grading based on style
if (style === 'vintage' || style === 'cinematic') {
// Sepia/Warm tone mix
const tr = 0.393*r + 0.769*g + 0.189*b;
const tg = 0.349*r + 0.686*g + 0.168*b;
const tb = 0.272*r + 0.534*g + 0.131*b;
r = r + (tr - r) * val;
g = g + (tg - g) * val;
b = b + (tb - b) * val;
// Add a slight cinematic contrast
r = factor * (r - 128) + 128;
g = factor * (g - 128) + 128;
b = factor * (b - 128) + 128;
} else if (style === 'noir') {
// Grayscale with high contrast
const gray = Math.round(0.299*r + 0.587*g + 0.114*b);
r = gray; g = gray; b = gray;
r = factor * (r - 128) + 128;
g = factor * (g - 128) + 128;
b = factor * (b - 128) + 128;
} else if (style === 'scifi') {
// Teal & Orange cinema look
const gray = (r + g + b) / 3;
if (gray > 120) {
// Highlights: Shift to warmer (orange/yellow)
r += (255 - r) * 0.2 * val;
b -= b * 0.1 * val;
} else {
// Shadows: Shift to cooler (teal/blue)
b += (255 - b) * 0.3 * val;
r -= r * 0.1 * val;
}
r = factor * (r - 128) + 128;
g = factor * (g - 128) + 128;
b = factor * (b - 128) + 128;
}
// 2. Add Film Grain
const noise = (Math.random() - 0.5) * 60 * val;
r += noise;
g += noise;
b += noise;
data[i] = clamp(r);
data[i+1] = clamp(g);
data[i+2] = clamp(b);
}
// Put modified image back
ctx.putImageData(imgData, 0, borderH);
// 3. Add Film Scanner Lines (Interlacing / Scanlines effect)
ctx.fillStyle = `rgba(0, 0, 0, ${(style==='noir'? 0.25 : 0.15) * val})`;
for (let y = borderH; y < h + borderH; y += 4) {
ctx.fillRect(0, y, w, 2);
}
// 4. Add Vignette
const cx = w / 2;
const cy = borderH + (h / 2);
const radius = Math.max(w, h) * 0.6;
const gradient = ctx.createRadialGradient(cx, cy, radius * 0.4, cx, cy, radius);
gradient.addColorStop(0, 'rgba(0,0,0,0)');
gradient.addColorStop(1, `rgba(0,0,0,${0.7 * val})`);
ctx.fillStyle = gradient;
ctx.fillRect(0, borderH, w, h);
// 5. Enhance Film Look: Draw Perforations (Sprocket holes)
ctx.fillStyle = '#cfcfcf'; // Light grey/white for the "holes" to simulate backlit light
const holeW = Math.max(8, Math.floor(w * 0.015));
const holeH = borderH * 0.45;
const spacing = holeW * 2.8;
const cornerR = holeW * 0.25;
const drawRoundedRect = (ctx, x, y, width, height, radius) => {
ctx.beginPath();
ctx.moveTo(x + radius, y);
ctx.lineTo(x + width - radius, y);
ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
ctx.lineTo(x + width, y + height - radius);
ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
ctx.lineTo(x + radius, y + height);
ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
ctx.lineTo(x, y + radius);
ctx.quadraticCurveTo(x, y, x + radius, y);
ctx.closePath();
ctx.fill();
};
for (let x = holeW; x < w - holeW; x += spacing) {
// Top perforations
drawRoundedRect(ctx, x, borderH * 0.25, holeW, holeH, cornerR);
// Bottom perforations
drawRoundedRect(ctx, x, canvas.height - borderH * 0.7, holeW, holeH, cornerR);
}
// 6. Draw Scanner Light Beam (Optional, based on param)
if (showScanner) {
const scanY = borderH + h * 0.35; // Position the scanner light 35% down
ctx.shadowBlur = 15;
ctx.shadowColor = '#00ffff'; // Cyan neon glow
ctx.fillStyle = `rgba(0, 255, 255, ${0.8 * val})`;
ctx.fillRect(0, scanY, w, 3);
ctx.shadowColor = '#ffffff';
ctx.fillStyle = `rgba(255, 255, 255, ${0.9 * val})`;
ctx.fillRect(0, scanY + 1, w, 1);
// Reset shadow
ctx.shadowBlur = 0;
}
// 7. Add Scanner Info Overlay (Movie Studio Film Company Text)
const fontSize = Math.max(12, Math.floor(borderH * 0.25));
ctx.font = `${fontSize}px "Courier New", Courier, monospace`;
ctx.fillStyle = 'rgba(255, 180, 0, 0.85)';
ctx.textAlign = 'left';
ctx.fillText(`Студии Фильмов Кинокомпании Года - СКАНЕР`, holeW, borderH - fontSize);
ctx.textAlign = 'right';
const timestamp = new Date().toISOString().replace('T', ' ').substring(0, 19);
ctx.fillText(`REC // ${timestamp} // 35MM SCAN`, w - holeW, canvas.height - borderH * 0.15);
return canvas;
}
Apply Changes