Please bookmark this page to avoid losing your image tool!

Image Post-Apocalyptic Wanted 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,
    nameStr = "VAULT DWELLER",
    crimeStr = "LOOTING & GENERAL MAYHEM",
    rewardStr = "500 CAPS",
    posterFont = "Metal Mania",
    paperColorStr = "210,180,140", // RGB string for paper, e.g., "210,180,140" for tan
    textColorStr = "56,34,15",       // RGB string for main text, e.g., "56,34,15" for dark brown
    wantedTextColorStr = "139,0,0"   // RGB string for "WANTED" text, e.g., "139,0,0" for dark red
) {

    const FONT_MAP = {
        "Metal Mania": "https://fonts.gstatic.com/s/metalmania/v20/ocrP2dEJ5_AIOsumS_cR_Y5KM_21.woff2",
        "Special Elite": "https://fonts.gstatic.com/s/specialelite/v17/X7nP4b8XQvSyT2S9SY9nPEhFVvo1WkY.woff2",
        "Bangers": "https://fonts.gstatic.com/s/bangers/v20/FeVQS0BTqb0h60ACL5k.woff2",
        "Creepster": "https://fonts.gstatic.com/s/creepster/v15/AlZy_zVVcp9OKAbP_rpalw.woff2",
        "Impact": null, // System font
        "Arial": null   // System font
    };

    async function loadCustomFont(fontFamily, fontUrl) {
        if (!fontUrl) return true; // System font, assumed available
        if (document.fonts) {
             // Check if already loaded by the browser or a previous call
            if (await document.fonts.check(`12px "${fontFamily}"`)) {
                return true;
            }
            const fontFace = new FontFace(fontFamily, `url(${fontUrl}) format('woff2')`);
            try {
                await fontFace.load();
                document.fonts.add(fontFace);
                // Verify it's truly usable
                if (!await document.fonts.check(`12px "${fontFamily}"`)) {
                     console.warn(`Font ${fontFamily} loaded but check failed.`);
                     // Fallback or error if critical? For now, continue, browser might still use it.
                }
                return true;
            } catch (e) {
                console.error(`Font ${fontFamily} from ${fontUrl} failed to load:`, e);
                return false;
            }
        } else {
            console.warn("document.fonts API not supported. Cannot load custom fonts.");
            return false; // Cannot load custom font
        }
    }

    let actualFontFamily = posterFont;
    const fontUrl = FONT_MAP[posterFont];
    if (fontUrl !== undefined) { // Known font
        const fontLoaded = await loadCustomFont(posterFont, fontUrl);
        if (!fontLoaded && fontUrl) { // Custom font failed to load
             console.warn(`Falling back to system font 'Arial' for ${posterFont}.`);
             actualFontFamily = "Arial"; // Fallback font
        }
    } else { // Unknown font, assume it's a system font or user knows what they're doing
        actualFontFamily = posterFont;
    }


    // Parse RGB color strings
    const parseRgb = (rgbStr, defaultColor = "0,0,0") => {
        try {
            const parts = rgbStr.split(',').map(s => parseInt(s.trim(), 10));
            if (parts.length === 3 && parts.every(p => !isNaN(p) && p >= 0 && p <= 255)) {
                return `rgb(${parts[0]},${parts[1]},${parts[2]})`;
            }
        } catch (e) { /* fall through to default */ }
        const defaultParts = defaultColor.split(',').map(s => parseInt(s.trim(), 10));
        return `rgb(${defaultParts[0]},${defaultParts[1]},${defaultParts[2]})`;
    };
    
    const parsedPaperColor = parseRgb(paperColorStr, "210,180,140");
    const parsedTextColor = parseRgb(textColorStr, "56,34,15");
    const parsedWantedTextColor = parseRgb(wantedTextColorStr, "139,0,0");


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

    const POSTER_WIDTH = 600;
    const POSTER_HEIGHT = 900;
    const MARGIN = 30;

    canvas.width = POSTER_WIDTH;
    canvas.height = POSTER_HEIGHT;

    // 1. Background
    ctx.fillStyle = parsedPaperColor;
    ctx.fillRect(0, 0, POSTER_WIDTH, POSTER_HEIGHT);

    // Add noise to background
    function addNoise(context, x, y, width, height, amount = 20) {
        const imageData = context.getImageData(x, y, width, height);
        const data = imageData.data;
        for (let i = 0; i < data.length; i += 4) {
            const noise = (Math.random() - 0.5) * amount;
            data[i]   = Math.max(0, Math.min(255, data[i] + noise));
            data[i+1] = Math.max(0, Math.min(255, data[i+1] + noise));
            data[i+2] = Math.max(0, Math.min(255, data[i+2] + noise));
        }
        context.putImageData(imageData, x, y);
    }
    addNoise(ctx, 0, 0, POSTER_WIDTH, POSTER_HEIGHT, 25);

    // Add stains
    function addStain(context, centerX, centerY, maxRadius, colorStr) {
        const [r,g,b] = colorStr.split(',').map(Number);
        context.fillStyle = `rgba(${r},${g},${b}, ${0.05 + Math.random() * 0.1})`; // Random opacity
        const segments = 10 + Math.floor(Math.random() * 10);
        context.beginPath();
        const startRadius = maxRadius * (0.7 + Math.random() * 0.3);
        context.moveTo(centerX + startRadius * Math.cos(0), centerY + startRadius * Math.sin(0));
        for (let i = 1; i <= segments; i++) {
            const angle = (i / segments) * 2 * Math.PI;
            const radius = maxRadius * (0.5 + Math.random() * 0.5); // Irregular radius
            context.lineTo(centerX + Math.cos(angle) * radius, centerY + Math.sin(angle) * radius);
        }
        context.closePath();
        context.fill();
    }
    const stainBaseColor = textColorStr.split(',').map(n => Math.max(0, parseInt(n) - 20)).join(','); // Slightly lighter than text
    for(let i=0; i < 5; i++) { // Add 5 random stains
        addStain(ctx, Math.random() * POSTER_WIDTH, Math.random() * POSTER_HEIGHT, Math.random() * 50 + 50, stainBaseColor);
    }


    // 2. Main Poster Border
    ctx.strokeStyle = parsedTextColor;
    ctx.lineWidth = MARGIN / 2; // 15px border
    ctx.strokeRect(MARGIN/4, MARGIN/4, POSTER_WIDTH - MARGIN/2, POSTER_HEIGHT - MARGIN/2);
    ctx.lineWidth = 2; // Reset for other strokes


    let currentY = MARGIN * 1.5; // Start Y position

    // 3. "WANTED" Text
    ctx.font = `bold 90px "${actualFontFamily}"`;
    ctx.fillStyle = parsedWantedTextColor;
    ctx.textAlign = 'center';
    currentY += 70;
    ctx.fillText("WANTED", POSTER_WIDTH / 2, currentY);

    // 4. "DEAD OR ALIVE" (or similar)
    currentY += 40;
    ctx.font = `30px "${actualFontFamily}"`;
    ctx.fillStyle = parsedTextColor; // Regular text color
    ctx.fillText("DEAD OR ALIVE (PREFERABLY DEAD)", POSTER_WIDTH / 2, currentY);
    currentY += 30; // Space before image

    // 5. Process and Draw Image
    const imgContainerX = MARGIN;
    const imgContainerY = currentY;
    const imgContainerWidth = POSTER_WIDTH - 2 * MARGIN;
    const imgContainerHeight = 300; // Max height for image

    // Create a temporary canvas for image processing
    const tempCanvas = document.createElement('canvas');
    const tempCtx = tempCanvas.getContext('2d');
    tempCanvas.width = originalImg.naturalWidth;
    tempCanvas.height = originalImg.naturalHeight;

    tempCtx.drawImage(originalImg, 0, 0);
    
    // Grayscale and Contrast filter
    const imageData = tempCtx.getImageData(0, 0, tempCanvas.width, tempCanvas.height);
    const data = imageData.data;
    const contrast = 1.8; // Adjusted contrast
    const brightness = -10; // Slight darkening

    for (let i = 0; i < data.length; i += 4) {
        let r = data[i], g = data[i+1], b = data[i+2];
        
        // Grayscale (luminosity)
        const avg = 0.299 * r + 0.587 * g + 0.114 * b;
        r = g = b = avg;

        // Apply brightness
        r += brightness; g += brightness; b += brightness;

        // Apply contrast
        const factor = (259 * (contrast * 255 + 255)) / (255 * (259 - contrast * 255)); // standard formula
        r = factor * (r - 128) + 128;
        g = factor * (g - 128) + 128;
        b = factor * (b - 128) + 128;

        data[i]   = Math.max(0, Math.min(255, r));
        data[i+1] = Math.max(0, Math.min(255, g));
        data[i+2] = Math.max(0, Math.min(255, b));
    }
    tempCtx.putImageData(imageData, 0, 0);
    addNoise(tempCtx, 0, 0, tempCanvas.width, tempCanvas.height, 35); // Add grain to image

    // Calculate drawing dimensions for the image to fit and be centered
    let drawWidth, drawHeight;
    const imgAspect = tempCanvas.width / tempCanvas.height;
    if ((imgContainerWidth / imgContainerHeight) > imgAspect) { // Container is wider aspect than image
        drawHeight = imgContainerHeight;
        drawWidth = drawHeight * imgAspect;
    } else { // Container is taller/squarer aspect than image
        drawWidth = imgContainerWidth;
        drawHeight = drawWidth / imgAspect;
    }

    const drawX = imgContainerX + (imgContainerWidth - drawWidth) / 2;
    const drawY = imgContainerY;

    ctx.drawImage(tempCanvas, drawX, drawY, drawWidth, drawHeight);
    // Border around the image
    ctx.strokeStyle = parsedTextColor;
    ctx.lineWidth = 4;
    ctx.strokeRect(drawX-2, drawY-2, drawWidth+4, drawHeight+4); // Slightly outset border
    currentY += imgContainerHeight + 20; // Update currentY past the image block

    // Helper for text wrapping
    function wrapText(context, text, x, y, maxWidth, lineHeight, fontStyle, fillStyle) {
        context.font = fontStyle;
        context.fillStyle = fillStyle;
        context.textAlign = 'center';
        const words = text.toUpperCase().split(' ');
        let line = '';
        let currentTextY = y;

        for (let n = 0; n < words.length; n++) {
            const testLine = line + words[n] + ' ';
            const metrics = context.measureText(testLine);
            const testWidth = metrics.width;
            if (testWidth > maxWidth && n > 0) {
                context.fillText(line.trim(), x, currentTextY);
                line = words[n] + ' ';
                currentTextY += lineHeight;
            } else {
                line = testLine;
            }
        }
        context.fillText(line.trim(), x, currentTextY);
        return currentTextY + lineHeight; // Return Y position after the last line of text
    }

    // 6. Name Text
    currentY += 40;
    ctx.font = `bold 50px "${actualFontFamily}"`;
    ctx.fillStyle = parsedTextColor;
    wrapText(ctx, nameStr, POSTER_WIDTH / 2, currentY, POSTER_WIDTH - 2 * MARGIN, 50, `bold 50px "${actualFontFamily}"`, parsedTextColor);
    // Estimate height of name text - assume one line mostly for simplicity here, adjust if wrapText changes currentY
    currentY += ctx.measureText(nameStr).actualBoundingBoxDescent || 50;


    // 7. Crime Text
    currentY += 30;
    ctx.font = `25px "${actualFontFamily}"`;
    ctx.fillText("KNOWN CRIMES:", POSTER_WIDTH / 2, currentY);
    currentY += 35;
    const crimeLineHeight = 30;
    currentY = wrapText(ctx, crimeStr, POSTER_WIDTH / 2, currentY, POSTER_WIDTH - 2 * MARGIN - 40, crimeLineHeight, `22px "${actualFontFamily}"`, parsedTextColor);
    // currentY is already updated by wrapText

    // 8. Reward Text
    currentY += 20; // Space before reward
    ctx.font = `bold 30px "${actualFontFamily}"`;
    ctx.fillStyle = parsedTextColor;
    ctx.fillText("REWARD", POSTER_WIDTH / 2, currentY);

    currentY += 50;
    ctx.font = `bold 45px "${actualFontFamily}"`;
    ctx.fillStyle = parsedWantedTextColor; // Use "wanted" color for reward amount
    ctx.fillText(rewardStr.toUpperCase(), POSTER_WIDTH / 2, currentY);

    // Final touch: ensure bottom margin
    // if (currentY > POSTER_HEIGHT - MARGIN) { ... canvas might need to be taller dynamically ... }
    // For fixed size, this is it.

    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 Post-Apocalyptic Wanted Poster Creator allows users to create custom wanted posters featuring a specified name, crime, and reward amount. Users can personalize the design by selecting different fonts, colors, and images, giving their poster a unique and vintage look. This tool can be used for creative projects, role-playing games, or as a fun way to showcase fictional characters in a post-apocalyptic setting. Whether for online sharing or printing, this tool transforms images into visually striking and amusing wanted posters.

Leave a Reply

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