Please bookmark this page to avoid losing your image tool!

Image Monster Hunter Bestiary 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,
    monsterName = "Unknown Creature",
    description = "A mysterious beast of unknown origin. Its habits and abilities are yet to be fully documented. Approach with extreme caution.",
    weaknesses = "Fire,Ice,Thunder",
    ailments = "Poison,Sleep,Paralysis",
    habitat = "Various Regions",
    themeColor = "#654321", // Dark Brown
    fontName = "Georgia" // Fallback font family
) {

    const canvas = document.createElement('canvas');
    const canvasWidth = 700;
    const canvasHeight = 1000; // Fixed height, content might be clipped if too long
    canvas.width = canvasWidth;
    canvas.height = canvasHeight;
    const ctx = canvas.getContext('2d');

    // --- Helper: Font Loader ---
    async function loadWebFont(fontFamilyToLoad, fontUrl) {
        // Check if font is already loaded/available
        if (document.fonts.check(`12px "${fontFamilyToLoad}"`)) {
            // console.log(`${fontFamilyToLoad} font already available.`);
            return true;
        }
        const fontFace = new FontFace(fontFamilyToLoad, `url(${fontUrl})`);
        try {
            await fontFace.load();
            document.fonts.add(fontFace);
            // console.log(`${fontFamilyToLoad} font loaded successfully.`);
            return true;
        } catch (e) {
            console.error(`Font ${fontFamilyToLoad} failed to load:`, e);
            return false;
        }
    }

    // --- Helper: Text Wrapper ---
    function wrapText(context, text, x, y, maxWidth, lineHeight, currentFontForWrap) {
        context.font = currentFontForWrap;
        let words = text.split(' ');
        let line = '';
        let currentLineY = y;

        for (let n = 0; n < words.length; n++) {
            let testLine = line + words[n] + ' ';
            let metrics = context.measureText(testLine);
            let testWidth = metrics.width;
            if (testWidth > maxWidth && n > 0) {
                context.fillText(line.trim(), x, currentLineY);
                line = words[n] + ' ';
                currentLineY += lineHeight;
            } else {
                line = testLine;
            }
        }
        context.fillText(line.trim(), x, currentLineY);
        return currentLineY + lineHeight; // Return Y position after the last line of text
    }
    
    // --- Helper: Draw Item List (Weaknesses/Ailments) ---
    const ELEMENT_DATA = {
        "fire": { name: "Fire", color: "#FF4500" },
        "water": { name: "Water", color: "#1E90FF" },
        "thunder": { name: "Thunder", color: "#FFD700" },
        "ice": { name: "Ice", color: "#00BFFF" },
        "dragon": { name: "Dragon", color: "#8A2BE2" }
    };
    const AILMENT_DATA = {
        "poison": { name: "Poison", color: "#9400D3" },
        "sleep": { name: "Sleep", color: "#AFEEEE" },
        "paralysis": { name: "Paralysis", color: "#FFA500" },
        "stun": { name: "Stun", color: "#FFFFE0" }, // LightYellow, ensure text contrast
        "blast": { name: "Blast", color: "#FF6347" }
    };

    function drawItemsList(context, itemsString, dataMap, title, x, startY, itemIconSize, itemLineHeight, spacingAfterTitle, titleFont, itemFont, listTitleColor, itemTextColor) {
        context.fillStyle = listTitleColor;
        context.font = titleFont;
        
        // Measure title height for better spacing
        const titleMetrics = context.measureText(title);
        const titleHeight = (titleMetrics.actualBoundingBoxAscent || parseInt(titleFont.match(/\d+px/)[0])) + (titleMetrics.actualBoundingBoxDescent || 0);
        context.fillText(title, x, startY + titleHeight * 0.8); // Approximate baseline adjustment
        
        let currentItemY = startY + titleHeight + spacingAfterTitle;
        const items = itemsString.toLowerCase().split(',').map(s => s.trim()).filter(s => s);

        items.forEach(itemKey => {
            const itemData = dataMap[itemKey.toLowerCase()]; // Ensure key is lowercase for lookup
            
            if (itemData) {
                // Draw colored square icon
                context.fillStyle = itemData.color;
                context.fillRect(x, currentItemY, itemIconSize, itemIconSize);
                
                // Draw item name text
                context.fillStyle = itemTextColor;
                context.font = itemFont;
                let textToDraw = itemData.name || (itemKey.charAt(0).toUpperCase() + itemKey.slice(1));
                
                context.textBaseline = 'middle';
                context.fillText(textToDraw, x + itemIconSize + 10, currentItemY + itemIconSize / 2);
                context.textBaseline = 'alphabetic'; // Reset to default

                currentItemY += itemLineHeight;
            }
        });
        return currentItemY; // Return Y position after last item
    }

    // --- Content Configuration ---
    const pagePadding = 30;
    let currentY = pagePadding;
    const contentX = pagePadding;
    const contentWidth = canvasWidth - 2 * pagePadding;
    const textColor = "#333333"; // Dark gray for general text

    // --- Font Setup ---
    const cinzelUrl = 'https://fonts.gstatic.com/s/cinzeldecorative/v19/daaCSScvJGqLYhGmdTQIHmywRcrJq8-30m0.woff2'; // Cinzel Decorative Regular
    let fontLoaded = await loadWebFont('Cinzel Decorative', cinzelUrl);
    const baseFontFamily = fontLoaded ? 'Cinzel Decorative' : fontName;

    // --- Styles ---
    const titleFont = `bold 36px "${baseFontFamily}"`;
    const sectionTitleFont = `bold 20px "${baseFontFamily}"`;
    const itemFont = `16px "${baseFontFamily}"`;
    const descriptionFont = `15px "${baseFontFamily}"`;
    const habitatFont = `italic 16px "${baseFontFamily}"`;
    const habitatValueFont = `16px "${baseFontFamily}"`; // Non-italic for value
    const regularTextLineHeight = 22; // Line height for description

    // 1. Background
    ctx.fillStyle = '#FAF0E6'; // Parchment color
    ctx.fillRect(0, 0, canvas.width, canvas.height);

    // 2. Monster Name
    ctx.font = titleFont;
    ctx.fillStyle = themeColor;
    ctx.textAlign = 'center';
    // Get height of title font for accurate placement
    const monsterNameMetrics = ctx.measureText(monsterName);
    const monsterNameHeight = (monsterNameMetrics.actualBoundingBoxAscent || parseInt(titleFont.match(/\d+px/)[0])) + (monsterNameMetrics.actualBoundingBoxDescent || 0);
    currentY += monsterNameHeight * 0.8; // Adjust Y to be baseline
    ctx.fillText(monsterName, canvasWidth / 2, currentY);
    ctx.textAlign = 'left'; // Reset alignment
    currentY += 30; // Spacing after title

    // 3. Original Image
    if (originalImg && originalImg.complete && originalImg.naturalWidth !== 0) {
        const maxImgDisplayWidth = contentWidth;
        const maxImgDisplayHeight = 300;
        let imgDispWidth, imgDispHeight;

        // Scaling logic to fit image within bounds, preserving aspect ratio, and not scaling up
        let scaleRatio;
        if (originalImg.width > maxImgDisplayWidth || originalImg.height > maxImgDisplayHeight) {
            scaleRatio = Math.min(maxImgDisplayWidth / originalImg.width, maxImgDisplayHeight / originalImg.height);
        } else {
            scaleRatio = 1; // Don't scale up small images
        }
        imgDispWidth = originalImg.width * scaleRatio;
        imgDispHeight = originalImg.height * scaleRatio;

        const imgX = contentX + (contentWidth - imgDispWidth) / 2;
        const imgY = currentY;

        // Draw image border
        ctx.strokeStyle = themeColor;
        ctx.lineWidth = 3;
        ctx.strokeRect(imgX - 3, imgY - 3, imgDispWidth + 6, imgDispHeight + 6);
        
        ctx.drawImage(originalImg, imgX, imgY, imgDispWidth, imgDispHeight);
        currentY += imgDispHeight + 30; // Spacing after image
    } else {
        // Placeholder if image is not loaded or invalid
        const placeholderHeight = 150;
        ctx.fillStyle = '#DDDDDD';
        ctx.fillRect(contentX + contentWidth/4, currentY, contentWidth/2, placeholderHeight);
        ctx.fillStyle = textColor;
        ctx.font = itemFont; // Use a generic font for placeholder text
        ctx.textAlign = 'center';
        ctx.textBaseline = 'middle';
        ctx.fillText("Image Not Available", contentX + contentWidth / 2, currentY + placeholderHeight / 2);
        ctx.textAlign = 'left';
        ctx.textBaseline = 'alphabetic';
        currentY += placeholderHeight + 30; // Spacing after placeholder
    }

    // 4. Habitat
    ctx.font = habitatFont;
    ctx.fillStyle = themeColor; 
    const habitatLabel = "Habitat: ";
    const habitatLabeMetrics = ctx.measureText(habitatLabel); // For positioning the value
    const habitatLineHeight = (ctx.measureText("M").actualBoundingBoxAscent || parseInt(habitatFont.match(/\d+px/)[0])) + (ctx.measureText("M").actualBoundingBoxDescent || 0);

    ctx.fillText(habitatLabel, contentX, currentY + habitatLineHeight * 0.8);
    
    ctx.font = habitatValueFont; 
    ctx.fillStyle = textColor;
    ctx.fillText(habitat, contentX + habitatLabeMetrics.width, currentY + habitatLineHeight * 0.8);
    currentY += habitatLineHeight + 20; // Spacing after habitat

    // 5. Separator Line
    function drawSeparator(yPos) {
        ctx.beginPath();
        ctx.moveTo(contentX, yPos);
        ctx.lineTo(contentX + contentWidth, yPos);
        ctx.strokeStyle = themeColor;
        ctx.lineWidth = 1.5;
        ctx.stroke();
        return yPos + 20; // Spacing after separator
    }
    currentY = drawSeparator(currentY);

    // 6. Weaknesses
    const itemIconRenderSize = 20;
    const itemLineRenderHeight = 28; // Icon height + vertical spacing between items
    const listTitleSpacing = 10; // Space between list title and first item

    currentY = drawItemsList(ctx, weaknesses, ELEMENT_DATA, "Elemental Weaknesses:", 
                           contentX, currentY, itemIconRenderSize, itemLineRenderHeight, listTitleSpacing,
                           sectionTitleFont, itemFont, themeColor, textColor);
    currentY += 15; // Extra spacing after the list of weaknesses

    // 7. Ailments
    currentY = drawItemsList(ctx, ailments, AILMENT_DATA, "Ailment Vulnerabilities:", 
                           contentX, currentY, itemIconRenderSize, itemLineRenderHeight, listTitleSpacing,
                           sectionTitleFont, itemFont, themeColor, textColor);
    currentY += 20; // Spacing after ailment list

    // 8. Separator Line
    currentY = drawSeparator(currentY);

    // 9. Description
    ctx.font = sectionTitleFont;
    ctx.fillStyle = themeColor;
    const descTitleMetrics = ctx.measureText("Description:");
    const descTitleHeight = (descTitleMetrics.actualBoundingBoxAscent || parseInt(sectionTitleFont.match(/\d+px/)[0])) + (descTitleMetrics.actualBoundingBoxDescent || 0);
    ctx.fillText("Description:", contentX, currentY + descTitleHeight * 0.8);
    currentY += descTitleHeight + 10; // Spacing after title

    ctx.fillStyle = textColor;
    currentY = wrapText(ctx, description, contentX, currentY, contentWidth, regularTextLineHeight, descriptionFont);
    // currentY already updated by wrapText to be below the description

    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 Monster Hunter Bestiary Creator is an online tool designed to create visually appealing bestiary entries for various monsters. Users can upload an image of a monster and provide details such as the monster’s name, description, weaknesses, ailments, habitat, and preferred theme color. The tool transforms these inputs into a stylized bestiary page, suitable for gamers, content creators, or enthusiasts looking to document or showcase their monster encounters in a visually engaging manner. This tool is particularly useful for game developers, tabletop RPG players, and fans of monster-themed media.

Leave a Reply

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

Other Image Tools:

Image Vintage Carnival Sideshow Poster Creator

Image Space Explorer’s Log Creator

Image Neolithic Petroglyph Frame Creator

Image Ukiyo-e Japanese Woodblock Print Creator

Image Persian Miniature Painting Creator

Image Sci-Fi Movie Poster Template Creator

Image Horror Movie Poster Template

Image Social Media Milestone Certificate Creator

Halloween Death Certificate Template

Image Anatomical Illustration Frame Creator

Image Romance Novel Cover Template Creator

Image Tabloid Headline Template

Image Space Mission Patch Template Creator

Image Cassette Tape Cover Template Creator

Image Passport Page Template Generator

Image Old Map Frame With Compass Rose Decorator

Image Diploma and Degree Certificate Framer

Image Soviet Propaganda Poster Style Generator

Image Yu-Gi-Oh Card Template Creator

Image Ancient Roman Greek Tablet Frame Creator

Image Marriage Certificate Template Creator

Image Video Game Achievement Frame Creator

Image Newspaper Front Page Template Creator

Image Botanical Illustration Frame Creator

Image Vinyl Record Sleeve Template Creator

Vintage Photo Booth Strip Template Generator

Image Cyberpunk Interface Frame Designer

Image Detective Novel Cover Template

Image Achievement Certificate Framer

Image Illuminated Manuscript Frame Generator

Image Art Deco Poster Frame Creator

Image Egyptian Papyrus Scroll Frame Designer

Image Vintage Postage Stamp Frame Creator

Image Magic: The Gathering Card Frame Generator

Image Birth Certificate Template Generator

Image Driver’s License Template Creator

See All →