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",
tintColor = "#3c4d4c",
contrast = 80,
darkness = 30,
noiseIntensity = 25,
vignetteStrength = 0.9
) {
// 1. Dynamically load the spooky movie poster font ("Cinzel")
const fontName = 'Cinzel';
try {
const link = document.createElement('link');
link.href = 'https://fonts.googleapis.com/css2?family=Cinzel:wght@400;700&display=swap';
link.rel = 'stylesheet';
document.head.appendChild(link);
// Wait for font to load, with a timeout fallback
await Promise.race([
document.fonts.load(`bold 20px "${fontName}"`),
new Promise(r => setTimeout(r, 2500))
]);
} catch (err) {
console.warn("Font loading failed, falling back to serif.", err);
}
// 2. Setup Canvas
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
canvas.width = originalImg.width;
canvas.height = originalImg.height;
// Draw original image
ctx.drawImage(originalImg, 0, 0);
// 3. Process image data (Contrast, Darkness, Desaturation, Gradient Map/Tint, Noise)
let imgData = ctx.getImageData(0, 0, canvas.width, canvas.height);
let data = imgData.data;
// Contrast factor
let c = Number(contrast);
let contrastFactor = (259 * (c + 255)) / (255 * (259 - c));
// Parse tint color
let m = tintColor.match(/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i);
let tr = m ? parseInt(m[1], 16) : 60;
let tg = m ? parseInt(m[2], 16) : 77;
let tb = m ? parseInt(m[3], 16) : 76;
for (let i = 0; i < data.length; i += 4) {
// Apply basic darkness
let r = data[i] - darkness;
let g = data[i + 1] - darkness;
let b = data[i + 2] - darkness;
// Apply contrast
r = contrastFactor * (r - 128) + 128;
g = contrastFactor * (g - 128) + 128;
b = contrastFactor * (b - 128) + 128;
// Clamp to 0-255
r = Math.max(0, Math.min(255, r));
g = Math.max(0, Math.min(255, g));
b = Math.max(0, Math.min(255, b));
// Grayscale conversion
let gray = 0.3 * r + 0.59 * g + 0.11 * b;
// Apply Gradient Map / Duotone effect
let outR, outG, outB;
if (gray < 128) {
// Map darks (0-128) to (Black -> Tint)
let f = gray / 128;
outR = f * tr;
outG = f * tg;
outB = f * tb;
} else {
// Map lights (128-255) to (Tint -> White)
let f = (gray - 128) / 127;
outR = tr + f * (255 - tr);
outG = tg + f * (255 - tg);
outB = tb + f * (255 - tb);
}
// Slight blend with heavily desaturated original for a little natural variety (9-to-1 ratio)
r = (r * 0.1) + (outR * 0.9);
g = (g * 0.1) + (outG * 0.9);
b = (b * 0.1) + (outB * 0.9);
// Add Film Grain / Noise
let noise = (Math.random() - 0.5) * noiseIntensity;
r += noise;
g += noise;
b += noise;
data[i] = r;
data[i + 1] = g;
data[i + 2] = b;
}
// Put modified data back
ctx.putImageData(imgData, 0, 0);
// 4. Apply heavy vignette
let cx = canvas.width / 2;
let cy = canvas.height / 2;
// Calculate radius to encompass the corners
let radius = Math.sqrt(cx * cx + cy * cy);
let grad = ctx.createRadialGradient(cx, cy, radius * 0.3, cx, cy, radius * 1.1);
grad.addColorStop(0, 'rgba(0,0,0,0)');
grad.addColorStop(0.6, `rgba(0,0,0,${vignetteStrength * 0.5})`);
grad.addColorStop(1, `rgba(0,0,0,${vignetteStrength})`);
ctx.fillStyle = grad;
ctx.fillRect(0, 0, canvas.width, canvas.height);
// 5. Draw Typography
const drawSpacedText = (text, y, fontSize, fontWeight, spacing) => {
ctx.font = `${fontWeight} ${fontSize}px "${fontName}", serif`;
ctx.fillStyle = 'rgba(235, 235, 225, 0.9)'; // Pale spooky white
ctx.shadowColor = 'rgba(0,0,0,0.8)';
ctx.shadowBlur = Math.max(5, fontSize / 5);
ctx.shadowOffsetX = 2;
ctx.shadowOffsetY = 2;
if ('letterSpacing' in ctx) {
ctx.textAlign = 'center';
ctx.letterSpacing = `${spacing}px`;
ctx.fillText(text, canvas.width / 2, y);
ctx.letterSpacing = '0px';
} else {
// Fallback for unsupported browsers
let totalWidth = ctx.measureText(text).width + (text.length - 1) * spacing;
let startX = (canvas.width - totalWidth) / 2;
ctx.textAlign = 'left';
let currentX = startX;
for (let i = 0; i < text.length; i++) {
ctx.fillText(text[i], currentX, y);
currentX += ctx.measureText(text[i]).width + spacing;
}
}
// Reset shadow
ctx.shadowColor = 'transparent';
ctx.shadowBlur = 0;
ctx.shadowOffsetX = 0;
ctx.shadowOffsetY = 0;
};
// Calculate responsive sizes
let scaleRef = Math.min(canvas.width, canvas.height * 0.8);
let titleSize = Math.floor(scaleRef / 10);
let subSize = Math.floor(titleSize * 0.25);
// Y-positions
let titleY = canvas.height * 0.82;
let subY = canvas.height * 0.88;
// Render texts
drawSpacedText(title, titleY, titleSize, 'bold', Math.floor(titleSize * 0.15));
if (subtitle) {
drawSpacedText(subtitle.toUpperCase(), subY, subSize, 'normal', Math.floor(subSize * 0.25));
}
return canvas;
}
Apply Changes