# pixfix — complete plain-text manual Rust CLI + desktop app (pixfix) that turns AI-generated "pixel art" — where the apparent pixels are misaligned, noisy, and rendered at fractional sizes — into clean, grid-aligned game assets. Install: cargo install pixfix Binary releases: https://github.com/lovelaced/pixfix/releases Browser demo: https://lovelaced.github.io/pixfix/#demo — the pipeline compiled to WebAssembly, entirely client-side (images never leave the page). ## PIPELINE (stage order) 1. Grid detection — estimates pitch (pixels per logical pixel, may be fractional, e.g. 10.667) and phase per axis from gradient profiles. Below a confidence floor it declines to snap and reports a best guess. Overrides: --grid-size (fractional ok), --grid-phase X,Y. 2. AA removal (opt-in via --aa-threshold) — snaps interpolation-artifact pixels to their dominant neighbor, multi-pass, with a dither guard. 3. Background/chroma mask — border-clustered background detection and/or global chroma keys build a transparency mask at full resolution. 4. Grid normalization — snap mode paints each block its winning color at original resolution; reduced modes emit one pixel per block. Alpha binarizes per block by default. Masked pixels never vote. 5. Mask applied — background/keyed pixels become transparent. 6. Dither detection — alternating color pairs found on the logical grid. 7. Quantization — seeded weighted k-means over unique colors, or snapping to a fixed palette; detected dither pairs are pinned so they survive. 8. Output sizing — snap keeps original dims; reduced modes emit logical size x round(pitch) (crisp integer upscale) or logical size with --logical-size. --target-width/--target-height force exact dims. ## COMMANDS pixfix process [OUTPUT] [flags] Normalize one image. OUTPUT defaults to _normalized. (extension always comes from --output-format, default png — never from the input). INPUT "-" reads stdin; OUTPUT "-" writes PNG to stdout (not combinable with --json). Animated GIFs (frame count > 1) are normalized as one animation: the grid is detected once on the first frame, the background color is resolved once, and one palette is extracted from all frames together, so nothing flickers between frames. Per-frame timing is preserved and the output is always an animated GIF (derived name _normalized.gif; --output-format may only be gif or left unset, anything else exits 2). Static GIFs take the single-image path. pixfix analyze [--sheet] [flags] Inspect without writing anything: grid pitch/phase/confidence, logical size, unique colors, detected background and border coverage. --sheet adds the auto-split sprite count. Honors grid overrides. pixfix batch [flags] INPUT is a directory or glob (extensions match case-insensitively). Parallel; one bad file never aborts the run. Output paths are planned up front and collisions rejected (use --preserve-dirs to keep the input tree, --suffix to change naming, '' allowed). Partial failure exits 6. pixfix sheet [OUTPUT] [flags] Sprite sheets. Fixed grid: --tile-width/--tile-height (+ --spacing, --margin). Auto-split: omit tile dims; tuned by --separator-threshold, --min-sprite-size, --pad. --output-dir also saves individual sprites; --no-normalize splits/reassembles without the pipeline. The sheet-level grid is detected once and rebased per tile. pixfix palette list|extract|fetch list: built-in palettes (pico-8, sweetie-16, endesga-32, endesga-64, gameboy, nes). extract --colors N [-o file.hex] [--overwrite]: k-means medoid palette from an image, sorted, deterministic. fetch [-o file.hex] [--refresh]: download from lospec.com, cached in the platform cache dir. pixfix tui [INPUT] Interactive terminal editor; requires building with --features tui. ## GLOBAL FLAGS --json one JSON result document on stdout; NDJSON progress + logs on stderr. Stable schema; fields always present (null when inapplicable). -q, --quiet nothing but errors. -v/-vv/-vvv tracing verbosity (stderr). RUST_LOG also honored. --config P explicit config file (missing path = error, exit 2). --no-config ignore config files entirely, including cwd discovery. ## PIPELINE FLAGS (process/analyze/batch/sheet) Note: `-h` prints a curated subset; `--help` lists every flag below. Grid: --grid-size N force pitch; fractional accepted (10.667) --grid-phase X,Y force phase (requires --grid-size) --no-grid-detect skip detection (requires --grid-size) --max-grid-candidate N max pitch searched (default scales with image) --min-confidence 0..1 confidence floor (default 0.35) below which detection declines to snap and only reports its best guess; 0 accepts anything. Config key: [grid] min_confidence. --coarsen N multiply the FINAL pitch (auto-detected or from --grid-size) by an integer >= 1. Exact semantics: detection finds the render quantum (the grid the generator painted on); the intended artistic resolution is often a multiple of it. Phase is preserved so coarse blocks align with the fine grid. No effect when detection declines (nothing to multiply). Reported grid values (incl. --json) show the multiplied pitch; grid_best_guess keeps the raw detection. Config key: [grid] coarsen. Downscale: --downscale-mode M snap (default) | center-weighted | majority-vote | center-pixel. snap keeps resolution; others reduce to logical pixels. --logical-size reduced modes: emit logical resolution instead of the default integer re-upscale by round(pitch) --keep-alpha preserve per-pixel alpha instead of binarizing each block to fully opaque/transparent Anti-aliasing: --aa-threshold 0..1 enable AA removal; HIGHER = more aggressive --aa-passes N max passes (default 3), for wide AA ramps Color (sources are mutually exclusive): --palette NAME | --palette-file F.hex | --lospec SLUG | --colors N --no-quantize skip quantization (conflicts with sources) --seed N RNG seed; same input + seed = identical bytes --flatten-dither disable dither detection, pinning, and AA guard Background: --remove-bg / --no-remove-bg border-clustered background removal --bg-color HEX explicit background color (else auto-detected; gradients/vignettes handled by OKLAB clustering) --bg-threshold 0..1 border coverage needed for auto-detect (0.4) --bg-tolerance N OKLAB match tolerance (0.05) --flood-fill / --no-flood-fill border-connected only (default) vs global replacement Chroma keying: --chroma-key HEX remove this color EVERYWHERE (green-screen style, interior regions included). Repeatable for multiple keys. Combines with --remove-bg. --chroma-tolerance N OKLAB match tolerance (0.05) Output: --target-width N / --target-height N exact output dims --output-format png|webp|bmp|gif encoding + derived extension (animated inputs: gif only) --overwrite / --no-overwrite overwrite policy (no-clobber default, everywhere) Diagnostics: --debug-overlay PATH (process only) also write a PNG of the source dimmed with each block tinted red in proportion to how contested its color vote was; a wrong pitch/phase tints the whole frame, while tinting confined to noisy regions means the grid is right. Skipped (with a note) when no grid was applied. Batch only: --suffix S output stem suffix (default _normalized; '' ok) --preserve-dirs recreate input tree under the output dir ## EXIT CODES 0 success 2 usage or config error (clap errors and config problems) 3 input could not be read 4 processing/detection/encoding failed 5 output already exists (pass --overwrite) 6 batch completed with some files failed ## JSON OUTPUT (--json) process/sheet document fields: input, output paths as given grid {pitch_x, pitch_y, phase:[x,y], confidence, source:"detected"|"override"} or null grid_best_guess same shape; set when detection declined to snap logical_size [w,h] or null output_size [w,h] frames frame count: 1 for stills, N for animated GIFs palette "pico-8" | "file:..." | "lospec:..." | "auto:N" | null colors_before, colors_after unique color counts (null if skipped) aa_pixels_changed, aa_passes AA stats bg_removed bool; bg_color "#RRGGBB" or null; bg_pixels_removed count or null dither_pairs [{color_a, color_b, alternating_blocks}] low_confidence_blocks, total_blocks contested-vote stats warnings human-readable strings duration_ms pipeline wall time sheet adds: sprites, tile_size [w,h], sprites_dir. analyze document: input, size, frames, grid, grid_best_guess, logical_size, unique_colors, detected_bg, bg_border_coverage, sheet_sprites, coarsen_candidates, suggested_coarsen, warnings. frames is 1 for stills; animated GIFs are analyzed on their first frame and report the animation's frame count. coarsen_candidates: when a grid is accepted, analyze also snaps at 1x/2x/4x of the detected pitch and reports each factor's contested-block rate ({factor, pitch_x, logical_size, contested_rate}). The detected pitch is the render quantum; a low rate at a multiple means the content also reads cleanly at that coarser artistic resolution. suggested_coarsen: largest factor whose rate stays within 15 percentage points of the 1x baseline and below 50% absolute; null when only 1x qualifies. The choice between qualifying factors is taste — use the published rates. batch document: {summary: {total, succeeded, failed, skipped}, files: [{input, output, status:"ok"|"failed"|"skipped", error}]}. Batch NDJSON progress on stderr: {"event":"batch_started",...} then {"event":"file_done","index":..,"total":..,"input":..,"output":.., "status":..,"error":..} per file. ## CONFIG FILE (.pixfix.toml, cwd-discovered or --config) Precedence: CLI flags > config file > built-in defaults. [grid] size (fractional ok), phase_x, phase_y, max_candidate, coarsen, skip [aa] threshold, skip [quantize] colors, palette, skip [background] enabled, color, border_threshold, color_tolerance, flood_fill, chroma_keys = ["FF00FF", ...], chroma_tolerance [output] overwrite [sheet] separator_threshold, min_sprite_size, pad ## RECIPES Inspect, then process only if a grid was found: pixfix analyze in.png --json | jq -e '.grid != null' \ && pixfix process in.png out.png --json Force a known grid on a low-confidence image: pixfix process in.png out.png --grid-size 10.667 Detected pitch is the render quantum; snap at 2x for era-honest resolution: pixfix process in.png out.png --coarsen 2 True-resolution asset with a fixed palette and transparent background: pixfix process in.png out.png --downscale-mode majority-vote \ --logical-size --palette pico-8 --remove-bg Normalize an animated GIF (one grid + one palette across all frames, timing preserved): pixfix process anim.gif out.gif --colors 16 Green-screen removal (interior regions too), two keys: pixfix process in.png out.png \ --chroma-key 00FF00 --chroma-key FF00FF Reproducible batch with preserved directory structure: pixfix batch "art/**/*.png" out/ --preserve-dirs --seed 7 Pipe through without touching disk: cat in.png | pixfix process - - --quiet > out.png