You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, numHoles = 15, stripColor = '#000000', cinematicFilter = 'contrast(1.1) saturate(0.85) sepia(0.15)', subtitleText = '') {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
const width = originalImg.width;
const height = originalImg.height;
// Calculate proportional film strip border height
const stripHeight = Math.max(30, Math.floor(height * 0.15));
// Canvas height is image height + top and bottom film strips
canvas.width = width;
canvas.height = height + (stripHeight * 2);
// 1. Draw the film strip background
ctx.fillStyle = stripColor;
ctx.fillRect(0, 0, canvas.width, canvas.height);
// 2. Draw the original image with an optional cinematic color grading
if (cinematicFilter !== 'none' && cinematicFilter !== '') {
ctx.filter = cinematicFilter;
}
ctx.drawImage(originalImg, 0, stripHeight, width, height);
ctx.filter = 'none'; // reset filter
// 3. Draw movie subtitle if provided
if (String(subtitleText).trim() !== '') {
ctx.fillStyle = '#FFFFFF'; // White text
ctx.strokeStyle = '#000000'; // Black stroke for visibility
ctx.lineWidth = Math.max(3, Math.floor(height * 0.006));
ctx.textAlign = 'center';
ctx.textBaseline = 'bottom';
const fontSize = Math.max(20, Math.floor(height * 0.06));
ctx.font = `italic 600 ${fontSize}px Arial, sans-serif`;
const textX = width / 2;
const textY = stripHeight + height - (height * 0.08); // Near the bottom of the image frame
ctx.strokeText(subtitleText, textX, textY);
ctx.fillText(subtitleText, textX, textY);
}
// 4. Punch perforated holes in the film strip
// Using 'destination-out' ensures the holes are perfectly transparent
ctx.globalCompositeOperation = 'destination-out';
ctx.fillStyle = '#000000'; // Color does not matter for destination-out
const hCount = Number(numHoles);
const cellWidth = canvas.width / hCount;
const holeWidth = cellWidth * 0.4;
const holeHeight = stripHeight * 0.45;
const topY = (stripHeight - holeHeight) / 2;
const bottomY = canvas.height - stripHeight + (stripHeight - holeHeight) / 2;
const cornerRadius = Math.min(holeWidth, holeHeight) * 0.2;
// Helper to draw rounded rectangle for authentic film holes
const drawRoundedHole = (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.closePath();
ctx.fill();
};
// Iterate and draw holes on top and bottom strips
for (let i = 0; i < hCount; i++) {
// Center the hole inside its distributed cell
const x = (i * cellWidth) + (cellWidth - holeWidth) / 2;
drawRoundedHole(x, topY, holeWidth, holeHeight, cornerRadius);
drawRoundedHole(x, bottomY, holeWidth, holeHeight, cornerRadius);
}
// Reset composite operation
ctx.globalCompositeOperation = 'source-over';
return canvas;
}
Apply Changes