You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, filmType = "motion", filter = "cinematic", text = "КИНО") {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
// Determine layout: "motion" (movie film) has vertical layout with side holes
// "still" (photo film) has horizontal layout with top/bottom holes
const isMotion = filmType.toLowerCase() !== "still";
const W = originalImg.width;
const H = originalImg.height;
// Base margins on the smallest dimension to ensure proportionality
const minDim = Math.min(W, H);
const margin = minDim * 0.15;
const frameGap = minDim * 0.05;
// Canvas dimensions
const cWidth = isMotion ? W + 2 * margin : W + 2 * frameGap;
const cHeight = isMotion ? H + 2 * frameGap : H + 2 * margin;
canvas.width = cWidth;
canvas.height = cHeight;
// Fill the film strip background with very dark gray (acting as the unexposed film strip)
ctx.fillStyle = '#080808';
ctx.fillRect(0, 0, cWidth, cHeight);
// Determine image placement
const imgX = isMotion ? margin : frameGap;
const imgY = isMotion ? frameGap : margin;
// Apply CSS filters if requested
if (filter === "grayscale") {
ctx.filter = "grayscale(100%) contrast(1.1)";
} else if (filter === "cinematic") {
// Dramatic cinematic color grade: higher contrast, desaturated, slight sepia warming
ctx.filter = "contrast(1.2) brightness(0.95) saturate(0.85) sepia(0.2)";
} else {
ctx.filter = "none";
}
// Draw the image onto the "frame"
ctx.drawImage(originalImg, imgX, imgY, W, H);
ctx.filter = "none";
// Apply film/cinematic visual overlays over the image
if (filter === "cinematic") {
// 1. Widescreen Letterboxing (Anamorphic Look ~ 2.35:1)
const targetRatio = 2.35;
const imgRatio = W / H;
ctx.fillStyle = '#080808';
if (imgRatio < targetRatio - 0.02) {
// Taller image: Add black bars on top and bottom inside the frame
const visibleHeight = W / targetRatio;
const letterboxHeight = (H - visibleHeight) / 2;
ctx.fillRect(imgX, imgY, W, letterboxHeight);
ctx.fillRect(imgX, imgY + H - letterboxHeight, W, letterboxHeight);
} else if (imgRatio > targetRatio + 0.02) {
// Wider image: Add black bars on sides inside the frame
const visibleWidth = H * targetRatio;
const letterboxWidth = (W - visibleWidth) / 2;
ctx.fillRect(imgX, imgY, letterboxWidth, H);
ctx.fillRect(imgX + W - letterboxWidth, imgY, letterboxWidth, H);
}
// 2. Cinematic Vignette (darken corners)
const cx = imgX + W / 2;
const cy = imgY + H / 2;
const grad = ctx.createRadialGradient(cx, cy, minDim * 0.4, cx, cy, Math.max(W, H) * 0.8);
grad.addColorStop(0, 'rgba(0,0,0,0)');
grad.addColorStop(1, 'rgba(0,0,0,0.7)');
ctx.fillStyle = grad;
ctx.fillRect(imgX, imgY, W, H);
// 3. Film Grain Emulation (create slightly translucent noise)
const grainCanvas = document.createElement('canvas');
const gW = Math.min(W, 800);
const gH = Math.min(H, 800);
grainCanvas.width = gW;
grainCanvas.height = gH;
const gCtx = grainCanvas.getContext('2d');
const imgData = gCtx.createImageData(gW, gH);
const data = imgData.data;
for (let i = 0; i < data.length; i += 4) {
const val = (Math.random() * 255) | 0;
data[i] = val; // R
data[i+1] = val; // G
data[i+2] = val; // B
data[i+3] = 20; // A (Low opacity)
}
gCtx.putImageData(imgData, 0, 0);
ctx.globalCompositeOperation = 'overlay';
ctx.drawImage(grainCanvas, 0, 0, gW, gH, imgX, imgY, W, H);
ctx.globalCompositeOperation = 'source-over';
// 4. Subtle Film Scratch (vertical line)
ctx.fillStyle = 'rgba(255, 255, 255, 0.06)';
const scratchX = imgX + W * 0.3 + Math.random() * (W * 0.4);
ctx.fillRect(scratchX, imgY, 1 + Math.random() * 2, H);
}
// --- Film Strip Perforations (Holes) ---
// Punch holes using 'destination-out' to make them transparent
ctx.globalCompositeOperation = 'destination-out';
ctx.fillStyle = '#000';
// Perforations physical geometry mimicking actual 35mm film stock
const hWidth = isMotion ? margin * 0.4 : margin * 0.2;
const hHeight = isMotion ? margin * 0.2 : margin * 0.4;
const radius = Math.min(hWidth, hHeight) * 0.2;
function roundRect(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();
}
if (isMotion) {
// Vertical layout: holes on left and right borders
const numHoles = Math.max(4, Math.floor(cHeight / (margin * 0.6)));
const pitch = cHeight / numHoles;
for (let i = 0; i < numHoles; i++) {
const cy = pitch * (i + 0.5);
const hY = cy - hHeight / 2;
const hXLeft = margin / 2 - hWidth / 2;
const hXRight = cWidth - margin / 2 - hWidth / 2;
roundRect(ctx, hXLeft, hY, hWidth, hHeight, radius);
roundRect(ctx, hXRight, hY, hWidth, hHeight, radius);
}
} else {
// Horizontal Layout: holes on top and bottom borders
const numHoles = Math.max(8, Math.floor(cWidth / (margin * 0.6)));
const pitch = cWidth / numHoles;
for (let i = 0; i < numHoles; i++) {
const cx = pitch * (i + 0.5);
const hX = cx - hWidth / 2;
const hYTop = margin / 2 - hHeight / 2;
const hYBottom = cHeight - margin / 2 - hHeight / 2;
roundRect(ctx, hX, hYTop, hWidth, hHeight, radius);
roundRect(ctx, hX, hYBottom, hWidth, hHeight, radius);
}
}
ctx.globalCompositeOperation = 'source-over';
// --- Typography & Edge Codes ---
ctx.fillStyle = '#db9c30'; // Kodak golden color
ctx.font = `bold ${Math.max(10, margin * 0.15)}px Courier New, monospace`;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
if (isMotion) {
// Left text margin
ctx.save();
ctx.translate(margin * 0.15, cHeight / 2);
ctx.rotate(-Math.PI / 2);
ctx.fillText(text, 0, 0);
ctx.restore();
// Right text margin
ctx.save();
ctx.translate(cWidth - margin * 0.15, cHeight / 2);
ctx.rotate(Math.PI / 2);
ctx.fillText("SAFETY FILM ▼", 0, 0);
ctx.restore();
} else {
// Top text margin
ctx.fillText(text, cWidth / 2, margin * 0.15);
// Bottom text margin
ctx.fillText("SAFETY FILM ▼", cWidth / 2, cHeight - margin * 0.15);
}
return canvas;
}
Apply Changes