You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(originalImg,
playerName = "PLAYER NAME",
teamName = "TEAM NAME",
primaryColor = "#D50032", // A common sports red
secondaryColor = "#002D72", // A common sports blue
textColor = "#FFFFFF",
fontFamily = "Anton", // Default to Anton (sporty, will be loaded from CDN if specified)
playerNumber = "00",
cardYear = new Date().getFullYear().toString()) {
let loadedFontFamily = fontFamily;
// For specific fonts like 'Anton' (our default example), load them.
// Otherwise, assume the fontFamily string (e.g., 'Arial', 'Impact') is a valid CSS font name.
if (fontFamily === "Anton") {
try {
let fontAvailable = false;
// Check if the font is already loaded and available in document.fonts
for (const font of document.fonts) {
// Check family name and if it's loaded. Note: status might not be 'loaded' immediately.
if (font.family === "Anton") { // Simplified check relying on FontFaceSet
if (font.status === 'loaded') { // explicit check if FontFace object reveals status
fontAvailable = true;
break;
}
// For a more robust check, one might rely on document.fonts.check()
// but it needs a size too e.g. document.fonts.check("12px Anton")
}
}
// A simpler way is to rely on document.fonts.check specifically for "Anton".
// If document.fonts.check("1em Anton") is true, it's likely usable.
// However, let's assume if we add it, it can be re-added without issue or checked against a flag.
// To prevent multiple load attempts in a session for the same font:
if (!window._antonFontLoaded) { // Use a global flag or similar mechanism
const antonFont = new FontFace('Anton', 'url(https://fonts.gstatic.com/s/anton/v23/1Ptgg87LROyAm3Kz-C8.woff2) format("woff2")');
await antonFont.load();
document.fonts.add(antonFont);
window._antonFontLoaded = true; // Set flag after successful load
console.log("Anton font loaded successfully.");
} else {
console.log("Anton font previously loaded or loading initiated.");
}
} catch (e) {
console.error("Failed to load Anton font, falling back to sans-serif:", e);
loadedFontFamily = "sans-serif"; // Fallback font
}
}
// For other font families, they will be used directly. Browser handles fallback if not found.
const canvas = document.createElement('canvas');
const cardWidth = 300;
const cardHeight = 420;
canvas.width = cardWidth;
canvas.height = cardHeight;
const ctx = canvas.getContext('2d');
// Card Base
ctx.fillStyle = "#FFFFFF"; // White card base
ctx.fillRect(0, 0, cardWidth, cardHeight);
// Top Angled Banner (Primary Color)
const topBannerHeight = cardHeight * 0.15;
ctx.fillStyle = primaryColor;
ctx.beginPath();
ctx.moveTo(0, 0);
ctx.lineTo(cardWidth, 0);
ctx.lineTo(cardWidth, topBannerHeight * 0.75);
ctx.lineTo(0, topBannerHeight);
ctx.closePath();
ctx.fill();
// Player Image Area Frame (Secondary Color)
const imgFrameMargin = cardWidth * 0.05;
const imgFrameY = topBannerHeight * 0.60;
const imgFrameWidth = cardWidth - 2 * imgFrameMargin;
const imgFrameHeight = cardHeight * 0.55;
ctx.fillStyle = secondaryColor;
ctx.fillRect(imgFrameMargin, imgFrameY, imgFrameWidth, imgFrameHeight);
// Player Image
if (originalImg && originalImg.width > 0 && originalImg.height > 0) {
const imgPadding = 4;
const dImgX = imgFrameMargin + imgPadding;
const dImgY = imgFrameY + imgPadding;
const dImgWidth = imgFrameWidth - 2 * imgPadding;
const dImgHeight = imgFrameHeight - 2 * imgPadding;
const imgAspect = originalImg.width / originalImg.height;
const destAspect = dImgWidth / dImgHeight;
let drawWidth, drawHeight, drawX, drawY;
if (imgAspect > destAspect) { // Image is wider
drawWidth = dImgWidth;
drawHeight = dImgWidth / imgAspect;
drawX = dImgX;
drawY = dImgY + (dImgHeight - drawHeight) / 2; // Center vertically
} else { // Image is taller or same aspect
drawHeight = dImgHeight;
drawWidth = dImgHeight * imgAspect;
drawY = dImgY;
drawX = dImgX + (dImgWidth - drawWidth) / 2; // Center horizontally
}
ctx.drawImage(originalImg, drawX, drawY, drawWidth, drawHeight);
} else {
// Draw a placeholder if image is invalid
ctx.fillStyle = "#CCCCCC";
ctx.fillRect(imgFrameMargin + 4, imgFrameY + 4, imgFrameWidth - 8, imgFrameHeight - 8);
ctx.fillStyle = "#000000";
ctx.textAlign = "center";
ctx.textBaseline = "middle";
ctx.font = `16px sans-serif`;
ctx.fillText("No Image", imgFrameMargin + imgFrameWidth/2, imgFrameY + imgFrameHeight/2);
}
// Bottom Angled Banner (Primary Color) for Name/Team
const bottomBannerTopY = imgFrameY + imgFrameHeight - (cardHeight * 0.1);
const bottomBannerBottomY = cardHeight;
const bottomBannerAngleOffset = cardHeight * 0.05;
ctx.fillStyle = primaryColor;
ctx.beginPath();
ctx.moveTo(0, bottomBannerTopY + bottomBannerAngleOffset);
ctx.lineTo(cardWidth, bottomBannerTopY);
ctx.lineTo(cardWidth, bottomBannerBottomY);
ctx.lineTo(0, bottomBannerBottomY);
ctx.closePath();
ctx.fill();
ctx.lineJoin = 'round'; // For text strokes
// Common font fallback string to append
const FONT_FALLBACK = `, "Arial Nova", Arial, sans-serif`;
// Player Name
ctx.fillStyle = textColor;
const playerNameFontSize = cardWidth * 0.095; // Approx 28.5px for 300px width
ctx.font = `bold ${playerNameFontSize}px "${loadedFontFamily}"${FONT_FALLBACK}`;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
const nameAreaEffectiveYStart = bottomBannerTopY + (bottomBannerAngleOffset / 2);
const reservedFooterHeight = cardHeight * 0.08; // For the year/number ribbon
const nameAreaEffectiveHeight = cardHeight - nameAreaEffectiveYStart - reservedFooterHeight;
const nameY = nameAreaEffectiveYStart + nameAreaEffectiveHeight * 0.35;
ctx.strokeStyle = 'rgba(0,0,0,0.6)'; // Text outline color
ctx.lineWidth = Math.max(1, playerNameFontSize * 0.08); // Relative outline thickness
ctx.strokeText(playerName.toUpperCase(), cardWidth / 2, nameY);
ctx.fillText(playerName.toUpperCase(), cardWidth / 2, nameY);
// Team Name
const teamNameFontSize = cardWidth * 0.055; // Approx 16.5px
ctx.font = `bold ${teamNameFontSize}px "${loadedFontFamily}"${FONT_FALLBACK}`; // Team name also bold
const teamNameY = nameY + playerNameFontSize * 0.55; // Position below player name
ctx.lineWidth = Math.max(1, teamNameFontSize * 0.08);
ctx.strokeText(teamName.toUpperCase(), cardWidth / 2, teamNameY);
ctx.fillText(teamName.toUpperCase(), cardWidth / 2, teamNameY);
// Footer Ribbon for Year and Number (Secondary Color)
const footerRibbonHeight = cardHeight * 0.08;
ctx.fillStyle = secondaryColor;
ctx.fillRect(0, cardHeight - footerRibbonHeight, cardWidth, footerRibbonHeight);
const detailFontSize = cardWidth * 0.05; // Approx 15px
ctx.font = `bold ${detailFontSize}px "${loadedFontFamily}"${FONT_FALLBACK}`;
ctx.fillStyle = textColor; // Text color for details on the secondaryColor ribbon
ctx.textBaseline = 'middle';
// Player Number (no stroke for small text for clarity)
ctx.textAlign = 'left';
const numText = playerNumber.startsWith('#') ? playerNumber : `#${playerNumber}`;
ctx.fillText(numText, cardWidth * 0.05, cardHeight - footerRibbonHeight / 2);
// Card Year (no stroke)
ctx.textAlign = 'right';
ctx.fillText(cardYear, cardWidth * 0.95, cardHeight - footerRibbonHeight / 2);
return canvas;
}
Apply Changes