Palette Apply
The deterministic pass that turns AI output into real pixel art
- image
- depth
- image
- name
Here's a sentence the pixel-art community keeps having to say: diffusion models don't make pixel art. They produce soft, full-color, off-grid pixels that look like pixel art from across the room. The real artifact is made afterward, by a deterministic pass - reduce to a limited palette, then dither to fake the shades in between. Palette Apply is that pass, as a ComfyUI node, from the same little pack (ComfyUI-MLUT) as the MLUT LUT nodes.
It's not only for pixel art. Lock an image to 16 colors for a stylized posterize look, push every element in a composited shot onto one palette so it reads as a single grade, or emulate a retro hardware palette. But pixel art is the job this node exists for, so that's where this article leans.
What it does
Drop a swatch - .aco (Photoshop), .act (the old Adobe palette format), .ase (Procreate/aseprite-style), or a palette PNG where every pixel is one color - into the SWATCH/ folder, and Palette Apply remaps every pixel of your image to the nearest swatch color. That's the whole algorithm. The craft is all in how the ties get broken, which is the mode input:
- nearest - hard posterize. The most "styled," the most banded. Instant pixel-art look.
- blue_noise - dithers with a pattern-free blue-noise field before quantizing. Reads as fine natural grain, and it's vectorized so it's fast. The one I'd reach for.
- ordered - a 4×4 Bayer threshold, the structured crosshatch look.
- floyd_steinberg - proper error diffusion, best tone, but serial and CPU-bound, so the slowest. Fine on small images, a drag on big ones.
The dither amplitude auto-scales to how spread out your palette's colors are (it measures the mean distance between neighboring swatches), so a 4-color PICO-8 palette gets chunkier noise than a 256-color one. That's a thoughtful touch you don't have to tune. amount blends the whole result back over the original - 1.0 is full posterize, 0.3 is a subtle "locked to this palette" tint.
The inputs
- category -
SWATCHis the one you care about. - source - set via the Browse… button, which opens a folder tree of your swatches. No typing paths.
- sub - for
.asefiles that hold multiple named palettes; pick a group or leave(default). - mode / amount - covered above.
- max_colors - optional; evenly subsamples a big palette down to N colors before mapping. 0 = use them all.
Outputs: the remapped image and a name STRING with the resolved swatch label - useful if you want the palette choice baked into a saved filename.
Installing
Same as the rest of the pack - no dependencies to speak of, since numpy/torch/Pillow already ship with ComfyUI:
cd ComfyUI/custom_nodes
git clone https://github.com/TrophiHunter/ComfyUI-MLUT
Then restart. There's no extra download step for this node (that gotcha belongs to the MLUT LUT category), but the SWATCH/ folder also ships empty - you bring the palettes. Subfolders to any depth are fine, and the catalog rebuilds itself at startup.
Troubleshooting
- Category dropdown empty - no files in
SWATCH/yet. Add some and restart. - Image unchanged, no error - a palette that failed to load (or a file with no usable colors) makes the node print
[MLUT]to the console and pass the image through untouched. Check the console, not the node. - floyd_steinberg is crawling - it's the serial path. Downscale before the node or switch to
blue_noise, which gets most of the look at a fraction of the cost.
One honest caveat: this pack is a small, quiet project with barely any community chatter around it, so past the README you're mostly on your own. But the node is well-built, and for "make my image actually look pixel-art," it's the cleanest deterministic answer in ComfyUI right now - which is exactly what the "the model can't do it, the post-process can" philosophy demands.
Inputs (14)
| Name | Type | Default | Description |
|---|---|---|---|
| image | IMAGE | — | |
| category | COMBO | 1 options: <none> | |
| source | STRING | — | |
| sub | COMBO | 1 options: (default) | |
| mode | COMBO | 4 options: nearest, blue_noise, ordered, floyd_steinberg | |
| amount | FLOAT | 1.000–1 | — |
| max_colorsopt | INT | 00–1024 | — |
| depthopt | IMAGE | — | |
| invert_depthopt | BOOLEAN | true | — |
| focus_distanceopt | FLOAT | 0.500–1 | — |
| fade_nearopt | FLOAT | 0.200–1 | — |
| fade_faropt | FLOAT | 0.200–1 | — |
| blend_power_nearopt | FLOAT | 1.000.01–5 | — |
| blend_power_faropt | FLOAT | 1.000.01–5 | — |
Outputs (2)
| Name | Type | Description |
|---|---|---|
| image | IMAGE | — |
| name | STRING | — |