You can edit the below JavaScript code to customize the image tool.
Apply Changes
function processImage(originalImg, movieTitle = "MY EPIC MOVIE", directorName = "AI VISIONARY", durationSec = 10, style = "cinematic") {
// 1. Load Cinematic Fonts from Google Fonts
const fontId = "movie-creator-fonts";
if (!document.getElementById(fontId)) {
const fontLink = document.createElement("link");
fontLink.id = fontId;
fontLink.rel = "stylesheet";
fontLink.href = "https://fonts.googleapis.com/css2?family=Cinzel:wght@600&family=Bebas+Neue&display=swap";
document.head.appendChild(fontLink);
}
// 2. Setup Canvas Frame
const canvas = document.createElement('canvas');
canvas.width = 1280;
canvas.height = 720; // Classic 16:9 HD resolution
const ctx = canvas.getContext('2d');
const parsedDuration = parseFloat(durationSec) || 10;
const isOldFilm = style.toLowerCase() === "oldfilm";
// 3. Generate Static Noise Map (Offscreen for performance)
const noiseCanvas = document.createElement('canvas');
noiseCanvas.width = 1600;
noiseCanvas.height = 1000;
const nctx = noiseCanvas.getContext('2d');
const imgData = nctx.createImageData(noiseCanvas.width, noiseCanvas.height);
for (let i = 0; i < imgData.data.length; i += 4) {
const val = Math.random() * 255;
imgData.data[i] = val;
imgData.data[i + 1] = val;
imgData.data[i + 2] = val;
imgData.data[i + 3] = Math.random() * (isOldFilm ? 40 : 15);
}
nctx.putImageData(imgData, 0, 0);
// 4. Calculate best-fit dimensions (Object-fit: cover logic)
const canvasRatio = canvas.width / canvas.height;
const imgRatio = originalImg.naturalWidth / originalImg.naturalHeight;
let sw = originalImg.naturalWidth, sh = originalImg.naturalHeight, sx = 0, sy = 0;
if (imgRatio > canvasRatio) {
sh = originalImg.naturalHeight;
sw = sh * canvasRatio;
sx = (originalImg.naturalWidth - sw) / 2;
} else {
sw = originalImg.naturalWidth;
sh = sw / canvasRatio;
sy = (originalImg.naturalHeight - sh) / 2;
}
// 5. Animation Engine
const startTime = performance.now();
let framesDetached = 0;
function renderText(text, font, yOff, fadeInSt, fadeInEn, fadeOutSt, fadeOutEn, t) {
if (t >= fadeInSt && t <= fadeOutEn) {
let alpha = 1;
if (t < fadeInEn) alpha = (t - fadeInSt) / (fadeInEn - fadeInSt);
else if (t > fadeOutSt) alpha = 1 - (t - fadeOutSt) / (fadeOutEn - fadeOutSt);
ctx.save();
ctx.globalAlpha = Math.max(0, Math.min(1, alpha));
ctx.fillStyle = "white";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.font = font;
ctx.shadowColor = "rgba(0,0,0,0.9)";
ctx.shadowBlur = 15;
ctx.shadowOffsetX = 3;
ctx.shadowOffsetY = 3;
ctx.fillText(text.toUpperCase(), canvas.width / 2, canvas.height / 2 + yOff);
ctx.restore();
}
}
function render(time) {
// Stop rendering if canvas is removed from DOM (memory leak prevention)
if (!document.body.contains(canvas) && framesDetached++ > 120) return;
else if (document.body.contains(canvas)) framesDetached = 0;
const elapsed = (time - startTime) / 1000;
const t = (elapsed % parsedDuration) / parsedDuration; // 0.0 to 1.0 (Timeline)
ctx.clearRect(0, 0, canvas.width, canvas.height);
// -- Layer 1: Image with Ken Burns Effect --
ctx.save();
ctx.translate(canvas.width / 2, canvas.height / 2);
// Slow pan and zoom
const scale = 1.0 + (0.3 * t); // Zoom from 1.0x to 1.3x
const panX = -30 + (60 * t); // Pan left to right
ctx.scale(scale, scale);
ctx.translate(panX, 0);
// Apply cinematic filters
if (isOldFilm) {
const flicker = 0.8 + Math.random() * 0.2;
ctx.filter = `sepia(0.9) grayscale(0.2) contrast(1.5) brightness(${flicker})`;
} else {
ctx.filter = `saturate(1.2) contrast(1.15) brightness(0.95)`;
}
ctx.drawImage(originalImg, sx, sy, sw, sh, -canvas.width / 2, -canvas.height / 2, canvas.width, canvas.height);
ctx.restore();
// -- Layer 2: Film Grain Overlay --
ctx.save();
ctx.globalCompositeOperation = "overlay";
const rx = - (Math.random() * 320) | 0;
const ry = - (Math.random() * 280) | 0;
ctx.drawImage(noiseCanvas, rx, ry);
ctx.restore();
// -- Layer 3: Old Film Scratches --
if (isOldFilm) {
ctx.save();
ctx.lineWidth = Math.random() * 2 + 1;
if (Math.random() > 0.4) {
const x = Math.random() * canvas.width;
ctx.strokeStyle = "rgba(255,255,255,0.3)";
ctx.beginPath(); ctx.moveTo(x, 0); ctx.lineTo(x, canvas.height); ctx.stroke();
}
if (Math.random() > 0.6) {
const x = Math.random() * canvas.width;
ctx.strokeStyle = "rgba(0,0,0,0.4)";
ctx.beginPath(); ctx.moveTo(x, 0); ctx.lineTo(x, canvas.height); ctx.stroke();
}
ctx.restore();
}
// -- Layer 4: Vignette --
ctx.save();
const grad = ctx.createRadialGradient(canvas.width/2, canvas.height/2, canvas.height/2.5, canvas.width/2, canvas.height/2, canvas.width/1.2);
grad.addColorStop(0, "rgba(0,0,0,0)");
grad.addColorStop(1, "rgba(0,0,0,0.8)");
ctx.fillStyle = grad;
ctx.fillRect(0, 0, canvas.width, canvas.height);
ctx.restore();
// -- Layer 5: Letterboxing (Cinematic Aspect Ratio 2.39:1) --
ctx.fillStyle = "#000";
const cinematicHeight = canvas.width / 2.39;
const barHeight = (canvas.height - cinematicHeight) / 2;
ctx.fillRect(0, 0, canvas.width, barHeight);
ctx.fillRect(0, canvas.height - barHeight, canvas.width, barHeight);
// -- Layer 6: Animated Titles --
if (!isOldFilm || movieTitle) {
// Main Title
renderText(movieTitle, "90px 'Cinzel', serif", -20, 0.05, 0.15, 0.40, 0.45, t);
// Director Subtitle
renderText("A FILM BY", "30px 'Bebas Neue', sans-serif", -20, 0.50, 0.55, 0.85, 0.90, t);
renderText(directorName, "60px 'Bebas Neue', sans-serif", 35, 0.52, 0.57, 0.85, 0.90, t);
} else {
// Classic silent film title card emulation
renderText(movieTitle, "60px 'Georgia', serif", 0, 0.1, 0.2, 0.8, 0.9, t);
}
// -- Layer 7: Cinematic Fade-In / Fade-Out Black Transition --
let fadeAlpha = 0;
if (t < 0.05) fadeAlpha = 1 - (t / 0.05);
else if (t > 0.95) fadeAlpha = (t - 0.95) / 0.05;
if (fadeAlpha > 0) {
ctx.fillStyle = `rgba(0, 0, 0, ${fadeAlpha})`;
ctx.fillRect(0, 0, canvas.width, canvas.height);
}
requestAnimationFrame(render);
}
// Mount canvas loop & styles
canvas.style.maxWidth = "100%";
canvas.style.boxShadow = "0 25px 50px -12px rgba(0,0,0,0.5)";
canvas.style.borderRadius = "8px";
canvas.style.backgroundColor = "#000";
requestAnimationFrame(render);
return canvas;
}
Apply Changes