Pixel Art Scaler (Edge-Aware Auto-Scale)
The Node That Undoes Smudgy Upscales
- image
- pixel_art_image
- manifest
First, a correction: the name is a lie. The "Pixel Art Scaler (Edge-Aware Auto-Scale)" doesn't scale anything up. It takes an image and downscales it to its native pixel-art resolution - the anti-upscaler, the "un-faker" in the pack's title. If you've ever run ESRGAN on a sprite and watched crisp pixels melt into blurry soup, you know the problem. This node runs the other direction: it finds the true pixel grid hiding inside a smeared image, then rebuilds clean, hard-edged pixels from it. It's a port of the JS library Unfake, which itself follows Jenissimo's "How to tame pixel art" writeup (Russian, worth a read if you like the math).
Why you'd reach for it
Pixel art upscaling is a category of its own, and generic restoration models are the wrong tool for it - they want to add smooth detail that pixel art structurally doesn't have. The community keeps re-learning this the hard way (see the "train a pixel art upscale model" thread in r/StableDiffusion, where the generic RealESRGAN path falls apart on sprites). Unfake-Pixels is the reverse operation: drop in a generated image, a scanned sprite, or an AI render that's been blown up with a smoothing filter, and it figures out the pixel size, aligns the grid, crunches the palette, and hands you back something that reads as intentional pixel art.
How it works
The pipeline, straight from the source:
- Edge-aware scale detection. The image goes grayscale, gets split into an N×N tile grid (
ea_tile_grid_size, default 3), and the most detail-rich tiles are kept. A Sobel filter builds edge profiles along each axis, peaks get detected (min distanceea_min_peak_distance, min prominenceea_peak_prominence_factor), and the distances between peaks are tallied. The final scale is the GCD of the most common distances - the clever bit, because if sprites were upscaled unevenly, real pixel boundaries still land on multiples of the true size. - Optimal crop. It slides the grid to find the offset that best aligns with the detected edges, so pixels land where they belong instead of splitting a sprite across two cells.
- Quantization. KMeans squeezes the palette down to
max_colors(default 16). - Downscale. Each cell is replaced by its dominant color (or
nearestif you pick that). - Jaggy cleanup (on by default) smooths stray pixels.
All of it runs on CPU via numpy/PIL/scipy - no GPU, no models, no API key.
The inputs that matter
You mostly touch three things:
max_colors- how small the palette gets. Lower reads more aggressively retro; set it to 256 and quantization is skipped entirely.cleanup_jaggies- leave it on for sprites; it replaces pixels that clash hard with their neighbors.downscale_method-dominant(default) picks the most common color per cell;nearestjust samples. Dominant is usually the look you want.
The three ea_* knobs tune the scale detector; the defaults handle normal sprites fine, so don't touch them until detection goes wrong. scale_detection_method currently only offers edge_aware, so ignore it.
The outputs are the key bit: pixel_art_image (wire it to a Save Image) and manifest - a JSON string with the detected scale, crop offset, color counts and timing. Drop that into a ShowText node; when the result looks wrong, it's the first place to look.
Installing
The README's way:
cd <your-comfy-dir>/custom_nodes
git clone https://github.com/tauraloke/ComfyUI-Unfake-Pixels.git
Restart ComfyUI and hard-refresh the browser. ComfyUI Manager can do it too - search "Unfake" or "Pixel Art Scaler". There are no model downloads. The catch: dependencies live in pyproject.toml (numpy, pillow, scikit-learn, scipy, torch), not a requirements.txt, so if the node errors on import, install what's missing into your venv:
pip install scikit-learn scipy
Where people get burned
The big one: the output is smaller than the input. You pixelized it; now it's tiny. That's correct behavior - upscale it back with an ImageScale node using nearest-neighbor, or save the sprite as-is for game assets. Other real gotchas from the code: it takes a single image, not a batch (a batched tensor throws a shape error); it's CPU-bound, so KMeans on a 2K render takes a while; and if you feed it a photo with no pixel grid, the detector can return scale 1 and you'll just get a quantized mush. Check the manifest - a detected scale of 1 means the node decided there was nothing to unfake.
Inputs (8)
| Name | Type | Default | Description |
|---|---|---|---|
| image | IMAGE | — | |
| max_colors | INT | 162–256 | — |
| cleanup_jaggies | BOOLEAN | true | — |
| downscale_method | COMBO | dominant | 2 options: dominant, nearest |
| scale_detection_method | COMBO | edge_aware | 1 options: edge_aware |
| ea_tile_grid_size | INT | 31–10 | Размер сетки для выбора информативных тайлов (NxN). |
| ea_min_peak_distance | INT | 51–50 | Минимальное расстояние между пиками в профиле. |
| ea_peak_prominence_factor | FLOAT | 0.100.01–1 | Минимальная 'поминка' пика как доля от макс. значения профиля. |
Outputs (2)
| Name | Type | Description |
|---|---|---|
| pixel_art_image | IMAGE | — |
| manifest | STRING | — |