Please bookmark this page to avoid losing your image tool!

Image Vintage Movie Poster Template

(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 LAST ALGORITHM",
    taglineText = "In a world of forgotten code, destiny will be compiled.",
    starringText = "Starring: ANNA BYTES, GREG KERNEL",
    additionalInfoText = "A PIXEL PERFECT PICTURES Release • Directed by CTRL+ALT+DEL",
    creditsText = "COPYRIGHT © MMXXIV VINTAGE CODERS GUILD - ALL RIGHTS RESERVED",
    fontFamilyTitleParam = "Bebas Neue", // Example: 'Bebas Neue', 'Anton', 'Impact'
    fontFamilyBodyParam = "Oswald",      // Example: 'Oswald', 'Roboto Condensed', 'Arial Narrow'
    textColorParam = "rgba(250, 240, 220, 0.95)", // Off-white/ivory for general text
    titleColorParam = "rgba(255, 215, 100, 1)",  // Vintage gold/yellow for title
    shadowColorParam = "rgba(0, 0, 0, 0.6)",    // Shadow color for text
    sepiaAmountParam = 0.75,          // 0 to 1 for sepia intensity
    contrastAmountParam = 1.15,       // e.g., 1.0 is normal, 1.5 is 150% contrast
    brightnessAmountParam = 0.95,     // e.g., 1.0 is normal, 0.8 is 80% brightness
    noiseAmountParam = 0.05           // 0 to 1 for noise/grain intensity (0 disables)
) {

    // Helper function to load Google Fonts
    async function _loadGoogleFontPosterHelper(fontFamilyName) {
        const cleanedName = String(fontFamilyName).split(',')[0].replace(/'/g, '').trim();
        if (!cleanedName) return; // No font name to load

        // Request regular (400) and bold (700) weights if typically available
        const fontQuery = cleanedName.replace(/ /g, '+') + ":wght@400;700";
        const fontCssUrl = `https://fonts.googleapis.com/css2?family=${fontQuery}&display=swap`;

        if (!document.querySelector(`link[href="${fontCssUrl}"]`)) {
            const link = document.createElement('link');
            link.href = fontCssUrl;
            link.rel = 'stylesheet';
            const p = new Promise((resolve) => {
                link.onload = resolve;
                link.onerror = () => {
                    console.warn(`Failed to load stylesheet for font: ${cleanedName} from ${fontCssUrl}`);
                    resolve(); // Resolve anyway to not block, will fallback to system fonts
                };
            });
            document.head.appendChild(link);
            await p;
        }

        try {
            // Ensure browser has processed the font definitions for canvas use
            await Promise.all([
                document.fonts.load(`12px "${cleanedName}"`),      // Test for regular weight
                document.fonts.load(`bold 12px "${cleanedName}"`) // Test for bold weight
            ]);
        } catch (e) {
            console.warn(`Font ${cleanedName} (weights regular/bold) might not be fully ready for canvas:`, e);
            // Fallback delay if document.fonts.load fails or raises an error
            await new Promise(resolve => setTimeout(resolve, 300));
        }
    }

    // Construct font stacks with fallbacks
    const fontFamilyTitle = `'${fontFamilyTitleParam}', Impact, 'Arial Black', sans-serif`;
    const fontFamilyBody = `'${fontFamilyBodyParam}', 'Arial Narrow', 'Roboto Condensed', sans-serif`;

    // Load specified fonts
    await Promise.all([
        _loadGoogleFontPosterHelper(fontFamilyTitleParam),
        _loadGoogleFontPosterHelper(fontFamilyBodyParam)
    ]);

    const canvas = document.createElement('canvas');
    const ctx = canvas.getContext('2d');

    const imgWidth = originalImg.naturalWidth;
    const imgHeight = originalImg.naturalHeight;

    // Define poster aspect ratio (standard movie one-sheet is 27x40 inches)
    const posterAspectRatio = 27 / 40; // width / height
    let canvasWidth, canvasHeight;

    // Determine canvas size
    const TARGET_POSTER_WIDTH = 800; // Target width for the poster in pixels
    canvasWidth = TARGET_POSTER_WIDTH;

    // Avoid excessive upscaling of the source image:
    // If image width (scaled by maxUpscaleFactor) is less than target poster width, reduce poster width.
    const maxUpscaleFactorImage = 2.0;
    if (imgWidth > 0 && imgWidth * maxUpscaleFactorImage < canvasWidth) {
        canvasWidth = imgWidth * maxUpscaleFactorImage;
    }
    // Similarly, if image height (scaled) implies an even smaller width due to aspect ratio, adjust further.
    if (imgHeight > 0 && (imgHeight * maxUpscaleFactorImage) / posterAspectRatio < canvasWidth) {
        canvasWidth = (imgHeight * maxUpscaleFactorImage) / posterAspectRatio * posterAspectRatio;
    }
    
    canvasWidth = Math.max(canvasWidth, 300); // Ensure a minimum poster width (e.g., 300px)
    canvasHeight = canvasWidth / posterAspectRatio;

    canvas.width = canvasWidth;
    canvas.height = canvasHeight;

    // Fill background (in case image doesn't cover all)
    ctx.fillStyle = '#1a1a1a'; // Dark, slightly desaturated color
    ctx.fillRect(0, 0, canvasWidth, canvasHeight);

    // Apply vintage image filters
    ctx.filter = `sepia(${sepiaAmountParam}) contrast(${contrastAmountParam}) brightness(${brightnessAmountParam})`;

    // Draw the image, scaled and cropped to fill the canvas (aspect fill/"cover")
    const canvasAspect = canvasWidth / canvasHeight;
    const imgAspect = imgWidth / imgHeight;
    let sx = 0, sy = 0, sWidth = imgWidth, sHeight = imgHeight;

    if (imgAspect > canvasAspect) { // Image is wider than canvas: fit height, crop width
        sWidth = imgHeight * canvasAspect;
        sx = (imgWidth - sWidth) / 2;
    } else if (imgAspect < canvasAspect) { // Image is taller than canvas: fit width, crop height
        sHeight = imgWidth / canvasAspect;
        sy = (imgHeight - sHeight) / 2;
    }
    if (sWidth > 0 && sHeight > 0) { // Ensure non-zero source dimensions
       ctx.drawImage(originalImg, sx, sy, sWidth, sHeight, 0, 0, canvasWidth, canvasHeight);
    }
    ctx.filter = 'none'; // Reset filter for text and other elements

    // Add noise/grain overlay
    if (noiseAmountParam > 0 && noiseAmountParam <= 1) {
        const noiseCanvas = document.createElement('canvas');
        noiseCanvas.width = canvasWidth;
        noiseCanvas.height = canvasHeight;
        const noiseCtx = noiseCanvas.getContext('2d');
        const noisePixelData = noiseCtx.createImageData(canvasWidth, canvasHeight);
        const d = noisePixelData.data;
        const randomMax = 255 * noiseAmountParam;

        for (let i = 0; i < d.length; i += 4) {
            const grainValue = Math.floor(Math.random() * randomMax);
            d[i] = grainValue;
            d[i+1] = grainValue;
            d[i+2] = grainValue;
            // Alpha for noise spots: make them somewhat opaque. Experiment with this for desired effect.
            // Subtle approach: low alpha overall for noise layer (see ctx.globalAlpha below)
            // This sets individual pixel alpha for the noise pattern itself.
            d[i+3] = Math.min(255, Math.floor(grainValue * 0.5 + 30)); 
        }
        noiseCtx.putImageData(noisePixelData, 0, 0);
        
        ctx.globalAlpha = 0.30; // Adjust overall opacity of the noise layer
        ctx.drawImage(noiseCanvas, 0, 0);
        ctx.globalAlpha = 1.0; // Reset global alpha
    }

    // Text rendering setup
    ctx.textAlign = 'center';
    ctx.shadowColor = shadowColorParam;
    // Scale shadow properties with canvas size for consistency
    const baseShadowBlur = Math.max(3, canvasWidth * 0.005);
    const baseShadowOffsetX = Math.max(1, canvasWidth * 0.002);
    ctx.shadowBlur = baseShadowBlur;
    ctx.shadowOffsetX = baseShadowOffsetX;
    ctx.shadowOffsetY = baseShadowOffsetX; // Typically Y offset is same as X or slightly more

    // Helper to draw wrapped text (uppercase for movie poster style)
    function drawWrappedText(text, x, y, maxWidth, lineHeight, font, color, strokeColor = null, strokeWidth = 0) {
        ctx.font = font;
        ctx.fillStyle = color;
        if (strokeColor && strokeWidth > 0) {
            ctx.strokeStyle = strokeColor;
            ctx.lineWidth = strokeWidth;
        }
        
        const words = String(text).toUpperCase().split(' ');
        let line = '';
        let currentY = y;
        const linesToRender = [];

        for (let n = 0; n < words.length; n++) {
            const testLine = line + words[n] + ' ';
            const metrics = ctx.measureText(testLine); // Measure uppercase text
            if (metrics.width > maxWidth && n > 0) {
                linesToRender.push({ text: line.trim(), y: currentY });
                line = words[n] + ' ';
                currentY += lineHeight;
            } else {
                line = testLine;
            }
        }
        linesToRender.push({ text: line.trim(), y: currentY });

        for (const lineObj of linesToRender) {
            if (strokeColor && strokeWidth > 0) ctx.strokeText(lineObj.text, x, lineObj.y);
            ctx.fillText(lineObj.text, x, lineObj.y);
        }
        return currentY + lineHeight; // Return Y position after this text block
    }

    // TITLE
    const titleSize = Math.max(30, canvasHeight * 0.10); // Relative to poster height
    let currentTextY = canvasHeight * 0.18; // Initial Y position for title block
    currentTextY = drawWrappedText(
        titleText,
        canvasWidth / 2,
        currentTextY,
        canvasWidth * 0.90, // Max width for title
        titleSize * 0.95,   // Line height for title
        `${titleSize}px ${fontFamilyTitle}`,
        titleColorParam,
        'rgba(0,0,0,0.8)', // Title stroke color
        Math.max(1, titleSize * 0.025) // Title stroke width
    );

    // TAGLINE
    const taglineSize = Math.max(14, canvasHeight * 0.03);
    currentTextY += taglineSize * 0.3; // Add small space after title
    currentTextY = drawWrappedText(
        taglineText,
        canvasWidth / 2,
        currentTextY,
        canvasWidth * 0.75,
        taglineSize * 1.2,
        `italic ${taglineSize}px ${fontFamilyBody}`, // Italic common for taglines
        textColorParam
    );
    currentTextY += taglineSize * 1.5; // Add more space after tagline

    // STARRING and ADDITIONAL INFO block
    // Position this block starting around 78% down, or flow if title/tagline were very long
    const creditsBlockYStart = canvasHeight * 0.78; 
    currentTextY = Math.max(currentTextY, creditsBlockYStart);

    const starringSize = Math.max(12, canvasHeight * 0.028);
    currentTextY = drawWrappedText(
        starringText,
        canvasWidth / 2,
        currentTextY,
        canvasWidth * 0.8,
        starringSize * 1.3,
        // Using 'bold' for starring if font supports it and it's loaded
        `bold ${starringSize}px ${fontFamilyBody}`,
        textColorParam
    );
    currentTextY += starringSize * 0.2; // Small space

    const additionalInfoSize = Math.max(10, canvasHeight * 0.022);
    currentTextY = drawWrappedText(
        additionalInfoText,
        canvasWidth / 2,
        currentTextY,
        canvasWidth * 0.85,
        additionalInfoSize * 1.3,
        `${additionalInfoSize}px ${fontFamilyBody}`,
        textColorParam
    );

    // CREDITS (Billing Block) at the very bottom
    const creditsSize = Math.max(8, canvasHeight * 0.018);
    // Position relative to bottom edge; ensure it doesn't overlap significantly if content above is long
    const creditsY = Math.max(currentTextY + creditsSize * 1.5, canvasHeight - creditsSize * 3);
    
    if (currentTextY > (canvasHeight - creditsSize * 5) && creditsText) { // Heuristic: if dynamic text is already very low
      console.warn("Text content may be pushing credits down or causing overlap. Consider shorter text inputs.");
    }

    drawWrappedText(
        creditsText,
        canvasWidth / 2,
        creditsY,
        canvasWidth * 0.9,
        creditsSize * 1.15,
        `${creditsSize}px ${fontFamilyBody}`,
        textColorParam
    );

    // Reset shadow effects for any subsequent drawing (if canvas context were reused)
    ctx.shadowColor = 'transparent';
    ctx.shadowBlur = 0;
    ctx.shadowOffsetX = 0;
    ctx.shadowOffsetY = 0;

    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 Vintage Movie Poster Template tool allows users to create custom movie posters by applying a vintage style to their images. Users can personalize their posters with a variety of text options, including title, tagline, and credits, all rendered in selectable fonts and colors to achieve a classic movie aesthetic. This tool is ideal for filmmakers, artists, and anyone looking to design eye-catching promotional materials or personal projects that evoke the charm of classic film posters.

Leave a Reply

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