Please bookmark this page to avoid losing your image tool!

Image Vintage Scientific Illustration Frame 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,
    bgColor = "rgb(245, 240, 230)", // Aged paper background
    frameColor = "rgb(80, 60, 40)", // Dark sepia/brown for frame lines
    textColor = "rgb(60, 40, 20)", // Darker brown for text
    pageMargin = 30, // Margin around the entire framed content
    imagePadding = 20, // Padding between inner frame line and image
    outerBorderWidth = 3, // Thickness of the outermost frame line
    innerBorderWidth = 1, // Thickness of the inner frame line
    borderGap = 4, // Gap between outer and inner frame lines
    labelBoxHeight = 70, // Height for the caption area below the image
    labelBoxInternalPadding = 10, // Padding inside label box for text
    titleText = "PLATE VII.", // Text above the frame
    figureText = "Fig. 3.", // Figure number text in label box
    captionText = "A curious specimen observed in the field.", // Caption text in label box
    fontFamily = "Georgia, serif", // Font for all text
    baseFontSize = 16 // Base font size for scaling title and caption
) {
    const canvas = document.createElement('canvas');
    const ctx = canvas.getContext('2d');

    const imgW = originalImg.width;
    const imgH = originalImg.height;

    // Calculate text-related heights
    const effectiveTitleFontSize = baseFontSize * 1.2;
    let titleAreaHeight = 0;
    if (titleText && titleText.trim() !== "") {
        titleAreaHeight = effectiveTitleFontSize + 15; // Font size + spacing
    } else {
        titleAreaHeight = 5; // Minimal top spacing if no title
    }

    const effectiveCaptionFontSize = baseFontSize * 0.85;

    // Calculate dimensions from the inside out

    // 1. Area for image content (image + its padding)
    const imgPaddedW = imgW + 2 * imagePadding;
    const imgPaddedH = imgH + 2 * imagePadding;

    // 2. Total content area width (this is consistent for the frame parts)
    //    This is the width *inside* the innermost border line.
    const contentAreaW = imgPaddedW;
    //    Total content area height (image part + label box part)
    //    This is the height *inside* the innermost border line.
    let contentAreaH = imgPaddedH;
    if (labelBoxHeight > 0 && (figureText.trim() !== "" || captionText.trim() !== "")) {
        contentAreaH += labelBoxHeight;
    } else if (labelBoxHeight > 0) { // reserve space even if text is empty but height is given
        contentAreaH += labelBoxHeight;
    }


    // 3. Dimensions including borders and gaps, building outwards
    let currentTotalContentW = contentAreaW;
    let currentTotalContentH = contentAreaH;
    
    // Add inner border thickness
    currentTotalContentW += 2 * innerBorderWidth;
    currentTotalContentH += 2 * innerBorderWidth;
    // Add gap between borders
    currentTotalContentW += 2 * borderGap;
    currentTotalContentH += 2 * borderGap;
    // Add outer border thickness
    currentTotalContentW += 2 * outerBorderWidth;
    currentTotalContentH += 2 * outerBorderWidth;
    
    const totalFramedW = currentTotalContentW; // Full width of the framed element
    const totalFramedH = currentTotalContentH; // Full height of the framed element

    // 4. Calculate final canvas dimensions
    canvas.width = totalFramedW + 2 * pageMargin;
    canvas.height = totalFramedH + 2 * pageMargin + titleAreaHeight;

    // --- Start Drawing ---

    // 1. Fill canvas background
    ctx.fillStyle = bgColor;
    ctx.fillRect(0, 0, canvas.width, canvas.height);

    // 2. Draw Title Text (if any)
    if (titleText && titleText.trim() !== "") {
        ctx.fillStyle = textColor;
        ctx.font = `${effectiveTitleFontSize}px ${fontFamily}`;
        ctx.textAlign = "center";
        const titleX = canvas.width / 2;
        const titleY = pageMargin + effectiveTitleFontSize; // Baseline for text
        ctx.fillText(titleText, titleX, titleY);
    }

    // 3. Draw the Frame
    //    Initial top-left for drawing the outermost frame element
    let frameBoundaryX = pageMargin;
    let frameBoundaryY = pageMargin + titleAreaHeight;
    let frameBoundaryW = totalFramedW;
    let frameBoundaryH = totalFramedH;

    ctx.strokeStyle = frameColor;

    // Draw Outer Border Line
    ctx.lineWidth = outerBorderWidth;
    ctx.strokeRect(
        frameBoundaryX + outerBorderWidth / 2, // Offset to keep line within bounds
        frameBoundaryY + outerBorderWidth / 2,
        frameBoundaryW - outerBorderWidth,
        frameBoundaryH - outerBorderWidth
    );
    // Move inwards past this border
    frameBoundaryX += outerBorderWidth;
    frameBoundaryY += outerBorderWidth;
    frameBoundaryW -= 2 * outerBorderWidth;
    frameBoundaryH -= 2 * outerBorderWidth;

    // Account for Gap (this area remains bgColor)
    frameBoundaryX += borderGap;
    frameBoundaryY += borderGap;
    frameBoundaryW -= 2 * borderGap;
    frameBoundaryH -= 2 * borderGap;

    // Draw Inner Border Line
    ctx.lineWidth = innerBorderWidth;
    ctx.strokeRect(
        frameBoundaryX + innerBorderWidth / 2,
        frameBoundaryY + innerBorderWidth / 2,
        frameBoundaryW - innerBorderWidth,
        frameBoundaryH - innerBorderWidth
    );
    // Move inwards past this border to get the content area
    frameBoundaryX += innerBorderWidth;
    frameBoundaryY += innerBorderWidth;
    // frameBoundaryW -= 2 * innerBorderWidth; // This is now contentAreaW
    // frameBoundaryH -= 2 * innerBorderWidth; // This is now contentAreaH

    const finalContentX = frameBoundaryX;
    const finalContentY = frameBoundaryY;
    
    // 4. Draw the Image
    const imgDestX = finalContentX + imagePadding;
    const imgDestY = finalContentY + imagePadding;
    ctx.drawImage(originalImg, imgDestX, imgDestY, imgW, imgH);

    // 5. Draw Separator Line and Label Box Texts (if labelBoxHeight is specified and text exists)
    if (labelBoxHeight > 0 && (figureText.trim() !== "" || captionText.trim() !== "")) {
        const separatorY = finalContentY + imgPaddedH; // Y-coordinate for the line
        
        ctx.strokeStyle = frameColor; 
        ctx.lineWidth = innerBorderWidth; // Separator line same as inner border
        ctx.beginPath();
        ctx.moveTo(finalContentX, separatorY);
        ctx.lineTo(finalContentX + contentAreaW, separatorY); // Line spans content width
        ctx.stroke();

        // Prepare for Label Box Text
        ctx.fillStyle = textColor;
        ctx.font = `${effectiveCaptionFontSize}px ${fontFamily}`;
        ctx.textAlign = "center"; // Default alignment for label text

        let textCurrentY = separatorY + labelBoxInternalPadding + effectiveCaptionFontSize; // Baseline for first line
        const textCenterX = finalContentX + contentAreaW / 2; // Centered horizontally

        if (figureText && figureText.trim() !== "") {
            ctx.fillText(figureText, textCenterX, textCurrentY);
            textCurrentY += effectiveCaptionFontSize + 5; // Move Y for next line (5px spacing)
        }

        if (captionText && captionText.trim() !== "") {
            const maxWidthForCaption = contentAreaW - 2 * labelBoxInternalPadding;
            const words = captionText.split(' ');
            let currentLine = '';
            
            for (let i = 0; i < words.length; i++) {
                const testLine = currentLine + words[i] + ' ';
                const metrics = ctx.measureText(testLine);
                if (metrics.width > maxWidthForCaption && i > 0) {
                    if (textCurrentY + effectiveCaptionFontSize <= separatorY + labelBoxHeight - labelBoxInternalPadding) {
                         ctx.fillText(currentLine.trim(), textCenterX, textCurrentY);
                         currentLine = words[i] + ' ';
                         textCurrentY += effectiveCaptionFontSize + 4; // Line height (font size + spacing)
                    } else { // Not enough vertical space for more lines
                        currentLine += "..."; // Indication of truncation
                        break;
                    }
                } else {
                    currentLine = testLine;
                }
            }
            if (textCurrentY + effectiveCaptionFontSize <= separatorY + labelBoxHeight - labelBoxInternalPadding) {
                 ctx.fillText(currentLine.trim(), textCenterX, textCurrentY);
            } else if (!currentLine.endsWith("...")) { // if truncated by overflow
                 // attempt to draw the last bit or indicate truncation more clearly
                 const lastLineTest = currentLine.substring(0, currentLine.length > 20 ? 20 : currentLine.length) + "...";
                 ctx.fillText(lastLineTest.trim(), textCenterX, textCurrentY - (effectiveCaptionFontSize+4)); // overwrite previous potentially
            }
        }
    }
    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 Vintage Scientific Illustration Frame Creator is an online tool designed to enhance images with a vintage scientific frame aesthetic. Users can upload their images and customize the presentation by selecting an aged paper background, a dark sepia frame color, and various text elements such as titles and captions. This tool is suitable for creating visually appealing scientific illustrations, educational materials, or art prints that mimic the look of classic scientific publications. It allows users to adjust margins, paddings, and text styling to ensure the final output meets their creative vision.

Leave a Reply

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