You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(
originalImg,
mainHeadline = "THE ASTOUNDING",
secondaryHeadline = "AMAZING FEATS!",
starburstText = "ALIVE!",
bottomLineText = "ADMISSION 25¢",
accentColor = "#B80000", // Deep Red (for stars, highlights, inner borders)
textColor = "#FFFFF0", // Ivory (for main text fill)
textOutlineColor = "#000000", // Black (for text stroke)
backgroundColor = "#F5E8C7", // Aged Paper Beige
frameColor = "#4A3B31" // Dark Brown (for main poster frame/border)
) {
const POSTER_WIDTH = 800;
const POSTER_HEIGHT = 1200;
const BORDER_WIDTH = 25; // Width of the main outer frame
const INNER_MARGIN = 30; // Margin inside the frame for content
const TEXT_STROKE_WIDTH = 3;
const FONT_HEADLINE = "Rye"; // Google Font
const FONT_SUB_HEADLINE = "Luckiest Guy"; // Google Font
const FONT_STARBURST = "Bangers"; // Google Font
const FONT_BOTTOM_LINE = "Carter One"; // Google Font
// Helper to load Google Fonts
async function loadGoogleFont(fontFamily) {
const fontName = fontFamily.replace(/\s+/g, '+');
const url = `https://fonts.googleapis.com/css2?family=${fontName}:wght@400&display=swap`;
if (document.querySelector(`link[href="${url}"]`)) {
try {
await document.fonts.load(`12px "${fontFamily}"`);
// console.log(`Font ${fontFamily} already linked and loaded.`);
return;
} catch (e) {
// console.warn(`Font ${fontFamily} link existed, but load failed. Will try to add again.`, e);
}
} else if (document.fonts.check(`12px "${fontFamily}"`)) {
// console.log(`Font ${fontFamily} already loaded (system or previously loaded).`);
return;
}
return new Promise((resolve, reject) => {
const link = document.createElement('link');
link.rel = 'stylesheet';
link.href = url;
link.onload = async () => {
try {
await document.fonts.load(`12px "${fontFamily}"`);
// console.log(`Font ${fontFamily} loaded successfully.`);
resolve();
} catch (e) {
console.error(`Error loading font metrics for ${fontFamily} after CSS load. Using fallback.`, e);
resolve(); // Resolve anyway to not break Promise.all
}
};
link.onerror = () => {
console.error(`Failed to load Google Font CSS: ${fontFamily}. Using fallback.`);
resolve(); // Resolve anyway
};
document.head.appendChild(link);
});
}
// Helper to draw stroked and filled text
function drawText(ctx, text, x, y, font, fontSize, fillColor, strokeColor, strokeWidth, textAlign = 'center', textBaseline = 'alphabetic') {
ctx.font = `${fontSize}px "${font}"`;
ctx.fillStyle = fillColor;
ctx.strokeStyle = strokeColor;
ctx.lineWidth = strokeWidth;
ctx.textAlign = textAlign;
ctx.textBaseline = textBaseline;
if (strokeWidth > 0) {
ctx.strokeText(text, x, y);
}
ctx.fillText(text, x, y);
}
// Helper for wrapped text
function wrapAndDrawText(ctx, text, x, y, maxWidth, lineHeight, font, fontSize, fillColor, strokeColor, strokeWidth, textAlign = 'center') {
const words = text.toUpperCase().split(' ');
let line = '';
let currentY = y;
const lines = [];
ctx.font = `${fontSize}px "${font}"`; // Set font for accurate measureText
for (let n = 0; n < words.length; n++) {
const testLine = line + words[n] + ' ';
const metrics = ctx.measureText(testLine.trim());
const testWidth = metrics.width;
if (testWidth > maxWidth && line !== '') { // Ensure line is not empty before pushing
lines.push({ text: line.trim(), y: currentY });
line = words[n] + ' ';
currentY += lineHeight;
} else {
line = testLine;
}
}
if (line.trim() !== '') { // Add the last line
lines.push({ text: line.trim(), y: currentY });
}
const totalTextHeight = lines.length * lineHeight;
// Optional: Adjust Y to center the block of text if needed; for now, `y` is the top.
lines.forEach(lineObj => {
drawText(ctx, lineObj.text, x, lineObj.y, font, fontSize, fillColor, strokeColor, strokeWidth, textAlign, 'top');
});
return currentY + (lines.length > 0 ? lineHeight : 0); // Return Y for the position *after* this text block
}
// Helper to draw a starburst
function drawStarburst(ctx, centerX, centerY, spikes, outerRadius, innerRadius, bgColor, borderColor, borderW, text, textColor, textFont, textFontSize) {
ctx.beginPath();
ctx.moveTo(centerX, centerY - outerRadius); // Start at top point
for (let i = 0; i < spikes; i++) {
const outerAngle = (i / spikes) * 2 * Math.PI - Math.PI / 2; // Offset to start at top
const innerAngle = outerAngle + Math.PI / spikes; // Angle for inner point
ctx.lineTo(centerX + Math.cos(outerAngle) * outerRadius, centerY + Math.sin(outerAngle) * outerRadius);
ctx.lineTo(centerX + Math.cos(innerAngle) * innerRadius, centerY + Math.sin(innerAngle) * innerRadius);
}
ctx.closePath();
ctx.fillStyle = bgColor;
ctx.fill();
if (borderColor && borderW > 0) {
ctx.strokeStyle = borderColor;
ctx.lineWidth = borderW;
ctx.stroke();
}
if (text) {
// Simple text splitting by \n for starburst
const lines = text.toUpperCase().split('\\N'); // Using \N as custom newline
const lineHeight = textFontSize * (lines.length > 1 ? 0.8 : 1); // Reduce line height for multi-line
const totalTextHeight = lines.length * lineHeight;
let textY = centerY - totalTextHeight / 2 + lineHeight / 2 - (lines.length > 1 ? textFontSize*0.1 : 0); // Adjusted centering
lines.forEach((line, index) => {
// Attempt to make text slightly curved or angled if it's on multiple lines for starbursts (complex)
// For now, straight text
drawText(ctx, line, centerX, textY + index * lineHeight, textFont, textFontSize, textColor, textOutlineColor, TEXT_STROKE_WIDTH / 2, 'center', 'middle');
});
}
}
await Promise.all([
loadGoogleFont(FONT_HEADLINE),
loadGoogleFont(FONT_SUB_HEADLINE),
loadGoogleFont(FONT_STARBURST),
loadGoogleFont(FONT_BOTTOM_LINE)
]);
const canvas = document.createElement('canvas');
canvas.width = POSTER_WIDTH;
canvas.height = POSTER_HEIGHT;
const ctx = canvas.getContext('2d');
// 1. Main Border (frameColor)
ctx.fillStyle = frameColor;
ctx.fillRect(0, 0, POSTER_WIDTH, POSTER_HEIGHT);
// 2. Background Fill (backgroundColor)
ctx.fillStyle = backgroundColor;
ctx.fillRect(BORDER_WIDTH, BORDER_WIDTH, POSTER_WIDTH - 2 * BORDER_WIDTH, POSTER_HEIGHT - 2 * BORDER_WIDTH);
// 3. Subtle Background Texture (on top of background color)
ctx.save();
ctx.rect(BORDER_WIDTH, BORDER_WIDTH, POSTER_WIDTH - 2 * BORDER_WIDTH, POSTER_HEIGHT - 2 * BORDER_WIDTH);
ctx.clip(); // Confine texture to the paper area
for (let i = 0; i < 20000; i++) {
const x = Math.random() * POSTER_WIDTH;
const y = Math.random() * POSTER_HEIGHT;
const alpha = Math.random() * 0.1 + 0.02;
const speckSize = Math.random() * 2 + 0.5;
ctx.fillStyle = `rgba(0, 0, 0, ${alpha})`; // Darker specks for aged paper
ctx.fillRect(x, y, speckSize, speckSize);
}
ctx.restore();
// 4. Inner Accent Border Line (thin, accentColor)
ctx.strokeStyle = accentColor;
ctx.lineWidth = 5;
const accentBorderOffset = BORDER_WIDTH + 8;
ctx.strokeRect(accentBorderOffset, accentBorderOffset,
POSTER_WIDTH - 2 * accentBorderOffset, POSTER_HEIGHT - 2 * accentBorderOffset);
// Text and image layout variables
let currentY = BORDER_WIDTH + INNER_MARGIN;
const contentWidth = POSTER_WIDTH - 2 * (BORDER_WIDTH + INNER_MARGIN);
const contentCenterX = POSTER_WIDTH / 2;
// 5. Main Headline
if (mainHeadline) {
const headlineFontSize = 80;
const headlineLineHeight = headlineFontSize * 0.9;
currentY = wrapAndDrawText(ctx, mainHeadline, contentCenterX, currentY, contentWidth * 0.95, headlineLineHeight,
FONT_HEADLINE, headlineFontSize, textColor, textOutlineColor, TEXT_STROKE_WIDTH, 'center');
currentY += headlineLineHeight * 0.3; // Extra spacing after main headline
}
// 6. Secondary Headline
if (secondaryHeadline) {
const subHeadlineFontSize = 45;
const subHeadlineLineHeight = subHeadlineFontSize * 0.9;
currentY = wrapAndDrawText(ctx, secondaryHeadline, contentCenterX, currentY, contentWidth * 0.85, subHeadlineLineHeight,
FONT_SUB_HEADLINE, subHeadlineFontSize, textColor, textOutlineColor, TEXT_STROKE_WIDTH, 'center');
currentY += subHeadlineLineHeight * 0.5; // Extra spacing
}
// 7. Image Area
const imageMaxHeight = POSTER_HEIGHT * 0.45;
const imageTopY = currentY;
const imageSpaceHeight = POSTER_HEIGHT - currentY - (BORDER_WIDTH + INNER_MARGIN + 80); // Reserve space for bottom text
let imgDrawWidth = originalImg.width;
let imgDrawHeight = originalImg.height;
const imgAspectRatio = originalImg.width / originalImg.height;
if (imgDrawWidth > contentWidth) {
imgDrawWidth = contentWidth;
imgDrawHeight = imgDrawWidth / imgAspectRatio;
}
if (imgDrawHeight > imageMaxHeight) {
imgDrawHeight = imageMaxHeight;
imgDrawWidth = imgDrawHeight * imgAspectRatio;
}
if (imgDrawHeight > imageSpaceHeight) { // Final check against remaining space
imgDrawHeight = imageSpaceHeight;
imgDrawWidth = imgDrawHeight * imgAspectRatio;
}
const imgDrawX = contentCenterX - imgDrawWidth / 2;
const imgDrawY = imageTopY + (imageSpaceHeight - imgDrawHeight) / 2; // Vertically center image in its allocated space
if (imgDrawWidth > 0 && imgDrawHeight > 0) {
ctx.save();
// Vintage filter
ctx.filter = 'sepia(0.6) contrast(1.1) brightness(0.95) saturate(0.7)';
ctx.drawImage(originalImg, imgDrawX, imgDrawY, imgDrawWidth, imgDrawHeight);
ctx.restore();
// Image Frame
ctx.strokeStyle = frameColor;
ctx.lineWidth = 12;
ctx.strokeRect(imgDrawX - ctx.lineWidth/2, imgDrawY - ctx.lineWidth/2, imgDrawWidth + ctx.lineWidth, imgDrawHeight + ctx.lineWidth);
ctx.strokeStyle = accentColor;
ctx.lineWidth = 4;
ctx.strokeRect(imgDrawX - ctx.lineWidth/2, imgDrawY - ctx.lineWidth/2, imgDrawWidth + ctx.lineWidth, imgDrawHeight + ctx.lineWidth);
}
currentY = imgDrawY + imgDrawHeight + INNER_MARGIN * 0.5; // Update currentY to below the image
// 8. Starburst Text (if image exists to position relative to)
if (starburstText && imgDrawWidth > 0) {
const starOuterRadius = Math.min(contentWidth * 0.22, 100) ;
const starInnerRadius = starOuterRadius * 0.5;
const starSpikes = 16;
const starFontSize = starOuterRadius * 0.4;
// Position starburst: e.g., bottom-right of image, or centered below
let starX = imgDrawX + imgDrawWidth - starOuterRadius * 0.8;
let starY = imgDrawY + imgDrawHeight - starOuterRadius * 0.7;
// Ensure starburst is within poster visible area (considering main border)
starX = Math.max(starX, BORDER_WIDTH + starOuterRadius);
starX = Math.min(starX, POSTER_WIDTH - BORDER_WIDTH - starOuterRadius);
starY = Math.max(starY, BORDER_WIDTH + starOuterRadius);
starY = Math.min(starY, POSTER_HEIGHT - BORDER_WIDTH - starOuterRadius - 50); // Keep from very bottom
drawStarburst(ctx, starX, starY, starSpikes, starOuterRadius, starInnerRadius,
accentColor, textOutlineColor, TEXT_STROKE_WIDTH, starburstText, textColor,
FONT_STARBURST, starFontSize);
}
// 9. Bottom Line Text
if (bottomLineText) {
const bottomTextFontSize = 35;
const bottomTextLineHeight = bottomTextFontSize * 1;
const bottomTextY = POSTER_HEIGHT - BORDER_WIDTH - INNER_MARGIN - bottomTextLineHeight; // Position from bottom
wrapAndDrawText(ctx, bottomLineText, contentCenterX, bottomTextY, contentWidth, bottomTextLineHeight,
FONT_BOTTOM_LINE, bottomTextFontSize, textColor, textOutlineColor, TEXT_STROKE_WIDTH, 'center');
}
// Optional: Add some decorative stars using text characters
ctx.fillStyle = accentColor;
ctx.font = `30px "${FONT_HEADLINE}"`; // Use one of the loaded fonts
ctx.textAlign = 'center';
// Place a few stars around headlines if desired
// Example: ctx.fillText("★", contentCenterX - contentWidth/2 * 0.6, BORDER_WIDTH + INNER_MARGIN + 30);
// Example: ctx.fillText("★", contentCenterX + contentWidth/2 * 0.6, BORDER_WIDTH + INNER_MARGIN + 30);
return canvas;
}
Apply Changes