You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(originalImg,
title = "COSMIC ECHOES",
tagline = "Beyond the stars, silence screams.",
releaseInfo = "IN CINEMAS SUMMER 2042",
creditsLine1 = "A FILM BY AI VISIONARY",
creditsLine2 = "STARRING CYBERIA, REX-0, NOVA",
primaryColor = "#00FFFF", // Cyan for main text, glow, tint
secondaryColor = "#FFFFFF", // White for supporting text
accentColor = "#FF6600", // Orange/Red for highlights, secondary glow
backgroundColor = "#0A0A1A", // Deep dark blue/purple background
fontName = "Orbitron" // Sci-fi font from Google Fonts
) {
const FONT_MAP = {
"Orbitron": "https://fonts.gstatic.com/s/orbitron/v25/yMJRMIjw5hLppbssPE_J4jQ_cg.woff2", // Regular 400
"Audiowide": "https://fonts.gstatic.com/s/audiowide/v17/l7gdbjpo0XMFxomM9GPhEHXyyNo.woff2",
"Russo One": "https://fonts.gstatic.com/s/russoone/v14/Z9XUDmZRWg6M1cHscdtpPrl0_w.woff2",
"Bungee": "https://fonts.gstatic.com/s/bungee/v10/N0bU2SZBIuF2PU_ECn50_g.woff2",
"Aldrich": "https://fonts.gstatic.com/s/aldrich/v17/MCoMzB_o2K0Uj3M28j_xHw.woff2",
"Geostar Fill": "https://fonts.gstatic.com/s/geostarfill/v20/AMW0WwTuJMhV6_uXp7_tBXV0Z0DE9Q.woff2",
"Electrolize": "https://fonts.gstatic.com/s/electrolize/v14/cIf5Ma1dtE0zSiGSiED7AUEGsoYA.woff2"
};
let actualFontName = fontName;
if (FONT_MAP[fontName]) {
try {
if (!document.fonts.check(`12px "${fontName}"`)) { // Check if font is already loaded/available
const fontFace = new FontFace(fontName, `url(${FONT_MAP[fontName]})`);
await fontFace.load();
document.fonts.add(fontFace);
}
} catch (e) {
console.error(`Font ${fontName} (${FONT_MAP[fontName]}) failed to load:`, e);
actualFontName = "Arial"; // Fallback font
}
} else {
if (!document.fonts.check(`12px "${fontName}"`)) { // Check for system font
actualFontName = "Arial"; // Fallback font if not in map and not system available
}
}
const canvas = document.createElement('canvas');
const posterWidth = 800;
const posterHeight = 1200;
canvas.width = posterWidth;
canvas.height = posterHeight;
const ctx = canvas.getContext('2d');
// Fill background (acts as base if image has transparency or for edges)
ctx.fillStyle = backgroundColor;
ctx.fillRect(0, 0, posterWidth, posterHeight);
// --- Image Processing ---
// Calculate cropping/scaling for the originalImg to fill poster dimensions
const canvasAspect = posterWidth / posterHeight;
const imgAspect = originalImg.width / originalImg.height;
let sx = 0, sy = 0, sWidth = originalImg.width, sHeight = originalImg.height;
if (imgAspect > canvasAspect) { // Image is wider than canvas aspect ratio
sWidth = originalImg.height * canvasAspect;
sx = (originalImg.width - sWidth) / 2;
} else if (imgAspect < canvasAspect) { // Image is taller than canvas aspect ratio
sHeight = originalImg.width / canvasAspect;
sy = (originalImg.height - sHeight) / 2;
}
// Create a temporary canvas for image processing steps
const imageProcCanvas = document.createElement('canvas');
imageProcCanvas.width = posterWidth;
imageProcCanvas.height = posterHeight;
const ipCtx = imageProcCanvas.getContext('2d');
// A. Draw image with filters to the temporary processing canvas
ipCtx.filter = 'grayscale(70%) contrast(130%) brightness(85%)';
ipCtx.drawImage(originalImg, sx, sy, sWidth, sHeight, 0, 0, posterWidth, posterHeight);
ipCtx.filter = 'none'; // Reset filter for ipCtx
// B. Apply color tint to the filtered image on the temporary processing canvas
ipCtx.globalCompositeOperation = 'color'; // 'color' blend mode is good for tinting
ipCtx.fillStyle = primaryColor;
ipCtx.globalAlpha = 0.4; // Adjust tint strength (0.3-0.5 is often good)
ipCtx.fillRect(0, 0, posterWidth, posterHeight);
ipCtx.globalAlpha = 1.0; // Reset alpha
ipCtx.globalCompositeOperation = 'source-over'; // Reset composite operation
// Draw the processed image from temp canvas to the main canvas
ctx.drawImage(imageProcCanvas, 0, 0, posterWidth, posterHeight);
// Add Vignette effect on the main canvas
const vignetteGradient = ctx.createRadialGradient(
posterWidth / 2, posterHeight / 2, Math.min(posterWidth, posterHeight) * 0.25, // Inner circle starts transparent
posterWidth / 2, posterHeight / 2, Math.max(posterWidth, posterHeight) * 0.8 // Outer circle fades to dark
);
vignetteGradient.addColorStop(0, 'rgba(0,0,0,0)');
vignetteGradient.addColorStop(1, 'rgba(0,0,0,0.9)'); // Adjust darkness of vignette
ctx.fillStyle = vignetteGradient;
ctx.fillRect(0, 0, posterWidth, posterHeight);
// --- Text Drawing Helper ---
function drawTextWithGlow(text, x, y, font, color, glowColor, glowBlur, align = 'center', baseline = 'middle') {
ctx.font = font;
ctx.fillStyle = color;
ctx.textAlign = align;
ctx.textBaseline = baseline;
if (glowColor && glowBlur > 0) {
ctx.shadowColor = glowColor;
ctx.shadowBlur = glowBlur;
ctx.shadowOffsetX = 0;
ctx.shadowOffsetY = 0;
}
ctx.fillText(text, x, y);
// Reset shadow for subsequent drawing operations not needing it
ctx.shadowColor = 'transparent';
ctx.shadowBlur = 0;
ctx.shadowOffsetX = 0;
ctx.shadowOffsetY = 0;
}
// --- Text Elements ---
// Main Title
const titleBaseFontSize = 100; // Base font size for a medium-length title
// Estimate max characters that fit with base font size. 0.55 is a heuristic for character width to font size ratio.
const estimatedMaxChars = posterWidth * 0.9 / (titleBaseFontSize * 0.55);
const titleFontSize = title.length > estimatedMaxChars ? (posterWidth * 0.9 / (title.length * 0.55)) : titleBaseFontSize;
drawTextWithGlow(
title.toUpperCase(),
posterWidth / 2,
posterHeight * 0.22, // Y position for title
`bold ${titleFontSize}px "${actualFontName}", sans-serif`,
primaryColor, // Main text color
accentColor, // Glow color
20 // Glow blur radius
);
// Tagline
const taglineFontSize = Math.max(18, titleFontSize * 0.22); // Scale tagline with title
drawTextWithGlow(
tagline,
posterWidth / 2,
posterHeight * 0.22 + titleFontSize * 0.7, // Position below title
`${taglineFontSize}px "${actualFontName}", sans-serif`,
secondaryColor,
primaryColor,
10
);
// Release Information (e.g., "COMING SOON")
const releaseInfoFontSize = Math.max(22, titleFontSize * 0.25);
drawTextWithGlow(
releaseInfo.toUpperCase(),
posterWidth / 2,
posterHeight * 0.78, // Position lower on the poster
`bold ${releaseInfoFontSize}px "${actualFontName}", sans-serif`,
accentColor,
secondaryColor,
15
);
// --- Credits Block (drawn from bottom-up) ---
const creditsFontSize = 14;
const billingFontSize = 9;
const creditLineHeight = creditsFontSize + 6; // Spacing for main credit lines
const billingLineHeight = billingFontSize + 4; // Spacing for billing block lines
let currentCreditY = posterHeight - 25; // Starting Y from the bottom edge
// Billing Block (small text, multiple lines)
// Example generic billing text
const billingText = `FILM EDITING BY NANO CUT PRO PRODUCTION DESIGN UNIT 731 COSTUMES BY CHRONO THREADS MUSIC COMPOSED & PERFORMED BY THE GALACTIC SYNTH ORCHESTRA VISUAL EFFECTS SUPERVISED BY HOLO REALITY STUDIOS SOUND DESIGN TEAM SONIC VOID`;
ctx.font = `${billingFontSize}px "${actualFontName}", sans-serif`; // Set font once for width measurement
const maxBillingWidth = posterWidth * 0.7; // Max width for billing lines
const billingWords = billingText.toUpperCase().split(/\s+/);
let billingLine = "";
const billingLines = [];
for(let word of billingWords) {
const testLine = billingLine + word + " ";
if (ctx.measureText(testLine).width > maxBillingWidth && billingLine.length > 0) {
billingLines.push(billingLine.trim());
billingLine = word + " ";
} else {
billingLine = testLine;
}
}
billingLines.push(billingLine.trim()); // Add the last line
for (let i = billingLines.length - 1; i >= 0; i--) { // Draw from last line to first
drawTextWithGlow(billingLines[i], posterWidth / 2, currentCreditY, `${billingFontSize}px "${actualFontName}", sans-serif`, secondaryColor, null, 0, 'center', 'bottom');
currentCreditY -= billingLineHeight;
}
currentCreditY -= 10; // Add padding between billing block and main credits
// Starring Line (creditsLine2)
ctx.font = `italic ${creditsFontSize}px "${actualFontName}", sans-serif`; // Set font for width measurement
const starsText = creditsLine2;
const starWords = starsText.split(' '); // Simple split by space for names
let starLine = "";
const starLines = [];
const maxCreditWidth = posterWidth * 0.85; // Max width for star lines
for(let word of starWords) {
const testStarLine = starLine + word + " ";
if (ctx.measureText(testStarLine).width > maxCreditWidth && starLine.length > 0) {
starLines.push(starLine.trim());
starLine = word + " ";
} else {
starLine = testStarLine;
}
}
starLines.push(starLine.trim()); // Add the last line
for (let i = starLines.length - 1; i >= 0; i--) {
drawTextWithGlow(starLines[i], posterWidth / 2, currentCreditY, `italic ${creditsFontSize}px "${actualFontName}", sans-serif`, secondaryColor, null, 0, 'center', 'bottom');
currentCreditY -= creditLineHeight;
}
currentCreditY -= 5; // Padding
// Director Line (creditsLine1)
drawTextWithGlow(creditsLine1.toUpperCase(), posterWidth / 2, currentCreditY, `bold ${creditsFontSize}px "${actualFontName}", sans-serif`, secondaryColor, null, 0, 'center', 'bottom');
return canvas;
}
Apply Changes