Turn a sprite-sheet PNG into clean cutouts and a Unity-ready atlas, in one local web UI.
Pipeline: background removal (BiRefNet, sharp edges with shadows preserved) → sprite detection (connected components on alpha) → atlas packing (grouped by native size, power-of-2, TexturePacker JSON Hash format).
python3 -m venv .venv
.venv/bin/pip install -r requirements.txtFirst run downloads the BiRefNet ONNX (~970 MB) into ~/.u2net/.
.venv/bin/python app.pyOpens http://127.0.0.1:7860 — drop a PNG, hit Process, download
cutout.png, atlas.png and atlas.json. All knobs are surfaced in the
collapsible accordions on the left.
# 1. Background removal
.venv/bin/python -m bg_remover.cli input.png -o cutout.png
# 2. Slice + pack atlas from a cutout
.venv/bin/python -m atlas.cli cutout.png -o atlas_out/- BiRefNet-general (SOTA dichotomous segmentation) for the alpha matte.
- rembg's default Gaussian
post_process_maskis disabled — that's the usual source of halo / bavure on edges. - Foreground decontamination via pymatting
estimate_foreground_mlrebuilds the true colour on partial-alpha pixels. Without this, semi- transparent edge pixels keep the original background colour baked in, which shows as a halo when compositing on a different backdrop. This is what protects highlights and glows. - Edge crispening is gated to steep alpha gradients — soft drop shadows are below the threshold and stay intact.
Three sprite-isolation strategies — pick the one that matches your input:
components— connected-component labelling on the alpha. Fast, robust when sprites are visibly separated.watershed— distance-transform peaks seed a watershed segmentation. Splits sprites that touch pixel-to-pixel (game tilesets, composite scenes). Tune--separation-distanceto the expected sprite half-size.grid— uniform cell slicing (--grid-size 64x64). For tilemaps where every cell is a sprite even when cells share borders.
After isolation, every method shares the same packer:
- Bbox is padded by a few px to recapture the full drop shadow.
- Sprites are bucketed by native size (±10 % tolerance). Each bucket is a uniform grid, buckets stack top-down (largest cell first).
- No resize, no rotation, no trim — native sizes are preserved exactly.
- Atlas dimensions are padded to the next power of 2 (
--no-pow2to disable).
The JSON is TexturePacker JSON Hash format. Either:
- Use the free TexturePacker Importer.
- Or load it manually — note Unity uses bottom-left origin, JSON is top-left:
var rect = new Rect(
f.frame.x,
atlas.height - f.frame.y - f.frame.h,
f.frame.w, f.frame.h);
var sprite = Sprite.Create(atlas, rect, new Vector2(f.pivot.x, f.pivot.y));bg_remover/ # background removal (core, CLI)
atlas/ # sprite detection, packer, Unity output, CLI
app.py # Gradio UI
- Drawn-as-dark "holes" inside an object (e.g. a padlock keyhole painted brown) are part of the silhouette to BiRefNet and stay opaque. Pure colour-based hole detection would harm objects of similar colour. A proper fix needs SAM-style point prompts or per-asset colour rules.
- Game tilesets where sprites touch pixel-to-pixel need
--method watershed(or--method gridif it's a uniform tilemap). The defaultcomponentsmethod merges them into single big blobs.