Please bookmark this page to avoid losing your image tool!

Hand Held Photo Restorer

(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,
    sharpenIntensity = "1.5",
    colorRestore = "1",
    addVintageFrame = "1"
) {
    const intensity = parseFloat(sharpenIntensity);
    const doColor = parseInt(colorRestore) === 1;
    const doFrame = parseInt(addVintageFrame) === 1;

    let width = originalImg.width;
    let height = originalImg.height;

    // Scale down image to a manageable max size for performance
    const MAX_SIZE = 1200;
    if (width > MAX_SIZE || height > MAX_SIZE) {
        const ratio = Math.min(MAX_SIZE / width, MAX_SIZE / height);
        width = Math.floor(width * ratio);
        height = Math.floor(height * ratio);
    }

    // Set up framing dimensions
    const paddingX = doFrame ? Math.max(width * 0.06, 25) : 0;
    const paddingYTop = doFrame ? Math.max(height * 0.06, 25) : 0;
    const paddingYBot = doFrame ? Math.max(height * 0.18, 90) : 0;

    const mainCanvas = document.createElement('canvas');
    mainCanvas.width = width + paddingX * 2;
    mainCanvas.height = height + paddingYTop + paddingYBot;
    const ctx = mainCanvas.getContext('2d');

    // 1. Draw the Vintage Frame
    if (doFrame) {
        // Base off-white color
        ctx.fillStyle = '#fcfcf7';
        ctx.fillRect(0, 0, mainCanvas.width, mainCanvas.height);

        // Add subtle paper grain/noise
        const frameImgData = ctx.getImageData(0, 0, mainCanvas.width, mainCanvas.height);
        for (let i = 0; i < frameImgData.data.length; i += 4) {
            const noise = (Math.random() - 0.5) * 8;
            frameImgData.data[i] += noise;     // R
            frameImgData.data[i + 1] += noise; // G
            frameImgData.data[i + 2] += noise; // B
        }
        ctx.putImageData(frameImgData, 0, 0);
    }

    // 2. Prepare the Image Processing Canvas
    const imgCanvas = document.createElement('canvas');
    imgCanvas.width = width;
    imgCanvas.height = height;
    const imgCtx = imgCanvas.getContext('2d');
    imgCtx.drawImage(originalImg, 0, 0, width, height);

    let imgData = imgCtx.getImageData(0, 0, width, height);
    const data = imgData.data;

    // 3. Color Restoration (Auto-leveling faded colors)
    if (doColor) {
        let rVals = new Int32Array(256);
        let gVals = new Int32Array(256);
        let bVals = new Int32Array(256);
        let validPixels = 0;

        for (let i = 0; i < data.length; i += 4) {
            if (data[i + 3] === 0) continue; // Skip transparency
            rVals[data[i]]++;
            gVals[data[i + 1]]++;
            bVals[data[i + 2]]++;
            validPixels++;
        }

        const threshold = validPixels * 0.01; // Clip bottom and top 1%

        const getBounds = (hist) => {
            let min = 0, max = 255;
            let sum = 0;
            for (let i = 0; i < 256; i++) {
                sum += hist[i];
                if (sum > threshold) { min = i; break; }
            }
            sum = 0;
            for (let i = 255; i >= 0; i--) {
                sum += hist[i];
                if (sum > threshold) { max = i; break; }
            }
            return [min, max];
        };

        const [rMin, rMax] = getBounds(rVals);
        const [gMin, gMax] = getBounds(gVals);
        const [bMin, bMax] = getBounds(bVals);

        for (let i = 0; i < data.length; i += 4) {
            if (data[i + 3] === 0) continue;
            data[i] = Math.min(255, Math.max(0, (data[i] - rMin) * 255 / (rMax - rMin || 1)));
            data[i + 1] = Math.min(255, Math.max(0, (data[i + 1] - gMin) * 255 / (gMax - gMin || 1)));
            data[i + 2] = Math.min(255, Math.max(0, (data[i + 2] - bMin) * 255 / (bMax - bMin || 1)));
        }
    }
    imgCtx.putImageData(imgData, 0, 0);

    // 4. Sharpening (Unsharp Mask for hand-held blur removal)
    if (intensity > 0) {
        const blurAmt = 3;
        const blurCanvas = document.createElement('canvas');
        blurCanvas.width = width;
        blurCanvas.height = height;
        const blurCtx = blurCanvas.getContext('2d');
        blurCtx.filter = `blur(${blurAmt}px)`;
        blurCtx.drawImage(imgCanvas, 0, 0);

        const blurredData = blurCtx.getImageData(0, 0, width, height).data;
        imgData = imgCtx.getImageData(0, 0, width, height);

        for (let i = 0; i < imgData.data.length; i += 4) {
            if (imgData.data[i + 3] === 0) continue;
            for (let c = 0; c < 3; c++) {
                const orig = imgData.data[i + c];
                const blurred = blurredData[i + c];
                const diff = orig - blurred;
                imgData.data[i + c] = Math.min(255, Math.max(0, orig + (diff * intensity)));
            }
        }
        imgCtx.putImageData(imgData, 0, 0);
    }

    // 5. Apply Faded Nostalgic Edges
    if (doFrame) {
        const grad = imgCtx.createRadialGradient(
            width / 2, height / 2, Math.min(width, height) * 0.4,
            width / 2, height / 2, Math.max(width, height) * 0.8
        );
        grad.addColorStop(0, 'rgba(0,0,0,0)');
        grad.addColorStop(1, 'rgba(110, 70, 30, 0.2)');
        imgCtx.fillStyle = grad;
        imgCtx.fillRect(0, 0, width, height);
    }

    // 6. Draw Processed Image onto Main Canvas
    ctx.drawImage(imgCanvas, paddingX, paddingYTop);

    // 7. Render "A Hand Once Held" Embellishments
    if (doFrame) {
        // Inner recessed stroke
        ctx.strokeStyle = "rgba(0, 0, 0, 0.15)";
        ctx.lineWidth = 1;
        ctx.strokeRect(paddingX, paddingYTop, width, height);

        // Faded Fingerprint Smudge (Bottom Right)
        const fpCenterY = mainCanvas.height - (paddingYBot / 2);
        const fpCenterX = mainCanvas.width - paddingX - (paddingYBot * 0.8);
        
        ctx.lineWidth = 1.5;
        ctx.strokeStyle = "rgba(100, 80, 60, 0.07)";
        for (let r = 8; r < paddingYBot * 0.4; r += 5) {
            ctx.beginPath();
            ctx.ellipse(fpCenterX, fpCenterY, r * 1.1, r * 1.4, Math.PI / 6, 0, Math.PI * 2);
            ctx.stroke();
        }

        // Faint Poetic Inscription (Bottom Left)
        ctx.font = `italic ${Math.max(16, paddingYBot * 0.2)}px "Georgia", serif`;
        ctx.fillStyle = "rgba(60, 50, 40, 0.65)";
        ctx.fillText("A hand once held...", paddingX + 5, mainCanvas.height - (paddingYBot * 0.35));
    }

    return mainCanvas;
}

Free Image Tool Creator

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

Description

The Hand Held Photo Restorer is an image processing tool designed to revitalize old or poorly captured photographs. It can automatically restore faded colors by adjusting color levels and improve image clarity using a sharpening technique to reduce blur. Additionally, the tool can add a vintage aesthetic by placing the photo within a textured paper-style frame complete with nostalgic design elements like soft edges and poetic inscriptions. This tool is ideal for digitizing old family memories, enhancing low-quality handheld shots, or creating stylized, vintage-looking images for creative projects.

Leave a Reply

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