Smart Object Remover Tool

Smart Object Remover Tool

    <div class="bg-white p-6 rounded-lg shadow-md">
        <div class="mb-6 flex justify-between items-center">
            <div>
                <input type="file" id="imageInput" accept="image/*" class="hidden">
                <button id="uploadBtn" class="btn btn-primary"><i class="fas fa-upload mr-2"></i>Upload Image</button>
            </div>
            <div>
                <button id="resetBtn" class="btn btn-danger" disabled><i class="fas fa-redo-alt mr-2"></i>Reset</button>
            </div>
        </div>

        <div id="editorContainer" class="hidden">
            <div class="mb-4">
                <div class="slider-container">
                    <span><i class="fas fa-paint-brush"></i></span>
                    <input type="range" id="brushSize" min="5" max="50" value="20">
                    <span id="brushSizeValue">20px</span>
                </div>
            </div>

            <div class="mb-4 flex space-x-2">
                <button id="eraseBtn" class="btn btn-success"><i class="fas fa-eraser mr-2"></i>Smart Remove</button>
                <button id="undoBtn" class="btn bg-yellow-500 text-white hover:bg-yellow-600"><i class="fas fa-undo mr-2"></i>Undo</button>
            </div>

            <div class="canvas-container mx-auto mb-4">
                <div id="loader" class="loader hidden"></div>
                <canvas id="imageCanvas"></canvas>
                <div id="brushPreview"></div>
            </div>

            <div class="mb-4">
                <p class="text-sm text-gray-600">
                    <i class="fas fa-info-circle mr-2"></i>
                    Instructions: Paint over objects you want to remove, then click "Smart Remove"
                </p>
            </div>
        </div>
    </div>

    <div class="mt-6 text-center text-sm text-gray-500">
        <p>Use brush to select objects, then click Smart Remove to erase them while preserving the background.</p>
    </div>
</div>

