Please bookmark this page to avoid losing your image tool!

Image Mad Scientist’s Laboratory Notes 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,
    overlayText = "PROJECT LOG: CLASSIFIED\nSUBJECT: SPECIMEN-Z\nSTATUS: UNSTABLE\nOBSERVATIONS: ERRATIC BEHAVIOR, INCREASED AGGRESSION, UNEXPECTED MUTATIONS NOTED...",
    textColor = "rgba(40, 30, 20, 0.9)", // Dark, slightly desaturated brown
    textFontFamily = "Permanent Marker", // Desired font
    vintageIntensity = 0.65,
    grungeIntensity = 0.55
) {
    // Dynamically load the font if FontFace API is available
    const FONT_UNIQUE_NAME = textFontFamily + "_MadScientistLoaded"; // Unique name for FontFace instance
    const FONT_URL = "https://fonts.gstatic.com/s/permanentmarker/v17/Fh4uPib9Iyv2ucM6pGQMWimMp004La2Cfw.woff2"; // URL for "Permanent Marker"
    let actualFontToUse = textFontFamily; // This will be updated upon successful load or fallback

    if (typeof FontFace !== 'undefined') {
        try {
            // Check if already loaded to avoid re-adding
            let fontLoaded = false;
            for (const font of document.fonts) {
                if (font.family === FONT_UNIQUE_NAME) {
                    fontLoaded = true;
                    break;
                }
            }
            if (!fontLoaded) {
                const font = new FontFace(FONT_UNIQUE_NAME, `url(${FONT_URL})`);
                await font.load();
                document.fonts.add(font);
            }
            actualFontToUse = FONT_UNIQUE_NAME; 
        } catch (e) {
            console.warn(`Font ${textFontFamily} from ${FONT_URL} failed to load:`, e);
            actualFontToUse = "cursive, sans-serif"; // Fallback
        }
    } else {
        console.warn("FontFace API not supported. Using fallback font.");
        actualFontToUse = "cursive, sans-serif"; // Fallback for very old browsers
    }

    const w = originalImg.naturalWidth;
    const h = originalImg.naturalHeight;

    const canvas = document.createElement('canvas');
    canvas.width = w;
    canvas.height = h;
    const ctx = canvas.getContext('2d');

    // 1. Fill with paper color background
    ctx.fillStyle = '#f3efde'; // Aged paper, slightly yellowish-beige
    ctx.fillRect(0, 0, w, h);

    // 2. Create a temporary canvas for processing the image with filters
    const imageProcessingCanvas = document.createElement('canvas');
    imageProcessingCanvas.width = w;
    imageProcessingCanvas.height = h;
    const imgProcCtx = imageProcessingCanvas.getContext('2d');
    imgProcCtx.drawImage(originalImg, 0, 0, w, h); // Draw original image

    // 3. Apply vintage filter effect to the image on the temporary canvas
    if (vintageIntensity > 0) {
        const sepiaVal = vintageIntensity;
        const brightnessVal = 1 - (vintageIntensity * 0.12); // e.g., 0.65 -> 0.922
        const contrastVal = 1 + (vintageIntensity * 0.18);   // e.g., 0.65 -> 1.117
        
        let filterOperations = [];
        if (sepiaVal > 0.01) filterOperations.push(`sepia(${sepiaVal})`);
        if (brightnessVal < 0.995) filterOperations.push(`brightness(${brightnessVal})`);
        if (contrastVal > 1.005) filterOperations.push(`contrast(${contrastVal})`);
        
        if (filterOperations.length > 0) {
            imgProcCtx.filter = filterOperations.join(' ');
            // To "bake" the filter: draw the canvas content onto itself while filter is active
            // This requires copying to another temporary element or drawing image from original source
            const tempFilteredImage = document.createElement('canvas');
            tempFilteredImage.width = w;
            tempFilteredImage.height = h;
            const tempFilteredCtx = tempFilteredImage.getContext('2d');
            tempFilteredCtx.drawImage(imageProcessingCanvas, 0, 0); // Copy current (unfiltered) state

            imgProcCtx.clearRect(0,0,w,h); // Clear the processing canvas
            imgProcCtx.filter = filterOperations.join(' '); // Re-apply filter
            imgProcCtx.drawImage(tempFilteredImage, 0, 0); // Draw with filter
            imgProcCtx.filter = 'none'; // Reset filter on the processing context
        }
    }

    // 4. Draw the (potentially filtered) image onto the main canvas
    ctx.drawImage(imageProcessingCanvas, 0, 0, w, h);

    // 5. Add grunge/stains
    if (grungeIntensity > 0) {
        const numStainsTarget = 5 + grungeIntensity * 25; // Base: 5 to 30 stains
        // Scale number of stains roughly with image area, sqrt to moderate scaling
        const numStains = Math.max(1, Math.floor(numStainsTarget * Math.sqrt(w * h / (600*600)) ));

        ctx.globalCompositeOperation = 'multiply'; // Stains blend well with multiply

        for (let i = 0; i < numStains; i++) {
            const stainX = Math.random() * w;
            const stainY = Math.random() * h;
            const diagonal = Math.sqrt(w*w + h*h); // Use diagonal for relative sizing
            const baseRadius = (Math.random() * 0.025 + 0.005) * diagonal; // 0.5% to 3% of diagonal

            const stainOpacity = (Math.random() * 0.3 + 0.05) * grungeIntensity; // Max ~0.35 * intensity
            
            const r_val = 50 + Math.random() * 60; // Dark Brown/Greyish: 50-110
            const g_val = Math.max(20, r_val - (25 + Math.random() * 35)); // Ensure G is lower for brownish
            const b_val = Math.max(10, g_val - (20 + Math.random() * 25)); // Ensure B is lower
            const stainColor = `rgba(${Math.floor(r_val)}, ${Math.floor(g_val)}, ${Math.floor(b_val)}, ${stainOpacity})`;

            const splotchCount = 1 + Math.floor(Math.random() * 3); // 1 to 3 splotches form a single "stain"
            for (let j = 0; j < splotchCount; j++) {
                const splotchRadius = baseRadius * (0.5 + Math.random()); // Vary individual splotch size
                const offsetX = (Math.random() - 0.5) * baseRadius * 1.2; // Allow splotches to spread
                const offsetY = (Math.random() - 0.5) * baseRadius * 1.2;
                
                ctx.beginPath();
                ctx.arc(stainX + offsetX, stainY + offsetY, splotchRadius, 0, Math.PI * 2);
                ctx.fillStyle = stainColor;
                ctx.fill();
            }
        }
        ctx.globalCompositeOperation = 'source-over'; // Reset composite mode for text
    }

    // 6. Overlay handwritten-style text
    if (overlayText.trim() !== "") {
        const lines = overlayText.split('\n');
        // Font size responsive to image dimensions, with min/max caps
        const baseFontSize = Math.max(14, Math.min(h * 0.035, w * 0.05, 36)); 
        ctx.fillStyle = textColor;
        ctx.textBaseline = 'middle'; // Easier for vertical alignment when rotating

        let currentY = h * 0.07; // Starting Y position for text block
        const marginX = w * 0.05; // Side margins
        const lineSpacingFactor = 1.6; // Relative line height

        for (const line of lines) {
            if (currentY > h - (marginX + baseFontSize)) break; // Stop if text overflows vertically

            const fontSizeVariation = 0.9 + Math.random() * 0.2; // 90% to 110% of base
            const effectiveFontSize = baseFontSize * fontSizeVariation;
            ctx.font = `${effectiveFontSize}px "${actualFontToUse}"`;

            const textMetrics = ctx.measureText(line);
            const textWidth = textMetrics.width;
            
            let lineX;
            // Basic text alignment: try to center short lines, left-align longer ones
            if (textWidth < w - 2 * marginX) { // If line fits within margins
                lineX = marginX + (w - 2 * marginX - textWidth) / 2; // Centered within margins
                lineX += (Math.random() - 0.5) * effectiveFontSize * 0.2; // Add jitter to centered position
            } else {
                lineX = marginX + (Math.random() - 0.5) * effectiveFontSize * 0.1; // Left-aligned with jitter
            }
            lineX = Math.max(marginX * 0.8, Math.min(lineX, w - marginX - textWidth)); // Keep text on pagebounds

            const yJitter = (Math.random() - 0.5) * effectiveFontSize * 0.1; // Baseline jitter
            const angle = (Math.random() - 0.5) * 0.035; // Rotation: +/- ~2 degrees

            // Calculate center of the text line for rotation
            const rotateCenterX = lineX + textWidth / 2;
            const rotateCenterY = currentY + yJitter + effectiveFontSize / 2;

            ctx.save();
            ctx.translate(rotateCenterX, rotateCenterY);
            ctx.rotate(angle);
            ctx.fillText(line, -textWidth / 2, 0); // Draw text centered around new (0,0)
            ctx.restore();
            
            // Advance Y for next line with some randomness in spacing
            currentY += effectiveFontSize * lineSpacingFactor * (0.9 + Math.random() * 0.2);
        }
    }
    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 Mad Scientist’s Laboratory Notes Creator is a versatile online tool that allows users to transform images into creative, vintage-styled laboratory notes. With this tool, users can overlay descriptive text in a handwritten font, apply vintage and grunge filters to create an aged appearance, and add unique visual effects to their images. This can be particularly useful for artistic projects, educational materials, or any context where a whimsical, scientific aesthetic is desired. Ideal for educators, artists, or hobbyists looking to create intriguing visual narratives or presentations.

Leave a Reply

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

Other Image Tools:

Image Underground Resistance Flyer Creator

Image Retro Video Game Box Art Creator

Image Captain’s Naval Journal Creator

Image Renaissance Painting Frame Creator

Image Lost Civilization Artifact Creator

Image Da Vinci Notebook Page Creator

Image Dystopian Citizen ID Creator

Image Monster Hunter Bestiary Creator

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

See All →