Please bookmark this page to avoid losing your image tool!

Image Story Visualizer

(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, numNodes = '7', tangleLevel = '15', themeColor = '#e74c3c', bgColor = '#1e272e') {
    // Parse parameters
    const safeNumNodes = Math.min(Math.max(Number(numNodes) || 7, 2), 20);
    const safeTangle = Math.min(Math.max(Number(tangleLevel) || 15, 1), 50);
    const pinColor = typeof themeColor === 'string' ? themeColor : '#e74c3c';
    const boardColor = typeof bgColor === 'string' ? bgColor : '#1e272e';

    // Canvas setup
    const canvas = document.createElement('canvas');
    const bW = 1400; // Board Width
    const bH = 1000; // Board Height
    canvas.width = bW;
    canvas.height = bH;
    const ctx = canvas.getContext('2d');
    ctx.imageSmoothingEnabled = true;
    ctx.imageSmoothingQuality = 'high';

    // 1. Draw Background (Detective Board)
    ctx.fillStyle = boardColor;
    ctx.fillRect(0, 0, bW, bH);
    
    // Board Gradient
    const grad = ctx.createRadialGradient(bW/2, bH/2, bH/3, bW/2, bH/2, bW);
    grad.addColorStop(0, 'rgba(0,0,0,0)');
    grad.addColorStop(1, 'rgba(0,0,0,0.6)');
    ctx.fillStyle = grad;
    ctx.fillRect(0, 0, bW, bH);

    // Board Grid
    ctx.strokeStyle = 'rgba(255,255,255,0.03)';
    ctx.lineWidth = 1;
    for(let i=0; i<bW; i+=40) { ctx.beginPath(); ctx.moveTo(i,0); ctx.lineTo(i,bH); ctx.stroke(); }
    for(let i=0; i<bH; i+=40) { ctx.beginPath(); ctx.moveTo(0,i); ctx.lineTo(bW,i); ctx.stroke(); }

    // Random Background Text (Clues)
    ctx.fillStyle = 'rgba(255,255,255,0.05)';
    ctx.font = 'bold 28px Courier New, Courier, monospace';
    const clues = ["WHO?", "WHERE?", "CONFIDENTIAL", "REPORT", "EVIDENCE", "CONNECT THE DOTS", "CLASSIFIED", "FILE", "?!"];
    for(let k=0; k<12; k++) {
        ctx.save();
        ctx.translate(Math.random() * bW, Math.random() * bH);
        ctx.rotate((Math.random() - 0.5) * 2);
        ctx.fillText(clues[Math.floor(Math.random() * clues.length)], 0, 0);
        ctx.restore();
    }

    // Math Utility
    function rotatePoint(cx, cy, x, y, angle) {
        const cos = Math.cos(angle), sin = Math.sin(angle);
        return {
            x: (cos * (x - cx)) - (sin * (y - cy)) + cx,
            y: (sin * (x - cx)) + (cos * (y - cy)) + cy
        };
    }

    // 2. Setup Dimensions for Main Polaroid (Center Image)
    const centerX = bW / 2;
    const centerY = bH / 2;
    let mainWidth = 650;
    let mainImgHeight = mainWidth * (originalImg.height / originalImg.width);

    // Scale down if image is extremely tall
    if (mainImgHeight > bH * 0.6) {
        mainImgHeight = bH * 0.6;
        mainWidth = mainImgHeight * (originalImg.width / originalImg.height);
    }
    
    const mainPh = mainImgHeight + 90; // Polarid outer height
    const mainAngle = (Math.random() - 0.5) * 0.08;

    // 3. Generate Small Nodes (Image Crops)
    const nodes = [];
    for (let i = 0; i < safeNumNodes; i++) {
        let placed = false;
        let nx, ny;
        let attempts = 0;

        // Spread nodes nicely around the main center image
        while (!placed && attempts < 150) {
            nx = 200 + Math.random() * (bW - 400);
            ny = 200 + Math.random() * (bH - 400);
            if (Math.abs(nx - centerX) > mainWidth/2 + 70 || Math.abs(ny - centerY) > mainPh/2 + 70) {
                placed = true;
            }
            attempts++;
        }

        // Random square harvest from the original
        let minDim = Math.min(originalImg.width, originalImg.height);
        let cropSize = minDim * (0.15 + Math.random() * 0.2); // 15% to 35%
        let sx = Math.random() * (originalImg.width - cropSize);
        let sy = Math.random() * (originalImg.height - cropSize);

        // Find location of this crop center on the MAIN polaroid (canvas coordinates)
        let imgTopLeftX = centerX - mainWidth/2 + 20;
        let imgTopLeftY = centerY - mainPh/2 + 20;

        let cropCenterXOnMain = imgTopLeftX + (sx + cropSize/2) / originalImg.width * (mainWidth - 40);
        let cropCenterYOnMain = imgTopLeftY + (sy + cropSize/2) / originalImg.height * mainImgHeight;

        let origPos = rotatePoint(centerX, centerY, cropCenterXOnMain, cropCenterYOnMain, mainAngle);

        nodes.push({
            x: nx, y: ny,
            angle: (Math.random() - 0.5) * 0.5,
            pw: 160 + Math.random() * 40,
            ph: 200 + Math.random() * 40,
            sx: sx, sy: sy, cropSize: cropSize,
            origX: origPos.x, origY: origPos.y,
            // Calculate coordinates later for exact pin
            pinPos: { x: 0, y: 0 }
        });
    }

    // Compute Exact Pin Anchor Coordinates for Nodes
    nodes.forEach(node => {
        node.pinPos = rotatePoint(node.x, node.y, node.x, node.y - node.ph/2 + 12, node.angle);
    });

    // 4. Draw Main Target Center Image
    ctx.save();
    ctx.translate(centerX, centerY);
    ctx.rotate(mainAngle);
    
    // Shadow
    ctx.shadowColor = 'rgba(0,0,0,0.8)';
    ctx.shadowBlur = 25;
    ctx.shadowOffsetX = 10;
    ctx.shadowOffsetY = 10;
    
    // Paper
    ctx.fillStyle = '#f5f6fa';
    ctx.fillRect(-mainWidth/2, -mainPh/2, mainWidth, mainPh);
    
    // Image
    ctx.shadowColor = 'transparent'; // stop shadow for inner operations
    ctx.drawImage(originalImg, -mainWidth/2 + 20, -mainPh/2 + 20, mainWidth - 40, mainImgHeight);
    
    // Tape/Pin
    ctx.fillStyle = '#2c3e50';
    ctx.beginPath(); ctx.arc(0, -mainPh/2 + 10, 6, 0, Math.PI*2); ctx.fill();
    // Inner Pin reflection
    ctx.fillStyle = 'rgba(255,255,255,0.4)';
    ctx.beginPath(); ctx.arc(-2, -mainPh/2 + 8, 2, 0, Math.PI*2); ctx.fill();

    // Text Label Main
    ctx.fillStyle = '#2d3436';
    ctx.font = 'bold 20px Courier New, Courier, monospace';
    ctx.fillText("SUBJECT PROFILE", -mainWidth/2 + 20, mainPh/2 - 25);
    ctx.restore();

    // 4.5 Draw Target Pins directly on the Main Image First
    nodes.forEach(node => {
        ctx.fillStyle = pinColor;
        ctx.beginPath();
        ctx.arc(node.origX, node.origY, 4.5, 0, Math.PI*2);
        ctx.fill();
        ctx.fillStyle = 'rgba(255,255,255,0.4)';
        ctx.beginPath();
        ctx.arc(node.origX - 1.5, node.origY - 1.5, 1.5, 0, Math.PI*2);
        ctx.fill();
    });

    // 5. Draw Tangled "Red" Strings
    ctx.strokeStyle = pinColor;
    
    // Primary Strings: From Nodes to Main Image Targets
    for (let node of nodes) {
        for (let t = 0; t < safeTangle; t++) {
            ctx.globalAlpha = 0.2 + Math.random() * 0.3;
            ctx.lineWidth = 0.5 + Math.random() * 1.2;
            ctx.beginPath();
            ctx.moveTo(node.pinPos.x, node.pinPos.y);
            
            // Random chaotic curve to the target point
            let cp1x = node.pinPos.x + (node.origX - node.pinPos.x)*0.33 + (Math.random()-0.5)*150;
            let cp1y = node.pinPos.y + (node.origY - node.pinPos.y)*0.33 + (Math.random()-0.5)*150;
            let cp2x = node.pinPos.x + (node.origX - node.pinPos.x)*0.66 + (Math.random()-0.5)*150;
            let cp2y = node.pinPos.y + (node.origY - node.pinPos.y)*0.66 + (Math.random()-0.5)*150;
            
            ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, node.origX, node.origY);
            ctx.stroke();
        }
    }

    // Secondary Strings: Random crazy connections between small nodes
    ctx.globalAlpha = 0.2;
    for (let i = 0; i < nodes.length; i++) {
        let n1 = nodes[i];
        let n2 = nodes[(i + 1) % nodes.length];
        if(Math.random() > 0.3) {
            for(let t=0; t < safeTangle/2; t++) {
                ctx.lineWidth = 0.5 + Math.random();
                ctx.beginPath();
                ctx.moveTo(n1.pinPos.x, n1.pinPos.y);
                let cx1 = n1.pinPos.x + (Math.random()-0.5)*300;
                let cy1 = n1.pinPos.y + (Math.random()-0.5)*300;
                let cx2 = n2.pinPos.x + (Math.random()-0.5)*300;
                let cy2 = n2.pinPos.y + (Math.random()-0.5)*300;
                ctx.bezierCurveTo(cx1, cy1, cx2, cy2, n2.pinPos.x, n2.pinPos.y);
                ctx.stroke();
            }
        }
    }
    
    ctx.globalAlpha = 1.0;

    // 6. Draw Small Nodes (Evidences / Focus points)
    for (let i = 0; i < nodes.length; i++) {
        let node = nodes[i];
        ctx.save();
        ctx.translate(node.x, node.y);
        ctx.rotate(node.angle);

        // Polaroid Shadow
        ctx.shadowColor = 'rgba(0,0,0,0.6)';
        ctx.shadowBlur = 15;
        ctx.shadowOffsetX = 6;
        ctx.shadowOffsetY = 6;

        // Paper
        ctx.fillStyle = '#fdfdfd';
        ctx.fillRect(-node.pw/2, -node.ph/2, node.pw, node.ph);
        
        ctx.shadowColor = 'transparent';

        // Draw Cropped Image
        const borderMargin = 12;
        const imgCanvasSize = node.pw - borderMargin * 2;
        ctx.drawImage(
            originalImg, 
            node.sx, node.sy, node.cropSize, node.cropSize,
            -imgCanvasSize/2, -node.ph/2 + borderMargin + 15, 
            imgCanvasSize, imgCanvasSize
        );

        // Node Pin
        ctx.fillStyle = pinColor; // nodes and strings share color theme
        ctx.beginPath();
        ctx.arc(0, -node.ph/2 + 12, 5.5, 0, Math.PI*2);
        ctx.fill();
        ctx.fillStyle = 'rgba(255,255,255,0.4)';
        ctx.beginPath();
        ctx.arc(-1.5, -node.ph/2 + 10, 2, 0, Math.PI*2);
        ctx.fill();

        // Scribble Label
        ctx.fillStyle = 'rgba(0,0,0,0.7)';
        ctx.font = 'bold 16px Courier New, Courier, monospace';
        ctx.fillText(`FIG-0${i+1}`, -node.pw/2 + 15, node.ph/2 - 15);

        ctx.restore();
    }

    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 Story Visualizer transforms a single uploaded image into a creative ‘detective board’ style collage. It places the original image as a central Polaroid-style photo and generates several smaller, cropped detail shots scattered around it. To enhance the investigative theme, the tool draws tangled, thread-like connections between the various image fragments and the main subject. Users can customize the aesthetic by adjusting the number of detail nodes, the complexity of the connections, and the color theme. This tool is ideal for creating dramatic storytelling visuals, mood boards, or stylized concept art for creative projects.

Leave a Reply

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