Nodes/Link Comfy Nodes/Pixel Art Studio
ComfyUI Node

Pixel Art Studio

The pixel-art node that knows the model didn't make pixel art — it makes this

By Mister-Link·Created 10 months ago·Updated 6 days ago· 0
Pixel Art Studio
  • frames
  • pixel_art
pixel_size5
kernel_size5
num_colors256
edge_stylehard
alpha_threshold0.58
stability1.00
looptrue
width0
height0

There's a well-earned community consensus on AI pixel art, and it's that diffusion models don't produce it - they produce something that looks like it from across the room: off-grid, blurry, hundreds of colors, inconsistent pixel sizes. Real pixel art is a deterministic post-process: grid-aware downscale, palette quantization, then clean upscaling. Pixel Art Studio is that entire pipeline in one node, and it's built for the case the consensus usually ignores - doing it to a video sequence without the result boiling and flickering frame to frame.

If you've ever rendered something in a pixel-art style and zoomed in to find your "pixels" aren't uniform or your palette is 300 colors, this is the fix. Feed it frames, get back clean, crisp, limited-palette pixel art.

How it works

It's three stages fused, all with temporal stability bolted on:

  1. Block reduction. Each block of pixel_size×pixel_size pixels is collapsed to one color via a per-block winner-take-all vote over 32 color bins - dominant-color voting, not averaging, which is exactly what the KB's pixel-art doctrine says or you get mush. kernel_size (default 5, equal to pixel_size) controls the sampling window: equal means exact tiling, larger overlaps neighboring blocks for a softer, chunkier look.
  2. Global palette. A k-means palette is fit once across all frames on the reduced blocks (256/128/64/48 colors), so the palette can't flicker frame to frame. Every frame's art pixels snap to the nearest palette entry in Oklab, with per-pixel temporal hysteresis so a static pixel doesn't pop a pixel thicker/thinner when its color drifts across a palette boundary.
  3. Output sizing. Optional width/height (0 = input size), resized with nearest-to-integer-multiple + box down so the pixel grid stays uniform at fractional scales.

The video-first thinking shows everywhere: stability (0–1) is temporal hysteresis on the block vote - a block keeps its previous winner unless clearly beaten. And loop does a hidden warm-up pass so looping footage doesn't snap at the last-to-first wrap. Turn loop off for non-looping video.

Inputs that matter

  • frames (IMAGE) - can be a single image or a video batch.
  • pixel_size (default 5) - the chunky-ness. Higher = chunkier.
  • num_colors (256/128/64/48) - the palette budget. 256 keeps it painterly; 48 reads as properly retro.
  • edge_style - hard (default) = crisp sprite edges with binary alpha, matching hand-drawn assets; soft = area-averaged, antialiased look. For sprites, hard.
  • stability (default 1) - video smoothness. 0 disables hysteresis.
  • loop (default true) - seamless loops; disable for non-looping footage.
  • alpha_threshold (default 0.58) - for alpha input; where "50% opaque" sits in the graded coverage.

Output is pixel_art (IMAGE), same batch as input. Frames with an embedded alpha channel come out with one.

Installing it

Part of Link Comfy Nodes:

cd ComfyUI/custom_nodes
git clone https://github.com/Mister-Link/link-comfy-nodes
cd link-comfy-nodes
pip install -r requirements.txt

or ComfyUI Manager → "Link Comfy Nodes", restart. The palette stage needs scikit-learn (it's in requirements.txt), which you won't have from stock ComfyUI - that's the most common install stumble.

Common issues

  • Import error about sklearn - install the pack's requirements; scikit-learn doesn't come with ComfyUI.
  • Stills look fine but video shimmers - raise stability toward 1. The hysteresis is what kills the boil.
  • Looping footage has a visible seam - loop defaults to true for a reason; if you turned it off, turn it back on. Conversely, non-looping footage with loop on gets a warm-up pass that wastes a little time for no benefit.
  • Pixels aren't uniform at fractional output sizes - the node handles it internally, but if you set a width/height that isn't a clean multiple, expect the grid to be nearest-integer rather than perfectly square.

One thing it won't do is invent a period-correct palette like PICO-8's 16 colors - you get k-means at your chosen budget, not a named hardware palette. For everything else - grid, palette, stability - this is the single node that replaces the three-step dance most people string together manually.

Categoryimage/transform

Inputs (10)

NameTypeDefaultDescription
framesIMAGE
pixel_sizeINT51–128
kernel_sizeINT51–128Sampling window per block. Equal to pixel_size = exact tiling; larger overlaps neighboring blocks for a softer, chunkier look.
num_colorsCOMBO256Global palette size, fit once across all frames on the reduced blocks (no per-frame palette flicker).
edge_styleCOMBOhardhard = crisp pixel-art edges: the art grid is reduced by nearest sampling (no invented in-between colors along contours) and output alpha is snapped to fully opaque/transparent, matching hand-drawn sprite edges. soft = area-averaged reduce and graded alpha (antialiased look).
alpha_thresholdFLOAT0.580–1Coverage level treated as ~50% opacity for the graded output alpha. 0 = raw coverage.
stabilityFLOAT1.000–1Temporal hysteresis on the block vote (video): previous winner keeps a block unless clearly beaten. 0 disables.
loopBOOLEANtrueTreat the batch as a seamless loop: every temporal mechanism (vote hysteresis, palette hysteresis, alpha trigger) gets a hidden warm-up pass so frame 1 starts from the end-of-sequence state -- no snap at the last-to-first wrap. Disable for non-looping footage.
widthoptINT00–8192Output width. 0 = input width. One of width/height set = keep aspect.
heightoptINT00–8192

Outputs (1)

NameTypeDescription
pixel_artIMAGE