# Pixel Snapper

> Recover the true low-resolution pixel grid from upscaled or AI-generated fake pixel art PNGs; palette-quantize and slice known-layout sheets.

- Skill: `kyh/pixel-snapper` (Agent Skill, multi-file: 8 files)
- Install (CLI): `npx skillmds@latest add kyh/pixel-snapper`
- Raw SKILL.md: https://api.skillmd.com/api/skills/kyh/pixel-snapper/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: DevOps & Infra
- Author: kyh (https://skillmd.com/u/kyh)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/kyh/pixel-snapper

---


# Pixel Snapper

Recover the underlying low-resolution pixel grid from images that _look_ like pixel art but are stored at high resolution with anti-aliased/smudged edges (e.g. a 1024×1024 AI-generated character that conceptually has ~100×100 chunky pixels).

`scripts/pixel-snapper.mjs` is a Node port of an MIT-licensed Rust implementation (see `references/credits.md`), dimensionally identical to upstream. `scripts/pixel-snapper-sheet.mjs` is a known-layout spritesheet helper: crops frames, snaps them together as one strip so every frame shares a single pixel grid, reassembles.

## Discover, Don't Resize

Naive downscale (Lanczos/bilinear/nearest) averages neighbors → blur or aliasing. Snapping instead _discovers_ where the conceptual pixel boundaries already exist and snaps to them. **Output resolution is a property of the input, not a parameter.**

**Before running, ask**:

- Is this genuinely upscaled/AI-faked pixel art, or a photo / continuous-tone illustration? (Snapping only fits the former.)
- Palette complexity? Bright cartoony art tolerates `--k-colors 256`; pre-quantized retro palettes may want a smaller `k` (16, 32, 64).
- Native snapped output, or a nearest-neighbour upscale for inspection? Usually you want both.
- Are the cells actually square? The snapper assumes one shared cell pitch for both axes.

**Core principles**: output resolution is discovered, not specified · `k_colors` is the only user-facing knob (other tunables live in `DEFAULT_SNAP_CONFIG`) · always inspect the output by eye — dimensions are a sanity check, not a quality check · keep the source PNG so you can re-snap with a different `k_colors` later · **key the background out before snapping** — a wide flat matte gives the walker nothing but its own edges to lock onto, and the recovered "grid" collapses to a handful of pixels · **snapping needs resolution to find the grid — feed it a large source (≥~512px across the subject; a lone character ~1024²). Undersized inputs (256²) blur the grid away or trip the 64×64 fallback; the fix is to regenerate larger, not to upscale a small source first.**

## When to Use

Use when the user has AI-generated "pixel art" (gpt-image, retro-diffusion, etc.) and wants a cleaner/smaller/palette-quantized version, needs a high-res mockup converted to a true pixel-art asset, wants to recover an upscaled retro asset's grid, or mentions "snap to pixel grid", "fake pixel art", "downsample to native res", or the Hugo-Dz repo.

Skip for: photographs / continuous-tone / vector art (no grid to recover); already-native pixel art (would just round-trip, possibly losing detail); spritesheet _layout_ recovery where rows/cols are unknown (probe first — `pixel-snapper-sheet.mjs` expects known `--cols`/`--rows`).

## Quick Start

Nothing to install — the scripts import a bundled `scripts/_lib/asset-tools.mjs` and need only Node:

```bash
# This skill's directory. Claude Code substitutes CLAUDE_SKILL_DIR (project, global
# or plugin install); other agents fall back to wherever `skills add` put it.
SKILL="${CLAUDE_SKILL_DIR}"
[ -d "$SKILL" ] || for d in .agents/skills .claude/skills ~/.agents/skills ~/.claude/skills; do
  [ -d "$d/pixel-snapper" ] && SKILL=$d/pixel-snapper && break
done
```

```bash
node $SKILL/scripts/pixel-snapper.mjs input.png output.png --k-colors 256
```

After `chmod +x`, the shebang `#!/usr/bin/env node` lets you call it directly:

```bash
$SKILL/scripts/pixel-snapper.mjs input.png output.png --k-colors 256
```

Output is one snapped PNG at the discovered native resolution. For inspection, follow up with an integer nearest-neighbour upscale — nearest at an integer factor keeps every pixel a hard square, so you judge the recovered art rather than a resampler's smoothing:

```bash
node $SKILL/scripts/image-util.mjs upscale snapped.png snapped-x8.png --factor 8
```

For a known-layout spritesheet, snap every frame to one shared pixel grid:

```bash
node $SKILL/scripts/pixel-snapper-sheet.mjs \
  sheet.png sheet-snapped.png --cols 6 --rows 1 --k-colors 256
```

See `references/usage-examples.md` for batch processing and verification recipes.

## Workflow

1. **Identify the source** — confirm a buried pixel-art design, not a photo/painting.
2. **Pick `k_colors`** — start 256 for AI renders; for quantized retro art try 16, 32, 64 ascending until detail survives without noise.
3. **Run** — sanity-check printed dims against expectation (a "32×32 sprite" snaps near 32×32, not 5×5 or 800×800).
4. **Inspect** the `iw*8` nearest-neighbour upscale.
5. **Iterate** — common fixes: different `k_colors` (halve/double); non-square cells (snapper picks the smaller pitch → may need pre-resize); exactly 64×64 output = step-detection fallback fired (input may lack detectable pixel structure). The script prints a warning for both collapse modes and `--strict` turns them into exit 1, so a batch can stop instead of writing a 2×2 file that looks like a success.
6. **Save to `experiments/`**, never directly into `public/assets/`. Promote only after visual approval.

## Anti-Patterns

DO NOT treat snapping as a mandatory cleanup step for every asset — use it only when the input has a recoverable pixel grid.

- **Generic downscaler** — on a photo it produces a low-color mess. Use Lanczos/bicubic for continuous images.
- **Default `k_colors=16` on vibrant AI renders** — crushes detail. Default to 256 for AI sources; drop only if output looks noisy.
- **Trusting dimensions as the only quality check** — a snapped 100×100 can be missing limbs/fingers/weapon edges. Always view the nearest-neighbour upscale side-by-side with the source.
- **Trying to set output resolution** — there's no `--width`/`--height` by design. Snap first (recover native), then nearest-neighbour upscale to a multiple; don't snap-and-resize in one step.
- **Running on already-snapped outputs** — re-snapping round-trips k-means and loses data. Always snap from the original source; keep snapped outputs as terminal artifacts.
- **Dropping snapped output straight into `public/assets/`** — save to `experiments/<timestamp>-pixel-snapper-<subject>/`, promote after review.

## Variation Guidance

Don't run the same parameters on every input.

- **Match `k_colors` to palette complexity** — a 16-color NES-style file doesn't need 256; an AI render with smooth shading may lose definition at 16.
- **Inspect at multiple zooms** — native for grid sanity, x8 for review, x16 to debug specific pixels.
- **Source vs style** — logo/character/tile may all be "pixel art" but want different `k_colors` (logos low, character medium, tile high).
- **Adapt sheet handling** — single sprites, known-layout sheets, and unknown-layout sheets need different workflows.
- **Don't chain runs** — one per source PNG; if bad, change `k_colors` and re-run from source.

## References

- `references/algorithm.md` — pipeline walkthrough (quantize → profile → step-size → walk → resample)
- `references/credits.md` — MIT license + attribution
- `references/usage-examples.md` — invocation patterns + inspection recipes

The snapper does one thing well: recover a hidden pixel grid. It's not a general downscaler, asset cleaner, or palette converter for arbitrary art. When the input fits — upscaled/AI-faked pixel art — it produces what a human pixel artist would have drawn. When it doesn't, no parameter tuning will save you; use a different tool.

