Please bookmark this page to avoid losing your image tool!

Photo Memory Box 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, caption = "Sweet Memories", woodColor = "#8B5A2B", liningColor = "#4A0E13") {
    // 1. Calculate dimensions (constrain photo to a max dimension to ensure beautiful proportions)
    const maxPhotoDim = 500;
    const scale = Math.min(1, maxPhotoDim / Math.max(originalImg.width, originalImg.height));
    const imgW = originalImg.width * scale;
    const imgH = originalImg.height * scale;

    // Layout configuration
    const pPadX = 25;       // Polaroid side borders
    const pPadTop = 25;     // Polaroid top border
    const pPadBot = 90;     // Polaroid bottom border (for caption)
    const prW = imgW + pPadX * 2;
    const prH = imgH + pPadTop + pPadBot;

    const floorPadX = 60;   // Padding around polaroid on the box floor
    const floorPadY = 60;
    const flW = prW + floorPadX * 2;
    const flH = prH + floorPadY * 2;

    const wallDepth = 45;   // Depth of the shadow box walls
    const fW = flW + wallDepth * 2;
    const fH = flH + wallDepth * 2;

    const outMarg = 50;     // Outer wood frame thickness
    const cw = fW + outMarg * 2;
    const ch = fH + outMarg * 2;

    // Create main canvas
    const canvas = document.createElement('canvas');
    canvas.width = cw;
    canvas.height = ch;
    const ctx = canvas.getContext('2d');

    // 2. Helper to generate a repeating procedural wood texture
    function createWoodPattern(color, vertical = false) {
        const size = 300;
        const pc = document.createElement('canvas');
        pc.width = size;
        pc.height = size;
        const pctx = pc.getContext('2d');

        pctx.fillStyle = color;
        pctx.fillRect(0, 0, size, size);

        // Fine grain
        pctx.globalAlpha = 0.04;
        pctx.fillStyle = '#000';
        for (let i = 0; i < size; i += 2) {
            if (vertical) pctx.fillRect(i, 0, 1, size);
            else pctx.fillRect(0, i, size, 1);
        }

        // Broad curved grain
        pctx.globalAlpha = 0.08;
        pctx.strokeStyle = '#000';
        for (let i = -20; i < size + 20; i += 25) {
            pctx.beginPath();
            if (vertical) {
                pctx.moveTo(i, 0);
                pctx.bezierCurveTo(i + 20, size / 3, i - 15, 2 * size / 3, i, size);
            } else {
                pctx.moveTo(0, i);
                pctx.bezierCurveTo(size / 3, i + 20, 2 * size / 3, i - 15, size, i);
            }
            pctx.lineWidth = 1.5 + Math.random() * 2;
            pctx.stroke();
        }
        return ctx.createPattern(pc, 'repeat');
    }

    const patH = createWoodPattern(woodColor, false);
    const patV = createWoodPattern(woodColor, true);

    // 3. Frame drawing logic (Miters)
    function drawFrame(rectOut, rectIn, shades) {
        const {x: xo, y: yo, w: wo, h: ho} = rectOut;
        const {x: xi, y: yi, w: wi, h: hi} = rectIn;

        const drawPiece = (pts, pat, shadeColor) => {
            ctx.save();
            ctx.beginPath();
            ctx.moveTo(pts[0].x, pts[0].y);
            ctx.lineTo(pts[1].x, pts[1].y);
            ctx.lineTo(pts[2].x, pts[2].y);
            ctx.lineTo(pts[3].x, pts[3].y);
            ctx.closePath();
            ctx.clip();

            ctx.fillStyle = pat;
            ctx.fillRect(0, 0, cw, ch); // Fill whole, clip truncates

            if (shadeColor) {
                ctx.fillStyle = shadeColor;
                ctx.fillRect(0, 0, cw, ch);
            }

            ctx.strokeStyle = 'rgba(0,0,0,0.4)';
            ctx.lineWidth = 1;
            ctx.stroke();
            ctx.restore();
        };

        // Top Piece
        drawPiece([{x: xo, y: yo}, {x: xo + wo, y: yo}, {x: xi + wi, y: yi}, {x: xi, y: yi}], patH, shades.top);
        // Bottom Piece
        drawPiece([{x: xo, y: yo + ho}, {x: xo + wo, y: yo + ho}, {x: xi + wi, y: yi + hi}, {x: xi, y: yi + hi}], patH, shades.bottom);
        // Left Piece
        drawPiece([{x: xo, y: yo}, {x: xi, y: yi}, {x: xi, y: yi + hi}, {x: xo, y: yo + ho}], patV, shades.left);
        // Right Piece
        drawPiece([{x: xo + wo, y: yo}, {x: xi + wi, y: yi}, {x: xi + wi, y: yi + hi}, {x: xo + wo, y: yo + ho}], patV, shades.right);
    }

    // Geometry Definitions
    const outerRect = {x: 0, y: 0, w: cw, h: ch};
    const frameRect = {x: outMarg, y: outMarg, w: fW, h: fH};
    const floorRect = {x: outMarg + wallDepth, y: outMarg + wallDepth, w: flW, h: flH};

    // Draw Outer Frame
    drawFrame(outerRect, frameRect, {
        top: 'rgba(0,0,0,0)',
        bottom: 'rgba(0,0,0,0.15)',
        left: 'rgba(0,0,0,0.1)',
        right: 'rgba(255,255,255,0.05)'
    });

    // Draw Inner Sloped Walls (Darker for depth)
    drawFrame(frameRect, floorRect, {
        top: 'rgba(0,0,0,0.65)',
        bottom: 'rgba(0,0,0,0.1)',
        left: 'rgba(0,0,0,0.45)',
        right: 'rgba(0,0,0,0.05)'
    });

    // 4. Draw Inner Floor (Velvet lining)
    ctx.save();
    ctx.beginPath();
    ctx.rect(floorRect.x, floorRect.y, floorRect.w, floorRect.h);
    ctx.clip();
    
    ctx.fillStyle = liningColor;
    ctx.fill();

    // Velvet noise pattern
    const noiseCv = document.createElement('canvas');
    noiseCv.width = 100; noiseCv.height = 100;
    const nCtx = noiseCv.getContext('2d');
    nCtx.fillStyle = '#000';
    for(let i=0; i<100; i+=2) {
        for(let j=0; j<100; j+=2) {
            if(Math.random() < 0.4) nCtx.fillRect(i, j, 2, 2);
        }
    }
    ctx.globalAlpha = 0.15;
    ctx.fillStyle = ctx.createPattern(noiseCv, 'repeat');
    ctx.fill();
    ctx.globalAlpha = 1.0;

    // Floor shadow from walls
    const floorShade = ctx.createLinearGradient(floorRect.x, floorRect.y, floorRect.x + 100, floorRect.y + 100);
    floorShade.addColorStop(0, 'rgba(0,0,0,0.8)');
    floorShade.addColorStop(1, 'rgba(0,0,0,0)');
    ctx.fillStyle = floorShade;
    ctx.fill();
    ctx.restore();

    // 5. Load Font for Caption
    const fontName = 'Caveat';
    if (!document.getElementById('m-box-font')) {
        const style = document.createElement('style');
        style.id = 'm-box-font';
        style.textContent = `@import url('https://fonts.googleapis.com/css2?family=Caveat:wght@600&display=swap');`;
        document.head.appendChild(style);
    }
    try {
        await document.fonts.load(`600 36px "${fontName}"`);
    } catch(e) {
        console.warn('Font load failed, falling back to system fonts.');
    }

    // 6. Draw Polaroid / Photo Memory
    ctx.save();
    ctx.translate(cw / 2, ch / 2);
    ctx.rotate(0.04 - Math.random() * 0.08); // Subtle authentic rotation

    // Polaroid Paper and Drop Shadow
    ctx.shadowColor = 'rgba(0,0,0,0.6)';
    ctx.shadowOffsetX = 12;
    ctx.shadowOffsetY = 16;
    ctx.shadowBlur = 20;
    ctx.fillStyle = '#FDFCF6'; // Slightly aged paper white
    ctx.fillRect(-prW / 2, -prH / 2, prW, prH);

    // Disable shadow for internal drawings
    ctx.shadowColor = 'transparent';

    // Draw the actual image
    const photoX = -prW / 2 + pPadX;
    const photoY = -prH / 2 + pPadTop;
    ctx.drawImage(originalImg, photoX, photoY, imgW, imgH);
    
    // Add a very subtle warm vintage wash
    ctx.fillStyle = 'rgba(150, 100, 50, 0.05)';
    ctx.fillRect(photoX, photoY, imgW, imgH);

    // Draw Caption
    ctx.fillStyle = '#222';
    ctx.font = `600 42px "${fontName}", "Comic Sans MS", cursive`;
    ctx.textAlign = 'center';
    ctx.textBaseline = 'middle';
    ctx.fillText(caption, 0, prH / 2 - pPadBot / 2 + 5);

    // Draw Vintage Photo Corners
    ctx.fillStyle = '#1A1A1A';
    const cSize = 25; // Corner triangle size
    function drawPhotoCorner(cx, cy, rotation) {
        ctx.save();
        ctx.translate(cx, cy);
        ctx.rotate(rotation);
        
        ctx.beginPath();
        ctx.moveTo(0, 0); ctx.lineTo(cSize, 0); ctx.lineTo(0, cSize);
        ctx.closePath();
        ctx.fill();

        // Corner sleeve shine
        ctx.fillStyle = 'rgba(255,255,255,0.15)';
        ctx.beginPath();
        ctx.moveTo(2, 2); ctx.lineTo(cSize - 3, 2); ctx.lineTo(2, cSize - 3);
        ctx.closePath();
        ctx.fill();
        
        ctx.restore();
    }

    drawPhotoCorner(photoX, photoY, 0);                         // Top-Left
    drawPhotoCorner(photoX + imgW, photoY, Math.PI / 2);        // Top-Right
    drawPhotoCorner(photoX + imgW, photoY + imgH, Math.PI);     // Bottom-Right
    drawPhotoCorner(photoX, photoY + imgH, -Math.PI / 2);       // Bottom-Left

    // Draw a small decorative dried leaf tucked beside the photo
    ctx.restore(); // reset to canvas coords
    ctx.save();
    ctx.translate(floorRect.x + 30, ch / 2 - 20);
    ctx.rotate(-0.3);
    
    ctx.shadowColor = 'rgba(0,0,0,0.5)';
    ctx.shadowOffsetX = 3;
    ctx.shadowOffsetY = 5;
    ctx.shadowBlur = 6;
    
    ctx.fillStyle = '#8B7355'; 
    ctx.beginPath();
    ctx.moveTo(0, 0);
    ctx.quadraticCurveTo(25, -15, 45, 0);
    ctx.quadraticCurveTo(25, 15, 0, 0);
    ctx.fill();
    
    ctx.shadowColor = 'transparent';
    ctx.strokeStyle = '#5A4A36';
    ctx.lineWidth = 1;
    ctx.beginPath();
    ctx.moveTo(0, 0);
    ctx.lineTo(43, 0);
    ctx.stroke();
    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 Photo Memory Box Creator is an image transformation tool that turns your digital photographs into a nostalgic, stylized visual composition. It places your image inside a digital Polaroid-style frame complete with a custom caption, and then nests that photo inside a realistic 3D-looking shadow box featuring wood textures and a velvet-lined interior. This tool is ideal for creating sentimental digital keepsakes, social media posts for special occasions, or personalized virtual scrapbooking projects.

Leave a Reply

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