Sprite Fusion Pixel Snapper
Give Messy AI Pixel Art a Grid and a Palette It Can Actually Use
- image
- IMAGE
If you've ever generated "pixel art" with a diffusion model you know the dirty secret: it isn't. The pixels are fake. Line widths drift, "pixels" are 3x4 instead of 3x3, edges are anti-aliased mush, and the palette has 400 colors where a sprite needs 16. Sprite Fusion Pixel Snapper is the cleanup step - it detects the actual grid hiding in your image, snaps every cell to its dominant color, and hands you back something that would actually tile as a game sprite.
It's a Python port of Hugo Duprez's Rust tool of the same name, dropped into ComfyUI as a single node in the image/transform category. No API, no key, no model download - it's plain image processing running locally. Think of it as a post-processor in the same drawer as the WAS Node Suite utilities, except built for exactly one job and doing it well.
How it works
Under the hood it's a four-stage pipeline, and knowing the order helps you debug it:
- Palette quantization. K-Means++ collapses the image down to
k_colorscolors (default 16), seeded byk_seedso results are reproducible. - Grid detection. It converts to grayscale, builds column and row gradient profiles, then finds the peaks - the boundaries between "pixels" - and estimates the step size from the median peak spacing.
- Snapping. It walks each axis placing cuts at the strongest gradient near each estimated step, and falls back to a uniform grid when the peaks are too weak or the X/Y steps are too skewed (
max_step_ratio, default 1.8). - Resampling. Each cell is replaced by its most frequent color - nearest-neighbor resampling, so no smoothing, no alpha blend, just hard pixels.
Then output_scale optionally nearest-neighbor-upscales the result by up to 16x, which is handy since a snapped sprite is often small.
The inputs that matter
Honestly, you'll touch three of these: image, k_colors, and k_seed. k_colors is your palette size - 16 is a good default for classic sprite work; push toward 32–64 for higher-res "pixel" styles. k_seed seeds the K-Means++ centroid init; bump it if the palette looks off, since different seeds land on different palettes. pixel_size (default 0) overrides auto-detection when the algorithm guesses wrong - set it to your true pixel size, but it must be between 1 and half the smallest image dimension or the node raises an error. output_scale is worth knowing: set it to 4 if you want a chunky upscale in one go.
The rest - max_kmeans_iterations, the peak_* and walker_* knobs, min_cuts_per_axis, fallback_target_segments - are tuning parameters you'll rarely need. When auto-detection fails, reaching for pixel_size beats fighting ten sliders.
The gotcha that will bite you
The output is a list of IMAGEs, not a batched IMAGE. The node sets OUTPUT_IS_LIST = True specifically so each frame keeps its own size. So if you feed a batch or an animation, you can't just wire the result straight into a Save Image that expects one tensor - route it through a "List of Images → Batch" node (from WAS or Impact Pack) or save frames individually. This trips up basically everyone on first use.
It also handles both channel-last and channel-first tensors and silently drops alpha if your input has 4 channels, so transparent sprites will come out opaque.
Install
Via ComfyUI Manager, search "Sprite Fusion Pixel Snapper", or:
cd ComfyUI/custom_nodes
git clone https://github.com/x0x0b/ComfyUI-spritefusion-pixel-snapper
# restart ComfyUI
That's it. The only declared dependencies are numpy and torch, which ComfyUI already ships, so there's nothing to download and nothing that can break your environment. Note this is a port, not a byte-for-byte wrapper of the Rust original - results can differ slightly from upstream. For a tool this niche that's a fair trade for not needing a Rust toolchain.
It's new, it's tiny, and community word-of-mouth is basically nonexistent yet, so if the grid comes out wrong, your first move is pixel_size - or accept the imperfection. It's a one-node cleanup pass, and for that it does the job no upscaler will.
Inputs (14)
| Name | Type | Default | Description |
|---|---|---|---|
| image | IMAGE | IMAGE tensor (batch supported). Returns list of images. | |
| k_colors | INT | 161–512 | Palette size for color quantization (K-Means) |
| k_seed | INT | 420–2147483647 | Random seed used when seeding K-Means++ centroids |
| output_scaleopt | INT | 11–16 | Integer upscaling factor (nearest-neighbor) applied after snapping |
| max_kmeans_iterationsopt | INT | 151–200 | Upper limit on K-Means iterations while learning the palette |
| peak_threshold_multiplieropt | FLOAT | 0.200–5 | Fraction of max gradient used as threshold to keep profile peaks |
| peak_distance_filteropt | INT | 41–512 | Minimum pixel spacing between retained peaks in the profile |
| walker_search_window_ratioopt | FLOAT | 0.350.01–5 | Search window size as a fraction of estimated step when walking cuts |
| walker_min_search_windowopt | FLOAT | 2.00.1–32 | Minimum search window (pixels) around each target cut |
| walker_strength_thresholdopt | FLOAT | 0.500–5 | Peak must exceed mean*threshold; otherwise fallback to uniform cut |
| min_cuts_per_axisopt | INT | 42–512 | Lowest number of cut positions per axis (including ends) |
| fallback_target_segmentsopt | INT | 641–2048 | Target number of cells when step detection fails; derives fallback step |
| max_step_ratioopt | FLOAT | 1.801–5 | Max allowed X/Y step ratio before snapping to a uniform grid |
| pixel_sizeopt | FLOAT | 0.00–10000 | Override detected pixel size in input pixels; 0 keeps auto-detection |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| IMAGE | IMAGE | — |