<script>
    document.addEventListener('DOMContentLoaded', function() {
        // Elements
        const imageInput = document.getElementById('imageInput');
        const uploadBtn = document.getElementById('uploadBtn');
        const resetBtn = document.getElementById('resetBtn');
        const eraseBtn = document.getElementById('eraseBtn');
        const undoBtn = document.getElementById('undoBtn');
        const brushSize = document.getElementById('brushSize');
        const brushSizeValue = document.getElementById('brushSizeValue');
        const imageCanvas = document.getElementById('imageCanvas');
        const ctx = imageCanvas.getContext('2d');
        const brushPreview = document.getElementById('brushPreview');
        const editorContainer = document.getElementById('editorContainer');
        const loader = document.getElementById('loader');

        // Variables
        let originalImage = null;
        let isDrawing = false;
        let maskCanvas = document.createElement('canvas');
        let maskCtx = maskCanvas.getContext('2d');
        let currentBrushSize = parseInt(brushSize.value);
        let history = [];
        let currentStep = -1;

        // Event listeners
        uploadBtn.addEventListener('click', () => imageInput.click());
        imageInput.addEventListener('change', handleImageUpload);
        resetBtn.addEventListener('click', resetApp);
        eraseBtn.addEventListener('click', smartRemove);
        undoBtn.addEventListener('click', undo);
        brushSize.addEventListener('input', updateBrushSize);

        // Canvas event listeners
        imageCanvas.addEventListener('mousedown', startDrawing);
        imageCanvas.addEventListener('mousemove', draw);
        imageCanvas.addEventListener('mouseup', stopDrawing);
        imageCanvas.addEventListener('mouseleave', stopDrawing);
        imageCanvas.addEventListener('mousemove', updateBrushPreview);
        imageCanvas.addEventListener('mouseleave', hideBrushPreview);

        // Also add touch events for mobile devices
        imageCanvas.addEventListener('touchstart', handleTouch);
        imageCanvas.addEventListener('touchmove', handleTouchMove);
        imageCanvas.addEventListener('touchend', stopDrawing);

        function handleImageUpload(e) {
            const file = e.target.files[0];
            if (!file) return;

            const reader = new FileReader();

            reader.onload = function(event) {
                // Show loader
                loader.classList.remove('hidden');

                const img = new Image();
                img.onload = function() {
                    // Determine canvas size to fit the window while maintaining aspect ratio
                    const maxWidth = Math.min(window.innerWidth - 40, 900);
                    const maxHeight = 600;

                    let width = img.width;
                    let height = img.height;

                    if (width > maxWidth) {
                        const ratio = maxWidth / width;
                        width = maxWidth;
                        height = height * ratio;
                    }

                    if (height > maxHeight) {
                        const ratio = maxHeight / height;
                        height = maxHeight;
                        width = width * ratio;
                    }

                    // Set canvas dimensions
                    imageCanvas.width = width;
                    imageCanvas.height = height;
                    maskCanvas.width = width;
                    maskCanvas.height = height;

                    // Draw image on canvas
                    ctx.drawImage(img, 0, 0, width, height);

                    // Save original image for reset
                    originalImage = new Image();
                    originalImage.src = imageCanvas.toDataURL();

                    // Clear mask
                    maskCtx.clearRect(0, 0, maskCanvas.width, maskCanvas.height);

                    // Save initial state to history
                    saveToHistory();

                    // Show editor UI
                    editorContainer.classList.remove('hidden');
                    resetBtn.disabled = false;

                    // Hide loader
                    loader.classList.add('hidden');
                };

                img.src = event.target.result;
            };

            reader.readAsDataURL(file);
        }

        function updateBrushSize() {
            currentBrushSize = parseInt(brushSize.value);
            brushSizeValue.textContent = currentBrushSize + 'px';

            // Update brush preview
            brushPreview.style.width = currentBrushSize + 'px';
            brushPreview.style.height = currentBrushSize + 'px';
        }

        function updateBrushPreview(e) {
            const rect = imageCanvas.getBoundingClientRect();
            const x = e.clientX - rect.left;
            const y = e.clientY - rect.top;

            brushPreview.style.display = 'block';
            brushPreview.style.left = (x - currentBrushSize/2) + 'px';
            brushPreview.style.top = (y - currentBrushSize/2) + 'px';
            brushPreview.style.width = currentBrushSize + 'px';
            brushPreview.style.height = currentBrushSize + 'px';
        }

        function hideBrushPreview() {
            brushPreview.style.display = 'none';
        }

        function startDrawing(e) {
            isDrawing = true;
            draw(e);
        }

        function draw(e) {
            if (!isDrawing) return;

            const rect = imageCanvas.getBoundingClientRect();
            const x = e.clientX - rect.left;
            const y = e.clientY - rect.top;

            // Draw on mask canvas
            maskCtx.fillStyle = 'white';
            maskCtx.beginPath();
            maskCtx.arc(x, y, currentBrushSize/2, 0, Math.PI * 2);
            maskCtx.fill();

            // Show selection on image canvas
            ctx.fillStyle = 'rgba(255, 0, 0, 0.5)';
            ctx.beginPath();
            ctx.arc(x, y, currentBrushSize/2, 0, Math.PI * 2);
            ctx.fill();
        }

        function stopDrawing() {
            if (isDrawing) {
                isDrawing = false;
            }
        }

        function handleTouch(e) {
            e.preventDefault();
            const touch = e.touches[0];
            const mouseEvent = new MouseEvent('mousedown', {
                clientX: touch.clientX,
                clientY: touch.clientY
            });
            imageCanvas.dispatchEvent(mouseEvent);
        }

        function handleTouchMove(e) {
            e.preventDefault();
            const touch = e.touches[0];
            const mouseEvent = new MouseEvent('mousemove', {
                clientX: touch.clientX,
                clientY: touch.clientY
            });
            imageCanvas.dispatchEvent(mouseEvent);
        }

        function smartRemove() {
            // Show loader
            loader.classList.remove('hidden');

            // Use setTimeout to allow the loader to be displayed
            setTimeout(() => {
                // Create a copy of the current image state
                const currentImageData = ctx.getImageData(0, 0, imageCanvas.width, imageCanvas.height);

                // Get mask data
                const maskData = maskCtx.getImageData(0, 0, maskCanvas.width, maskCanvas.height);

                // Perform content-aware fill
                performInpainting(currentImageData, maskData);

                // Clear mask after processing
                maskCtx.clearRect(0, 0, maskCanvas.width, maskCanvas.height);

                // Save to history
                saveToHistory();

                // Hide loader
                loader.classList.add('hidden');
            }, 50);
        }

        function performInpainting(imageData, maskData) {
            const width = imageData.width;
            const height = imageData.height;
            const pixels = imageData.data;
            const mask = maskData.data;

            // Identify masked areas and store their coordinates
            let maskedPixels = [];
            for (let y = 0; y < height; y++) {
                for (let x = 0; x < width; x++) {
                    const i = (y * width + x) * 4;
                    if (mask[i] > 0) {
                        maskedPixels.push({x, y});
                    }
                }
            }

            // Calculate border pixels (pixels adjacent to the masked area)
            let borderPixels = new Map();
            for (const {x, y} of maskedPixels) {
                // Check 8 neighboring pixels
                for (let dy = -1; dy <= 1; dy++) {
                    for (let dx = -1; dx <= 1; dx++) {
                        if (dx === 0 && dy === 0) continue;

                        const nx = x + dx;
                        const ny = y + dy;

                        if (nx >= 0 && nx < width && ny >= 0 && ny < height) {
                            const ni = (ny * width + nx) * 4;
                            if (mask[ni] === 0) {
                                // This is a border pixel (outside the mask but adjacent to it)
                                const key = `${nx},${ny}`;
                                if (!borderPixels.has(key)) {
                                    borderPixels.set(key, {
                                        x: nx,
                                        y: ny,
                                        r: pixels[ni],
                                        g: pixels[ni + 1],
                                        b: pixels[ni + 2],
                                        a: pixels[ni + 3]
                                    });
                                }
                            }
                        }
                    }
                }
            }

            // Convert to array for easier iteration
            borderPixels = Array.from(borderPixels.values());

            // Process each masked pixel
            for (const {x, y} of maskedPixels) {
                const i = (y * width + x) * 4;

                // Find the closest border pixels and blend their colors
                let totalWeight = 0;
                let r = 0, g = 0, b = 0, a = 0;

                for (const bp of borderPixels) {
                    const distance = Math.sqrt((x - bp.x) ** 2 + (y - bp.y) ** 2);
                    const weight = 1 / (distance + 0.1); // Adding 0.1 to avoid division by zero

                    totalWeight += weight;
                    r += bp.r * weight;
                    g += bp.g * weight;
                    b += bp.b * weight;
                    a += bp.a * weight;
                }

                // Normalize and set the pixel color
                pixels[i] = Math.round(r / totalWeight);
                pixels[i + 1] = Math.round(g / totalWeight);
                pixels[i + 2] = Math.round(b / totalWeight);
                pixels[i + 3] = Math.round(a / totalWeight);
            }

            // Apply the processed image data back to the canvas
            ctx.putImageData(imageData, 0, 0);

            // Optional: Apply a subtle blur to blend edges better
            const tempCanvas = document.createElement('canvas');
            tempCanvas.width = width;
            tempCanvas.height = height;
            const tempCtx = tempCanvas.getContext('2d');
            tempCtx.putImageData(imageData, 0, 0);

            ctx.filter = 'blur(0.5px)';
            ctx.drawImage(tempCanvas, 0, 0);
            ctx.filter = 'none';
        }

        function saveToHistory() {
            // Remove any states after the current state if we did an undo before
            if (currentStep < history.length - 1) {
                history = history.slice(0, currentStep + 1);
            }

            // Add current state to history
            history.push(imageCanvas.toDataURL());
            currentStep = history.length - 1;

            // Enable/disable undo button
            undoBtn.disabled = currentStep <= 0;
        }

        function undo() {
            if (currentStep > 0) {
                currentStep--;
                const img = new Image();
                img.onload = function() {
                    ctx.clearRect(0, 0, imageCanvas.width, imageCanvas.height);
                    ctx.drawImage(img, 0, 0);
                };
                img.src = history[currentStep];
                undoBtn.disabled = currentStep <= 0;
            }
        }

        function resetApp() {
            if (confirm('Are you sure you want to reset? This will clear your current image.')) {
                ctx.clearRect(0, 0, imageCanvas.width, imageCanvas.height);
                maskCtx.clearRect(0, 0, maskCanvas.width, maskCanvas.height);
                editorContainer.classList.add('hidden');
                resetBtn.disabled = true;
                imageInput.value = '';
                originalImage = null;
                history = [];
                currentStep = -1;
            }
        }

        // Initialize
        updateBrushSize();
    });
