🎨PixelArt Palette Converter
Recolor any image into NES or Game Boy palette — and actually make it look good
- images
- paletteList
- IMAGE
This is the flagship node of the ComfyUI PixelArt Detector pack: take any image, drop it onto a retro palette, and make the result actually look like it was drawn for a 1989 console instead of just "tinted with a color filter." A naive palette swap maps every pixel to its nearest palette color, which produces posterized noise and muddy bands. The Converter gets around that the way real games did - with color reduction, dithering, and cleanup, and it gives you knobs for all three.
What's under the hood
The node is based on Astropulse's pixeldetector code. The pipeline is: optionally resize → optionally reduce colors (with a choice of clustering algorithms) → swap to the target palette → optionally clean up rare colors → optionally dither. Resizing is smart about order: if your input is smaller than the target, it upscales before pixelizing; if it's bigger, it downscales after quantization. Set both W and H to 0 and resize is skipped entirely.
The palette swap itself uses Pillow's Image.quantize, which maps each pixel to the nearest color in the target palette - and the dither options (floyd-steinberg, or ordered bayer-2/4/8/16) fake intermediate shades with checkerboard patterns so gradients don't band.
The inputs that matter
This node has a lot of inputs, but you can ignore most of them at first. The ones you'll actually set:
- palette - the embedded pick:
NESorGAMEBOY. If you want the 50+ lospec palettes, that's thepaletteListinput from the Palette Loader or Palette Generator instead. - paletteList (optional) - a LIST of palettes. When it has more than one entry, the node renders a comparison grid of your image under every palette. Great for picking.
- pixelize - the swap algorithm.
Image.quantizeis the fast default;Grid.pixelatescans pixel-by-pixel and is slow;NP.quantizeis a NumPy-based variant that's also slower. - reduce_colors_before_palette_swap - off by default. Flip it on to cut the image to
reduce_colors_max_colorsbefore the palette swap, which gives the final result far less banding. This is the biggest quality lever in the node. - reduce_colors_method - which clustering does the reduction.
Image.quantize(fast),OpenCV.kmeans(slow but picks good centers, withRANDOM_CENTERSvs the more consistentPP_CENTERS), or the pyclusteringkmeans/kmedians. - dither -
noneor one of the ordered/Floyd-Steinberg options. A little bayer dither is what sells the "Game Boy photo" look. - cleanup_colors + cleanup_pixels_threshold - after swapping, deletes colors that cover less than the threshold fraction of the image. Good values are 0.01–0.05; lower threshold = more colors kept.
There's also image_quantize_reduce_method (MAXCOVERAGE is the author's recommendation for pixel art) and a pile of OpenCV/pyclustering tuning knobs. Leave those alone until you're chasing a specific look.
Output is a single IMAGE list - wire it to a Preview or Save node. Hook reduce_colors_before_palette_swap on, set the palette, pick a small bayer dither, and you're 80% of the way to a convincing conversion.
Installing the pack
This node ships in dimtoneff/ComfyUI-PixelArt-Detector. Install via ComfyUI Manager (search "ComfyUI PixelArt Detector") or:
cd ComfyUI/custom_nodes
git clone https://github.com/dimtoneff/ComfyUI-PixelArt-Detector
Restart ComfyUI. On first load it auto-installs opencv-python, scipy, pyclustering, Pillow and numpy. pyclustering is the fragile one - it has no wheels for newer Pythons, so the pack pulls a prebuilt wheel from the author's releases at startup. On x86_64 it just works; on Apple Silicon or unusual Python builds it may fail and you'll need to install it manually.
Gotchas
- Bayer dither on a huge image is slow - it's per-pixel math. Downscale first.
Grid.pixelatewith scan size 1 is brutally slow - it's pixel-by-pixel. The author says increasing the size speeds it up but kills quality; the other two methods are better defaults.- The embedded NES/GAMEBOY palettes only appear if you don't feed a
paletteList. Wire a Loader in and the dropdown is ignored. - Old nodes embedded in workflows won't update in place - delete and re-add from the Image/PixelArt menu.
- The
opencv_settingsandcleanupstring widgets are just help text. Ignore them; the real toggles are the booleans below them.
Inputs (22)
| Name | Type | Default | Description |
|---|---|---|---|
| images | IMAGE | — | |
| palette | COMBO | GAMEBOY | 2 options: NES, GAMEBOY |
| resize_w | INT | 5120–2048 | — |
| resize_h | INT | 5120–2048 | — |
| resize_type | COMBO | contain | 3 options: contain, fit, stretch |
| pixelize | COMBO | Image.quantize | 3 options: Image.quantize, Grid.pixelate, NP.quantize |
| grid_pixelate_grid_scan_size | INT | 21–32 | — |
| reduce_colors_before_palette_swap | BOOLEAN | false | — |
| reduce_colors_method | COMBO | Image.quantize | 4 options: Image.quantize, OpenCV.kmeans.reduce, Pycluster.kmeans.reduce, Pycluster.kmedians.reduce |
| reduce_colors_max_colors | INT | 1281–256 | — |
| apply_pixeldetector_max_colors | BOOLEAN | true | — |
| image_quantize_reduce_method | COMBO | MAXCOVERAGE | 3 options: MAXCOVERAGE, MEDIANCUT, FASTOCTREE |
| opencv_settings | STRING | OpenCV.kmeans: only when reducing is enabled. RANDOM_CENTERS: Fast but doesn't guarantee same labels for the same image. PP_CENTERS: Slow but will yield optimum and consistent results for same input image. attempts: to run criteria_max_iterations so it gets the best labels. Increasing this value will slow down the runtime a lot, but improves the colors! | — |
| opencv_kmeans_centers | COMBO | RANDOM_CENTERS | 2 options: RANDOM_CENTERS, PP_CENTERS |
| opencv_kmeans_attempts | INT | 101–150 | — |
| opencv_criteria_max_iterations | INT | 101–150 | — |
| pycluster_kmeans_metrics | COMBO | EUCLIDEAN_SQUARE | 6 options: EUCLIDEAN, EUCLIDEAN_SQUARE, MANHATTAN, CHEBYSHEV, CANBERRA, CHI_SQUARE |
| cleanup | STRING | Clean up colors: Iterate and eliminate pixels while there was none left covering less than the 'cleanup_pixels_threshold' of the image. Optionally, enable the 'reduce colors' option, which runs before this cleanup. Good cleanup_threshold values: between .01 & .05 | — |
| cleanup_colors | BOOLEAN | false | — |
| cleanup_pixels_threshold | FLOAT | 0.0200.001–1 | — |
| dither | COMBO | 6 options: none, floyd-steinberg, bayer-2, bayer-4, bayer-8, bayer-16 | |
| paletteListopt | LIST | — |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| IMAGE | IMAGE | — |