You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(
originalImg,
characterName = "FIGHTER",
playerName = "PLAYER 1",
backgroundColor = "rgba(30, 30, 70, 1)", // Dark blueish-purple
frameColor = "rgba(200, 200, 0, 1)", // Yellow/Gold
nameplateColor = "rgba(0, 0, 0, 0.75)", // Semi-transparent black
textColor = "#FFFFFF",
fontFamily = "Bangers, Impact, Arial Black, sans-serif",
titleText = "CHOOSE YOUR FIGHTER!",
titleColor = "#FFD700" // Gold, matches frame accents
) {
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
const canvasWidth = 800;
const canvasHeight = 600;
canvas.width = canvasWidth;
canvas.height = canvasHeight;
// --- Font Loading ---
// Dynamically load "Bangers" font from CDN if it's the primary font.
const primaryFontName = fontFamily.split(',')[0].trim().replace(/['"]/g, ''); // Get the first font name
if (primaryFontName === "Bangers") {
let fontIsRegistered = false;
for (const f of document.fonts.values()) {
if (f.family === primaryFontName) {
fontIsRegistered = true;
break;
}
}
if (!fontIsRegistered) {
// Font is not yet in document.fonts. Try to load and add it.
try {
const fontFace = new FontFace(
primaryFontName,
'url(https://fonts.gstatic.com/s/bangers/v24/FeVQS0BTqb0h60ACL5k.woff2)' // Direct URL to Bangers woff2
);
document.fonts.add(fontFace); // Add to the set; its status will be 'unloaded' initially
await fontFace.load(); // Fetch and load the font resource
// console.log(`${primaryFontName} font loaded and added to document.fonts.`);
} catch (e) {
console.warn(`Could not load the font "${primaryFontName}":`, e);
// If loading fails, the browser will use fallback fonts from the fontFamily string.
}
} else {
// Font is already registered (e.g., by a previous call).
// We still await its readiness to ensure it's rendered.
try {
await document.fonts.load(`1em "${primaryFontName}"`); // Check/wait for font by name
// console.log(`${primaryFontName} font was already registered, ensured it's loaded.`);
} catch (e) {
// console.warn(`Error while waiting for already registered font "${primaryFontName}":`, e);
}
}
}
// For any other primaryFontName, it's assumed to be a system font or loaded by other means.
// The full fontFamily string (e.g., "Bangers, Impact, ...") will be used for rendering,
// allowing browser to pick fallbacks if "Bangers" (or other primary) isn't available.
// --- End Font Loading ---
// Set common text baseline for consistency where appropriate
ctx.textBaseline = 'middle';
// 1. Draw background
ctx.fillStyle = backgroundColor;
ctx.fillRect(0, 0, canvasWidth, canvasHeight);
// Optional: Add a subtle vignette for depth
const vignetteGradient = ctx.createRadialGradient(
canvasWidth / 2, canvasHeight / 2, Math.min(canvasWidth, canvasHeight) / 3.5, // Inner circle
canvasWidth / 2, canvasHeight / 2, Math.max(canvasWidth, canvasHeight) / 1.8 // Outer circle
);
vignetteGradient.addColorStop(0, "rgba(0,0,0,0)"); // Transparent center
vignetteGradient.addColorStop(1, "rgba(0,0,0,0.35)"); // Darker edges
ctx.fillStyle = vignetteGradient;
ctx.fillRect(0, 0, canvasWidth, canvasHeight);
// 2. Draw Title
const titleY = 70; // Vertical position of the title's middle
ctx.font = `bold 48px ${fontFamily}`;
ctx.fillStyle = titleColor;
ctx.textAlign = 'center';
ctx.fillText(titleText, canvasWidth / 2, titleY);
ctx.strokeStyle = 'rgba(0,0,0,0.8)'; // Dark stroke for contrast
ctx.lineWidth = 3;
ctx.strokeText(titleText, canvasWidth / 2, titleY);
// 3. Draw Player Indicator (e.g., "PLAYER 1" at top-left)
ctx.font = `bold 24px ${fontFamily}`;
ctx.fillStyle = textColor;
ctx.textAlign = 'left';
ctx.textBaseline = 'top'; // Align to top-left corner for easier positioning
const playerIndicatorX = 30;
const playerIndicatorY = 30;
ctx.fillText(playerName, playerIndicatorX, playerIndicatorY);
ctx.strokeStyle = 'rgba(0,0,0,0.7)';
ctx.lineWidth = 1.5;
ctx.strokeText(playerName, playerIndicatorX, playerIndicatorY);
ctx.textBaseline = 'middle'; // Reset to common baseline
// 4. Character Art Frame
const frameWidth = 320;
const frameHeight = 420;
const frameX = (canvasWidth - frameWidth) / 2;
// Position frame below the title text (title is 48px high, centered at titleY)
const frameY = titleY + (48 / 2) + 35; // 48/2 is half title height, 35 is spacing
const frameBorderThickness = 12;
// Draw main frame border background
ctx.fillStyle = frameColor;
ctx.fillRect(
frameX - frameBorderThickness,
frameY - frameBorderThickness,
frameWidth + (2 * frameBorderThickness),
frameHeight + (2 * frameBorderThickness)
);
// Simple bevel effect for the frame border
const bevelOffset = frameBorderThickness / 2;
ctx.fillStyle = "rgba(0,0,0,0.3)"; // Darker shade for top/left inner edges of border
ctx.fillRect(frameX - frameBorderThickness, frameY - frameBorderThickness, frameWidth + (2 * frameBorderThickness), bevelOffset); // Top shadow
ctx.fillRect(frameX - frameBorderThickness, frameY - frameBorderThickness + bevelOffset, bevelOffset, frameHeight + (2 * frameBorderThickness) - bevelOffset); // Left shadow
ctx.fillStyle = "rgba(255,255,255,0.25)"; // Lighter shade for bottom/right inner edges of border
ctx.fillRect(frameX - frameBorderThickness + bevelOffset, frameY + frameHeight + frameBorderThickness - bevelOffset, frameWidth + (2 * frameBorderThickness) - bevelOffset, bevelOffset); // Bottom highlight
ctx.fillRect(frameX + frameWidth + frameBorderThickness - bevelOffset, frameY - frameBorderThickness + bevelOffset, bevelOffset, frameHeight + (2*frameBorderThickness) - (2*bevelOffset)); // Right highlight
// 5. Character Image (Aspect Fill / "Cover" Behavior)
ctx.save();
ctx.beginPath();
ctx.rect(frameX, frameY, frameWidth, frameHeight); // Clipping path for the image
ctx.clip();
const imgIntrinsicWidth = originalImg.width; // For Image objects, .width is intrinsic
const imgIntrinsicHeight = originalImg.height;
if (imgIntrinsicWidth > 0 && imgIntrinsicHeight > 0) {
const imgAspect = imgIntrinsicWidth / imgIntrinsicHeight;
const frameAspect = frameWidth / frameHeight;
let drawWidth, drawHeight, dx, dy;
if (imgAspect > frameAspect) { // Image is wider than frame slot (fit height, crop width)
drawHeight = frameHeight;
drawWidth = imgAspect * drawHeight;
dx = frameX - (drawWidth - frameWidth) / 2; // Center horizontally
dy = frameY;
} else { // Image is taller or same aspect (fit width, crop height)
drawWidth = frameWidth;
drawHeight = drawWidth / imgAspect;
dx = frameX;
dy = frameY - (drawHeight - frameHeight) / 2; // Center vertically
}
ctx.drawImage(originalImg, dx, dy, drawWidth, drawHeight);
} else {
// Fallback if image has no dimensions (e.g., not loaded, though parameter implies loaded)
ctx.fillStyle = 'rgba(50,50,50,1)'; // Dark grey placeholder
ctx.fillRect(frameX, frameY, frameWidth, frameHeight);
ctx.fillStyle = 'rgba(200,200,200,1)';
ctx.textAlign = 'center';
ctx.font = `20px ${fontFamily.split(',').slice(1).join(',') || "sans-serif"}`; // Use fallback fonts
ctx.fillText("Image N/A", frameX + frameWidth / 2, frameY + frameHeight / 2);
}
ctx.restore(); // Remove clipping path
// Optional: Add subtle inner border/highlight on the image rectangle
ctx.strokeStyle = "rgba(255, 255, 255, 0.15)";
ctx.lineWidth = 3;
ctx.strokeRect(frameX, frameY, frameWidth, frameHeight);
// 6. Nameplate for Character Name
const nameplateHeight = 65;
// Position nameplate to slightly overlap the bottom of the character frame
const nameplateY = frameY + frameHeight - (nameplateHeight / 2.2);
const nameplateX = frameX - frameBorderThickness; // Align with outer frame edge
const nameplateWidth = frameWidth + (2 * frameBorderThickness); // Match outer frame width
ctx.fillStyle = nameplateColor;
ctx.fillRect(nameplateX, nameplateY, nameplateWidth, nameplateHeight);
// Border for nameplate, matching the main frame's color
ctx.strokeStyle = frameColor;
ctx.lineWidth = 2.5;
ctx.strokeRect(nameplateX, nameplateY, nameplateWidth, nameplateHeight);
// Character Name Text (auto-shrinks if too long)
ctx.fillStyle = textColor;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle'; // Vertically center text in nameplate
let currentFontSize = 40; // Initial font size for character name
const maxCharNameWidth = nameplateWidth - 30; // Max width for text (15px padding each side)
ctx.font = `bold ${currentFontSize}px ${fontFamily}`;
// Reduce font size until text fits within the nameplate
while (ctx.measureText(characterName).width > maxCharNameWidth && currentFontSize > 12) {
currentFontSize--;
ctx.font = `bold ${currentFontSize}px ${fontFamily}`;
}
const charNameY = nameplateY + nameplateHeight / 2;
ctx.fillText(characterName, nameplateX + nameplateWidth / 2, charNameY);
ctx.strokeStyle = 'rgba(0,0,0,0.9)'; // Dark stroke for readability
ctx.lineWidth = currentFontSize > 20 ? 1.5 : 1; // Adjust stroke based on final font size
ctx.strokeText(characterName, nameplateX + nameplateWidth / 2, charNameY);
return canvas;
}
Apply Changes