</script>



Smart Object Remover Tool

Smart Object Remover Tool

    <div class="bg-white p-6 rounded-lg shadow-md">
        <div class="mb-6 flex justify-between items-center">
            <div>
                <input type="file" id="imageInput" accept="image/*" class="hidden">
                <button id="uploadBtn" class="btn btn-primary"><i class="fas fa-upload mr-2"></i>Upload Image</button>
            </div>
            <div>
                <button id="resetBtn" class="btn btn-danger" disabled><i class="fas fa-redo-alt mr-2"></i>Reset</button>
            </div>
        </div>

        <div id="editorContainer" class="hidden">
            <div class="mb-4">
                <div class="slider-container">
                    <span><i class="fas fa-paint-brush"></i></span>
                    <input type="range" id="brushSize" min="5" max="50" value="20">
                    <span id="brushSizeValue">20px</span>
                </div>
            </div>

            <div class="mb-4 flex space-x-2">
                <button id="eraseBtn" class="btn btn-success"><i class="fas fa-eraser mr-2"></i>Smart Remove</button>
                <button id="undoBtn" class="btn bg-yellow-500 text-white hover:bg-yellow-600"><i class="fas fa-undo mr-2"></i>Undo</button>
            </div>

            <div class="canvas-container mx-auto mb-4">
                <div id="loader" class="loader hidden"></div>
                <canvas id="imageCanvas"></canvas>
                <div id="brushPreview"></div>
            </div>

            <div class="mb-4">
                <p class="text-sm text-gray-600">
                    <i class="fas fa-info-circle mr-2"></i>
                    Instructions: Paint over objects you want to remove, then click "Smart Remove"
                </p>
            </div>
        </div>
    </div>

    <div class="mt-6 text-center text-sm text-gray-500">
        <p>Use brush to select objects, then click Smart Remove to erase them while preserving the background.</p>
    </div>
