Nodes/ComfyUI-Unfake-Pixels/Pixel Art Scaler (Edge-Aware Auto-Scale)
ComfyUI Node

Pixel Art Scaler (Edge-Aware Auto-Scale)

The Node That Undoes Smudgy Upscales

By tauraloke·Created about a year ago·Updated about a year ago· 51
Pixel Art Scaler (Edge-Aware Auto-Scale)
  • image
  • pixel_art_image
  • manifest
max_colors16
cleanup_jaggiestrue
downscale_methoddominant
scale_detection_methodedge_aware
ea_tile_grid_size3
ea_min_peak_distance5
ea_peak_prominence_factor0.10

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:

  1. 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 distance ea_min_peak_distance, min prominence ea_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.
  2. 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.
  3. Quantization. KMeans squeezes the palette down to max_colors (default 16).
  4. Downscale. Each cell is replaced by its dominant color (or nearest if you pick that).
  5. 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; nearest just 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.

Categoryimage/postprocessing

Inputs (8)

NameTypeDefaultDescription
imageIMAGE
max_colorsINT162–256
cleanup_jaggiesBOOLEANtrue
downscale_methodCOMBOdominant2 options: dominant, nearest
scale_detection_methodCOMBOedge_aware1 options: edge_aware
ea_tile_grid_sizeINT31–10Размер сетки для выбора информативных тайлов (NxN).
ea_min_peak_distanceINT51–50Минимальное расстояние между пиками в профиле.
ea_peak_prominence_factorFLOAT0.100.01–1Минимальная 'поминка' пика как доля от макс. значения профиля.

Outputs (2)

NameTypeDescription
pixel_art_imageIMAGE
manifestSTRING