The starting problem: which background removal is actually feasible for free?

The initial request was broad: a tool to remove backgrounds and, if possible, unwanted objects from photos too. The two tasks have very different difficulty levels. Removing objects requires inpainting — plausibly reconstructing the erased area — a task that in 2026 is still the domain of heavy generative models, impractical to run in the browser with good quality on an average device. Removing a background, on the other hand, is a segmentation problem: separating a subject from a background, a task with much lighter models available, runnable entirely via WebAssembly with no server at all.

So I decided to start with background removal only, shelving the object-removal idea for now. The next question was: which library should run a segmentation model entirely client-side, while keeping the zero-cost model I already follow for the rest of the site?

The AGPL license trap

The first library that seemed perfect for the job turned out to be distributed under an AGPL license, a strong copyleft license: free to use, but with a significant catch for anyone embedding it in a public, non-open-source web service. In short, AGPL can require releasing the full source code of the project that embeds it, under the same license — a real constraint for a site that isn't open source. "Free for the end user" and "safe to integrate into a closed-source commercial product" are two different things, and it's better to find that out before writing code, not after deploying.

⚠️

Before integrating any "free" AI library into a commercial project, it's always worth checking the exact license, not just the price. AGPL, GPL, and other strong copyleft licenses are fine for personal or internal use, but risky for a public closed-source product.

So I looked for a permissively licensed alternative (Apache-2.0 or MIT), built on Transformers.js — Hugging Face's library for running machine learning models in the browser on top of ONNX Runtime Web — instead of the proprietary wrapper I'd dropped. Same principle (an ONNX model downloaded once from a CDN, then cached by the browser), clean license.

A light CNN, not a heavy transformer

In choosing the model I followed a lesson useful for anyone working with AI in the browser: architecture matters more than file size. A transformer-based model, even a qualitatively excellent one with a permissive license, can crash from memory exhaustion during WASM inference on many machines — transformer attention consumes memory that grows quickly with the number of image "patches," and at typical photo resolution the intermediate tensors become huge. A smaller CNN model, with an architecture designed for segmentation (in my case from the IS-Net family), runs far more reliably in WASM instead.

The model chosen as the default is therefore a lightweight CNN with an Apache-2.0 license, downloading just a few MB and producing an RGBA image with a transparency alpha channel as output.

LayerChoice
RuntimeTransformers.js on top of ONNX Runtime Web (WASM)
Default modelLight CNN, Apache-2.0 license, optimized for people
Optional modelHeavier transformer, MIT license, more general-purpose
Processing100% local, no image upload
CacheBrowser + service worker, one-time download

The "raw matte": why a color halo remains at the edges

The model often leaves a residual color halo in the transparent area, because the output is a soft alpha matte rather than a clean binary mask: background pixels can carry residual alpha values of 5-15% instead of 0%. The fix doesn't require another model, just simple post-processing of the alpha curve: low values forced to full transparency, high values forced to full opacity, with a smoothstep transition in between to preserve soft edges (hair, fine detail). On a test image, this approach turned over 60% of pixels fully transparent and over 35% fully opaque, leaving only a small fraction in a mid-range band — a clean result.

The real limit: a specialized model isn't a general-purpose model

The first tests with real images showed disappointing results on two cases: a flat illustration (where the model only removed the margin around the figure, leaving the whole drawing opaque) and a photo taken in a dim, cluttered room (where the model nearly erased the main subject too). To isolate the cause, I temporarily added a third diagnostic panel showing the model's raw mask before any alpha cleanup, comparing it against the final result.

The comparison made the cause immediately clear: the raw mask and the final result were nearly identical, so the problem wasn't in my post-processing — it was upstream, in the model itself. Checking the model card confirmed it: the default model turned out to be trained specifically on a human segmentation dataset — not a general-purpose model for any subject. On a well-lit, close-up portrait the result is indeed clean, hair included; on complex scenes or ones far removed from the training dataset (illustrations, dark photos, cluttered framing), quality drops in a predictable way.

💡

General lesson: a "light and WASM-reliable" model is often exactly that because it's trained on a narrow domain. Before judging a model "poor," it's worth checking what it was actually trained on — the problem may be domain mismatch, not quality.

A superior model, optional, not default

Instead of replacing the light model with a heavier, more general one for everyone — making everyone pay the load-time cost even for a simple use case — I added a second model, an MIT-licensed transformer better suited to generic scenes, available only on request: after the first result, a "Try the superior model" button appears that reprocesses the same image with the heavier model, without overwriting the previous result until processing completes successfully. If the heavier model fails from insufficient memory, the first result stays intact and the user sees a clear error message instead of a broken tool.

This trade-off — light by default, heavy on explicit request — avoids penalizing most users with a large download they'll never use.

The real bug: the main thread freezes during computation

Moving to the heavier model surfaced a much more serious problem than mediocre quality: during processing the entire page stopped responding — not just the tool, but the top navigation links too. The cause is that, by default, the ONNX Runtime Web engine runs WASM computation on the browser's main thread, the same one that handles clicks, scrolling, and rendering: during heavy inference that thread stays busy and the whole interface freezes.

