Nodes/ComfyUI-spritefusion-pixel-snapper/Sprite Fusion Pixel Snapper
ComfyUI Node

Sprite Fusion Pixel Snapper

Give Messy AI Pixel Art a Grid and a Palette It Can Actually Use

By x0x0b·Created 9 months ago·Updated 3 months ago· 25
Sprite Fusion Pixel Snapper
  • image
  • IMAGE
k_colors16
k_seed42
output_scale1
max_kmeans_iterations15
peak_threshold_multiplier0.20
peak_distance_filter4
walker_search_window_ratio0.35
walker_min_search_window2.0
walker_strength_threshold0.50
min_cuts_per_axis4
fallback_target_segments64
max_step_ratio1.80
pixel_size0.0

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:

  1. Palette quantization. K-Means++ collapses the image down to k_colors colors (default 16), seeded by k_seed so results are reproducible.
  2. 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.
  3. 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).
  4. 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.

Categoryimage/transform

Inputs (14)

NameTypeDefaultDescription
imageIMAGEIMAGE tensor (batch supported). Returns list of images.
k_colorsINT161–512Palette size for color quantization (K-Means)
k_seedINT420–2147483647Random seed used when seeding K-Means++ centroids
output_scaleoptINT11–16Integer upscaling factor (nearest-neighbor) applied after snapping
max_kmeans_iterationsoptINT151–200Upper limit on K-Means iterations while learning the palette
peak_threshold_multiplieroptFLOAT0.200–5Fraction of max gradient used as threshold to keep profile peaks
peak_distance_filteroptINT41–512Minimum pixel spacing between retained peaks in the profile
walker_search_window_ratiooptFLOAT0.350.01–5Search window size as a fraction of estimated step when walking cuts
walker_min_search_windowoptFLOAT2.00.1–32Minimum search window (pixels) around each target cut
walker_strength_thresholdoptFLOAT0.500–5Peak must exceed mean*threshold; otherwise fallback to uniform cut
min_cuts_per_axisoptINT42–512Lowest number of cut positions per axis (including ends)
fallback_target_segmentsoptINT641–2048Target number of cells when step detection fails; derives fallback step
max_step_ratiooptFLOAT1.801–5Max allowed X/Y step ratio before snapping to a uniform grid
pixel_sizeoptFLOAT0.00–10000Override detected pixel size in input pixels; 0 keeps auto-detection

Outputs (1)

NameTypeDescription
IMAGEIMAGE