Please bookmark this page to avoid losing your image tool!

Image Vintage Travel Poster 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,
    overlayText = "VISIT LANDMARK",
    textColor = "rgb(255, 245, 230)", // A slightly off-white for vintage feel
    fontName = "Impact", // Default to a common bold poster font like Impact
    fontSize = 70, // Font size in pixels
    textPlacement = "bottom", // "top", "middle", or "bottom"
    textureOpacity = 0.1, // Opacity for vintage grain texture (0 to 1, 0 for no texture)
    colorFilter = "sepia(0.5) contrast(1.1) brightness(0.95) saturate(1.2) hue-rotate(-5deg)", // CSS filter string for vintage color effect
    textStrokeColor = "rgba(0,0,0,0.6)", // Color for text stroke, e.g., "rgba(0,0,0,0.5)"
    textStrokeWidth = 3 // Width of the text stroke in pixels (0 for no stroke)
) {
    const canvas = document.createElement('canvas');
    const ctx = canvas.getContext('2d');

    const imgWidth = originalImg.naturalWidth || originalImg.width;
    const imgHeight = originalImg.naturalHeight || originalImg.height;

    if (!imgWidth || !imgHeight) {
        console.error("Image has zero dimensions. Ensure it's loaded before processing.");
        // Return a minimal canvas or handle error as preferred
        canvas.width = 1;
        canvas.height = 1;
        return canvas;
    }

    canvas.width = imgWidth;
    canvas.height = imgHeight;

    // 1. Apply color filter and draw the original image
    if (colorFilter && colorFilter.trim() !== "") {
        ctx.filter = colorFilter;
    }
    ctx.drawImage(originalImg, 0, 0, canvas.width, canvas.height);
    ctx.filter = 'none'; // Reset filter for subsequent drawing operations (texture, text)

    // 2. Add vintage grain/texture overlay
    if (textureOpacity > 0 && textureOpacity <= 1) {
        ctx.save();
        
        // Create a small canvas for the grain pattern
        const grainCanvas = document.createElement('canvas');
        const grainCtx = grainCanvas.getContext('2d');
        const grainPatternSize = 100; // Size of the pattern tile for grain, e.g., 100x100
        grainCanvas.width = grainPatternSize;
        grainCanvas.height = grainPatternSize;

        // Create noise in the pattern tile
        // Adjust numGrainsFactor for more/less dense grain
        const numGrainsFactor = 5; 
        const numGrains = (grainPatternSize * grainPatternSize) / numGrainsFactor;

        for (let i = 0; i < numGrains; i++) {
            const x = Math.random() * grainPatternSize;
            const y = Math.random() * grainPatternSize;
            const size = Math.random() * 1.5 + 0.5; // Grain particle size
            // Random alpha for speckles to make them less uniform
            const alpha = Math.random() * 0.5 + 0.2; 
            
            // Alternate between dark and light speckles for a more natural grain
            if (Math.random() < 0.5) {
                grainCtx.fillStyle = `rgba(0,0,0,${alpha})`; // Dark grain
            } else {
                // Lighter grain, slightly less opaque than dark ones for subtlety
                grainCtx.fillStyle = `rgba(255,255,255,${alpha * 0.7})`; 
            }
            grainCtx.fillRect(x, y, size, size);
        }
        
        const pattern = ctx.createPattern(grainCanvas, 'repeat');
        if (pattern) {
            ctx.globalAlpha = textureOpacity;
            ctx.fillStyle = pattern;
            ctx.fillRect(0, 0, canvas.width, canvas.height);
        }
        ctx.restore(); // Restores globalAlpha
    }

    // 3. Prepare and Load Font if necessary
    let finalFontFamily = fontName; // This will be the font-family string for ctx.font
    let fontWeight = "bold"; // Posters typically use bold fonts

    // Generic CSS font families that don't need dynamic loading
    const genericFontFamilies = /^(serif|sans-serif|monospace|fantasy|cursive|system-ui|arial|verdana|helvetica|tahoma|trebuchet ms|times new roman|georgia|garamond|courier new|brush script mt)$/i;
    // Check if fontName is a generic family or a CSS list with fallbacks
    const isGenericOrHasFallbacks = genericFontFamilies.test(fontName.trim().toLowerCase()) || fontName.includes(',');

    if (!isGenericOrHasFallbacks) {
        // Attempt to load from Google Fonts if it's a specific name (e.g., "Lobster", "Bebas Neue")
        const googleFontName = fontName.replace(/ /g, '+'); // Format for Google Fonts URL
        // Request typical weights 400 (normal) and 700 (bold)
        const fontCssUrl = `https://fonts.googleapis.com/css2?family=${googleFontName}:wght@400;700&display=swap`;

        // Check if style element for this font URL already exists to avoid re-adding
        let styleElement = document.head.querySelector(`style[data-font-url="${fontCssUrl}"]`);
        if (!styleElement) {
            styleElement = document.createElement('style');
            styleElement.setAttribute('data-font-url', fontCssUrl); // Mark our style tag by URL
            styleElement.textContent = `@import url('${fontCssUrl}');`;
            document.head.appendChild(styleElement);
        }
        
        try {
            // Attempt to load bold weight (700) first as it's common for posters
            await document.fonts.load(`700 ${fontSize}px "${fontName}"`);
            console.log(`Font "${fontName}" (bold) loaded or available.`);
            finalFontFamily = `"${fontName}"`; // Ensure font name with spaces is quoted
            fontWeight = "700";
        } catch (err) {
            try {
                // If bold fails or isn't specified, try normal weight (400)
                await document.fonts.load(`400 ${fontSize}px "${fontName}"`);
                console.log(`Font "${fontName}" (normal) loaded or available.`);
                finalFontFamily = `"${fontName}"`;
                fontWeight = "400";
            } catch (e) {
                const fallbackFont = "Impact, Charcoal, sans-serif";
                console.warn(`Failed to load font "${fontName}". Using fallback "${fallbackFont}". Error: ${e}`);
                finalFontFamily = fallbackFont; 
                fontWeight = "bold"; // Use bold for the fallback
            }
        }
    } else {
        // For generic/fallback lists, ensure proper quoting for names with spaces
        finalFontFamily = fontName.split(',')
            .map(f => {
                const trimmed = f.trim();
                // Quote font names with spaces if not already quoted
                if (trimmed.includes(' ') && !trimmed.startsWith('"') && !trimmed.startsWith("'")) {
                    return `"${trimmed}"`;
                }
                return trimmed;
            })
            .join(', ');
        // fontWeight remains "bold" default for system/generic fonts
    }
    
    ctx.font = `${fontWeight} ${fontSize}px ${finalFontFamily}`;
    ctx.fillStyle = textColor;
    ctx.textAlign = 'center';
    
    // 4. Render Text (supports multiline text using \n as a separator)
    const lines = overlayText.toUpperCase().split('\\n'); // Poster text is often uppercase
    // Adjust line height for better readability of multiline text
    const lineHeight = fontSize * 1.15; 
    // Calculate approximate total height of the text block
    const totalTextHeight = (lines.length - 1) * lineHeight + fontSize; 

    // Margin from edge, at least 20px or proportional to font size
    const margin = Math.max(20, fontSize * 0.3); 

    for (let i = 0; i < lines.length; i++) {
        const line = lines[i];
        let yPos;

        if (textPlacement.toLowerCase() === 'top') {
            ctx.textBaseline = 'top';
            yPos = margin + (i * lineHeight);
        } else if (textPlacement.toLowerCase() === 'middle') {
            // Calculate Y for the first line of the block, so the entire block is centered
            const blockTopY = (canvas.height - totalTextHeight) / 2;
            ctx.textBaseline = 'top'; // Use 'top' baseline for consistent multiline spacing
            yPos = blockTopY + (i * lineHeight);
        } else { // 'bottom' is the default placement
            ctx.textBaseline = 'bottom';
            // Calculate Y for each line, positioning the block relative to the bottom margin
            yPos = canvas.height - margin - ((lines.length - 1 - i) * lineHeight);
        }

        // Apply stroke for better readability, common in vintage posters
        if (textStrokeWidth > 0 && textStrokeColor && textStrokeColor.trim() !== "" && textStrokeColor !== "transparent") {
            ctx.strokeStyle = textStrokeColor;
            ctx.lineWidth = textStrokeWidth;
            ctx.strokeText(line, canvas.width / 2, yPos);
        }
        // Fill the text itself
        ctx.fillText(line, canvas.width / 2, yPos);
    }

    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 Travel Poster Creator tool allows users to transform their images into stylized vintage travel posters. By applying a range of customizable effects, including sepia tones and grain textures, and overlaying personalized text, users can create unique and visually appealing posters. This tool is ideal for designers, travel enthusiasts, or anyone looking to create nostalgic artwork for personal projects, promotional materials, or as gifts. Whether you want to advertise a travel destination or decorate a space, this tool offers a fun and creative way to enhance images with a classic vintage aesthetic.

Leave a Reply

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