You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(originalImg, mainTitle = "ANNABELLE", subTitle = "COMES HOME") {
// Determine canvas dimensions from original image
const w = originalImg.width;
const h = originalImg.height;
const canvas = document.createElement('canvas');
canvas.width = w;
canvas.height = h;
const ctx = canvas.getContext('2d', { willReadFrequently: true });
// Try to load the cinematic Google Font
let fontFamily = '"Cinzel", serif';
try {
const fontLink = document.createElement('link');
fontLink.href = 'https://fonts.googleapis.com/css2?family=Cinzel:wght@700&display=swap';
fontLink.rel = 'stylesheet';
document.head.appendChild(fontLink);
await document.fonts.load('700 10px "Cinzel"');
} catch (e) {
fontFamily = 'serif'; // Fallback
}
// Draw the initial image
ctx.drawImage(originalImg, 0, 0, w, h);
// --- COLOR GRADING (Horror & Cinematic Teal/Orange effect) ---
// Extract pixel data for manipulation
const imgData = ctx.getImageData(0, 0, w, h);
const data = imgData.data;
for (let i = 0; i < data.length; i += 4) {
let r = data[i];
let g = data[i+1];
let b = data[i+2];
// 1. Calculate luminance (grayscale)
let gray = 0.299 * r + 0.587 * g + 0.114 * b;
// 2. Desaturate the image to make it look bleak
r = r * 0.4 + gray * 0.6;
g = g * 0.4 + gray * 0.6;
b = b * 0.4 + gray * 0.6;
// 3. Cinematic Split Toning
let lum = gray / 255; // Normalized brightness (0 to 1)
let invLum = 1 - lum;
// Push shadows toward dark teal
// Push highlights toward pale yellow/orange
r = r - (25 * invLum) + (20 * lum);
g = g + (5 * invLum) + (10 * lum);
b = b + (35 * invLum) - (25 * lum);
// 4. Boost Contrast and Darken overall
let contrast = 1.35;
let brightnessOffset = -35;
r = ((r / 255 - 0.5) * contrast + 0.5) * 255 + brightnessOffset;
g = ((g / 255 - 0.5) * contrast + 0.5) * 255 + brightnessOffset;
b = ((b / 255 - 0.5) * contrast + 0.5) * 255 + brightnessOffset;
// 5. Add organic film grain / noise
let noise = (Math.random() - 0.5) * 15;
// Clamping values between 0 and 255
data[i] = Math.max(0, Math.min(255, r + noise));
data[i+1] = Math.max(0, Math.min(255, g + noise));
data[i+2] = Math.max(0, Math.min(255, b + noise));
}
// Put modified pixels back
ctx.putImageData(imgData, 0, 0);
// --- VIGNETTE EFFECT ---
// Heavy dark edges typical of horror posters to focus on the center
const cx = w / 2;
const cy = h / 2;
const maxRadius = Math.max(w, h);
const vignetteOffset = 0.2; // Keep center somewhat clear
const grad = ctx.createRadialGradient(cx, cy, maxRadius * vignetteOffset, cx, cy, maxRadius * 0.7);
grad.addColorStop(0, 'rgba(0, 0, 0, 0)');
grad.addColorStop(0.6, 'rgba(10, 15, 20, 0.6)');
grad.addColorStop(1, 'rgba(5, 10, 10, 0.95)');
ctx.fillStyle = grad;
ctx.fillRect(0, 0, w, h);
// --- DISTRESS / SCRATCHES ---
// Add subtle scratches to give an aged/dirty artifact room feel
ctx.strokeStyle = 'rgba(255, 255, 255, 0.07)';
ctx.beginPath();
const scratchCount = Math.floor((w * h) / 10000);
for(let i = 0; i < scratchCount; i++) {
let x = Math.random() * w;
let y = Math.random() * h;
let len = Math.random() * h * 0.15;
ctx.moveTo(x, y);
// Draw slightly slanted line
ctx.lineTo(x + (Math.random() - 0.5) * 15, y + len);
}
ctx.stroke();
// --- MOVIE POSTER TEXT ---
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
// Helper dynamically sizes font to fit the canvas width safely
const measureAndSetFont = (text, startSize, fontFam) => {
let size = startSize;
ctx.font = `700 ${size}px ${fontFam}`;
if ('letterSpacing' in ctx) ctx.letterSpacing = `${Math.floor(size * 0.15)}px`;
while (ctx.measureText(text).width > w * 0.85 && size > 10) {
size -= 2;
ctx.font = `700 ${size}px ${fontFam}`;
if ('letterSpacing' in ctx) ctx.letterSpacing = `${Math.floor(size * 0.15)}px`;
}
return size;
};
// Draw Main Title
let mainTitleUpper = mainTitle.toUpperCase();
measureAndSetFont(mainTitleUpper, Math.floor(w * 0.16), fontFamily);
const titleY = h * 0.73;
// Haunting Glow effect
ctx.shadowColor = 'rgba(200, 220, 255, 0.5)';
ctx.shadowBlur = Math.floor(w * 0.03);
ctx.fillStyle = '#f0f3f5';
ctx.fillText(mainTitleUpper, w / 2, titleY);
// Draw Subtitle (sinister red coloring typical for Annabelle subtitles)
if (subTitle) {
let subTitleUpper = subTitle.toUpperCase();
ctx.shadowBlur = 0; // reset to measure accurately
measureAndSetFont(subTitleUpper, Math.floor(w * 0.045), fontFamily);
if ('letterSpacing' in ctx) {
// wider tracking for subtitle
ctx.letterSpacing = `${Math.floor(w * 0.015)}px`;
}
const subtitleY = titleY + (h * 0.1);
ctx.shadowColor = 'rgba(255, 0, 0, 0.8)';
ctx.shadowBlur = Math.floor(w * 0.02);
ctx.fillStyle = '#b30000'; // Dark sinister red
ctx.fillText(subTitleUpper, w / 2, subtitleY);
}
// Reset Context States
ctx.shadowBlur = 0;
if ('letterSpacing' in ctx) ctx.letterSpacing = '0px';
return canvas;
}
Apply Changes