The first fix attempt — enabling an internal library option meant to delegate computation to a separate thread — didn't solve the problem, likely due to delicate initialization timing. The reliable fix was to write and control a real dedicated Web Worker myself: a separate thread that loads the library, downloads the model, and runs all the inference, communicating with the main page only through messages (the input image, progress, the final result). This way the main thread never does heavy computation by construction, not by a configuration I hoped would hold.

⚠️

If client-side AI processing freezes the whole interface and not just the component using it, the top suspect is computation running on the main thread. A configuration flag may not be enough: a dedicated, explicitly written Web Worker is the more reliable fix.

Handling a real two-minute wait

Even once the interface freeze was fixed, a perception problem remained: the heavier model takes about two minutes to process an image on common hardware, and the library doesn't expose real percentage progress during computation (only during the model download). A bar sitting "full and still" for two minutes signals a broken tool, not a slow one.

The final solution combines two elements: a bar that advances on an estimated curve over roughly two and a half minutes, with elapsed seconds shown below, and — if processing runs past that estimate — an automatic switch to an indeterminate animation (a continuously scrolling stripe), the universal signal for "still working" without faking a percentage the model doesn't provide. A wider safety timeout still unlocks the interface if something genuinely goes wrong, letting the person retry.

When browser-based background removal makes sense (and when it doesn't)

When to use it

  • Privacy-first by design: the image never leaves the user's device, relevant for personal photos or confidential material that shouldn't be uploaded to a third-party server.
  • Zero recurring server costs: inference happens entirely client-side with ONNX Runtime Web, with no cloud GPU to pay for per processed image.
  • Standard subject photos (people, common objects) where a lightweight specialized CNN model gives good enough results for most uses.
  • Commercial-safe license required: an Apache-2.0 or MIT model allows commercial use with no legal risk, unlike AGPL alternatives.

When not to use it

  • Maximum quality on complex edges is needed (hair, fur, transparency): a lightweight specialized model often leaves a color halo at the edges that a heavier model or manual editing would handle better.
  • Huge batch volume of images to process: client-side processing, while free, is slower than a server-side service optimized for throughput.
  • User devices with limited resources: ONNX inference in the browser requires CPU/memory that on older devices can be slow or blocking without a well-implemented Web Worker.

Alternatives

For those who need maximum quality and have no privacy constraints, a server-side service with heavier models (e.g. Remove.bg) offers better results at the cost of uploading the image and, often, a per-image fee. Within this same tool, an optional more accurate model (MIT instead of the default Apache-2.0) remains available for those who prefer quality over speed.

Common mistakes

  • Choosing a model without checking its license: an AGPL license requires any software using it to also be open source — a common trap for those seeking "the best model" without checking legal terms.
  • Running inference on the main thread: without a dedicated Web Worker, the model computation blocks the UI during processing, giving the impression the page is frozen.
  • Not handling real wait time with visual feedback: processing that can take up to two minutes on less powerful devices needs an honest progress bar, not just a generic spinner, to avoid users abandoning thinking there's an error.

What I take away from this project

The most useful lesson isn't about a single bug, but about a priority order: check the license before the quality, check a model's training domain before judging its results, and don't trust a library flag for a critical property like "doesn't block the main thread" — if it's critical, verify it with a real test, don't assume it. Managing expectations on the page (a notice about when the tool works best) turned out to be as useful as the code itself: a known, clearly communicated limitation is far less frustrating than an unexplained result.

If you're interested in the rest of the image/PDF cluster built on the same client-side processing principle, I also wrote about real PDF redaction with PDF.js and pdf-lib. And if the topic is the real cost of adding AI to a free tool, I ran the numbers in detail in the article on summarizing PDFs with Gemini Flash-Lite.

Frequently asked questions

Is it safe to remove a photo's background online without uploading it to a server?

Yes, if the tool runs the AI model entirely in the browser via WebAssembly. The model is downloaded once and then cached, but the uploaded image never leaves the device: no upload to any server.

Why aren't all open source browser-AI libraries usable in a commercial project?

Some are distributed under strong copyleft licenses like AGPL, which can require releasing the full source code of the project embedding them. Permissive licenses like Apache-2.0 or MIT don't impose this constraint.

Why can a browser AI model freeze the entire page?

By default WASM computation runs on the main thread, the same one that handles clicks and scrolling. The reliable fix is a dedicated Web Worker, a separate thread that keeps the interface free by construction.

Why does background removal sometimes leave a color halo at the edges?

The model produces a soft alpha matte, not a clean binary mask. Post-processing with thresholds and a smoothstep transition removes the residual halo without needing a heavier model.

Why does a background removal tool work well on one photo and poorly on another?

Many light models are trained on a specific domain, often mostly photos of people. On scenes far removed from the training dataset, quality drops in a predictable way, not because of a bug.