Please bookmark this page to avoid losing your image tool!

Image Superhero Movie Poster Maker

(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,
    titleText = "THE PIXEL HERO",
    taglineText = "IN A WORLD OF CODE, ONE IMAGE STANDS OUT",
    releaseText = "COMING SOON TO YOUR BROWSER",
    titleFont = "Bangers",
    bodyFont = "Roboto",
    titleColor = "#FFFF00", // Bright Yellow
    taglineColor = "#FFFFFF", // White
    releaseTextColor = "#DDDDDD", // Light Gray
    titleStrokeColor = "#000000", // Black
    bgGradientStart = "#000033", // Dark Navy Blue
    bgGradientEnd = "#101010", // Very Dark Gray / Off-black
    imageEffect = "none" // 'none', 'grayscale', 'sepia', 'darken'
) {

    const FONT_SOURCES = {
        "Bangers": { regular: "https://fonts.gstatic.com/s/bangers/v24/FeVQS0BTqb0h60ACL5la2bxii28.woff2" },
        "Roboto": {
            regular: "https://fonts.gstatic.com/s/roboto/v30/KFOmCnqEu92Fr1Mu4mxK.woff2",
            bold: "https://fonts.gstatic.com/s/roboto/v30/KFOlCnqEu92Fr1MmWUlvAx0.woff2"
        },
        "Anton": { regular: "https://fonts.gstatic.com/s/anton/v25/1Ptgg87LROyWN4o4AMpKrental.woff2" },
        "Luckiest Guy": { regular: "https://fonts.gstatic.com/s/luckiestguy/v18/_gP_1RsWVWG0YO2uTA7uXcrFQRZTbF48QA.woff2" }
    };

    async function loadFontVariant(fontFamily, weight = 'normal', style = 'normal', url) {
        const fontFace = new FontFace(fontFamily, `url(${url})`, { weight, style });
        try {
            await fontFace.load();
            document.fonts.add(fontFace);
        } catch (e) {
            console.error(`Error loading font variant ${fontFamily} ${weight} ${style} from ${url}:`, e);
            // Not re-throwing, to allow fallback to system fonts if loading fails
        }
    }

    async function ensureFontIsAvailable(fontFamilyName) {
        const fontData = FONT_SOURCES[fontFamilyName];
        if (!fontData) {
            if (!document.fonts.check(`12px "${fontFamilyName}"`)) {
                console.warn(`Font ${fontFamilyName} is not in predefined list and may not be available. System fallback may occur.`);
            }
            return;
        }

        const loadPromises = [];
        if (fontData.regular) {
            // Check before loading: font specification string includes weight and style if they are non-normal
            if (!document.fonts.check(`12px "${fontFamilyName}"`)) {
                 loadPromises.push(loadFontVariant(fontFamilyName, 'normal', 'normal', fontData.regular));
            }
        }
        if (fontData.bold) {
            if (!document.fonts.check(`bold 12px "${fontFamilyName}"`)) {
                 loadPromises.push(loadFontVariant(fontFamilyName, 'bold', 'normal', fontData.bold));
            }
        }
        // Add more variants (italic, etc.) if needed in FONT_SOURCES

        if (loadPromises.length > 0) {
            await Promise.all(loadPromises);
        }
    }

    // 1. Load fonts
    // Use a Set to avoid loading the same font family multiple times if titleFont and bodyFont are the same
    const fontsToLoad = new Set([titleFont, bodyFont]);
    const fontLoadingPromises = [];
    fontsToLoad.forEach(fontName => {
        fontLoadingPromises.push(ensureFontIsAvailable(fontName));
    });
    await Promise.all(fontLoadingPromises);

    // 2. Canvas setup
    const canvas = document.createElement('canvas');
    const canvasWidth = 600;
    const canvasHeight = 900;
    canvas.width = canvasWidth;
    canvas.height = canvasHeight;
    const ctx = canvas.getContext('2d');

    // 3. Background
    const bgGradient = ctx.createRadialGradient(
        canvasWidth / 2, canvasHeight / 2, 0,
        canvasWidth / 2, canvasHeight / 2, Math.max(canvasWidth, canvasHeight) / 1.5
    );
    bgGradient.addColorStop(0, bgGradientStart);
    bgGradient.addColorStop(1, bgGradientEnd);
    ctx.fillStyle = bgGradient;
    ctx.fillRect(0, 0, canvasWidth, canvasHeight);

    // 4. Main Image
    const imgContainerMaxW = canvasWidth * 0.90; // Corrected: Removed underscore
    const imgContainerMaxH = canvasHeight * 0.50; // Slightly reduced height for more text space
    const imgContainerY = canvasHeight * 0.22;  // Adjusted Y position for image

    let scaledImgW = originalImg.width;
    let scaledImgH = originalImg.height;
    const aspectRatio = originalImg.width / originalImg.height;

    if (scaledImgW > imgContainerMaxW) {
        scaledImgW = imgContainerMaxW;
        scaledImgH = scaledImgW / aspectRatio;
    }
    if (scaledImgH > imgContainerMaxH) {
        scaledImgH = imgContainerMaxH;
        scaledImgW = scaledImgH * aspectRatio;
    }
    // Ensure it still fits if one dimension was initially smaller but the other got clamped
    if (scaledImgW > imgContainerMaxW) {
        scaledImgW = imgContainerMaxW;
        scaledImgH = scaledImgW / aspectRatio;
    }


    const imgX = (canvasWidth - scaledImgW) / 2;
    const imgY = imgContainerY;

    // Apply image effect
    let filterString = "none";
    if (imageEffect === "grayscale") filterString = "grayscale(100%)";
    else if (imageEffect === "sepia") filterString = "sepia(100%)";
    else if (imageEffect === "darken") filterString = "brightness(60%)";
    
    ctx.save(); // Save context state before applying filter
    if (filterString !== "none") {
        ctx.filter = filterString;
    }
    ctx.drawImage(originalImg, imgX, imgY, scaledImgW, scaledImgH);
    
    // Optional: Add a subtle border to the image
    ctx.filter = "none"; // Reset filter before drawing border if border shouldn't be filtered
    ctx.strokeStyle = "#444444"; // Dark gray border
    ctx.lineWidth = 2;
    ctx.strokeRect(imgX, imgY, scaledImgW, scaledImgH);
    ctx.restore(); // Restore context state (removes filter)


    // 5. Text Elements
    ctx.fillStyle = titleColor; // Default for most text operations from here

    // --- Title Text ---
    const titleFontSize = Math.floor(canvasWidth / (titleText.length > 15 ? 9 : 7)); // Adjust size for longer titles
    ctx.font = `${titleFontSize}px "${titleFont}", sans-serif`; // Fallback to sans-serif
    ctx.textAlign = "left"; // For calculating position based on measureText correctly
    const titleMetrics = ctx.measureText(titleText.toUpperCase());
    const titleActualX = (canvasWidth - titleMetrics.width) / 2;
    const titleActualY = canvasHeight * 0.17; // Positioned above the image

    ctx.strokeStyle = titleStrokeColor;
    ctx.lineWidth = Math.max(2, titleFontSize / 18);
    ctx.strokeText(titleText.toUpperCase(), titleActualX, titleActualY);
    ctx.fillStyle = titleColor;
    ctx.fillText(titleText.toUpperCase(), titleActualX, titleActualY);

    // Common Y start for text below image
    const textBlockStartY = imgY + scaledImgH + canvasHeight * 0.04;

    // --- Release Text ---
    const releaseFontSize = Math.floor(canvasWidth / 22);
    ctx.font = `bold ${releaseFontSize}px "${bodyFont}", sans-serif`;
    ctx.fillStyle = releaseTextColor;
    ctx.textAlign = "center";
    const releaseTextY = textBlockStartY + releaseFontSize;
    ctx.fillText(releaseText.toUpperCase(), canvasWidth / 2, releaseTextY);

    // --- Tagline Text ---
    const taglineFontSize = Math.floor(canvasWidth / 28);
    ctx.font = `bold ${taglineFontSize}px "${bodyFont}", sans-serif`; // Using bold body font
    ctx.fillStyle = taglineColor;
    ctx.textAlign = "center";
    const taglineTextY = releaseTextY + taglineFontSize * 1.5 + canvasHeight * 0.02; // Add some space
    
    // Simple line wrapping for tagline
    const maxTaglineWidth = canvasWidth * 0.8;
    const words = taglineText.toUpperCase().split(' ');
    let line = '';
    let currentTaglineY = taglineTextY;

    for (let n = 0; n < words.length; n++) {
        const testLine = line + words[n] + ' ';
        const metrics = ctx.measureText(testLine);
        const testWidth = metrics.width;
        if (testWidth > maxTaglineWidth && n > 0) {
            ctx.fillText(line.trim(), canvasWidth / 2, currentTaglineY);
            line = words[n] + ' ';
            currentTaglineY += taglineFontSize * 1.2; // Line height
        } else {
            line = testLine;
        }
    }
    ctx.fillText(line.trim(), canvasWidth / 2, currentTaglineY);


    // --- Credits Text (Placeholder) ---
    const creditsFontSize = Math.floor(canvasWidth / 45);
    ctx.font = `${creditsFontSize}px "${bodyFont}", sans-serif`;
    ctx.fillStyle = "#999999"; // Dimmer color for credits
    ctx.textAlign = "center";
    const creditsText = "A JAVASCRIPT PRODUCTION \u2022 PROPERTY OF THE INTERNET";
    const creditsY = canvasHeight - creditsFontSize * 1.5; // Corrected: Removed underscore, near bottom
    ctx.fillText(creditsText, canvasWidth / 2, creditsY);

    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 Superhero Movie Poster Maker allows users to create customized movie posters by combining their images with stylized text elements. Users can input a title, tagline, and release text, choose from various fonts and colors, and apply effects such as grayscale or sepia to their images. This tool is ideal for graphic designers, filmmakers, and hobbyists looking to design eye-catching promotional materials, social media graphics, or fan art. With a user-friendly interface, it enables the creation of visually appealing posters suitable for both personal and professional use.

Leave a Reply

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