You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(originalImg, titleText = "STRANGER THINGS", taglineText = "THE WORLD IS TURNING UPSIDE DOWN", actorNames = "WINONA RYDER, DAVID HARBOUR, FINN WOLFHARD, MILLIE BOBBY BROWN", releaseDate = "JULY 15") {
const P_WIDTH = 600;
const P_HEIGHT = 900;
const mainTitleColor = '#E50914'; // A strong red, similar to Netflix/Stranger Things
const glowColor = '#FF0000'; // Bright red glow
// --- Font Loading ---
// Ensure fonts are loaded. We'll use 'Bungee' for the title and 'Montserrat' for other text.
// These are Google Fonts and will be imported via a dynamically added style tag.
let fontsSuccessfullyLoaded = false;
try {
if (!document.getElementById('stranger-poster-fonts-style')) {
const style = document.createElement('style');
style.id = 'stranger-poster-fonts-style';
style.innerHTML = `
@import url('https://fonts.googleapis.com/css2?family=Bungee&family=Montserrat:wght@300;400;700&display=swap');
`;
document.head.appendChild(style);
}
// Wait for fonts to be available for rendering on canvas
await document.fonts.load('1em Bungee');
await document.fonts.load('1em Montserrat'); // Check for regular weight
await document.fonts.load('bold 1em Montserrat'); // Check for bold
await document.fonts.load('300 1em Montserrat'); // Check for light
fontsSuccessfullyLoaded = true;
} catch (e) {
console.warn("Font loading failed or document.fonts API not fully supported. Using fallback fonts.", e);
// The script will continue with browser default fonts if this fails.
}
const titleFontName = fontsSuccessfullyLoaded ? 'Bungee' : 'sans-serif';
const textFontName = fontsSuccessfullyLoaded ? 'Montserrat' : 'sans-serif';
// --- Canvas Setup ---
const canvas = document.createElement('canvas');
canvas.width = P_WIDTH;
canvas.height = P_HEIGHT;
const ctx = canvas.getContext('2d');
// --- Draw Background Image (Cover and Center) ---
ctx.fillStyle = '#000000'; // Fallback background color
ctx.fillRect(0, 0, P_WIDTH, P_HEIGHT);
if (originalImg && originalImg.width > 0 && originalImg.height > 0) {
const imgAspect = originalImg.width / originalImg.height;
const canvasAspect = P_WIDTH / P_HEIGHT;
let sx = 0, sy = 0, sWidth = originalImg.width, sHeight = originalImg.height;
if (imgAspect > canvasAspect) { // Image wider than canvas: crop image sides
sWidth = originalImg.height * canvasAspect;
sx = (originalImg.width - sWidth) / 2;
} else if (imgAspect < canvasAspect) { // Image taller than canvas: crop image top/bottom
sHeight = originalImg.width / canvasAspect;
sy = (originalImg.height - sHeight) / 2;
}
ctx.drawImage(originalImg, sx, sy, sWidth, sHeight, 0, 0, P_WIDTH, P_HEIGHT);
}
// Dark overlay for mood
ctx.fillStyle = 'rgba(0, 0, 0, 0.4)'; // Adjust opacity as needed
ctx.fillRect(0, 0, P_WIDTH, P_HEIGHT);
// --- Title Text Styling and Drawing ---
let titleLine1 = "";
let titleLine2 = "";
const words = titleText.toUpperCase().split(" ");
if (titleText.toUpperCase() === "STRANGER THINGS") { // Specific handling for the classic
titleLine1 = "STRANGER";
titleLine2 = "THINGS";
} else if (words.length === 1) {
titleLine1 = words[0];
} else if (words.length >= 2) { // Split into two lines for other titles
const splitPoint = Math.ceil(words.length / 2);
titleLine1 = words.slice(0, splitPoint).join(" ");
titleLine2 = words.slice(splitPoint).join(" ");
}
// Calculate Title Font Size dynamically
let FONT_SIZE_TITLE = 90; // Initial large size
const maxTitleWidth = P_WIDTH * 0.90; // Title should occupy max 90% of poster width
ctx.textAlign = 'center';
const textToMeasureForSize = titleLine2 && titleLine2.length > titleLine1.length ? titleLine2 : titleLine1;
ctx.font = `${FONT_SIZE_TITLE}px ${titleFontName}`;
while (ctx.measureText(textToMeasureForSize).width > maxTitleWidth && FONT_SIZE_TITLE > 15) {
FONT_SIZE_TITLE--;
ctx.font = `${FONT_SIZE_TITLE}px ${titleFontName}`;
}
// If split, ensure the other line also fits with the determined font size (if it was longer)
if (titleLine2 && titleLine1.length > titleLine2.length) {
while (ctx.measureText(titleLine1).width > maxTitleWidth && FONT_SIZE_TITLE > 15) {
FONT_SIZE_TITLE--;
ctx.font = `${FONT_SIZE_TITLE}px ${titleFontName}`;
}
}
const drawStyledTitleLine = (text, yPos, currentFontSize, font, color, shadowColor, context) => {
context.textBaseline = 'middle'; // Critical for vertical centering
const isSpecialStrangerThingsWord = (text === "STRANGER" || text === "THINGS");
if (isSpecialStrangerThingsWord && text.length > 2) { // Apply special S-T-Y-L-E
const scaleFactor = 1.2; // First/last letters larger
const parts = [];
parts.push({ char: text[0], size: currentFontSize * scaleFactor });
parts.push({ char: text.substring(1, text.length - 1), size: currentFontSize });
parts.push({ char: text[text.length - 1], size: currentFontSize * scaleFactor });
let totalWidth = 0;
parts.forEach(part => {
context.font = `${part.size}px ${font}`;
totalWidth += context.measureText(part.char).width;
});
let currentX = (P_WIDTH - totalWidth) / 2;
context.textAlign = 'left'; // Temporarily switch for piece-by-piece drawing
parts.forEach(part => {
context.font = `${part.size}px ${font}`;
// Draw shadow/glow layer
context.shadowColor = shadowColor;
context.shadowBlur = 20; // Adjust glow intensity
context.shadowOffsetX = 0;
context.shadowOffsetY = 0;
context.fillStyle = color; // Glow color for shadowed text
context.fillText(part.char, currentX, yPos);
// Draw main text layer (crisp, on top)
context.shadowColor = 'transparent';
context.fillStyle = color;
context.fillText(part.char, currentX, yPos);
currentX += context.measureText(part.char).width;
});
context.textAlign = 'center'; // Reset textAlign
} else { // Standard drawing for other titles
context.font = `${currentFontSize}px ${font}`;
context.shadowColor = shadowColor;
context.shadowBlur = 20;
context.shadowOffsetX = 0;
context.shadowOffsetY = 0;
context.fillStyle = color;
context.fillText(text, P_WIDTH / 2, yPos);
context.shadowColor = 'transparent';
context.fillStyle = color;
context.fillText(text, P_WIDTH / 2, yPos);
}
};
// Position and draw title lines
const titleY1 = P_HEIGHT * (titleLine2 ? 0.18 : 0.22); // Adjust based on 1 or 2 lines
const titleLineHeight = FONT_SIZE_TITLE * (titleLine2 ? 1.0 : 1.0); // Compact for Bungee
drawStyledTitleLine(titleLine1, titleY1, FONT_SIZE_TITLE, titleFontName, mainTitleColor, glowColor, ctx);
if (titleLine2) {
const titleY2 = titleY1 + titleLineHeight;
drawStyledTitleLine(titleLine2, titleY2, FONT_SIZE_TITLE, titleFontName, mainTitleColor, glowColor, ctx);
}
ctx.shadowColor = 'transparent'; // Reset all shadows
// --- Draw Tagline ---
const taglineFontSize = Math.max(12, P_WIDTH / 38);
ctx.font = `italic ${taglineFontSize}px ${textFontName}`;
ctx.fillStyle = '#FFFFFF';
const taglineY = (titleLine2 ? titleY1 + titleLineHeight : titleY1) + FONT_SIZE_TITLE * 0.7 + taglineFontSize;
ctx.fillText(taglineText.toUpperCase(), P_WIDTH / 2, taglineY);
// --- Draw Actor Names (Billing Block Style) ---
const actorsFontSize = Math.max(8, P_WIDTH / 60);
ctx.font = `300 ${actorsFontSize}px ${textFontName}`; // Light weight
ctx.fillStyle = '#B0B0B0'; // Light Grey
const actorsArray = actorNames.split(',').map(name => name.trim().toUpperCase());
const actorsLine = actorsArray.join(' '); // Typical billing block uses spaces, not dots usually
const actorsY = P_HEIGHT - P_HEIGHT * 0.10;
// Simple fit for actors line, reduce font size if too long
let finalActorsFontSize = actorsFontSize;
while(ctx.measureText(actorsLine).width > P_WIDTH * 0.95 && finalActorsFontSize > 6) {
finalActorsFontSize--;
ctx.font = `300 ${finalActorsFontSize}px ${textFontName}`;
}
ctx.fillText(actorsLine, P_WIDTH / 2, actorsY);
// --- Draw Release Date ---
const releaseFontSize = Math.max(10, P_WIDTH / 48);
ctx.font = `normal ${releaseFontSize}px ${textFontName}`; // Normal weight, or bold
ctx.fillStyle = '#FFFFFF';
const releaseY = actorsY + finalActorsFontSize * 1.8;
ctx.fillText(releaseDate.toUpperCase(), P_WIDTH / 2, releaseY);
return canvas;
}
Apply Changes