You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, width = 3200, height = 1912, warmth = 0.35, grainOpacity = 0.12) {
const canvas = document.createElement('canvas');
canvas.width = parseInt(width);
canvas.height = parseInt(height);
const ctx = canvas.getContext('2d');
// Film format specs (IMAX 15-perf 70mm layout approximation)
const filmWidth = canvas.width;
const filmHeight = canvas.height;
const borderHeight = filmHeight * 0.12;
const imageAreaY = borderHeight;
const imageAreaHeight = filmHeight - (borderHeight * 2);
// 1. Draw light table background (slightly off-white for perforations)
ctx.fillStyle = '#f0f0f0';
ctx.fillRect(0, 0, filmWidth, filmHeight);
// 2. Draw black film base
ctx.fillStyle = '#0a0a0a';
ctx.fillRect(0, 0, filmWidth, filmHeight);
// 3. Calculate Image dimensions (Cover strategy to fill frame)
const imgRatio = originalImg.width / originalImg.height;
const areaRatio = filmWidth / imageAreaHeight;
let drawW, drawH, drawX, drawY;
if (imgRatio > areaRatio) {
drawH = imageAreaHeight;
drawW = drawH * imgRatio;
drawX = (filmWidth - drawW) / 2;
drawY = imageAreaY;
} else {
drawW = filmWidth;
drawH = drawW / imgRatio;
drawX = 0;
drawY = imageAreaY + (imageAreaHeight - drawH) / 2;
}
// 4. Draw Image Frame
ctx.save();
ctx.beginPath();
ctx.rect(0, imageAreaY, filmWidth, imageAreaHeight);
ctx.clip(); // Ensure image doesn't overflow into sprockets
ctx.drawImage(originalImg, drawX, drawY, drawW, drawH);
// 5. Apply "1994 Animation" Warm Color Grade
ctx.fillStyle = `rgba(255, 175, 45, ${parseFloat(warmth)})`;
ctx.globalCompositeOperation = 'soft-light';
ctx.fillRect(0, imageAreaY, filmWidth, imageAreaHeight);
// 6. Apply Cinematic Vignette
const cx = filmWidth / 2;
const cy = imageAreaY + (imageAreaHeight / 2);
const vignette = ctx.createRadialGradient(cx, cy, filmWidth * 0.3, cx, cy, filmWidth * 0.8);
vignette.addColorStop(0, 'rgba(0,0,0,0)');
vignette.addColorStop(1, 'rgba(0,0,0,0.65)');
ctx.globalCompositeOperation = 'multiply';
ctx.fillStyle = vignette;
ctx.fillRect(0, imageAreaY, filmWidth, imageAreaHeight);
// 7. Generate and Apply Film Grain (Procedural noise pattern)
const noiseSize = 250;
const noiseCanvas = document.createElement('canvas');
noiseCanvas.width = noiseSize;
noiseCanvas.height = noiseSize;
const nCtx = noiseCanvas.getContext('2d');
const nImgData = nCtx.createImageData(noiseSize, noiseSize);
const nData = nImgData.data;
const gAlpha = Math.min(255, Math.floor(parseFloat(grainOpacity) * 255));
for (let i = 0; i < nData.length; i += 4) {
const val = Math.random() * 255;
nData[i] = val; // R
nData[i + 1] = val; // G
nData[i + 2] = val; // B
nData[i + 3] = gAlpha; // A
}
nCtx.putImageData(nImgData, 0, 0);
ctx.globalCompositeOperation = 'overlay';
ctx.fillStyle = ctx.createPattern(noiseCanvas, 'repeat');
ctx.fillRect(0, imageAreaY, filmWidth, imageAreaHeight);
ctx.restore();
// 8. Film Edges & Sprocket Perforations (15 perfs for IMAX 70mm)
const numPerfs = 15;
const perfSpacing = filmWidth / numPerfs;
const perfWidth = perfSpacing * 0.45;
const perfHeight = borderHeight * 0.40;
const topPerfY = (borderHeight - perfHeight) / 2;
const bottomPerfY = filmHeight - borderHeight + (borderHeight - perfHeight) / 2;
const cornerRadius = perfWidth * 0.15;
ctx.fillStyle = '#e8e8e8'; // White/gray backlight color for holes
function drawRoundedPerf(x, y, w, h, r) {
ctx.beginPath();
ctx.moveTo(x + r, y);
ctx.lineTo(x + w - r, y);
ctx.quadraticCurveTo(x + w, y, x + w, y + r);
ctx.lineTo(x + w, y + h - r);
ctx.quadraticCurveTo(x + w, y + h, x + w - r, y + h);
ctx.lineTo(x + r, y + h);
ctx.quadraticCurveTo(x, y + h, x, y + h - r);
ctx.lineTo(x, y + r);
ctx.quadraticCurveTo(x, y, x + r, y);
ctx.fill();
}
for (let i = 0; i < numPerfs; i++) {
const perfX = (i * perfSpacing) + (perfSpacing - perfWidth) / 2;
// Top perforations
drawRoundedPerf(perfX, topPerfY, perfWidth, perfHeight, cornerRadius);
// Bottom perforations
drawRoundedPerf(perfX, bottomPerfY, perfWidth, perfHeight, cornerRadius);
}
// 9. Film Edge Markings
ctx.fillStyle = '#d4aa00'; // Kodak film roll edge text color
ctx.font = `bold ${Math.floor(borderHeight * 0.16)}px Courier New, monospace`;
ctx.textBaseline = 'middle';
// Top border text
ctx.textAlign = 'left';
ctx.fillText("KODAK SAFETY FILM", filmWidth * 0.02, topPerfY / 2);
ctx.textAlign = 'right';
ctx.fillText("IMAX 70MM / 15 PERF", filmWidth * 0.98, topPerfY / 2);
// Bottom border text
const textYBottom = bottomPerfY + perfHeight + ((borderHeight - perfHeight) / 2);
ctx.textAlign = 'left';
ctx.fillText("THE LION KING (1994)", filmWidth * 0.02, textYBottom);
ctx.textAlign = 'right';
ctx.fillText("TRAILER REEL 1", filmWidth * 0.98, textYBottom);
return canvas;
}
Apply Changes