You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(
originalImg,
title = "THE CONJURING",
subtitle = "BASED ON THE TRUE CASE FILES OF THE WARRENS",
releaseText = "IN THEATERS EVERYWHERE",
colorTone = "cyan" // "cyan", "sepia", or "dark"
) {
// 1. Intercept and load Google Fonts dynamically (Cinzel for title, Montserrat for modern clean text)
const fontId = 'movie-poster-fonts';
if (!document.getElementById(fontId)) {
const link = document.createElement('link');
link.id = fontId;
link.rel = 'stylesheet';
link.href = 'https://fonts.googleapis.com/css2?family=Cinzel:wght@700&family=Montserrat:wght@400;700&display=swap';
document.head.appendChild(link);
// Wait for CSS to load
await new Promise(resolve => {
link.onload = resolve;
link.onerror = resolve; // proceed on error gracefully
});
// Wait for fonts to be parsed and ready in the document
if (document.fonts && document.fonts.ready) {
await document.fonts.ready;
}
// Small buffer to guarantee rendering
await new Promise(r => setTimeout(r, 150));
}
// 2. Setup standard movie poster canvas (Aspect ratio 2:3)
const w = 800;
const h = 1200;
const canvas = document.createElement('canvas');
canvas.width = w;
canvas.height = h;
const ctx = canvas.getContext('2d');
// 3. Draw and center-crop the original image to act as the cover background
const scale = Math.max(w / originalImg.width, h / originalImg.height);
const nw = originalImg.width * scale;
const nh = originalImg.height * scale;
const nx = (w - nw) / 2;
const ny = (h - nh) / 2;
// Apply eerie foundational filters (high contrast, de-saturated)
ctx.filter = 'grayscale(80%) contrast(140%) brightness(70%)';
ctx.drawImage(originalImg, nx, ny, nw, nh);
ctx.filter = 'none'; // reset filter
// 4. Color Grading (Cinematic Tint)
// We add a multiplying color layer to give it the cold or murky ambiance
ctx.globalCompositeOperation = 'multiply';
if (colorTone.toLowerCase() === 'sepia') {
ctx.fillStyle = '#4a4435'; // dark murky yellow/brown
} else if (colorTone.toLowerCase() === 'cyan') {
ctx.fillStyle = '#2d434a'; // dark cold slate blue/cyan
} else {
ctx.fillStyle = '#333333'; // basic dark
}
ctx.fillRect(0, 0, w, h);
// Deepen shadows further
ctx.globalCompositeOperation = 'overlay';
ctx.fillStyle = 'rgba(0, 0, 0, 0.4)';
ctx.fillRect(0, 0, w, h);
ctx.globalCompositeOperation = 'source-over'; // reset
// 5. Apply Vignettes & Shadows (Directing the light and preparing for text)
// Dark radial vignette
const rx = w / 2;
const ry = h / 2;
const gradRadial = ctx.createRadialGradient(rx, ry, h * 0.2, rx, ry, h * 0.8);
gradRadial.addColorStop(0, 'rgba(0, 0, 0, 0)');
gradRadial.addColorStop(0.6, 'rgba(0, 0, 0, 0.5)');
gradRadial.addColorStop(1, 'rgba(0, 0, 0, 0.95)');
ctx.fillStyle = gradRadial;
ctx.fillRect(0, 0, w, h);
// Top Linear Gradient (Ensures top subtitle is readable)
const gradTop = ctx.createLinearGradient(0, 0, 0, h * 0.35);
gradTop.addColorStop(0, 'rgba(0,0,0,0.85)');
gradTop.addColorStop(1, 'rgba(0,0,0,0)');
ctx.fillStyle = gradTop;
ctx.fillRect(0, 0, w, h * 0.35);
// Bottom Linear Gradient (Ensures main title and credits are readable)
const gradBot = ctx.createLinearGradient(0, h, 0, h * 0.5);
gradBot.addColorStop(0, 'rgba(0,0,0,0.95)');
gradBot.addColorStop(1, 'rgba(0,0,0,0)');
ctx.fillStyle = gradBot;
ctx.fillRect(0, h * 0.5, w, h * 0.5);
// 6. Generate Cinematic Film Grain overlay
const noiseCanvas = document.createElement('canvas');
noiseCanvas.width = 400; // Generate smaller noise map then scale up for performance & chunkier grain
noiseCanvas.height = 600;
const nCtx = noiseCanvas.getContext('2d');
const nData = nCtx.createImageData(noiseCanvas.width, noiseCanvas.height);
for (let i = 0; i < nData.data.length; i += 4) {
const val = Math.random() * 255;
nData.data[i] = val;
nData.data[i+1] = val;
nData.data[i+2] = val;
nData.data[i+3] = 18; // Subdued opacity for the grain
}
nCtx.putImageData(nData, 0, 0);
ctx.globalCompositeOperation = 'overlay';
ctx.drawImage(noiseCanvas, 0, 0, w, h);
ctx.globalCompositeOperation = 'source-over'; // reset
// 7. Render Typography
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.shadowColor = 'rgba(0, 0, 0, 0.9)';
ctx.shadowBlur = 15;
ctx.shadowOffsetY = 4;
// Helper function to auto-scale font size
const drawText = (text, startSize, fontFm, yPos, color, lSpacing) => {
if ('letterSpacing' in ctx) {
ctx.letterSpacing = lSpacing;
}
let fSize = startSize;
ctx.font = `700 ${fSize}px "${fontFm}", sans-serif`;
// Check overflow
while (ctx.measureText(text).width > w - 60 && fSize > 12) {
fSize -= 2;
ctx.font = `700 ${fSize}px "${fontFm}", sans-serif`;
}
ctx.fillStyle = color;
ctx.fillText(text, w / 2, yPos);
// Clean letter-spacing for generic environment resets just in case
if ('letterSpacing' in ctx) {
ctx.letterSpacing = '0px';
}
};
// Subtitle rendering (Top)
drawText(subtitle, 24, "Montserrat", h * 0.12, "#c4c4c4", "8px");
// Main Title rendering (Mid-Bottom)
// Simulating distressed aged white/yellow text
drawText(title, 95, "Cinzel", h * 0.72, "#ececdb", "12px");
// Release Text rendering (Bottom)
drawText(releaseText, 22, "Montserrat", h * 0.84, "#bd2626", "6px"); // Eerie deeply saturated red
// 8. Add Faux Billing Block / Credits to complete the poster illusion
ctx.shadowBlur = 8;
ctx.shadowOffsetY = 2;
const credits1 = "PRODUCED BY PETER SAFRAN DIRECTED BY JAMES WAN";
const credits2 = "MUSIC BY JOSEPH BISHARA WRITTEN BY CHAD HAYES & CAREY W. HAYES";
drawText(credits1, 12, "Montserrat", h * 0.92, "#888888", "2px");
drawText(credits2, 12, "Montserrat", h * 0.945, "#888888", "2px");
return canvas;
}
Apply Changes