Please bookmark this page to avoid losing your image tool!

Image Personal Information Remover

(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, censorColor = "#222222") {
    // Create the final canvas to be returned
    const canvas = document.createElement('canvas');
    canvas.width = originalImg.width;
    canvas.height = originalImg.height;
    const ctx = canvas.getContext('2d');
    
    // Draw the original image onto the canvas
    ctx.drawImage(originalImg, 0, 0);

    // Limit image size for OCR to avoid browser crashes and speed up processing
    const MAX_OCR_DIMENSION = 1200;
    let scale = 1;
    let ocrCanvas = canvas;

    if (canvas.width > MAX_OCR_DIMENSION || canvas.height > MAX_OCR_DIMENSION) {
        scale = MAX_OCR_DIMENSION / Math.max(canvas.width, canvas.height);
        ocrCanvas = document.createElement('canvas');
        ocrCanvas.width = canvas.width * scale;
        ocrCanvas.height = canvas.height * scale;
        const ocrCtx = ocrCanvas.getContext('2d');
        ocrCtx.drawImage(originalImg, 0, 0, ocrCanvas.width, ocrCanvas.height);
    }

    // Dynamically load Tesseract.js if not already present
    if (typeof Tesseract === 'undefined') {
        await new Promise((resolve, reject) => {
            const script = document.createElement('script');
            script.src = 'https://cdn.jsdelivr.net/npm/tesseract.js@5/dist/tesseract.min.js';
            script.onload = resolve;
            script.onerror = reject;
            document.head.appendChild(script);
        });
    }

    // Initialize Tesseract Worker
    const worker = await Tesseract.createWorker('eng');
    
    // Perform Optical Character Recognition
    const { data } = await worker.recognize(ocrCanvas);
    await worker.terminate();

    const lines = data.lines;
    const keywordBBoxes = { name: [], dob: [], address: [] };

    // Helper to translate bounding box back to original image scale
    const adjustBBox = (b) => ({
        x0: b.x0 / scale,
        y0: b.y0 / scale,
        x1: b.x1 / scale,
        y1: b.y1 / scale
    });

    // Pass 1: Identify keywords and map their locations
    for (const line of lines) {
        for (const word of line.words) {
            const cleanText = word.text.replace(/[^a-z]/gi, '').toLowerCase();
            
            if (['name', 'first', 'last', 'full'].includes(cleanText)) {
                keywordBBoxes.name.push(adjustBBox(word.bbox));
            } else if (['dob', 'birth', 'date'].includes(cleanText)) {
                keywordBBoxes.dob.push(adjustBBox(word.bbox));
            } else if (['address', 'addr'].includes(cleanText)) {
                keywordBBoxes.address.push(adjustBBox(word.bbox));
            }
        }
    }

    // Helper function used to check if a word is contextually placed next to or immediately below a keyword
    const isRelatedLocally = (targetBbox, keywordBbox) => {
        const height = keywordBbox.y1 - keywordBbox.y0;
        const wordYCenter = (targetBbox.y0 + targetBbox.y1) / 2;
        
        // Checks if the target is to the right of the keyword explicitly on the same line
        const isRight = wordYCenter >= keywordBbox.y0 - 10 && 
                        wordYCenter <= keywordBbox.y1 + 10 && 
                        targetBbox.x0 > keywordBbox.x0;
                        
        // Checks if the target is placed on the line immediately below the keyword label
        const isBelow = targetBbox.y0 >= keywordBbox.y1 - 5 && 
                        targetBbox.y0 <= keywordBbox.y1 + (height * 3) && 
                        targetBbox.x0 >= keywordBbox.x0 - (height * 5) && 
                        targetBbox.x0 <= keywordBbox.x1 + (height * 20);
                        
        return isRight || isBelow;
    };

    ctx.fillStyle = censorColor;

    // RegEx patterns for standard Personal Identifiable Information details (PII)
    const addressLine1Regex = /\b\d{1,5}\s+[a-z0-9\s]+\s+(st|street|ave|avenue|blvd|boulevard|rd|road|dr|drive|ln|lane|ct|court|apt|suite|unit|pl|place|hw|hwy|highway|pkwy)\b/i;
    const poBoxRegex = /\bp\.?o\.?\s*box\s+\d+\b/i;

    // Pass 2: Analyze & redact matched details
    for (const line of lines) {
        const lineText = line.text;
        
        // Address checks (street address, po box, or zip code format states)
        const hasAddress = addressLine1Regex.test(lineText) || 
                           poBoxRegex.test(lineText) || 
                           /\b[A-Z]{2}[,]?\s+\d{5}(-\d{4})?\b/.test(lineText);
                           
        // General Date checks                
        const hasMonth = /\b(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)[a-z]*\b/i.test(lineText);
        const has4Digits = /\b\d{4}\b/.test(lineText);

        for (const word of line.words) {
            const wordText = word.text.trim();
            const cleanWord = wordText.replace(/[^a-z]/gi, '').toLowerCase();

            // Do not paint over structural document labels to maintain visibility of layout context
            if (/^(name|first|last|dob|birth|date|address|addr|street|city|state|zip|sex|gender|height|weight|eyes|hair|class|exp|expires|issued)$/i.test(cleanWord)) {
                continue;
            }

            let shouldCensor = false;

            // 1. Redact Address lines completely
            if (hasAddress) shouldCensor = true;
            
            // 2. Redact strictly numerical Dates (e.g. DD/MM/YYYY)
            if (/\b(\d{1,2}[/-]\d{1,2}[/-]\d{2,4}|\d{4}[/-]\d{1,2}[/-]\d{1,2})\b/.test(wordText)) {
                shouldCensor = true;
            }
            
            // 3. Redact descriptive string formatted dates (e.g. Month Day Year)
            if (hasMonth && has4Digits && (/\b(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)[a-z]*\b/i.test(wordText) || /\d/.test(wordText))) {
                shouldCensor = true;
            }
            
            // 4. Redact numbers that resemble Zip Codes or SSNs
            if (/\b\d{5}(-\d{4})?\b/.test(wordText) || /\b\d{3}-\d{2}-\d{4}\b/.test(wordText)) {
                shouldCensor = true;
            }

            const adjustedBBox = adjustBBox(word.bbox);

            // 5. Redact contextually via logical alignment (placed directly right or accurately below a label indicator)
            for (const kb of keywordBBoxes.name) if (isRelatedLocally(adjustedBBox, kb)) shouldCensor = true;
            for (const kb of keywordBBoxes.dob) if (isRelatedLocally(adjustedBBox, kb)) shouldCensor = true;
            for (const kb of keywordBBoxes.address) if (isRelatedLocally(adjustedBBox, kb)) shouldCensor = true;

            // Paint Censor block with minor 3px padding
            if (shouldCensor) {
                const { x0, y0, x1, y1 } = adjustedBBox;
                ctx.fillRect(Math.max(0, x0 - 3), Math.max(0, y0 - 3), (x1 - x0) + 6, (y1 - y0) + 6);
            }
        }
    }

    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

This tool uses Optical Character Recognition (OCR) to automatically identify and redact sensitive personal information from images. It can detect and censor details such as names, dates of birth, physical addresses, zip codes, and Social Security numbers by analyzing the text and its context within the document. This is useful for protecting privacy when sharing scans of IDs, official documents, or sensitive paperwork online.

Leave a Reply

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