You can edit the below JavaScript code to customize the image tool.
Apply Changes
async function processImage(originalImg,
titleText = "SUPER ADVENTURES",
issueNumberText = "#1",
publicationText = "YOUR COMICS",
taglineText = "ACTION-PACKED!",
priceText = "$3.99 US",
authorText = "STORY & ART: YOU",
monthYearText = "JAN 2025",
mainFontFamily = "Bangers", // Default to 'Bangers', which we'll try to load
secondaryFontFamily = "Arial",
primaryColor = "#FFD700", // Gold/Yellow for boxes/highlights
secondaryColor = "#FF0000", // Red for accents/banners
textColorOnPrimary = "black", // Text color on primaryColor background
textColorOnSecondary = "white", // Text color on secondaryColor background
titleTextColor = "white",
titleOutlineColor = "black"
) {
const canvasWidth = 600;
const canvasHeight = 923; // Approx 6.625" x 10.1875" comic ratio
const canvas = document.createElement('canvas');
canvas.width = canvasWidth;
canvas.height = canvasHeight;
const ctx = canvas.getContext('2d');
// Font handling: Try to load 'Bangers' if specified as mainFontFamily
let currentMainFontFamily = mainFontFamily; // Variable to store potentially modified font name
if (mainFontFamily.toLowerCase() === 'bangers') {
const bangersFontUrl = 'https://fonts.gstatic.com/s/bangers/v23/FeVQS0BTqb0h60ACH5FQ2Ixi.woff2';
// Check if 'Bangers' font is already loaded/available using the exact family name.
if (!document.fonts.check(`12px Bangers`)) {
try {
const font = new FontFace('Bangers', `url(${bangersFontUrl})`); // Use 'Bangers' as the family name
await font.load();
document.fonts.add(font);
currentMainFontFamily = 'Bangers'; // Ensure 'Bangers' (with correct casing) is used
console.log('Bangers font loaded.');
} catch (e) {
console.error('Failed to load Bangers font:', e);
currentMainFontFamily = "Impact"; // Fallback if Bangers loading fails
}
} else {
currentMainFontFamily = 'Bangers'; // Already loaded, ensure correct name for CSS
}
}
// At this point, currentMainFontFamily is user's custom font, 'Bangers' (if loaded/requested), or 'Impact' (fallback for Bangers).
// Default drawing styles
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
// Background (mostly covered by image)
ctx.fillStyle = 'lightgray';
ctx.fillRect(0, 0, canvasWidth, canvasHeight);
// Define image area (leaving space for header and a defined footer bar)
const headerHeight = 70; // Reduced slightly
const actualFooterAreaHeight = 70; // Space at the very bottom for barcode, price, seal
const imageAreaX = 0;
const imageAreaY = headerHeight;
const imageAreaWidth = canvasWidth;
const imageAreaHeight = canvasHeight - headerHeight - actualFooterAreaHeight;
// Draw originalImg (scaled and cropped to cover imageArea)
let sourceX = 0, sourceY = 0, sourceWidth = originalImg.width, sourceHeight = originalImg.height;
const imgAspect = originalImg.width / originalImg.height;
const destAspect = imageAreaWidth / imageAreaHeight;
if (imgAspect > destAspect) { // Image wider than area: fit height, crop width
sourceWidth = originalImg.height * destAspect;
sourceX = (originalImg.width - sourceWidth) / 2;
} else { // Image taller or same aspect: fit width, crop height
sourceHeight = originalImg.width / destAspect;
sourceY = (originalImg.height - sourceHeight) / 2;
}
ctx.drawImage(originalImg, sourceX, sourceY, sourceWidth, sourceHeight,
imageAreaX, imageAreaY, imageAreaWidth, imageAreaHeight);
// Helper function for text with outline
function drawTextWithOutline(text, x, y, fillStyle, strokeStyle, outlineThickness, fontToUse) {
if(fontToUse) ctx.font = fontToUse;
ctx.strokeStyle = strokeStyle;
ctx.lineWidth = outlineThickness * 2; // Visual thickness of outlineThickness
ctx.lineJoin = 'round';
ctx.miterLimit = 2;
ctx.strokeText(text, x, y);
ctx.fillStyle = fillStyle;
ctx.fillText(text, x, y);
}
// --- Header Elements ---
ctx.fillStyle = primaryColor;
ctx.fillRect(0, 0, canvasWidth, headerHeight); // Header background
ctx.textAlign = 'left';
ctx.textBaseline = 'middle';
ctx.fillStyle = textColorOnPrimary;
// Quote font family names if they might contain spaces or are not generic keywords
const pubFont = `bold 22px "${secondaryFontFamily}", sans-serif`;
ctx.fillText(publicationText.toUpperCase(), 15, headerHeight * 0.5 - 10); // Slightly adjusted Y
ctx.textAlign = 'right';
const issueFont = `bold 18px "${secondaryFontFamily}", sans-serif`;
ctx.fillText(issueNumberText.toUpperCase(), canvasWidth - 15, headerHeight * 0.5 - 10);
const dateFont = `14px "${secondaryFontFamily}", sans-serif`;
ctx.fillText(monthYearText.toUpperCase(), canvasWidth - 15, headerHeight * 0.5 + 12);
// --- Main Title ---
const titleBaseY = imageAreaY + 60; // Y position for first line of title
const titleLineHeight = 70;
const titleLines = titleText.toUpperCase().split('\n'); // User can use \n for line breaks
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
titleLines.forEach((line, index) => {
const titleY = titleBaseY + (index * titleLineHeight);
drawTextWithOutline(line, canvasWidth / 2, titleY,
titleTextColor, titleOutlineColor, 3.5, // Outline thickness
`bold 80px "${currentMainFontFamily}", Impact, sans-serif`); // Title font size
});
// --- Tagline (optional, in a banner) ---
if (taglineText && taglineText.trim() !== "") {
const taglineBannerHeight = 35;
let taglineBannerY = imageAreaY + imageAreaHeight - taglineBannerHeight - 15; // Positioned above footer elements
ctx.fillStyle = secondaryColor; // Banner color
ctx.beginPath();
const bannerNotch = 15; // Size of the V-cut in banner ends
ctx.moveTo(0, taglineBannerY);
ctx.lineTo(canvasWidth, taglineBannerY);
ctx.lineTo(canvasWidth - bannerNotch, taglineBannerY + taglineBannerHeight / 2);
ctx.lineTo(canvasWidth, taglineBannerY + taglineBannerHeight);
ctx.lineTo(0, taglineBannerY + taglineBannerHeight);
ctx.lineTo(bannerNotch, taglineBannerY + taglineBannerHeight / 2);
ctx.closePath();
ctx.fill();
drawTextWithOutline(taglineText.toUpperCase(), canvasWidth / 2, taglineBannerY + taglineBannerHeight / 2,
textColorOnSecondary, 'black', 1.5,
`bold 20px "${currentMainFontFamily}", Impact, sans-serif`);
}
// --- Footer Area (Bottom actualFooterAreaHeight px of canvas) ---
// This area can have its own background or elements are placed directly.
// For example, draw bottom bar for footer:
// ctx.fillStyle = primaryColor;
// ctx.fillRect(0, canvasHeight - actualFooterAreaHeight, canvasWidth, actualFooterAreaHeight);
// Barcode placeholder
const barcodeWidth = 80;
const barcodeHeight = 45;
const barcodeX = 15; // Left padding
// Vertically center in the actualFooterAreaHeight, at the bottom of canvas
const barcodeY = canvasHeight - actualFooterAreaHeight + (actualFooterAreaHeight - barcodeHeight) / 2;
ctx.fillStyle = 'white';
ctx.fillRect(barcodeX, barcodeY, barcodeWidth, barcodeHeight);
ctx.strokeStyle = 'black';
ctx.lineWidth = 0.5; // Thinner lines for barcode
ctx.strokeRect(barcodeX, barcodeY, barcodeWidth, barcodeHeight);
for (let i = 2; i < barcodeWidth - 2; i += (Math.random() * 2 + 1.5)) {
ctx.beginPath();
ctx.moveTo(barcodeX + i, barcodeY + 2);
ctx.lineTo(barcodeX + i, barcodeY + barcodeHeight - 2 - (Math.random() * 6));
ctx.stroke();
}
ctx.fillStyle = 'black';
ctx.font = `5px "${secondaryFontFamily}", sans-serif`;
ctx.textAlign = 'center';
ctx.textBaseline = 'alphabetic';
ctx.fillText("0123456789012", barcodeX + barcodeWidth/2, barcodeY + barcodeHeight - 2);
// Price Box (e.g., circle)
const priceBoxRadius = 30;
const priceBoxCX = canvasWidth - priceBoxRadius - 15; // Right padding
const priceBoxCY = barcodeY + barcodeHeight/2; // Align vertically with barcode center
ctx.fillStyle = primaryColor;
ctx.beginPath();
ctx.arc(priceBoxCX, priceBoxCY, priceBoxRadius, 0, Math.PI * 2);
ctx.fill();
ctx.strokeStyle = 'black';
ctx.lineWidth = 2;
ctx.stroke();
drawTextWithOutline(priceText, priceBoxCX, priceBoxCY,
textColorOnPrimary, 'black', 1,
`bold 20px "${currentMainFontFamily}", Impact, sans-serif`);
// "Comics Code Authority" seal placeholder
const sealWidth = 40;
const sealHeight = sealWidth * 0.75; // Adjusted aspect ratio
const sealX = barcodeX + barcodeWidth + 10; // Positioned to the right of barcode
const sealY = barcodeY + (barcodeHeight - sealHeight)/2; // Vertically align with barcode
ctx.fillStyle = 'white';
ctx.strokeStyle = 'black';
ctx.lineWidth = 0.5;
ctx.fillRect(sealX, sealY, sealWidth, sealHeight);
ctx.strokeRect(sealX, sealY, sealWidth, sealHeight);
ctx.fillStyle = 'black';
const sealFont = `bold 6px "${secondaryFontFamily}", sans-serif`; // Slightly larger font for seal
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
const sealText = "APPROVED\nBY THE\nCODE";
const sealLines = sealText.split('\n');
const sealLineHeight = 6;
sealLines.forEach((line, i) => {
ctx.fillText(line, sealX + sealWidth/2, sealY + sealHeight/2 + (i - (sealLines.length-1)/2) * sealLineHeight);
});
// Author Text
ctx.textAlign = 'center';
ctx.textBaseline = 'bottom'; // Align text to very bottom edge
const authorY = canvasHeight - 5; // 5px from canvas bottom
drawTextWithOutline(authorText.toUpperCase(), canvasWidth / 2, authorY,
'white', 'black', 1, // Thinner outline for small text
`bold 12px "${secondaryFontFamily}", sans-serif`);
return canvas;
}
Apply Changes