Please bookmark this page to avoid losing your image tool!

Image Sherlock Holmes Case File 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,
    caseTitle = "CONFIDENTIAL REPORT - CASE No. " + Math.floor(Math.random() * 8999 + 1001),
    subjectName = "CLASSIFIED",
    caseNotes = "Initial findings suggest a matter of considerable intricacy. The subject exhibits peculiar habits. Further surveillance is recommended before drawing definitive conclusions. Discretion is paramount.",
    stampText = "CLASSIFIED",
    textColor = "#3a2b20", // Dark Brown/Black
    stampColor = "#8B0000", // Dark Red
    paperColor = "#f3eadd", // A light, aged paper color (e.g., antique white, beige)
    bodyFontFamily = "'Courier New', Courier, monospace",
    headingFontFamily = "'Georgia', 'Times New Roman', serif",
    applySepiaToImage = "true"
) {
    const canvas = document.createElement('canvas');
    const ctx = canvas.getContext('2d');

    // Layout dimensions
    const canvasWidth = 800;
    const canvasHeight = 1130; // Approx A4 ratio, taller for content
    canvas.width = canvasWidth;
    canvas.height = canvasHeight;

    const sepiaActive = applySepiaToImage.toLowerCase() === 'true';

    // 1. Background
    ctx.fillStyle = paperColor;
    ctx.fillRect(0, 0, canvasWidth, canvasHeight);

    // Define margins and content areas
    const margin = 50;
    const contentWidth = canvasWidth - 2 * margin;
    let currentY = margin;

    // 2. Case Title Header
    ctx.fillStyle = textColor;
    ctx.font = `bold 32px ${headingFontFamily}`;
    ctx.textAlign = 'center';
    currentY += 40; // Top padding for title
    ctx.fillText(caseTitle.toUpperCase(), canvasWidth / 2, currentY);
    currentY += 25; // Space after title

    // Horizontal line
    ctx.beginPath();
    ctx.moveTo(margin, currentY);
    ctx.lineTo(canvasWidth - margin, currentY);
    ctx.strokeStyle = textColor;
    ctx.lineWidth = 1.5;
    ctx.stroke();
    currentY += 30; // Space after line

    // 3. Image Area
    const imgSectionYStart = currentY;
    const imgMaxContainerHeight = 330; // Max height for the image and its matte
    const imgMaxContainerWidth = contentWidth * 0.75; // Image container can take 75% of content width

    let scale = 1;
    if (originalImg.width > 0 && originalImg.height > 0) { // Ensure image has dimensions
        scale = Math.min(imgMaxContainerWidth / originalImg.width, imgMaxContainerHeight / originalImg.height);
    } else { // Handle cases where image might not be fully loaded or is invalid
        scale = 0; // Effectively don't draw the image
    }

    const imgDisplayWidth = originalImg.width * scale;
    const imgDisplayHeight = originalImg.height * scale;

    const imgX = (canvasWidth - imgDisplayWidth) / 2; // Center the image
    const imgY = currentY;
    const mattePadding = 8;

    if (imgDisplayWidth > 0 && imgDisplayHeight > 0) {
        // Photo matte/background
        ctx.fillStyle = '#E0E0E0'; // Light gray for photo matte
        ctx.shadowColor = 'rgba(0,0,0,0.3)';
        ctx.shadowBlur = 8;
        ctx.shadowOffsetX = 3;
        ctx.shadowOffsetY = 3;
        ctx.fillRect(imgX - mattePadding, imgY - mattePadding, imgDisplayWidth + 2 * mattePadding, imgDisplayHeight + 2 * mattePadding);
        ctx.shadowColor = 'transparent'; // Reset shadow

        ctx.strokeStyle = '#707070'; // Darker gray border for matte
        ctx.lineWidth = 1;
        ctx.strokeRect(imgX - mattePadding, imgY - mattePadding, imgDisplayWidth + 2 * mattePadding, imgDisplayHeight + 2 * mattePadding);

        // Draw the image
        if (sepiaActive) {
            const tempCanvas = document.createElement('canvas');
            tempCanvas.width = imgDisplayWidth;
            tempCanvas.height = imgDisplayHeight;
            const tempCtx = tempCanvas.getContext('2d');
            tempCtx.filter = 'sepia(80%) grayscale(10%) contrast(110%) brightness(95%)';
            tempCtx.drawImage(originalImg, 0, 0, imgDisplayWidth, imgDisplayHeight);
            ctx.drawImage(tempCanvas, imgX, imgY, imgDisplayWidth, imgDisplayHeight);
        } else {
            ctx.drawImage(originalImg, imgX, imgY, imgDisplayWidth, imgDisplayHeight);
        }
        
        // Thin border directly on the image
        ctx.strokeStyle = textColor;
        ctx.lineWidth = 0.5;
        ctx.strokeRect(imgX, imgY, imgDisplayWidth, imgDisplayHeight);
    }
    currentY += Math.max(imgDisplayHeight + 2 * mattePadding, 0) + 30; // Space after image block (includes matte)

    // 4. Subject Name
    ctx.fillStyle = textColor;
    ctx.font = `bold 18px ${headingFontFamily}`;
    ctx.textAlign = 'left';
    const subjectLabel = "SUBJECT OF INQUIRY:";
    ctx.fillText(subjectLabel, margin, currentY);
    
    ctx.font = `18px ${bodyFontFamily}`;
    const subjectNameX = margin + ctx.measureText(subjectLabel).width + 10;
    ctx.fillText(subjectName, subjectNameX, currentY);
    currentY += 30;

    // Horizontal line
    ctx.beginPath();
    ctx.moveTo(margin, currentY);
    ctx.lineTo(canvasWidth - margin, currentY);
    ctx.strokeStyle = textColor;
    ctx.globalAlpha = 0.6;
    ctx.lineWidth = 0.5;
    ctx.stroke();
    ctx.globalAlpha = 1.0;
    currentY += 30;

    // 5. Case Notes heading
    ctx.fillStyle = textColor;
    ctx.font = `bold 18px ${headingFontFamily}`;
    ctx.fillText("OBSERVATIONS & NOTES:", margin, currentY);
    currentY += 30; // Space before notes content

    // Case Notes content
    ctx.font = `16px ${bodyFontFamily}`;
    const lineHeight = 22;
    const maxNoteLines = Math.floor((canvasHeight - currentY - 100) / lineHeight); // Dynamically calculate max lines based on remaining space
    let lineCount = 0;
    const words = caseNotes.split(' ');
    let line = '';

    for (let n = 0; n < words.length; n++) {
        const testLine = line + words[n] + ' ';
        const metrics = ctx.measureText(testLine);
        const testWidth = metrics.width;
        
        if (testWidth > contentWidth && line.length > 0) { // Ensure 'line' is not empty before printing
            if (lineCount === maxNoteLines - 1 && n < words.length) { // Check if this is the last allowed line
                let textToPrint = line.trimRight();
                if (ctx.measureText(textToPrint + "...").width > contentWidth) {
                    while(textToPrint.length > 0 && ctx.measureText(textToPrint + "...").width > contentWidth) {
                        textToPrint = textToPrint.slice(0,-1);
                    }
                }
                ctx.fillText(textToPrint + "...", margin, currentY);
                line = ''; // Clear line as it's handled
            } else {
                ctx.fillText(line, margin, currentY);
            }
            
            line = words[n] + ' '; // Start new line with current word
            currentY += lineHeight;
            lineCount++;
            if (lineCount >= maxNoteLines) {
                if (lineCount === maxNoteLines && line.trim() !== '' && n < words.length -1) { // If we have more words after max lines
                    let lastBits = line.trimRight();
                     while(lastBits.length > 0 && ctx.measureText(lastBits + "...").width > contentWidth) {
                        lastBits = lastBits.slice(0,-1);
                    }
                    ctx.fillText(lastBits + "...", margin, currentY - lineHeight); // Overwrite previous line's end partially
                }
                line = ''; // Clear line as we've hit limit
                break; 
            }
        } else {
            line = testLine;
        }
    }
    if (line.trim() !== '' && lineCount < maxNoteLines) { // Print any remaining line if not over limit
         ctx.fillText(line, margin, currentY);
         currentY += lineHeight;
    }
    currentY += 20; // Space after notes

    // 6. Stamp
    if (stampText && stampText.trim() !== "") {
        const stampWidth = 160;
        const stampHeight = 60;
        
        let stampBaseY = Math.max(imgSectionYStart + imgMaxContainerHeight + 100, currentY - stampHeight - 10); // Place after image or notes
        stampBaseY = Math.min(stampBaseY, canvasHeight - margin - stampHeight - 100); // Not too close to signature
        const stampX = canvasWidth - margin - stampWidth - 20; 
        const stampY = stampBaseY;

        ctx.save();
        ctx.translate(stampX + stampWidth / 2, stampY + stampHeight / 2);
        ctx.rotate(-12 * Math.PI / 180);

        ctx.strokeStyle = stampColor;
        ctx.lineWidth = 2.5;
        ctx.beginPath();
        ctx.moveTo(-stampWidth / 2 + (Math.random()-0.5)*4 , -stampHeight / 2 + (Math.random()-0.5)*4);
        ctx.lineTo(stampWidth / 2 + (Math.random()-0.5)*4, -stampHeight / 2 + (Math.random()-0.5)*4);
        ctx.lineTo(stampWidth / 2 + (Math.random()-0.5)*4, stampHeight / 2 + (Math.random()-0.5)*4);
        ctx.lineTo(-stampWidth / 2 + (Math.random()-0.5)*4, stampHeight / 2 + (Math.random()-0.5)*4);
        ctx.closePath();
        ctx.stroke();
        
        ctx.fillStyle = stampColor;
        ctx.font = `bold 24px ${headingFontFamily}`;
        ctx.textAlign = 'center';
        ctx.textBaseline = 'middle';
        ctx.fillText(stampText.toUpperCase(), 0, 2); // Slight Y offset for visual centering

        ctx.restore();
    }

    // 7. Signature line
    const sigY = canvasHeight - margin + 5; 
    ctx.fillStyle = textColor;
    ctx.font = `italic 15px ${bodyFontFamily}`;
    ctx.textAlign = 'right';
    
    const signatureName = "Det. S.H. (Consulting)";
    ctx.fillText(signatureName, canvasWidth - margin, sigY - 5);

    ctx.beginPath();
    ctx.moveTo(canvasWidth - margin - 220, sigY);
    ctx.lineTo(canvasWidth - margin, sigY);
    ctx.strokeStyle = textColor;
    ctx.lineWidth = 0.75;
    ctx.stroke();

    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 Sherlock Holmes Case File Creator is an online tool that allows users to create visually engaging case files resembling detective reports. Users can upload an image and customize the case title, subject name, and notes to produce a stylized document. This tool is ideal for creating mock case files for educational purposes, detective-themed parties, or storytelling. With features such as sepia filtering for a vintage look, customizable text, and a classification stamp, the tool provides a creative way to present information in a unique format.

Leave a Reply

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

Other Image Tools:

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

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

See All →