pixfix

AI image generators make beautiful pixel art that isn't actually pixel art — the "pixels" are misaligned, noisy, and rendered at fractional sizes like 10.667px. This tool detects the real grid and rebuilds the image into a clean, game-ready asset.

$ cargo install pixfix $ pixfix process sprite.png

## The pipeline

Every image runs through the same stages. Each one is skippable and configurable, and everything the pipeline learns is reported back to you.

  1. Grid detection

    The image is reduced to gradient profiles and scanned with a fractional comb — pitch doesn't have to be an integer, so a 48-cell sprite rendered at 512px detects as exactly 10.667px per cell. When confidence is too low to trust, the pipeline says so and leaves your image unsnapped instead of inventing a grid.

  2. Anti-aliasing removal opt-in

    Pixels that sit on the interpolation line between their two dominant neighbors snap to the closer one, over multiple passes for wide AA ramps. A guard protects checkerboard dithering from being eaten.

  3. Background & chroma masking

    Border colors are clustered in OKLAB (gradients and vignettes work), masks are built at full resolution where fringes are one pixel wide, and chroma keys remove their color everywhere. Nothing is cleared yet — the mask rides along so the next stage can't bake halos into blocks.

  4. Grid snapping

    Each block votes on its color with near-duplicates pooled in OKLAB, and the winner paints its most representative actual color — never an invented average. Alpha binarizes per block for clean game-asset edges. Blocks with contested votes are flagged so you can spot a bad grid fast.

  5. Dither detection

    Checkerboard and row/column alternation between color pairs is detected on the logical grid. Dithering is how pixel art fakes gradients — it looks better kept, so detected pairs get pinned through quantization.

  6. Quantization

    Seeded weighted k-means over unique colors — deterministic, exact, and rare structural colors like 1px outlines can't be dropped. Palettes come from extraction, built-ins, .hex files, or Lospec; matching uses hyAB so dark blues become navy, not black.

  7. Output

    Snap mode keeps the original resolution. Reduced modes emit the true logical size or a crisp integer re-upscale — never a stretch back to dimensions that recreate the misalignment you just removed.

## Quick start

Clean up an AI sprite (grid auto-detected):

$ pixfix process sprite.png
Grid: 10.667x10.667 pixels, phase: (0, 0), confidence: 85%, logical: 48x48
Saved: sprite_normalized.png

Inspect before you commit to anything:

$ pixfix analyze sprite.png --json | jq .grid

Reduce to true resolution with a 16-color palette and transparent background:

$ pixfix process sprite.png out.png \
    --downscale-mode majority-vote --logical-size --colors 16 --remove-bg

Key out a green screen (works on interior regions too):

$ pixfix process sprite.png out.png --chroma-key 00FF00

A whole directory in parallel, PICO-8 palette:

$ pixfix batch ./sprites out/ --palette pico-8

## Try it

The same pipeline, compiled to WebAssembly. Everything runs right here in the page — your image is never uploaded anywhere.

drop a sprite here, or click to choose — nothing leaves your browser

## Commands

processNormalize a single image, or an animated GIF with one grid and one palette shared across frames. - pipes through stdin/stdout.
analyzeGrid, background, and color inspection with zero writes.
batchParallel directory/glob processing; collisions detected up front, partial failures itemized.
sheetSplit, normalize, and reassemble sprite sheets — fixed grid or auto-split.
paletteList built-ins, extract from an image, fetch from Lospec.
tuiInteractive terminal editor (build with --features tui).

## The flags that matter

--grid-size 10.667Force a pitch, fractional welcome. --grid-phase X,Y for the offset.
--coarsen 2Multiply the final pitch (detected or forced) by an integer: detection finds the render quantum, but the intended art resolution is often 2x coarser. Phase preserved; no effect when detection declines.
--downscale-modesnap (default, keeps resolution) · majority-vote · center-weighted · center-pixel
--logical-sizeReduced modes: emit true logical resolution instead of the crisp integer re-upscale.
--colors N / --palette / --lospec / --palette-fileMutually exclusive palette sources.
--remove-bgBorder-clustered background removal with de-fringing. --bg-color, --bg-tolerance, --no-flood-fill tune it.
--chroma-key HEXGreen-screen removal, everywhere in the image. Repeatable; --chroma-tolerance tunes the match.
--keep-alphaPreserve per-pixel alpha instead of binarizing blocks opaque/transparent.
--flatten-ditherTurn off dither preservation entirely.
--aa-threshold 0.5Enable AA removal; higher is more aggressive. --aa-passes for wide ramps.
--seed NDeterminism: same input + seed → identical bytes.

Full reference: pixfix help process or the README. Config files (.pixfix.toml) fill in anything the CLI doesn't set.

## For scripts & agents

The CLI is built to be driven blind. --json puts exactly one machine-readable document on stdout — stable schema, fields always present — and keeps logs on stderr. Exit codes are distinct, outputs are no-clobber by default, and llms.txt / llms-full.txt carry this whole manual in plain text.

$ pixfix process sprite.png out.png --json
{
  "grid": { "pitch_x": 10.667, "phase": [0.0, 0.0], "confidence": 0.85, "source": "detected" },
  "logical_size": [48, 48],
  "colors_after": 16,
  "low_confidence_blocks": 3,
  "warnings": [],
  "duration_ms": 412
}
0success4processing failed
2usage or config error5output exists (pass --overwrite)
3input unreadable6batch partially failed

## pixfix, the desktop app

The same pipeline with live before/after previews: drop an image in, scrub the settings, watch it re-process. Grid scores, contested blocks, and color histograms are right there in the diagnostics pane. Bundles ship for macOS, Linux, and Windows on every release.

Grab it from the releases page.