Recover the original pixel art from upscaled AI-generated images.
AI image generators (NanoBanana, Midjourney, DALL-E, etc.) often output pixel art at high resolution with anti-aliasing artifacts, inconsistent grid alignment, and noisy colors. crispx detects the underlying pixel grid and produces a clean, downscaled version — the pixel art as it should have been.
The pipeline has 7 stages:
-
Edge detection — Combines absolute and locally-normalized Sobel gradients to find pixel boundaries, even in low-contrast regions.
-
Size detection — Projects edge strengths onto each axis and uses autocorrelation (with harmonic detection) to find the grid spacing.
-
Square detection — Slides a window at the detected size across the image, scoring each candidate by boundary edge strength, coherence, and interior cleanliness. Non-maximum suppression removes overlaps.
-
Grid assignment — BFS from a central seed assigns integer grid coordinates to detected squares. Disconnected squares are placed by position.
-
Edge-guided expansion — BFS outward from seeds fills remaining grid cells, snapping to actual edge boundaries where possible and smoothly extrapolating elsewhere.
-
Output rendering — Each grid cell gets the mean color of its source region in the input image.
-
Color reduction — Union-find spatial merge combines adjacent pixels with similar colors, cleaning up averaging artifacts while preserving intentional gradients. Optional median-cut quantization can further reduce the palette.
See ALGORITHM.md for full details.
npm install -g crispx# Single image
crispx input.png
# Custom output path
crispx input.png -o output.png
# Multiple images / glob
crispx images/*.png -o output-dir/
# Process all images in a directory
crispx ./my-images/ -o ./results/
# Save debug visualizations
crispx input.png --debug| Flag | Description | Default |
|---|---|---|
-o, --output <path> |
Output file or directory | <input>_opixel.png |
--merge-threshold <n> |
Color merge aggressiveness (higher = fewer colors) | 50 |
--colors <n> |
Target palette size via median-cut | (no limit) |
--edge-threshold <n> |
Edge detection threshold (0.0–1.0) | (auto) |
--max-size <n> |
Max opixel size to consider | (auto) |
--debug |
Save debug visualizations alongside output | false |
With --debug, additional images are saved next to the output:
*_debug_edges.png— edge map (red = above threshold)*_debug_seeds.png— detected squares overlaid on the original*_debug_coverage.png— detected squares filled with their mean colors
import { crispx } from "crispx";
// Pass image data as a Uint8Array (PNG, JPEG, etc.)
const result = await crispx(imageBytes);
// result.png - Uint8Array of the output PNG
// result.width, result.height - output dimensions
// With options
const result = await crispx(imageBytes, {
mergeThreshold: 50, // Color merge aggressiveness (higher = fewer colors, default: 50)
colors: 32, // Target palette size via median-cut (optional)
edgeThreshold: 0.1, // Edge detection threshold 0.0–1.0 (default: auto)
maxSize: 20, // Max opixel size to consider (default: auto)
});git clone <repo-url>
cd crispx
bun cli.ts --helpThe Rust core builds automatically on first run.
MIT