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>
