Please bookmark this page to avoid losing your image tool!

UFO Case Photo Generator

(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, lang = "RU", addUfo = 1, dirtLevel = 70) {
    // Standard A4-like proportions for the document folder
    const docW = 850;
    const docH = 1100;
    
    // Create main document canvas
    const docCanvas = document.createElement('canvas');
    docCanvas.width = docW;
    docCanvas.height = docH;
    const ctx = docCanvas.getContext('2d');

    // 1. Draw Vintage Paper Background with Vignette
    const gradient = ctx.createRadialGradient(docW/2, docH/2, 200, docW/2, docH/2, docW);
    gradient.addColorStop(0, '#dad2bf');
    gradient.addColorStop(1, '#a69e8b');
    ctx.fillStyle = gradient;
    ctx.fillRect(0, 0, docW, docH);

    // 2. Add organic paper grain to the document
    const docDataObj = ctx.getImageData(0, 0, docW, docH);
    const dData = docDataObj.data;
    for (let i = 0; i < dData.length; i += 4) {
        const noise = (Math.random() - 0.5) * 15;
        dData[i] = Math.max(0, Math.min(255, dData[i] + noise));
        dData[i+1] = Math.max(0, Math.min(255, dData[i+1] + noise));
        dData[i+2] = Math.max(0, Math.min(255, dData[i+2] + noise));
    }
    ctx.putImageData(docDataObj, 0, 0);

    // 3. Process Original Image into a UFO Photographic Evidence
    // Calculate dimensions to fit inside the polaroid frame
    const maxPhotoW = 660;
    const maxPhotoH = 500;
    const aspect = originalImg.height / originalImg.width;
    
    let pW = maxPhotoW;
    let pH = pW * aspect;
    if (pH > maxPhotoH) {
        pH = maxPhotoH;
        pW = pH / aspect;
    }

    const pCanvas = document.createElement('canvas');
    pCanvas.width = pW;
    pCanvas.height = pH;
    const pCtx = pCanvas.getContext('2d');
    
    // Draw original image
    pCtx.drawImage(originalImg, 0, 0, pW, pH);

    // 4. Add the UFO Anomaly if requested (Drawn BEFORE the vintage filter so it blends perfectly)
    if (Number(addUfo) === 1) {
        // Position it somewhat randomly in the upper area
        const cx = pW * (0.2 + 0.6 * Math.random());
        const cy = pH * (0.05 + 0.35 * Math.random());
        
        pCtx.save();
        pCtx.filter = 'blur(3px)';
        pCtx.fillStyle = 'rgba(80, 80, 80, 0.9)'; // Dark gray that will be contrasted later
        
        // Main saucer body
        pCtx.beginPath();
        pCtx.ellipse(cx, cy, pW * 0.05, pW * 0.012, Math.random() * 0.4 - 0.2, 0, 2 * Math.PI);
        pCtx.fill();
        
        // Saucer dome
        pCtx.beginPath();
        pCtx.ellipse(cx, cy - pW * 0.007, pW * 0.018, pW * 0.01, 0, Math.PI, 0);
        pCtx.fill();
        pCtx.restore();
    }

    // 5. Apply Vintage BW, High Contrast, and Heavy Film Grain to the photo
    const pDataObj = pCtx.getImageData(0, 0, pW, pH);
    const pData = pDataObj.data;
    const contrastFactor = 2.0; 
    const contrastPoint = 130;  
    
    for (let i = 0; i < pData.length; i += 4) {
        // Luminance math out of RGB
        const avg = 0.3 * pData[i] + 0.59 * pData[i+1] + 0.11 * pData[i+2];
        
        // Push contrast to make it look like a harsh old duplicate
        let val = contrastFactor * (avg - contrastPoint) + contrastPoint;
        val = Math.max(0, Math.min(255, val));
        
        // Add chunky film grain based on dirtLevel string/number
        const grain = (Math.random() - 0.5) * Number(dirtLevel);
        val += grain;
        
        // Assign with a tiny sepia tint to match vintage look
        pData[i]   = Math.max(0, Math.min(255, val + 15)); // R
        pData[i+1] = Math.max(0, Math.min(255, val + 8));  // G
        pData[i+2] = Math.max(0, Math.min(255, val - 10)); // B
    }
    pCtx.putImageData(pDataObj, 0, 0);

    // 6. Draw the Photo Base (White Polaroid-style frame)
    const framePad = 18;
    const bottomPad = 50;
    const photoX = Math.floor((docW - pW) / 2);
    const photoY = 300;

    ctx.save();
    // Shadow for the photo
    ctx.shadowColor = 'rgba(0, 0, 0, 0.5)';
    ctx.shadowBlur = 15;
    ctx.shadowOffsetX = 4;
    ctx.shadowOffsetY = 4;
    ctx.fillStyle = '#f2ecd8'; // Aged white
    ctx.fillRect(photoX - framePad, photoY - framePad, pW + framePad * 2, pH + framePad + bottomPad);
    ctx.restore();

    // 7. Draw the processed Photo onto the document
    ctx.drawImage(pCanvas, photoX, photoY);

    // 8. Helper function to draw sticky tape
    function drawTape(x, y, angle) {
        ctx.save();
        ctx.translate(x, y);
        ctx.rotate(angle);
        ctx.fillStyle = 'rgba(230, 230, 210, 0.7)';
        ctx.shadowColor = 'rgba(0, 0, 0, 0.2)';
        ctx.shadowBlur = 3;
        ctx.shadowOffsetX = 1;
        ctx.shadowOffsetY = 1;
        ctx.fillRect(-45, -12, 90, 24);
        
        // Add tape wrinkles/stripes
        ctx.strokeStyle = 'rgba(255, 255, 255, 0.4)';
        ctx.lineWidth = 1;
        ctx.beginPath();
        for(let i = -40; i < 40; i+= 6) {
             ctx.moveTo(i, -12);
             ctx.lineTo(i + (Math.random()*4-2), 12);
        }
        ctx.stroke();
        ctx.restore();
    }

    // Attach corners with tape
    drawTape(photoX - 5, photoY - 5, -Math.PI / 6);
    drawTape(photoX + pW + 5, photoY - 5, Math.PI / 5);
    drawTape(photoX - 5, photoY + pH + 30, Math.PI / 7);
    drawTape(photoX + pW + 5, photoY + pH + 30, -Math.PI / 8);

    // 9. Typography and Text details
    const textData = String(lang).toUpperCase() === "EN" ? {
        title: "D.O.D. ARCHIVAL RECORD",
        caseNum: "CASE #: 71-X / " + Math.floor(1960 + Math.random()*20),
        subject: "SUBJECT: UNIDENTIFIED FLYING OBJECT",
        locPre: "COORDINATES: ",
        locRedacted: "[REDACTED]",
        datePre: "ENCOUNTER DATE: ",
        dateRedacted: "[ CLASSIFIED ]",
        attachment: "ATTACHMENT A: PHOTOGRAPHIC EVIDENCE",
        stamp: " T O P   S E C R E T "
    } : {
        title: "КГБ СССР: МАТЕРИАЛЫ ДЕЛА",
        caseNum: "ДЕЛО №: 47-X / " + Math.floor(1960 + Math.random()*20),
        subject: "ОБЪЕКТ: НЕОПОЗНАННЫЙ ЛЕТАЮЩИЙ АППАРАТ",
        locPre: "МЕСТО: ",
        locRedacted: "[ЗАСЕКРЕЧЕНО]",
        datePre: "ДАТА КОНТАКТА: ",
        dateRedacted: "[ ИЗЪЯТО ДЛЯ АНАЛИЗА ]",
        attachment: "ПРИЛОЖЕНИЕ: ФОТОФИКСАЦИЯ АНОМАЛИИ",
        stamp: "СОВЕРШЕННО СЕКРЕТНО"
    };

    ctx.fillStyle = 'rgba(30, 30, 30, 0.9)'; // Slightly faded typewriter ink
    ctx.textBaseline = "middle";

    // Header block
    ctx.font = 'bold 36px "Courier New", Courier, monospace';
    ctx.fillText(textData.title, 80, 80);
    
    // Details block
    ctx.font = 'bold 22px "Courier New", Courier, monospace';
    let currentY = 140;
    
    ctx.fillText(textData.caseNum, 80, currentY); currentY += 35;
    ctx.fillText(textData.subject, 80, currentY); currentY += 35;

    // Helper for Redaction Box
    function fillRedaction(prefix, redacted, x, y) {
        ctx.fillStyle = 'rgba(30, 30, 30, 0.9)';
        ctx.fillText(prefix + redacted, x, y);
        const w1 = ctx.measureText(prefix).width;
        const w2 = ctx.measureText(redacted).width;
        ctx.fillStyle = '#111'; // Pure marker black
        // Slightly unaligned marker box for realism
        ctx.fillRect(x + w1 - 4, y - 14 + (Math.random()*4-2), w2 + 8, 28);
    }

    fillRedaction(textData.datePre, textData.dateRedacted, 80, currentY); currentY += 35;
    fillRedaction(textData.locPre, textData.locRedacted, 80, currentY); currentY += 50;
    
    // Bottom attachment note
    ctx.fillStyle = 'rgba(30, 30, 30, 0.9)';
    ctx.fillText(textData.attachment, photoX - framePad + 10, photoY + pH + framePad + 15);

    // 10. Giant Red Stamp (Rotated and Blended)
    ctx.save();
    ctx.globalCompositeOperation = 'multiply';
    ctx.translate(docW - 270, 150);
    ctx.rotate(0.2 - Math.random() * 0.4); 
    
    const stampColor = 'rgba(180, 20, 20, 0.7)';
    ctx.strokeStyle = stampColor;
    ctx.fillStyle = stampColor;
    ctx.lineWidth = 6;
    
    // Outer border
    ctx.strokeRect(0, 0, 380, 64);
    // Inner border
    ctx.lineWidth = 2;
    ctx.strokeRect(6, 6, 368, 52);
    
    // Text inside stamp
    ctx.font = 'bold 32px "Arial", sans-serif';
    ctx.textAlign = 'center';
    ctx.fillText(textData.stamp, 190, 32);
    
    // Draw some scratches to "distress" the stamp
    ctx.globalCompositeOperation = 'destination-out';
    ctx.lineWidth = 2;
    for(let i = 0; i < 30; i++) {
        ctx.beginPath();
        let sx = Math.random() * 380;
        let sy = Math.random() * 64;
        ctx.moveTo(sx, sy);
        ctx.lineTo(sx + Math.random() * 10 - 5, sy + Math.random() * 10 - 5);
        ctx.stroke();
    }
    ctx.restore();

    return docCanvas;
}

Free Image Tool Creator

Can't find the image tool you're looking for?
Create one based on your own needs now!

Description

The UFO Case Photo Generator is a creative tool that transforms your standard images into vintage, classified archival documents. It processes uploaded photos with a high-contrast, grainy, black-and-white film effect and overlays them onto an aged paper background styled like a top-secret government file. The tool can automatically add a subtle UFO anomaly to your image and includes thematic details such as redacted text, ‘Top Secret’ stamps, and tape-attached Polaroid-style frames. This tool is ideal for creating props for role-playing games, themed party invitations, social media content, or creative storytelling projects.

Leave a Reply

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