Please bookmark this page to avoid losing your image tool!

Image Concert Gig Poster Template Creator

(Free & Supports Bulk Upload)

Drag & drop your images here or

The result will appear here...
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!

Description

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.

Leave a Reply

Your email address will not be published. Required fields are marked *