Background Remover

Drag & drop a JPG or PNG here, or click to browse
Please upload a JPG or PNG file.
Smart Removal engine failed to load. Use Color Match instead.
Iterations 3

Draw a rectangle around the subject on the image above, then click Remove Background.

Tolerance 30
Feathering 2px
Background Color
Click image to pick
Loading…
Loading Smart Removal engine…
This takes a moment on first use.
Before
Original
After
Size 30px

Background Remover

Drag & drop a JPG or PNG here, or click to browse
Please upload a JPG or PNG file.
Smart Removal engine failed to load. Use Color Match instead.
Iterations 3

Draw a rectangle around the subject on the image above, then click Remove Background.

Tolerance 30
Feathering 2px
Background Color
Click image to pick
Loading…
Loading Smart Removal engine…
This takes a moment on first use.
Before
Original
After
Size 30px
} function runColorMatch() { const sourceFile = currentImageFile; // capture before any async gap const tolerance = parseInt(document.getElementById('ctrl-tolerance').value, 10); const feathering = parseInt(document.getElementById('ctrl-feathering').value, 10); const threshold = tolerance * 4.41; // map 0–100 → 0–441 (max RGB distance) const imageData = processingCtx.getImageData( 0, 0, processingCanvas.width, processingCanvas.height ); const data = imageData.data; const { r: pr, g: pg, b: pb } = pickedColor; for (let i = 0; i < data.length; i += 4) { const dr = data[i] - pr; const dg = data[i+1] - pg; const db = data[i+2] - pb; if (Math.sqrt(dr*dr + dg*dg + db*db) <= threshold) { data[i+3] = 0; // transparent } } if (feathering > 0) { boxBlurAlpha(data, processingCanvas.width, processingCanvas.height, feathering); } const resultCanvas = document.createElement('canvas'); resultCanvas.width = processingCanvas.width; resultCanvas.height = processingCanvas.height; resultCanvas.getContext('2d').putImageData(imageData, 0, 0); resultCanvas.toBlob(blob => { setProcessLoading(false); displayResult(blob, sourceFile); }, 'image/png'); } // === SMART REMOVAL === function runGrabCut() { const sourceFile = currentImageFile; // capture before any async gap const iterations = parseInt(document.getElementById('ctrl-iterations').value, 10); // Read original RGBA from processing canvas const rgba = cv.imread(processingCanvas); // Convert to BGR (3-channel) — GrabCut requires CV_8UC3, not RGBA const bgr = new cv.Mat(); cv.cvtColor(rgba, bgr, cv.COLOR_RGBA2BGR); const mask = new cv.Mat(); const bgdModel = new cv.Mat(); const fgdModel = new cv.Mat(); const rect = new cv.Rect(grabRect.x, grabRect.y, grabRect.w, grabRect.h); try { cv.grabCut(bgr, mask, rect, bgdModel, fgdModel, iterations, cv.GC_INIT_WITH_RECT); // Apply mask to the RGBA Mat: background pixels → alpha 0 for (let row = 0; row < rgba.rows; row++) { for (let col = 0; col < rgba.cols; col++) { const m = mask.ucharAt(row, col); if (m === cv.GC_BGD || m === cv.GC_PR_BGD) { rgba.ucharPtr(row, col)[3] = 0; } } } // cv.imshow is synchronous — copies all pixels into canvas before returning. // toBlob reads from canvas buffer (independent of Mat memory), so it is safe // to delete Mats in finally even though toBlob is async. const resultCanvas = document.createElement('canvas'); resultCanvas.width = rgba.cols; resultCanvas.height = rgba.rows; cv.imshow(resultCanvas, rgba); resultCanvas.toBlob(blob => { setProcessLoading(false); displayResult(blob, sourceFile); }, 'image/png'); } catch (err) { setProcessLoading(false); alert('Smart Removal failed: ' + err.message); } finally { rgba.delete(); bgr.delete(); mask.delete(); bgdModel.delete(); fgdModel.delete(); } } // === PROCESS BUTTON === function initProcessButton() { document.getElementById('btn-process').addEventListener('click', async () => { setProcessLoading(true); // Double rAF: first schedules the paint, second fires after it has committed to screen. // Without this, GrabCut's synchronous pixel loop blocks the thread before the spinner renders. await new Promise(r => requestAnimationFrame(() => requestAnimationFrame(r))); try { if (currentMode === 'smart') { runGrabCut(); // async via toBlob callback — setProcessLoading(false) called inside } else { runColorMatch(); // async via toBlob callback — setProcessLoading(false) called inside } } catch (err) { alert('Removal failed: ' + err.message); setProcessLoading(false); updateProcessButtonState(); } }); } // === BRUSH TOOLS === function applyBrush(ac, { x, y }) { const ctx = ac.getContext('2d'); ctx.save(); ctx.beginPath(); ctx.arc(x, y, brushSize / 2, 0, Math.PI * 2); ctx.clip(); if (activeBrush === 'erase') { ctx.globalCompositeOperation = 'destination-out'; ctx.fillStyle = 'black'; ctx.fill(); } else { ctx.globalCompositeOperation = 'source-over'; ctx.drawImage(processingCanvas, 0, 0, ac.width, ac.height); } ctx.restore(); } function initBrushTools() { const ac = document.getElementById('after-canvas'); const wrap = document.getElementById('after-canvas-wrap'); const cursor = document.getElementById('brush-cursor'); // Apply current zoom + pan transform to canvas function applyTransform() { const label = document.getElementById('zoom-label'); if (zoomLevel === 1.0 && panX === 0 && panY === 0) { ac.style.transform = ''; label.style.display = 'none'; } else { ac.style.transform = `translate(${panX}px, ${panY}px) scale(${zoomLevel})`; label.style.display = 'block'; label.textContent = zoomLevel.toFixed(1) + '\u00d7'; } } // Brush button toggles ['btn-brush-erase', 'btn-brush-restore'].forEach(id => { document.getElementById(id).addEventListener('click', () => { const mode = id === 'btn-brush-erase' ? 'erase' : 'restore'; if (activeBrush === mode) { // Deactivate — switch to pan mode activeBrush = null; document.getElementById(id).classList.remove('active'); ac.classList.remove('brush-active'); cursor.style.display = 'none'; } else { activeBrush = mode; document.getElementById('btn-brush-erase').classList.toggle('active', mode === 'erase'); document.getElementById('btn-brush-restore').classList.toggle('active', mode === 'restore'); ac.classList.add('brush-active'); } }); }); // Size slider document.getElementById('ctrl-brush-size').addEventListener('input', () => { brushSize = parseInt(document.getElementById('ctrl-brush-size').value, 10); document.getElementById('val-brush-size').textContent = brushSize; }); // Coordinate helper: screen → canvas pixels (accounts for CSS transform) function getCanvasCoords(e) { const rect = ac.getBoundingClientRect(); return { x: (e.clientX - rect.left) * (ac.width / rect.width), y: (e.clientY - rect.top) * (ac.height / rect.height), }; } // Cursor sizing helper function updateBrushCursor(e) { if (!ac.width) return; const rect = ac.getBoundingClientRect(); const screenDiam = brushSize * (rect.width / ac.width); cursor.style.width = screenDiam + 'px'; cursor.style.height = screenDiam + 'px'; cursor.style.left = e.clientX + 'px'; cursor.style.top = e.clientY + 'px'; cursor.style.display = 'block'; } // Undo / redo helpers function saveUndoState() { undoStack.push(ac.getContext('2d').getImageData(0, 0, ac.width, ac.height)); if (undoStack.length > MAX_UNDO) undoStack.shift(); redoStack = []; document.getElementById('btn-undo').disabled = false; document.getElementById('btn-redo').disabled = true; } function applyUndo() { if (!undoStack.length) return; redoStack.push(ac.getContext('2d').getImageData(0, 0, ac.width, ac.height)); ac.getContext('2d').putImageData(undoStack.pop(), 0, 0); document.getElementById('btn-undo').disabled = undoStack.length === 0; document.getElementById('btn-redo').disabled = false; } function applyRedo() { if (!redoStack.length) return; undoStack.push(ac.getContext('2d').getImageData(0, 0, ac.width, ac.height)); ac.getContext('2d').putImageData(redoStack.pop(), 0, 0); document.getElementById('btn-undo').disabled = false; document.getElementById('btn-redo').disabled = redoStack.length === 0; } document.getElementById('btn-undo').addEventListener('click', applyUndo); document.getElementById('btn-redo').addEventListener('click', applyRedo); document.addEventListener('keydown', (e) => { if (e.ctrlKey && e.key === 'z') { e.preventDefault(); applyUndo(); } if (e.ctrlKey && (e.key === 'y' || (e.shiftKey && e.key === 'z'))) { e.preventDefault(); applyRedo(); } }); // Pan state (local — only used in mouse handlers) let panStart = null; // Canvas mouse events ac.addEventListener('mousedown', (e) => { if (!ac.width) return; if (e.ctrlKey || !activeBrush) { panStart = { x: e.clientX - panX, y: e.clientY - panY }; } else { saveUndoState(); isPainting = true; applyBrush(ac, getCanvasCoords(e)); } }); ac.addEventListener('mousemove', (e) => { if (panStart) { panX = e.clientX - panStart.x; panY = e.clientY - panStart.y; applyTransform(); } else if (activeBrush) { updateBrushCursor(e); if (isPainting) applyBrush(ac, getCanvasCoords(e)); } }); ac.addEventListener('mouseup', () => { isPainting = false; panStart = null; }); ac.addEventListener('mouseleave', () => { isPainting = false; panStart = null; cursor.style.display = 'none'; }); ac.addEventListener('mouseenter', (e) => { if (activeBrush) updateBrushCursor(e); }); // Scroll-wheel zoom (attached to wrap so the whole checkerboard area responds) wrap.addEventListener('wheel', (e) => { if (!ac.width) return; e.preventDefault(); const delta = e.deltaY < 0 ? 0.15 : -0.15; zoomLevel = Math.min(8.0, Math.max(1.0, zoomLevel + delta)); applyTransform(); }, { passive: false }); } // === INIT === document.addEventListener('DOMContentLoaded', () => { initUploadZone(); initModeSelector(); initOpenCv(); initPreviewListeners(); initSliders(); initProcessButton(); initResultActions(); initBrushTools(); });
Skip to Content
hillspringcrafts
Shop
Our Story
Journal
Contact
Creator
Remove Image Background
Image to Vector Converter
Login Account
English
0
0
hillspringcrafts
Shop
Our Story
Journal
Contact
Creator
Remove Image Background
Image to Vector Converter
Login Account
English
0
0
Shop
Our Story
Journal
Contact
Creator
Remove Image Background
Image to Vector Converter
Login Account
English
Back

HillSpringCrafts

Legal details:
Gundars Miezitis
VAT: 31128711314

Agenskalna iela 24-29, Riga, LV-1083, Latvia
hillspringcrafts@gmail.com
+371 26164250

Journal
Contact

Shop
Our Story

Made with Squarespace