You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(originalImg, title = "ANNABELLE", subtitle = "CREATION", tintColor = "#8a6b32") {
// Dynamically load the 'Cinzel' font for the cinematic title look
const fontName = 'Cinzel';
if (!document.fonts.check(`12px "${fontName}"`)) {
const link = document.createElement('link');
link.href = `https://fonts.googleapis.com/css2?family=Cinzel:wght@600;700&display=swap`;
link.rel = 'stylesheet';
document.head.appendChild(link);
try {
await document.fonts.ready;
} catch (e) {
console.warn("Cinzel font loading failed, falling back to default serif.");
}
}
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
// Set dimensions based on the original image
const width = originalImg.width;
const height = originalImg.height;
canvas.width = width;
canvas.height = height;
// Step 1: Draw base image with a moody, high-contrast, desaturated look
ctx.filter = 'grayscale(70%) contrast(140%) brightness(75%) sepia(20%)';
ctx.drawImage(originalImg, 0, 0, width, height);
ctx.filter = 'none';
// Step 2: Apply eerie amber/yellow-brown tinting (The classic horror poster color grading)
ctx.globalCompositeOperation = 'color';
ctx.fillStyle = tintColor;
ctx.fillRect(0, 0, width, height);
ctx.globalCompositeOperation = 'soft-light';
ctx.fillStyle = 'rgba(138, 107, 50, 0.4)'; // extra punch to the midtones
ctx.fillRect(0, 0, width, height);
ctx.globalCompositeOperation = 'source-over';
// Step 3: Draw heavy, creeping vignette (dark edges)
const grad = ctx.createRadialGradient(
width / 2, height / 2, Math.min(width, height) * 0.2,
width / 2, height / 2, Math.max(width, height) * 0.75
);
grad.addColorStop(0, 'rgba(0,0,0,0)');
grad.addColorStop(0.4, 'rgba(0,0,0,0.3)');
grad.addColorStop(1, 'rgba(0,0,0,0.98)');
ctx.globalCompositeOperation = 'multiply';
ctx.fillStyle = grad;
ctx.fillRect(0, 0, width, height);
ctx.globalCompositeOperation = 'source-over';
// Step 4: Add film grain/noise for the horror texture
const noiseCanvas = document.createElement('canvas');
const noiseSize = 256;
noiseCanvas.width = noiseSize;
noiseCanvas.height = noiseSize;
const nCtx = noiseCanvas.getContext('2d');
const imgData = nCtx.createImageData(noiseSize, noiseSize);
const buffer32 = new Uint32Array(imgData.data.buffer);
const noiseOpacity = 28; // out of 255
for (let i = 0; i < buffer32.length; i++) {
const val = Math.random() * 255 | 0;
// Constructing RGBA pixel
buffer32[i] = (noiseOpacity << 24) | (val << 16) | (val << 8) | val;
}
nCtx.putImageData(imgData, 0, 0);
const pattern = ctx.createPattern(noiseCanvas, 'repeat');
ctx.globalCompositeOperation = 'overlay';
ctx.fillStyle = pattern;
ctx.fillRect(0, 0, width, height);
ctx.globalCompositeOperation = 'source-over';
// Step 5: Render Text (Title)
const midX = width / 2;
const baseY = height * 0.75;
// Add glowing drop shadow to make text pop off the dark background
ctx.shadowColor = 'rgba(0, 0, 0, 0.9)';
ctx.shadowBlur = width * 0.02;
ctx.shadowOffsetX = 0;
ctx.shadowOffsetY = height * 0.005;
const titleSize = width * 0.12;
ctx.font = `700 ${titleSize}px "${fontName}", serif`;
ctx.fillStyle = '#eadecc'; // slightly yellowed/aged white
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
const titleSpacing = titleSize * 0.05;
drawSpacedText(ctx, title.toUpperCase(), midX, baseY, titleSpacing);
// Step 6: Render Text (Subtitle)
const subSize = width * 0.035;
ctx.font = `700 ${subSize}px Arial, sans-serif`;
ctx.fillStyle = '#b0a9a1'; // muted light gray/brown
ctx.shadowBlur = width * 0.01;
const subY = baseY + titleSize * 0.65;
const subSpacing = subSize * 0.7; // Very wide spacing for the subtitle
drawSpacedText(ctx, subtitle.toUpperCase(), midX, subY, subSpacing);
// Helper function for customizable letter spacing
function drawSpacedText(context, text, x, y, letterSpacing) {
if ('letterSpacing' in context) {
const originalSpacing = context.letterSpacing;
context.letterSpacing = `${letterSpacing}px`;
context.fillText(text, x, y);
context.letterSpacing = originalSpacing;
} else {
// Manual fallback if letterSpacing isn't supported in browser capabilities
let totalWidth = 0;
for (let i = 0; i < text.length; i++) {
totalWidth += context.measureText(text[i]).width;
}
totalWidth += (text.length - 1) * letterSpacing;
let startX = x - totalWidth / 2;
const origAlign = context.textAlign;
context.textAlign = 'left';
for (let i = 0; i < text.length; i++) {
const char = text[i];
// Manually draw character and advance X
context.fillText(char, startX, y);
startX += context.measureText(char).width + letterSpacing;
}
context.textAlign = origAlign;
}
}
return canvas;
}
Apply Changes