You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(
originalImg,
titleText = "THE CONJURING",
subtitleText = "BASED ON THE TRUE CASE FILES",
durationMs = "15000"
) {
// Dynamically load the spooky 'Cinzel' font for the title sequence
const fontName = 'Cinzel';
const link = document.createElement('link');
link.href = `https://fonts.googleapis.com/css2?family=${fontName}:wght@400;700&display=swap`;
link.rel = 'stylesheet';
document.head.appendChild(link);
try {
await document.fonts.load(`400 20px "${fontName}"`);
} catch(e) {
console.warn("Font failed to load. Using fallback.");
}
const duration = Number(durationMs) || 15000;
// Set up main rendering canvas
const canvas = document.createElement('canvas');
const width = 800; // Fixed width for performance and consistency
const height = (originalImg.height / originalImg.width) * width;
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d');
// Create an off-screen canvas to process the input image into the creepy "Conjuring" style
const bgCanvas = document.createElement('canvas');
bgCanvas.width = width;
bgCanvas.height = height;
const bCtx = bgCanvas.getContext('2d');
// Filter step 1: Grayscale, high contrast, darkened
bCtx.filter = 'grayscale(100%) contrast(140%) brightness(35%)';
bCtx.drawImage(originalImg, 0, 0, width, height);
// Filter step 2: Greenish/Yellow Erie Tint (Multiply)
bCtx.filter = 'none';
bCtx.globalCompositeOperation = 'multiply';
bCtx.fillStyle = '#4a5440';
bCtx.fillRect(0, 0, width, height);
// Filter step 3: Blown out, blooming highlights (Overlay/Soft Light)
bCtx.globalCompositeOperation = 'soft-light';
bCtx.filter = 'blur(8px)';
bCtx.drawImage(originalImg, 0, 0, width, height);
// Filter step 4: Heavy Vignette
bCtx.filter = 'none';
bCtx.globalCompositeOperation = 'source-over';
const cx = width / 2;
const cy = height / 2;
const radius = Math.max(width, height) * 0.7;
const grad = bCtx.createRadialGradient(cx, cy, radius * 0.2, cx, cy, radius);
grad.addColorStop(0, 'rgba(0,0,0,0)');
grad.addColorStop(1, 'rgba(0,0,0,0.95)');
bCtx.fillStyle = grad;
bCtx.fillRect(0, 0, width, height);
// Generate floating dust particles
const particles = [];
for (let i = 0; i < 80; i++) {
particles.push({
x: Math.random() * width,
y: Math.random() * height,
vx: (Math.random() - 0.5) * 0.3,
vy: (Math.random() - 0.5) * 0.3,
size: Math.random() * 1.5 + 0.5,
alpha: Math.random() * 0.6
});
}
// Text Drawing Helper function
function drawText(text, p, startIn, endIn, startOut, endOut, size, yOffset) {
if (p < startIn || p > endOut) return;
let opacity = 1;
if (p < endIn) {
opacity = (p - startIn) / (endIn - startIn); // Fading in
} else if (p > startOut) {
opacity = 1 - (p - startOut) / (endOut - startOut); // Fading out
}
// Very slow text scaling
let scale = 1 + ((p - startIn) * 0.15);
ctx.save();
ctx.translate(width / 2, height / 2 + yOffset);
ctx.scale(scale, scale);
ctx.fillStyle = `rgba(230, 225, 200, ${opacity})`;
ctx.shadowColor = `rgba(0, 0, 0, ${opacity})`;
ctx.shadowBlur = 12;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.font = `700 ${size}px "${fontName}", serif`;
// Modern browsers support canvas letterSpacing
if ('letterSpacing' in ctx) {
ctx.letterSpacing = '12px';
}
ctx.fillText(text, 0, 0);
ctx.restore();
}
const startTime = performance.now();
// Animation Loop
function renderFrame(timestamp) {
const elapsed = timestamp - startTime;
const p = (elapsed % duration) / duration; // Loops from 0.0 to 1.0
ctx.clearRect(0, 0, width, height);
// 1. Draw slowly zooming spooky background
const bgScale = 1 + (p * 0.1);
ctx.save();
ctx.translate(width / 2, height / 2);
ctx.scale(bgScale, bgScale);
ctx.drawImage(bgCanvas, -width / 2, -height / 2);
ctx.restore();
// 2. Draw Dust particles
ctx.save();
ctx.fillStyle = 'white';
particles.forEach(pt => {
pt.x += pt.vx;
pt.y += pt.vy;
// wrap around
if (pt.x < 0) pt.x = width;
if (pt.x > width) pt.x = 0;
if (pt.y < 0) pt.y = height;
if (pt.y > height) pt.y = 0;
ctx.globalAlpha = pt.alpha * (p < 0.1 ? p/0.1 : (p > 0.9 ? (1-p)/0.1 : 1));
ctx.beginPath();
ctx.arc(pt.x, pt.y, pt.size, 0, Math.PI * 2);
ctx.fill();
});
ctx.restore();
// 3. Draw Title Sequence Text
// Main Title (Fades in slightly after start, stays a while, fades out)
drawText(titleText, p, 0.15, 0.35, 0.75, 0.90, 50, -20);
// Subtitle (Fades in later, fades out with main title)
drawText(subtitleText, p, 0.45, 0.60, 0.75, 0.90, 18, 50);
// 4. Global Black Fade In/Out for loop continuity
let fadeAlpha = 0;
if (p < 0.05) fadeAlpha = 1 - (p / 0.05); // Fade from black at 0%
if (p > 0.95) fadeAlpha = (p - 0.95) / 0.05; // Fade to black at 100%
if (fadeAlpha > 0) {
ctx.fillStyle = `rgba(0, 0, 0, ${fadeAlpha})`;
ctx.fillRect(0, 0, width, height);
}
requestAnimationFrame(renderFrame);
}
// Start the animation
requestAnimationFrame(renderFrame);
// Return the animated canvas element
return canvas;
}
Apply Changes