Unfake Pixel Art (AI to Real)
Your AI 'pixel art' is fake. This node makes it real
- image
- pixel_art_image
- manifest
- mask
- upscaled_image
Every pixel-art model on Civitai has the same dirty secret: it doesn't output pixel art. It outputs an image that looks like pixel art from a distance - off-grid, blurry, and carrying hundreds of near-duplicate colors from compression and hallucinated shading. Squint and it's fine. Zoom in, or try to drop it into a game engine, and the grid falls apart. Unfake Pixel Art (AI to Real) is the fix: a postprocessing node that detects the real pseudo-pixel grid hiding in that mush, snaps it into true alignment, and quantizes it down to an honest, retro palette.
This is a ComfyUI port of jenissimo's unfake.js, which blew up on r/StableDiffusion in mid-2025 (+600 votes, 116 comments) for a reason - the demo genuinely makes AI garbage into usable sprites. Tauraloke's ComfyUI-Unfake-Pixels did the first rough port; this pack (CalaKuad1) is the more complete rewrite, adding background removal, exact target sizing, and an in-node preview. No model files, no API, no key - it's pure math running on CPU.
How it works
The node runs a fixed pipeline, and understanding the order explains most of your troubleshooting:
- Background removal (if enabled) - takes the most common corner color, and using connected components deletes only the exterior region, not matching colors trapped inside the subject. It's heuristic, not a BiRefNet-style model, so it's free and instant but blunt.
- Scale detection - the clever part. It splits the image into tiles, runs a Sobel edge profile on the most informative ones, and finds the repeating peak distance. That spacing is your pixel size, even when the grid is blurry or upscaled.
- Optimal crop - shifts the crop by the smallest offset that lands every pixel on the detected grid, killing half-pixels.
- Downscale + quantize - reduces the image by the detected scale, then runs K-Means clustering to cap the palette (default 16 colors).
- Jaggy cleanup - replaces isolated noise pixels with their neighbors' average for cleaner lines.
The inputs that matter
You'll touch maybe four of these. The rest have sane defaults.
- target_width / target_height (default 64 each) - set both to your final resolution and the node computes scale, crops, and pads to hit it exactly. Set both to 0 to keep the auto-detected original aspect. Gotcha from the source: it's all-or-nothing - one zero and the pair stops forcing the target.
- manual_scale_factor - override when auto-detection botches it. If your input is 1024 and you want 16, that's a scale of 64. The README is honest that noisy textures like dirt and ores defeat the auto-detector.
- remove_background + background_tolerance - turn this off if your subject touches a corner, or the "background" color shows up inside the art.
- max_colors (2–256) - palette cap; 8–16 is authentic retro, 32+ drifts toward "AI mush" again.
- downscale_method -
nearestis crisp and punchy;dominantaverages each block's most common color, which is gentler on gradients.
The ea_* knobs (ea_tile_grid_size, ea_min_peak_distance, ea_peak_prominence_factor) tune the edge detection. Leave them alone until you've confirmed a bad detection.
Outputs
pixel_art_image (IMAGE) - your cleaned sprite. Wire it to a Save Image or preview. The node also renders an 8x nearest-neighbor preview (the upscale_preview_factor input) inside the UI so it doesn't look 16×16-tiny in the graph.
manifest (STRING) - a dictionary string with detected_scale, crop_offset, and processing_time_ms. This is your diagnostic: if detected_scale is 1, detection failed and the node bailed to "no downscale," which is why your output still looks blurry.
Installation
ComfyUI Manager is easiest - search "PixelArt Unfaker." Or manually:
cd ComfyUI/custom_nodes
git clone https://github.com/CalaKuad1/ComfyUI-PixelArt-Unfaker
cd ComfyUI-PixelArt-Unfaker
pip install -r requirements.txt
Then restart. The dependency that matters: requirements.txt pulls in scikit-learn and scipy on top of the torch/Pillow/numpy ComfyUI already ships. If the node throws an import error, that's the missing piece. On the portable Windows build use .\python_embeded\python.exe -m pip install -r requirements.txt.
Where people get burned
Auto-detection failing is the big one - check the manifest's detected_scale and reach for manual_scale_factor. Feed it a sprite that's already clean and it can over-quantize into posterized blocks, so set max_colors honestly. And remember the background removal is corner-color logic: on busy scenes just switch it off and do transparency in a proper removal pass later. For a node this niche, it does the one job - turning fake pixel art into something a game engine will actually accept - remarkably well.
Inputs (16)
| Name | Type | Default | Description |
|---|---|---|---|
| image | IMAGE | — | |
| target_width | INT | 640–4096 | Exact output width in art pixels (e.g. 64). 0 = derive it from target_height keeping the aspect ratio. Both 0 = auto-detect the pixel size. |
| target_height | INT | 640–4096 | Exact output height in art pixels (e.g. 64). 0 = derive it from target_width keeping the aspect ratio. Both 0 = auto-detect the pixel size. |
| manual_scale_factor | INT | 00–128 | Size of one art pixel in input pixels (e.g. 16 turns 1024 into 64). 0 = auto-detect. Only used when both target sizes are 0. |
| remove_background | BOOLEAN | true | Make the background transparent. The background color is taken from the image border, and only regions connected to the border are removed. |
| background_tolerance | INT | 100–255 | Maximum per-channel difference from the background color. Raise it for noisy / JPEG backgrounds. |
| max_colors | INT | 160–256 | Palette size. 0 = keep all colors. |
| cleanup_jaggies | BOOLEAN | false | Replace isolated stray pixels with the color of their neighbors (never adds new colors). May remove intentional 1-pixel details. |
| downscale_method | COMBO | nearest | nearest: center pixel of each cell (crisp, fast). dominant: most common color of each cell (best for noisy AI images). |
| scale_detection_method | COMBO | edge_aware | 1 options: edge_aware |
| ea_tile_grid_size | INT | 31–10 | Auto-detection: the image is split into NxN tiles and the most detailed half is analyzed. |
| ea_min_peak_distance | INT | 51–50 | Auto-detection: minimum distance between edges. Lower it if the art pixels are smaller than this. |
| ea_peak_prominence_factor | FLOAT | 0.100.01–1 | Auto-detection: minimum edge strength, relative to the strongest edge. |
| upscale_preview_factor | INT | 81–32 | Nearest-neighbor upscale for the node preview and the upscaled_image output (capped so the largest side stays under 4096px). |
| fit_mode | COMBO | crop | When the input aspect ratio differs from the target size. crop: fill the target and cut the excess. pad: fit inside and add transparent borders. stretch: distort to fill. |
| merge_similar_colors | INT | 240–100 | Merge palette colors closer than this RGB distance. Removes the near-identical shades that AI noise creates (speckled areas). Lower it if you lose subtle shading; 0 = off. |
Outputs (4)
| Name | Type | Description |
|---|---|---|
| pixel_art_image | IMAGE | Real-size pixel art. RGBA when the background is removed or padding is added, RGB otherwise. |
| manifest | STRING | JSON with the detected pixel size, grid offset, output size and palette. |
| mask | MASK | ComfyUI-style mask of the transparent area (1 = transparent), as returned by Load Image. |
| upscaled_image | IMAGE | The pixel art upscaled with nearest neighbor, ready to view or save. |