Please bookmark this page to avoid losing your image tool!

Image Paleontological Specimen Label 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.
function processImage(
    originalImg,
    specimenId = "SPECIMEN ID",
    genus = "Genus",
    species = "species",
    locality = "Locality",
    formation = "Formation/Horizon",
    collector = "Collector",
    dateCollected = "Date Collected",
    institution = "MUSEUM OF PALEONTOLOGY",
    fontFamily = "Times New Roman, serif",
    baseFontSize = 12, // px
    textColor = "black",
    backgroundColor = "white",
    borderColor = "black",
    borderWidth = 2, // px
    labelWidth = 600, // px
    imagePortion = 0.4 // Ratio of (content width after padding) for image block
) {

    // Helper function for text wrapping. Returns array of lines.
    function _addTextLinesToFit(ctx, text, maxWidth, fontFamilyName, fSize, fStyle, fItalic) {
        ctx.font = `${fStyle} ${fItalic ? "italic " : ""}${fSize}px ${fontFamilyName}`;
        const words = String(text).split(' '); // Ensure text is a string
        const lines = [];
        
        if (String(text).trim().length === 0) return [""]; // Empty text, one empty line.

        let currentLine = words[0] || "";
        for (let i = 1; i < words.length; i++) {
            const word = words[i];
            const testLine = currentLine + " " + word;
            if (ctx.measureText(testLine).width <= maxWidth && currentLine) { // Also check currentLine is not empty
                currentLine = testLine;
            } else {
                lines.push(currentLine);
                currentLine = word;
            }
        }
        lines.push(currentLine); // Add the last line
        return lines;
    }

    // --- 1. Initial canvas and context for measurements ---
    const tempCanvas = document.createElement('canvas');
    const ctx = tempCanvas.getContext('2d');

    // --- 2. Define parameters, fonts, layout constants ---
    const padding = baseFontSize; // General padding unit
    const institutionFontSize = baseFontSize * 1.5;
    const mainTextFontSize = baseFontSize;
    const lineHeight = baseFontSize * 1.4; // Line height for main text
    const institutionLineHeight = institutionFontSize * 1.2; // Line height for institution text

    // --- 3. Calculate Institution Text Height & Lines ---
    let calculatedCurrentY = padding;
    ctx.font = `bold ${institutionFontSize}px ${fontFamily}`; // Font for measurement
    const institutionLines = _addTextLinesToFit(ctx, institution.toUpperCase(), labelWidth - 2 * padding, fontFamily, institutionFontSize, "bold", false);
    institutionLines.forEach(() => calculatedCurrentY += institutionLineHeight);
    calculatedCurrentY += padding; // Padding after institution block

    const imageAndTextStartY = calculatedCurrentY;

    // --- 4. Calculate Image Dimensions (scaled to fit its allocated portion) ---
    // Image block width calculation
    const totalContentWidth = labelWidth - 2 * padding; // Width available between outer paddings
    const imageBlockMaxWidth = (totalContentWidth - padding) * imagePortion; // Max width for image, with padding between image and text
    
    let displayImgWidth = imageBlockMaxWidth;
    let displayImgHeight = 0;

    if (originalImg && originalImg.naturalWidth > 0 && originalImg.naturalHeight > 0) {
        const imgAspectRatio = originalImg.naturalWidth / originalImg.naturalHeight;
        displayImgHeight = displayImgWidth / imgAspectRatio;

        // Optional: cap max image height (e.g., to prevent extremely tall images from dominating)
        const maxPracticalImgHeight = labelWidth * 0.6; // مثلاً %60 از عرض لیبل
        if (displayImgHeight > maxPracticalImgHeight) {
            displayImgHeight = maxPracticalImgHeight;
            displayImgWidth = displayImgHeight * imgAspectRatio;
        }
        // Ensure it still fits width-wise if height capping changed width beyond max
        if (displayImgWidth > imageBlockMaxWidth) {
            displayImgWidth = imageBlockMaxWidth;
            displayImgHeight = displayImgWidth / imgAspectRatio;
        }
    } else { // Handle cases where image is not valid or not loaded
        displayImgWidth = imageBlockMaxWidth; // Reserve space
        displayImgHeight = imageBlockMaxWidth; // Default to a square-ish placeholder area
    }


    // --- 5. Calculate Text Block final X position and Width ---
    const textBlockX = padding + displayImgWidth + padding; // X-coord for text block start
    const textBlockWidth = labelWidth - textBlockX - padding; // Actual width for text block content

    // --- 6. Calculate Text Stack Height & Prepare Drawable Text Items ---
    let textStackHeight = 0;
    const processedTextItems = []; // Stores objects describing each text line/block for drawing

    const itemsToDrawConfig = [
        { type: "field", label: "Genus: ", value: genus, isValueItalic: true },
        { type: "field", label: "Species: ", value: species, isValueItalic: true },
        { type: "field", label: "Specimen ID: ", value: specimenId, isValueItalic: false },
        { type: "separator", height: lineHeight * 0.5 },
        { type: "full_wrap_field", label: "Locality: ", value: locality, isItalic: false },
        { type: "full_wrap_field", label: "Formation: ", value: formation, isItalic: false },
        { type: "field", label: "Collector: ", value: collector, isValueItalic: false },
        { type: "field", label: "Date Collected: ", value: dateCollected, isValueItalic: false },
    ];

    for (const item of itemsToDrawConfig) {
        if (item.type === "separator") {
            processedTextItems.push(item);
            textStackHeight += item.height;
        } else if (item.type === "full_wrap_field") {
            const fullText = item.label + item.value;
            const wrappedLines = _addTextLinesToFit(ctx, fullText, textBlockWidth, fontFamily, mainTextFontSize, "normal", item.isItalic);
            wrappedLines.forEach(line => {
                processedTextItems.push({ type: "text_line", text: line, fontStyle: "normal", isItalic: item.isItalic });
                textStackHeight += lineHeight;
            });
        } else if (item.type === "field") { // mixed_style_line logic
            ctx.font = `normal ${mainTextFontSize}px ${fontFamily}`; // Font for label part measurement
            const labelMetrics = ctx.measureText(item.label);
            const valueAvailableWidth = textBlockWidth - labelMetrics.width;
            
            const valueLines = _addTextLinesToFit(ctx, item.value, valueAvailableWidth, fontFamily, mainTextFontSize, "normal", item.isValueItalic);
            
            processedTextItems.push({
                type: "mixed_style_line",
                label: item.label,
                valueLines: valueLines,
                isValueItalic: item.isValueItalic
            });
            textStackHeight += lineHeight * Math.max(1, valueLines.length); // Each wrapped value line takes height
        }
    }

    // --- 7. Determine Main Content Height and Final Canvas Height ---
    const mainContentBlockHeight = Math.max(displayImgHeight, textStackHeight);
    const canvasHeight = imageAndTextStartY + mainContentBlockHeight + padding;

    // --- 8. Create final canvas and get its context ---
    const canvas = document.createElement('canvas');
    canvas.width = labelWidth;
    canvas.height = canvasHeight;
    const drawCtx = canvas.getContext('2d');

    // --- 9. Fill background and draw border ---
    drawCtx.fillStyle = backgroundColor;
    drawCtx.fillRect(0, 0, canvas.width, canvas.height);

    if (borderWidth > 0) {
        drawCtx.strokeStyle = borderColor;
        drawCtx.lineWidth = borderWidth;
        drawCtx.strokeRect(borderWidth / 2, borderWidth / 2, canvas.width - borderWidth, canvas.height - borderWidth);
    }

    // --- 10. Draw elements ---
    drawCtx.fillStyle = textColor;
    drawCtx.textBaseline = "top";

    // 10.1. Draw Institution Text
    let currentDrawY = padding;
    drawCtx.font = `bold ${institutionFontSize}px ${fontFamily}`;
    drawCtx.textAlign = "center";
    institutionLines.forEach(line => {
        drawCtx.fillText(line, labelWidth / 2, currentDrawY);
        currentDrawY += institutionLineHeight;
    });
    currentDrawY += padding; // This should now be == imageAndTextStartY

    // 10.2. Draw Image (Vertically centered in its part of mainContentBlockHeight)
    let imgDrawY = imageAndTextStartY;
    if (mainContentBlockHeight > displayImgHeight) {
        imgDrawY += (mainContentBlockHeight - displayImgHeight) / 2;
    }
    if (originalImg && originalImg.naturalWidth > 0) { // Only draw if valid image
         drawCtx.drawImage(originalImg, padding, imgDrawY, displayImgWidth, displayImgHeight);
    } else { // Optional: draw a placeholder box for image if not available
        drawCtx.strokeStyle = '#ccc';
        drawCtx.lineWidth = 1;
        drawCtx.strokeRect(padding, imgDrawY, displayImgWidth, displayImgHeight);
        drawCtx.textAlign = "center";
        drawCtx.font = `${mainTextFontSize}px ${fontFamily}`;
        drawCtx.fillText("Image", padding + displayImgWidth/2, imgDrawY + displayImgHeight/2 - mainTextFontSize/2);
    }
   

    // 10.3. Draw Text Block (Vertically centered in its part of mainContentBlockHeight)
    let textDrawCurrentY = imageAndTextStartY;
    if (mainContentBlockHeight > textStackHeight) {
        textDrawCurrentY += (mainContentBlockHeight - textStackHeight) / 2;
    }
    drawCtx.textAlign = "left";

    for (const pItem of processedTextItems) {
        if (pItem.type === "separator") {
            drawCtx.beginPath();
            drawCtx.moveTo(textBlockX, textDrawCurrentY + pItem.height / 2);
            drawCtx.lineTo(textBlockX + textBlockWidth, textDrawCurrentY + pItem.height / 2);
            drawCtx.strokeStyle = textColor;
            drawCtx.lineWidth = 1;
            drawCtx.stroke();
            textDrawCurrentY += pItem.height;
        } else if (pItem.type === "text_line") {
            drawCtx.font = `${pItem.fontStyle} ${pItem.isItalic ? "italic " : ""}${mainTextFontSize}px ${fontFamily}`;
            drawCtx.fillText(pItem.text, textBlockX, textDrawCurrentY);
            textDrawCurrentY += lineHeight;
        } else if (pItem.type === "mixed_style_line") {
            drawCtx.font = `normal ${mainTextFontSize}px ${fontFamily}`; // Label part is normal
            const labelPartMetrics = drawCtx.measureText(pItem.label); // Measure for positioning value
            drawCtx.fillText(pItem.label, textBlockX, textDrawCurrentY);

            drawCtx.font = `normal ${pItem.isValueItalic ? "italic " : ""}${mainTextFontSize}px ${fontFamily}`; // Value part font
            
            let valueLineY = textDrawCurrentY;
            pItem.valueLines.forEach((vLine, index) => {
                if (index === 0) {
                    drawCtx.fillText(vLine, textBlockX + labelPartMetrics.width, valueLineY);
                } else {
                    // Subsequent wrapped lines of value align with start of value text
                    drawCtx.fillText(vLine, textBlockX + labelPartMetrics.width, valueLineY);
                }
                if (index < pItem.valueLines.length - 1) { // If not the last value line
                     valueLineY += lineHeight;
                }
            });
            textDrawCurrentY += lineHeight * Math.max(1, pItem.valueLines.length);
        }
    }

    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 Paleontological Specimen Label Creator is a web-based tool designed for creating custom labels for paleontological specimens. Users can input various details such as specimen ID, genus, species, locality, formation, collector, and the date of collection. The tool allows the integration of an image representing the specimen, formatting the label in a structured layout to enhance clarity and presentation. This tool is particularly useful for paleontologists, researchers, and educators who need to document and present paleontological data effectively. By automating the label creation process, it helps in maintaining consistency and improving the aesthetics of specimen documentation.

Leave a Reply

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