</div>

<script>
    document.addEventListener('DOMContentLoaded', function() {
        // Elements
        const imageInput = document.getElementById('imageInput');
        const uploadBtn = document.getElementById('uploadBtn');
        const resetBtn = document.getElementById('resetBtn');
        const eraseBtn = document.getElementById('eraseBtn');
        const undoBtn = document.getElementById('undoBtn');
        const brushSize = document.getElementById('brushSize');
        const brushSizeValue = document.getElementById('brushSizeValue');
        const imageCanvas = document.getElementById('imageCanvas');
        const ctx = imageCanvas.getContext('2d');
        const brushPreview = document.getElementById('brushPreview');
        const editorContainer = document.getElementById('editorContainer');
        const loader = document.getElementById('loader');

        // Variables
        let originalImage = null;
        let isDrawing = false;
        let maskCanvas = document.createElement('canvas');
        let maskCtx = maskCanvas.getContext('2d');
        let currentBrushSize = parseInt(brushSize.value);
        let history = [];
        let currentStep = -1;

        // Event listeners
        uploadBtn.addEventListener('click', () => imageInput.click());
        imageInput.addEventListener('change', handleImageUpload);
        resetBtn.addEventListener('click', resetApp);
        eraseBtn.addEventListener('click', smartRemove);
        undoBtn.addEventListener('click', undo);
        brushSize.addEventListener('input', updateBrushSize);

        // Canvas event listeners
        imageCanvas.addEventListener('mousedown', startDrawing);
        imageCanvas.addEventListener('mousemove', draw);
        imageCanvas.addEventListener('mouseup', stopDrawing);
        imageCanvas.addEventListener('mouseleave', stopDrawing);
        imageCanvas.addEventListener('mousemove', updateBrushPreview);
        imageCanvas.addEventListener('mouseleave', hideBrushPreview);

        // Also add touch events for mobile devices
        imageCanvas.addEventListener('touchstart', handleTouch);
        imageCanvas.addEventListener('touchmove', handleTouchMove);
        imageCanvas.addEventListener('touchend', stopDrawing);

        function handleImageUpload(e) {
            const file = e.target.files[0];
            if (!file) return;

            const reader = new FileReader();

            reader.onload = function(event) {
                // Show loader
                loader.classList.remove('hidden');

                const img = new Image();
                img.onload = function() {
                    // Determine canvas size to fit the window while maintaining aspect ratio
                    const maxWidth = Math.min(window.innerWidth - 40, 900);
                    const maxHeight = 600;

                    let width = img.width;
                    let height = img.height;

                    if (width > maxWidth) {
                        const ratio = maxWidth / width;
                        width = maxWidth;
                        height = height * ratio;
                    }

                    if (height > maxHeight) {
                        const ratio = maxHeight / height;
                        height = maxHeight;
                        width = width * ratio;
                    }

                    // Set canvas dimensions
                    imageCanvas.width = width;
                    imageCanvas.height = height;
                    maskCanvas.width = width;
                    maskCanvas.height = height;

                    // Draw image on canvas
                    ctx.drawImage(img, 0, 0, width, height);

                    // Save original image for reset
                    originalImage = new Image();
                    originalImage.src = imageCanvas.toDataURL();

                    // Clear mask
                    maskCtx.clearRect(0, 0, maskCanvas.width, maskCanvas.height);

                    // Save initial state to history
                    saveToHistory();

                    // Show editor UI
                    editorContainer.classList.remove('hidden');
                    resetBtn.disabled = false;

                    // Hide loader
                    loader.classList.add('hidden');
                };

                img.src = event.target.result;
            };

            reader.readAsDataURL(file);
        }

        function updateBrushSize() {
            currentBrushSize = parseInt(brushSize.value);
            brushSizeValue.textContent = currentBrushSize + 'px';

            // Update brush preview
            brushPreview.style.width = currentBrushSize + 'px';
            brushPreview.style.height = currentBrushSize + 'px';
        }

        function updateBrushPreview(e) {
            const rect = imageCanvas.getBoundingClientRect();
            const x = e.clientX - rect.left;
            const y = e.clientY - rect.top;

            brushPreview.style.display = 'block';
            brushPreview.style.left = (x - currentBrushSize/2) + 'px';
            brushPreview.style.top = (y - currentBrushSize/2) + 'px';
            brushPreview.style.width = currentBrushSize + 'px';
            brushPreview.style.height = currentBrushSize + 'px';
        }

        function hideBrushPreview() {
            brushPreview.style.display = 'none';
        }

        function startDrawing(e) {
            isDrawing = true;
            draw(e);
        }

        function draw(e) {
            if (!isDrawing) return;

            const rect = imageCanvas.getBoundingClientRect();
            const x = e.clientX - rect.left;
            const y = e.clientY - rect.top;

            // Draw on mask canvas
            maskCtx.fillStyle = 'white';
            maskCtx.beginPath();
            maskCtx.arc(x, y, currentBrushSize/2, 0, Math.PI * 2);
            maskCtx.fill();

            // Show selection on image canvas
            ctx.fillStyle = 'rgba(255, 0, 0, 0.5)';
            ctx.beginPath();
            ctx.arc(x, y, currentBrushSize/2, 0, Math.PI * 2);
            ctx.fill();
        }

        function stopDrawing() {
            if (isDrawing) {
                isDrawing = false;
            }
        }

        function handleTouch(e) {
            e.preventDefault();
            const touch = e.touches[0];
            const mouseEvent = new MouseEvent('mousedown', {
                clientX: touch.clientX,
                clientY: touch.clientY
            });
            imageCanvas.dispatchEvent(mouseEvent);
        }

        function handleTouchMove(e) {
            e.preventDefault();
            const touch = e.touches[0];
            const mouseEvent = new MouseEvent('mousemove', {
                clientX: touch.clientX,
                clientY: touch.clientY
            });
            imageCanvas.dispatchEvent(mouseEvent);
        }

        function smartRemove() {
            // Show loader
            loader.classList.remove('hidden');

            // Use setTimeout to allow the loader to be displayed
            setTimeout(() => {
                // Create a copy of the current image state
                const currentImageData = ctx.getImageData(0, 0, imageCanvas.width, imageCanvas.height);

                // Get mask data
                const maskData = maskCtx.getImageData(0, 0, maskCanvas.width, maskCanvas.height);

                // Perform content-aware fill
                performInpainting(currentImageData, maskData);

                // Clear mask after processing
                maskCtx.clearRect(0, 0, maskCanvas.width, maskCanvas.height);

                // Save to history
                saveToHistory();

                // Hide loader
                loader.classList.add('hidden');
            }, 50);
        }

        function performInpainting(imageData, maskData) {
            const width = imageData.width;
            const height = imageData.height;
            const pixels = imageData.data;
            const mask = maskData.data;

            // Identify masked areas and store their coordinates
            let maskedPixels = [];
            for (let y = 0; y < height; y++) {
                for (let x = 0; x < width; x++) {
                    const i = (y * width + x) * 4;
                    if (mask[i] > 0) {
                        maskedPixels.push({x, y});
                    }
                }
            }

            // Calculate border pixels (pixels adjacent to the masked area)
            let borderPixels = new Map();
            for (const {x, y} of maskedPixels) {
                // Check 8 neighboring pixels
                for (let dy = -1; dy <= 1; dy++) {
                    for (let dx = -1; dx <= 1; dx++) {
                        if (dx === 0 && dy === 0) continue;

                        const nx = x + dx;
                        const ny = y + dy;

                        if (nx >= 0 && nx < width && ny >= 0 && ny < height) {
                            const ni = (ny * width + nx) * 4;
                            if (mask[ni] === 0) {
                                // This is a border pixel (outside the mask but adjacent to it)
                                const key = `${nx},${ny}`;
                                if (!borderPixels.has(key)) {
                                    borderPixels.set(key, {
                                        x: nx,
                                        y: ny,
                                        r: pixels[ni],
                                        g: pixels[ni + 1],
                                        b: pixels[ni + 2],
                                        a: pixels[ni + 3]
                                    });
                                }
                            }
                        }
                    }
                }
            }

            // Convert to array for easier iteration
            borderPixels = Array.from(borderPixels.values());

            // Process each masked pixel
            for (const {x, y} of maskedPixels) {
                const i = (y * width + x) * 4;

                // Find the closest border pixels and blend their colors
                let totalWeight = 0;
                let r = 0, g = 0, b = 0, a = 0;

                for (const bp of borderPixels) {
                    const distance = Math.sqrt((x - bp.x) ** 2 + (y - bp.y) ** 2);
                    const weight = 1 / (distance + 0.1); // Adding 0.1 to avoid division by zero

                    totalWeight += weight;
                    r += bp.r * weight;
                    g += bp.g * weight;
                    b += bp.b * weight;
                    a += bp.a * weight;
                }

                // Normalize and set the pixel color
                pixels[i] = Math.round(r / totalWeight);
                pixels[i + 1] = Math.round(g / totalWeight);
                pixels[i + 2] = Math.round(b / totalWeight);
                pixels[i + 3] = Math.round(a / totalWeight);
            }

            // Apply the processed image data back to the canvas
            ctx.putImageData(imageData, 0, 0);

            // Optional: Apply a subtle blur to blend edges better
            const tempCanvas = document.createElement('canvas');
            tempCanvas.width = width;
            tempCanvas.height = height;
            const tempCtx = tempCanvas.getContext('2d');
            tempCtx.putImageData(imageData, 0, 0);

            ctx.filter = 'blur(0.5px)';
            ctx.drawImage(tempCanvas, 0, 0);
            ctx.filter = 'none';
        }

        function saveToHistory() {
            // Remove any states after the current state if we did an undo before
            if (currentStep < history.length - 1) {
                history = history.slice(0, currentStep + 1);
            }

            // Add current state to history
            history.push(imageCanvas.toDataURL());
            currentStep = history.length - 1;

            // Enable/disable undo button
            undoBtn.disabled = currentStep <= 0;
        }

        function undo() {
            if (currentStep > 0) {
                currentStep--;
                const img = new Image();
                img.onload = function() {
                    ctx.clearRect(0, 0, imageCanvas.width, imageCanvas.height);
                    ctx.drawImage(img, 0, 0);
                };
                img.src = history[currentStep];
                undoBtn.disabled = currentStep <= 0;
            }
        }

        function resetApp() {
            if (confirm('Are you sure you want to reset? This will clear your current image.')) {
                ctx.clearRect(0, 0, imageCanvas.width, imageCanvas.height);
                maskCtx.clearRect(0, 0, maskCanvas.width, maskCanvas.height);
                editorContainer.classList.add('hidden');
                resetBtn.disabled = true;
                imageInput.value = '';
                originalImage = null;
                history = [];
                currentStep = -1;
            }
        }

        // Initialize
        updateBrushSize();
    });
</script>