Please bookmark this page to avoid losing your image tool!

Image Wild West ‘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, wantedName = "UNKNOWN DESPERADO", reward = "$5,000", crime = "Horse Thievery & General Ruffianism") {

    // Helper function to wrap text and return the Y position after the text
    // Assumes ctx.textAlign and ctx.textBaseline are set by the caller (e.g., 'center', 'top')
    function wrapTextAndGetNextY(ctx, text, x, y, maxWidth, lineHeight) {
        const words = String(text).split(' ');
        let line = '';
        let currentDrawY = y;

        for (let n = 0; n < words.length; n++) {
            const word = words[n];
            let testLine = line + (line ? ' ' : '') + word; // Add space if line is not empty
            
            if (ctx.measureText(testLine).width > maxWidth && line !== '') {
                ctx.fillText(line, x, currentDrawY); // Draw the current line
                line = word; // Start a new line with the current word
                currentDrawY += lineHeight;
            } else {
                line = testLine; // Add word to current line
            }
        }

        // Draw the last remaining line
        if (line.trim() !== '') {
            ctx.fillText(line, x, currentDrawY);
        }
        
        if (String(text).trim() === '') {
             return currentDrawY; // No text, no height increase
        }
        // Return Y for the *start* of the next text block (after this one's last line + lineHeight)
        return currentDrawY + lineHeight; 
    }

    // Helper function to add subtle texture to background
    function addBackgroundTexture(ctx, width, height) {
        ctx.save();
        const numSpeckles = 70000; // Adjust for density
        for (let i = 0; i < numSpeckles; i++) {
            const speckleX = Math.random() * width;
            const speckleY = Math.random() * height;
            const size = Math.random() * 1.5 + 0.5; 
            const r = Math.floor(Math.random() * 30) + 40; // Dark brownish speckles
            const g = Math.floor(Math.random() * 30) + 30;
            const b = Math.floor(Math.random() * 30) + 20;
            const alpha = Math.random() * 0.08 + 0.02; // Very low opacity
            ctx.fillStyle = `rgba(${r}, ${g}, ${b}, ${alpha})`;
            ctx.fillRect(speckleX, speckleY, size, size);
        }
        ctx.restore();
    }

    // 1. Font Loading
    const FONT_MAIN = 'Rye';
    const FONT_SUB = 'IM Fell English SC'; // Note: Google Fonts serves "IM Fell English SC" correctly
    const fontUrl = `https://fonts.googleapis.com/css2?family=${FONT_MAIN.replace(' ', '+')}&family=${FONT_SUB.replace(' ', '+').replace('"', '')}&display=swap`;
    const fontStyleId = 'wanted-poster-fonts';

    if (!document.getElementById(fontStyleId)) {
        const link = document.createElement('link');
        link.id = fontStyleId;
        link.rel = 'stylesheet';
        link.href = fontUrl;
        document.head.appendChild(link);
        try {
            // Wait for fonts to be loaded and ready
            await Promise.all([
                document.fonts.load(`40px "${FONT_MAIN}"`), // Quotes for safety, though Rye has no spaces
                document.fonts.load(`20px "${FONT_SUB}"`)  // IM Fell English SC has spaces
            ]);
        } catch (err) {
            console.warn("Font loading failed or timed out. Using fallback fonts.", err);
        }
    }

    // 2. Canvas Setup
    const canvas = document.createElement('canvas');
    const C_WIDTH = 600;
    const C_HEIGHT = 900;
    canvas.width = C_WIDTH;
    canvas.height = C_HEIGHT;
    const ctx = canvas.getContext('2d');

    // 3. Colors
    const BG_COLOR = '#F5E8C9'; // Parchment
    const TEXT_COLOR_MAIN = '#4A3B31'; // Dark brown
    const TEXT_COLOR_SUB = '#5A4B41'; // Slightly lighter brown
    const BORDER_COLOR = '#3A2B21'; // Very dark brown

    // 4. Background
    ctx.fillStyle = BG_COLOR;
    ctx.fillRect(0, 0, C_WIDTH, C_HEIGHT);
    addBackgroundTexture(ctx, C_WIDTH, C_HEIGHT);

    // 5. Borders
    const OUTER_BORDER_WIDTH = 15;
    ctx.strokeStyle = BORDER_COLOR;
    ctx.lineWidth = OUTER_BORDER_WIDTH;
    ctx.strokeRect(OUTER_BORDER_WIDTH / 2, OUTER_BORDER_WIDTH / 2, C_WIDTH - OUTER_BORDER_WIDTH, C_HEIGHT - OUTER_BORDER_WIDTH);
    
    const INNER_BORDER_MARGIN = OUTER_BORDER_WIDTH + 10;
    ctx.lineWidth = 2;
    ctx.strokeStyle = TEXT_COLOR_MAIN;
    ctx.strokeRect(INNER_BORDER_MARGIN, INNER_BORDER_MARGIN, C_WIDTH - 2 * INNER_BORDER_MARGIN, C_HEIGHT - 2 * INNER_BORDER_MARGIN);

    // --- Content Layout ---
    const contentMarginX = INNER_BORDER_MARGIN + 25;
    const contentMarginTop = INNER_BORDER_MARGIN + 20;
    const contentWidth = C_WIDTH - 2 * contentMarginX;
    let currentY = contentMarginTop;

    ctx.textAlign = 'center';
    ctx.textBaseline = 'top';

    // 6. "WANTED" Text
    ctx.fillStyle = TEXT_COLOR_MAIN;
    const wantedText = "WANTED";
    const wantedFontSize = Math.max(20, Math.min(90, contentWidth / Math.max(1, wantedText.length * 0.65)));
    ctx.font = `bold ${wantedFontSize}px "${FONT_MAIN}"`;
    ctx.fillText(wantedText, C_WIDTH / 2, currentY);
    currentY += wantedFontSize * 1.0 + 10; 

    // 7. "DEAD OR ALIVE" Text
    ctx.fillStyle = TEXT_COLOR_SUB;
    const deadOrAliveText = "DEAD OR ALIVE";
    const deadOrAliveFontSize = Math.max(15, Math.min(30, contentWidth / Math.max(1, deadOrAliveText.length * 0.5)));
    ctx.font = `${deadOrAliveFontSize}px "${FONT_SUB}"`;
    ctx.fillText(deadOrAliveText, C_WIDTH / 2, currentY);
    currentY += deadOrAliveFontSize * 1.0 + 20;

    // 8. Image Section
    const imgContainerX = contentMarginX;
    const imgContainerY = currentY;
    const imgContainerWidth = contentWidth;
    const imgContainerHeight = 300; 

    ctx.strokeStyle = BORDER_COLOR;
    ctx.lineWidth = 4;
    ctx.strokeRect(imgContainerX, imgContainerY, imgContainerWidth, imgContainerHeight);

    let imgRenderedSuccessfully = false;
    const imgValid = originalImg && typeof originalImg.width === 'number' && originalImg.width > 0 && 
                     typeof originalImg.height === 'number' && originalImg.height > 0;

    if (imgValid) {
        const imgPadding = 5;
        const availableWidthForImage = imgContainerWidth - 2 * imgPadding;
        const availableHeightForImage = imgContainerHeight - 2 * imgPadding;
        const imgAspectRatio = originalImg.width / originalImg.height;

        let drawWidth = availableWidthForImage;
        let drawHeight = drawWidth / imgAspectRatio;
        if (drawHeight > availableHeightForImage) {
            drawHeight = availableHeightForImage;
            drawWidth = drawHeight * imgAspectRatio;
        }
        drawWidth = Math.max(1, drawWidth); 
        drawHeight = Math.max(1, drawHeight);

        const imgDrawX = imgContainerX + imgPadding + (availableWidthForImage - drawWidth) / 2;
        const imgDrawY = imgContainerY + imgPadding + (availableHeightForImage - drawHeight) / 2;
        
        ctx.save(); 
        ctx.filter = 'sepia(0.75) grayscale(0.15) contrast(1.05) brightness(0.95)';
        try {
            ctx.drawImage(originalImg, imgDrawX, imgDrawY, drawWidth, drawHeight);
            imgRenderedSuccessfully = true;
        } catch (e) {
            console.error("Error drawing image:", e);
        }
        ctx.restore(); 
    }

    if (!imgRenderedSuccessfully) {
        const phRectX = imgContainerX + 5;
        const phRectY = imgContainerY + 5;
        const phRectW = imgContainerWidth - 10;
        const phRectH = imgContainerHeight - 10;

        ctx.fillStyle = '#D8C8B8'; // Placeholder box color
        ctx.fillRect(phRectX, phRectY, phRectW, phRectH);
        
        ctx.fillStyle = TEXT_COLOR_MAIN;
        const prevFont = ctx.font; // Save current settings
        const prevBaseline = ctx.textBaseline;
        const prevAlign = ctx.textAlign;

        ctx.font = `22px "${FONT_SUB}"`; // Use sub font for placeholder text
        ctx.textBaseline = 'middle'; 
        ctx.textAlign = 'center'; 
        ctx.fillText("IMAGE UNAVAILABLE", phRectX + phRectW / 2, phRectY + phRectH / 2);
        
        ctx.font = prevFont; // Restore settings
        ctx.textBaseline = prevBaseline;
        ctx.textAlign = prevAlign;
    }
    
    currentY = imgContainerY + imgContainerHeight + 25; // Space after image area

    // Restore main text drawing settings (wrapText helper assumes these are set)
    ctx.textAlign = 'center'; 
    ctx.textBaseline = 'top';

    // 9. Wanted Name
    ctx.fillStyle = TEXT_COLOR_MAIN;
    const nameFontSize = Math.max(18, Math.min(50, contentWidth / Math.max(1, wantedName.length * 0.35)));
    const nameLineHeight = nameFontSize * 1.15;
    ctx.font = `${nameFontSize}px "${FONT_MAIN}"`;
    currentY = wrapTextAndGetNextY(ctx, wantedName.toUpperCase(), C_WIDTH / 2, currentY, contentWidth, nameLineHeight);
    currentY += 15; 

    // 10. Crime Text
    ctx.fillStyle = TEXT_COLOR_SUB;
    const crimeFontSize = Math.max(14, Math.min(28, contentWidth / Math.max(1, crime.length * 0.25)));
    const crimeLineHeight = crimeFontSize * 1.2;
    ctx.font = `${crimeFontSize}px "${FONT_SUB}"`;
    const crimeFormatted = `FOR ${crime.toUpperCase()}`;
    currentY = wrapTextAndGetNextY(ctx, crimeFormatted, C_WIDTH / 2, currentY, contentWidth * 0.95, crimeLineHeight);
    currentY += 30;

    // 11. Reward Text
    ctx.fillStyle = TEXT_COLOR_MAIN;
    const rewardFontSize = Math.max(20, Math.min(60, contentWidth / Math.max(1, reward.length * 0.4)));
    const rewardLineHeight = rewardFontSize * 1.0;
    ctx.font = `${rewardFontSize}px "${FONT_MAIN}"`;
    currentY = wrapTextAndGetNextY(ctx, reward.toUpperCase(), C_WIDTH / 2, currentY, contentWidth, rewardLineHeight);
    
    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 Wild West ‘WANTED’ Poster Creator allows users to design custom ‘WANTED’ posters by uploading an image of a person and specifying details such as their name, the reward for their capture, and the crime they are wanted for. This tool is ideal for creating themed decorations for events, parties, or for fun activities where users can generate personalized posters that mimic the style of historical wanted posters. It features a user-friendly interface, ensuring that anyone can create their own unique wanted poster with ease.

Leave a Reply

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