You can edit the below JavaScript code to customize the image tool.
async function processImage(
originalImg,
bandName = "THE COSMIC VOYAGERS",
eventTitle = "GALACTIC GROOVE TOUR",
dateText = "FRIDAY, NOVEMBER 22ND",
timeText = "DOORS: 7PM - MUSIC: 8PM",
venueName = "THE ORBIT ROOM",
venueAddress = "42 ASTRO LANE, STAR CITY",
supportingActs = "DJ STELLAR, THE QUASARS", // Comma-separated
ticketInfo = "TICKETS @ WWW.COSMICGROOVE.SHOW",
mainFontFamily = "Bebas Neue",
detailFontFamily = "Arial",
primaryColor = "#00FFFF", // Cyan
secondaryColor = "#FFFFFF", // White
backgroundColor = "#101028" // Deep Indigo
) {
// Canvas setup
const canvas = document.createElement('canvas');
const canvasWidth = 600;
const canvasHeight = 900; // Common poster aspect ratio
canvas.width = canvasWidth;
canvas.height = canvasHeight;
const ctx = canvas.getContext('2d');
// Font loading logic
let actualMainFontToUse = mainFontFamily; // This will be the string used in ctx.font
// Special handling for Bebas Neue: attempt to load from CDN
if (mainFontFamily.toLowerCase().replace(/\s/g, '') === 'bebasneue') {
const fontUrl = "https://fonts.gstatic.com/s/bebasneue/v9/JTUSjIg69CK48gW7PXoo9Wlhyw.woff2";
try {
// Use the canonical 'Bebas Neue' name for FontFace an ctx.font
const customFont = new FontFace('Bebas Neue', `url(${fontUrl})`);
await customFont.load();
document.fonts.add(customFont);
actualMainFontToUse = 'Bebas Neue'; // Ensure this exact name is used if loaded
console.log(`Font 'Bebas Neue' loaded successfully.`);
} catch (error) {
console.warn(`Failed to load 'Bebas Neue' font. Using fallback: ${detailFontFamily}. Error:`, error);
actualMainFontToUse = detailFontFamily; // Fallback if loading fails
}
} else {
// For other font names, assume they are system fonts or already loaded.
console.log(`Using system font or pre-loaded font for main font: ${mainFontFamily}.`);
}
// Helper function for drawing wrapped text
function _drawWrappedText(currentCtx, text, x, y, font, color, maxWidth, desiredLineHeight, align = 'center') {
currentCtx.font = font;
currentCtx.fillStyle = color;
currentCtx.textAlign = align;
currentCtx.textBaseline = 'top'; // Consistent baseline for layout math
const words = text.split(' ');
let line = '';
let currentDrawY = y;
let linesDrawn = 0;
for (let n = 0; n < words.length; n++) {
const testLine = line + words[n] + ' ';
// Font must be set before measureText for accuracy
currentCtx.font = font;
const metrics = currentCtx.measureText(testLine);
const testWidth = metrics.width;
if (testWidth > maxWidth && n > 0) { // If line is too wide (and it's not the first word)
currentCtx.fillText(line.trim(), x, currentDrawY); // Draw the previous line
line = words[n] + ' '; // Start new line with current word
currentDrawY += desiredLineHeight;
linesDrawn++;
} else {
line = testLine; // Add word to current line
}
}
currentCtx.fillText(line.trim(), x, currentDrawY); // Draw the last line
linesDrawn++;
// Return the Y coordinate for the start of the next content block (position below this text)
return y + (linesDrawn * desiredLineHeight);
}
// 1. Fill Background
ctx.fillStyle = backgroundColor;
ctx.fillRect(0, 0, canvasWidth, canvasHeight);
// 2. Draw Image (top section)
const imageAreaHeightFraction = 0.60; // Image takes top 60% of poster height
const imageAreaHeight = canvasHeight * imageAreaHeightFraction;
const imageAreaWidth = canvasWidth;
if (originalImg && originalImg.width > 0 && originalImg.height > 0) {
const imgAspectRatio = originalImg.width / originalImg.height;
const targetAreaAspectRatio = imageAreaWidth / imageAreaHeight;
let sx = 0, sy = 0, sWidth = originalImg.width, sHeight = originalImg.height;
// Calculate source image crop to make it "cover" the target area
if (imgAspectRatio > targetAreaAspectRatio) {
// Image is wider than target area: fit height, crop sides of source image
sWidth = originalImg.height * targetAreaAspectRatio;
sHeight = originalImg.height;
sx = (originalImg.width - sWidth) / 2;
sy = 0;
} else {
// Image is taller or same aspect ratio as target area: fit width, crop top/bottom of source image
sHeight = originalImg.width / targetAreaAspectRatio;
sWidth = originalImg.width;
sx = 0;
sy = (originalImg.height - sHeight) / 2;
}
ctx.drawImage(originalImg, sx, sy, sWidth, sHeight, 0, 0, imageAreaWidth, imageAreaHeight);
} else {
// Draw a placeholder if the image is invalid or not loaded
ctx.fillStyle = 'rgba(255,255,255,0.1)'; // Darker placeholder
ctx.fillRect(0, 0, imageAreaWidth, imageAreaHeight);
ctx.fillStyle = secondaryColor;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.font = `20px ${detailFontFamily}`;
ctx.fillText("Image Area", imageAreaWidth / 2, imageAreaHeight / 2);
}
// --- Text Content ---
let currentY = imageAreaHeight; // Initial Y position for text, below the image
const centerX = canvasWidth / 2;
const contentSidePadding = 30; // Horizontal padding for text from canvas edges
const textMaxWidth = canvasWidth - (2 * contentSidePadding);
// Vertical spacing constants
const spaceInitialMargin = 35; // Margin from image to first text item
const spaceAfterTitle = 25;
const spaceBetweenBlocks = 20;
const spaceBetweenLines = 10; // Smaller spacing for items within a block (e.g., date and time)
const spaceBeforeLastBlock = 30;
currentY += spaceInitialMargin;
// Band Name
const bandNameFontSize = 65;
const bandNameLineHeight = bandNameFontSize * 1.05; // Bebas Neue is compact vertically
currentY = _drawWrappedText(ctx, bandName.toUpperCase(), centerX, currentY, `${bandNameFontSize}px ${actualMainFontToUse}`, primaryColor, textMaxWidth, bandNameLineHeight);
currentY += spaceAfterTitle;
// Event Title (optional)
if (eventTitle && eventTitle.trim() !== "") {
const eventTitleFontSize = 28;
const eventTitleLineHeight = eventTitleFontSize * 1.1;
currentY = _drawWrappedText(ctx, eventTitle.toUpperCase(), centerX, currentY, `${eventTitleFontSize}px ${actualMainFontToUse}`, secondaryColor, textMaxWidth, eventTitleLineHeight);
// Decorative line after Event Title
currentY += spaceBetweenLines; // Space before line
ctx.beginPath();
const decorativeLineWidth = 100;
ctx.moveTo(centerX - decorativeLineWidth / 2, currentY);
ctx.lineTo(centerX + decorativeLineWidth / 2, currentY);
ctx.strokeStyle = primaryColor;
ctx.lineWidth = 1.5;
ctx.stroke();
currentY += decorativeLineWidth * 0.05; // Tiny space based on line thickeness
currentY += spaceBetweenBlocks; // Space after line for next text element
}
// Supporting Acts
if (supportingActs && supportingActs.trim() !== "") {
const supActsFontSize = 18;
const supActsLineHeight = supActsFontSize * 1.2;
const actsPrefix = "FEATURING:";
currentY = _drawWrappedText(ctx, actsPrefix, centerX, currentY, `${supActsFontSize}px ${detailFontFamily}`, primaryColor, textMaxWidth, supActsLineHeight);
currentY += spaceBetweenLines / 2; // Small gap after prefix
const acts = supportingActs.split(',').map(act => act.trim().toUpperCase());
for (const act of acts) {
if (act) { // Ensure act is not an empty string from " ,, "
currentY = _drawWrappedText(ctx, act, centerX, currentY, `${supActsFontSize}px ${detailFontFamily}`, secondaryColor, textMaxWidth, supActsLineHeight);
currentY += spaceBetweenLines / 3; // Tighter spacing between listed acts
}
}
// Remove last small gap to prevent too much space if it's the last act
if (acts.length > 0) currentY -= spaceBetweenLines / 3;
currentY += spaceBetweenBlocks;
}
// Date & Time
const dateTimeFontSize = 20;
const dateTimeLineHeight = dateTimeFontSize * 1.2;
currentY = _drawWrappedText(ctx, dateText.toUpperCase(), centerX, currentY, `${dateTimeFontSize}px ${detailFontFamily}`, secondaryColor, textMaxWidth, dateTimeLineHeight);
currentY += spaceBetweenLines / 2 ;
currentY = _drawWrappedText(ctx, timeText.toUpperCase(), centerX, currentY, `${dateTimeFontSize}px ${detailFontFamily}`, secondaryColor, textMaxWidth, dateTimeLineHeight);
currentY += spaceBetweenBlocks;
// Venue
const venueNameFontSize = 20;
const venueNameLineHeight = venueNameFontSize * 1.2;
const venueAddressFontSize = 16;
const venueAddressLineHeight = venueAddressFontSize * 1.2;
currentY = _drawWrappedText(ctx, venueName.toUpperCase(), centerX, currentY, `${venueNameFontSize}px ${detailFontFamily}`, secondaryColor, textMaxWidth, venueNameLineHeight);
currentY += spaceBetweenLines / 2;
currentY = _drawWrappedText(ctx, venueAddress.toUpperCase(), centerX, currentY, `${venueAddressFontSize}px ${detailFontFamily}`, secondaryColor, textMaxWidth, venueAddressLineHeight);
// Ticket Info (flowed at the end)
// Ensure there's enough space; if not, it might get clipped or overlap with design elements fixed to bottom
currentY += spaceBeforeLastBlock;
const ticketFontSize = 16;
const ticketLineHeight = ticketFontSize * 1.2;
// Check if currentY is too close to the bottom for ticket info, adjust if needed (optional advanced)
// For this template, fixed height means potential clipping if too much text.
if (currentY < canvasHeight - (ticketLineHeight + contentSidePadding)) { // Basic check for some space
_drawWrappedText(ctx, ticketInfo.toUpperCase(), centerX, currentY, `${ticketFontSize}px ${detailFontFamily}`, primaryColor, textMaxWidth, ticketLineHeight);
} else { // Fallback: try to put it at the very bottom if content ran too long
let fallbackTicketY = canvasHeight - contentSidePadding - ticketLineHeight;
// Potentially calculate number of lines for ticket info to position it more precisely at bottom
_drawWrappedText(ctx, ticketInfo.toUpperCase(), centerX, fallbackTicketY, `${ticketFontSize}px ${detailFontFamily}`, primaryColor, textMaxWidth, ticketLineHeight);
}
return canvas;
}
Free Image Tool Creator
Can't find the image tool you're looking for? Create one based on your own needs now!
The Image Concert Gig Poster Template Creator is an online tool designed to help users create visually appealing concert gig posters. Users can upload an image and customize various elements such as band name, event title, date, time, venue details, and supporting acts. This tool allows for the selection of primary and secondary colors, as well as font choices to personalize the poster’s appearance. It is ideal for musicians, event organizers, and promoters looking to create professional-looking promotional materials for live music events, festivals, and concerts.