Please bookmark this page to avoid losing your image tool!

Image Cryptozoology Specimen Card 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,
    specimenName = "Unknown Creature",
    locationFound = "Undisclosed Location",
    dateObserved = "Date Unknown",
    classification = "Cryptid Exemplar",
    observerName = "Dr. E. Blackwood",
    notes = "Further study required. Handle with extreme caution. Sightings are rare and often disputed, contributing to its mythical status. The specimen exhibits unusual biological markers not consistent with known terrestrial fauna.",
    cardTitle = "CRYPTOZOOLOGICAL SPECIMEN RECORD",
    specimenId = "", // If empty, will be auto-generated
    stampText = "CLASSIFIED"
) {

    const FONT_FAMILY_NAME = 'Special Elite';
    const FONT_URL = 'https://fonts.gstatic.com/s/specialelite/v19/XLYgIZbkc4JPVQnNIAb0bHdSFM3s_3so_Pef.woff2';
    const FONT_STRING_WITH_FALLBACK = `'${FONT_FAMILY_NAME}', 'Courier New', 'Courier', monospace`;

    async function ensureFontIsLoaded(fontFamilyName, fontUrl) {
        if (document.fonts && document.fonts.check && document.fonts.check(`1em ${fontFamilyName}`)) {
            return true;
        }
        if (typeof FontFace === 'function') {
            const fontFace = new FontFace(fontFamilyName, `url(${fontUrl}) format('woff2')`);
            try {
                await fontFace.load();
                document.fonts.add(fontFace);
                await new Promise(resolve => setTimeout(resolve, 50)); // Brief pause
                return document.fonts.check(`1em ${fontFamilyName}`);
            } catch (e) {
                console.error(`FontFace API: Font ${fontFamilyName} failed to load:`, e);
            }
        }
        // Fallback for older systems or if FontFace API fails: inject @font-face
        const styleId = `font-style-${fontFamilyName.replace(/\s+/g, '-')}`;
        if (!document.getElementById(styleId)) {
            const style = document.createElement('style');
            style.id = styleId;
            style.textContent = `@font-face { font-family: '${fontFamilyName}'; src: url('${fontUrl}') format('woff2'); font-display: swap; }`;
            document.head.appendChild(style);
            await new Promise(resolve => setTimeout(resolve, 200)); // Wait longer
            if (document.fonts && document.fonts.check && document.fonts.check(`1em ${fontFamilyName}`)) return true;
        }
        console.warn(`Font ${fontFamilyName} may not be loaded. Using fallback.`);
        return false;
    }

    const fontLoaded = await ensureFontIsLoaded(FONT_FAMILY_NAME, FONT_URL);
    const currentFontFamily = fontLoaded ? FONT_STRING_WITH_FALLBACK : `'Courier New', 'Courier', monospace`;

    const canvas = document.createElement('canvas');
    const canvasWidth = 800;
    const canvasHeight = 600;
    canvas.width = canvasWidth;
    canvas.height = canvasHeight;
    const ctx = canvas.getContext('2d');

    const bgColor = '#f5e8d0'; 
    const textColor = '#3D2B1F';
    const labelColor = '#4a3b32';
    const borderColor = '#5A3A22';
    const stampColor = '#A03030';

    const padding = 30;
    const baseLineHeight = 22; 
    const detailFontSize = "15px";
    const labelFontSize = "bold 15px";
    const titleFontSize = "bold 26px";
    const notesTitleFontSize = "bold 16px"; // Adjusted size for notes title

    function drawWrappedText(context, text, x, y, maxWidth, lineHeight, fontStyle) {
        context.font = fontStyle;
        const words = String(text).split(' ');
        let lineContent = '';
        let currentLineY = y; 
        let linesOutput = 0;

        if (String(text).trim() === '') return y; 

        for (let i = 0; i < words.length; i++) {
            const testLine = lineContent + words[i] + ' ';
            if (context.measureText(testLine).width > maxWidth && i > 0 && lineContent !== '') {
                context.fillText(lineContent.trim(), x, currentLineY);
                lineContent = words[i] + ' ';
                currentLineY += lineHeight;
                linesOutput++;
            } else {
                lineContent = testLine;
            }
        }
        if (lineContent.trim() !== '') {
            context.fillText(lineContent.trim(), x, currentLineY);
            linesOutput++;
        }
        return y + (linesOutput * lineHeight); 
    }
    
    ctx.fillStyle = bgColor;
    ctx.fillRect(0, 0, canvasWidth, canvasHeight);

    ctx.strokeStyle = borderColor;
    ctx.lineWidth = 3; ctx.strokeRect(5, 5, canvasWidth - 10, canvasHeight - 10);
    ctx.lineWidth = 1; ctx.strokeRect(15, 15, canvasWidth - 30, canvasHeight - 30);

    let currentY = padding + 20;

    ctx.textAlign = 'center';
    ctx.font = `${titleFontSize} ${currentFontFamily}`;
    ctx.fillStyle = textColor;
    ctx.fillText(cardTitle.toUpperCase(), canvasWidth / 2, currentY);
    currentY += 35;

    const finalSpecimenId = specimenId || `CSX-${String(Math.floor(Math.random() * 900000) + 100000).padStart(6, '0')}`;
    ctx.textAlign = 'right';
    ctx.font = `${detailFontSize} ${currentFontFamily}`;
    ctx.fillStyle = labelColor;
    ctx.fillText(`ID: ${finalSpecimenId}`, canvasWidth - padding - 10, currentY);
    currentY += 25;

    ctx.beginPath();
    ctx.moveTo(padding + 10, currentY); ctx.lineTo(canvasWidth - padding - 10, currentY);
    ctx.lineWidth = 1.5; ctx.strokeStyle = borderColor; ctx.stroke();
    currentY += 25;

    const mainContentStartY = currentY;
    const imageSectionX = padding + 10;
    const imageSectionMaxWidth = canvasWidth * 0.42;
    const imageSectionMaxHeight = canvasHeight * 0.48;
    let imageSectionActualHeight = 0;

    if (originalImg && originalImg.width > 0 && originalImg.height > 0 && originalImg.complete) {
        const ar = originalImg.width / originalImg.height;
        let dw = imageSectionMaxWidth, dh = dw / ar;
        if (dh > imageSectionMaxHeight) { dh = imageSectionMaxHeight; dw = dh * ar; }
        if (dw > imageSectionMaxWidth) { dw = imageSectionMaxWidth; dh = dw / ar; }
        const ix = imageSectionX + (imageSectionMaxWidth - dw) / 2, iy = mainContentStartY;
        ctx.drawImage(originalImg, ix, iy, dw, dh);
        imageSectionActualHeight = dh;
        ctx.strokeStyle = labelColor; ctx.lineWidth = 2;
        ctx.strokeRect(ix - 2, iy - 2, dw + 4, dh + 4);
    } else {
        const phX = imageSectionX, phY = mainContentStartY, phW = imageSectionMaxWidth, phH = imageSectionMaxHeight;
        ctx.strokeStyle = labelColor; ctx.lineWidth = 1;
        ctx.strokeRect(phX, phY, phW, phH);
        ctx.textAlign = 'center';
        drawWrappedText(ctx, "[Image Unavailable]", phX + phW / 2, phY + phH / 2 - baseLineHeight / 2, phW - 10, baseLineHeight, `${detailFontSize} ${currentFontFamily}`);
        imageSectionActualHeight = phH;
    }
    const imageSectionEndY = mainContentStartY + imageSectionActualHeight;

    const textFieldsX = imageSectionX + imageSectionMaxWidth + 20; // Reduced gap
    const textFieldsWidth = canvasWidth - textFieldsX - padding - 10;
    let currentTextY = mainContentStartY;

    const fieldsData = [
        { label: "SPECIMEN NAME:", value: specimenName }, { label: "CLASSIFICATION:", value: classification },
        { label: "LOCATION:", value: locationFound }, { label: "DATE OBSERVED:", value: dateObserved },
        { label: "FILED BY:", value: observerName }
    ];
    
    const fixedLabelWidth = 130; 
    const fieldItemSpacing = baseLineHeight * 0.5; 

    for (const field of fieldsData) {
        ctx.fillStyle = labelColor; ctx.textAlign = 'left';
        const labelEndY = drawWrappedText(ctx, field.label, textFieldsX, currentTextY, fixedLabelWidth - 5, baseLineHeight, `${labelFontSize} ${currentFontFamily}`);
        ctx.fillStyle = textColor;
        const valueX = textFieldsX + fixedLabelWidth;
        const valueMaxWidth = textFieldsWidth - fixedLabelWidth;
        const valueEndY = drawWrappedText(ctx, field.value, valueX, currentTextY, valueMaxWidth, baseLineHeight, `${detailFontSize} ${currentFontFamily}`);
        currentTextY = Math.max(labelEndY, valueEndY) + fieldItemSpacing;
    }
    const textFieldsEndY = currentTextY - fieldItemSpacing;

    currentY = Math.max(imageSectionEndY, textFieldsEndY) + 15; // Reduced spacing
    const notesSectionMaxY = canvasHeight - padding - (stampText && stampText.trim() !== "" ? 45 : 10); // Max Y before stamp/bottom padding

    if (currentY > notesSectionMaxY - 60) { // Ensure minimum space for notes title and a bit of text
        currentY = Math.max(imageSectionEndY, textFieldsEndY) + 10;  // Minimal spacing if already too cramped
        if (currentY > notesSectionMaxY - 60) currentY = notesSectionMaxY - 60; // Absolute cap
    }
    
    ctx.beginPath();
    ctx.moveTo(padding + 10, currentY); ctx.lineTo(canvasWidth - padding - 10, currentY);
    ctx.lineWidth = 1.5; ctx.strokeStyle = borderColor; ctx.stroke();
    currentY += 20;

    ctx.textAlign = 'left'; ctx.fillStyle = labelColor;
    const notesTitleEndY = drawWrappedText(ctx, "ADDITIONAL NOTES:", padding + 10, currentY, textFieldsWidth, baseLineHeight, `${notesTitleFontSize} ${currentFontFamily}`);
    currentY = notesTitleEndY + 5; // Space after notes title


    ctx.fillStyle = textColor;
    const notesX = padding + 10;
    const notesMaxWidth = canvasWidth - 2 * (padding + 10);
    // Draw notes, but respect the notesSectionMaxY
    // This simple drawWrappedText doesn't truncate; it will draw all lines.
    // For trucation, drawWrappedText would need to accept a max_height or max_lines.
    let finalNotesY = drawWrappedText(ctx, notes, notesX, currentY, notesMaxWidth, baseLineHeight, `${detailFontSize} ${currentFontFamily}`);
    if (finalNotesY > notesSectionMaxY) {
        // Basic handling: if notes overflow, they will be clipped by the canvas bottom or stamp.
        // To implement "..." if truncated, drawWrappedText would need modification.
    }


    if (stampText && stampText.trim() !== "") {
        const stampW = 110, stampH = 30;
        const stampPosX = canvasWidth - padding - stampW - 10;
        const stampPosY = canvasHeight - padding - stampH - 5;

        ctx.save();
        ctx.translate(stampPosX + stampW / 2, stampPosY + stampH / 2);
        ctx.rotate(-10 * Math.PI / 180);
        ctx.strokeStyle = stampColor;  ctx.lineWidth = 2;
        ctx.strokeRect(-stampW / 2, -stampH / 2, stampW, stampH);
        ctx.font = `bold 16px ${currentFontFamily}`;
        ctx.fillStyle = stampColor; ctx.textAlign = 'center'; ctx.textBaseline = 'middle';
        ctx.fillText(stampText.toUpperCase(), 0, 1); // Small Y offset for better visual centering
        ctx.restore();
    }
    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 Cryptozoology Specimen Card Creator is an online tool designed to generate customized specimen cards for cryptozoological entities. Users can upload an image of a creature, input details such as the specimen’s name, location, date observed, and additional notes. The tool formats this information onto a visually appealing card layout, complete with a designated stamp for classified cases. This tool is useful for educators, researchers, and enthusiasts interested in documenting and sharing information about mythical or rare creatures. It serves as an engaging method for organizing cryptozoological data and can be used for presentations, reports, or personal collections.

Leave a Reply

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

Other Image Tools:

Image Impressionist Painting Frame Creator

Image Paleontological Specimen Label Creator

Image Wizard’s Magical Tome Creator

Image Space Bounty Hunter Poster Creator

Image Government Classified Document Creator

Image Evidence File Creator for Ghost Hunters

Image Multiverse Passport Creator

Celtic Illuminated Manuscript Image Creator

Image Occult Grimoire Page Creator

Image Steampunk Invention Blueprint Creator

Image Cathedral Window Frame Creator

Image 8-Bit Pixel Art Frame Creator

Image Revolutionary Manifesto Page Creator

Mythical Kingdom Image Document Creator

Image Role-Playing Game Character Sheet Creator

Baroque Portrait Frame Creator Tool

Image Royal Decree Proclamation Creator

Image Paranormal Investigation File Creator

Victorian Séance Invitation Image Creator

Image Vintage Apothecary Label Creator

Image Archaeological Discovery Document Creator

Image Witch’s Spell Book Page Creator

Image Expedition Map Creator

Image Sherlock Holmes Case File Creator

Image Time Traveler’s Document Creator

Image Cyberpunk Corporate ID Creator

Image Time Period Authentication Creator

Viking Runestone Frame Image Creator

Image Vintage Arcade Cabinet Art Creator

Byzantine Mosaic Frame Creator

Image Fighting Game Character Select Screen Creator

Image UFO Encounter Documentation Creator

Image Fantasy Realm Map Creator

Image Interdimensional Travel Permit Creator

Image Mad Scientist’s Laboratory Notes Creator

Image Underground Resistance Flyer Creator

See All →