ComfyUI Node

Dither

Make your AI image look like it came off a risograph, not a GPU

By jnxmx·Created 4 months ago·Updated 4 months ago· 0
Dither
  • image
  • IMAGE
method
threshold16
contrast150
brightness65
gamma2.0
grain8
dot_size6
color1#000000
color2#FFFFFF
scale2.0

The name is the whole pitch. Dither (class DitherImage) takes any image you feed it and pushes it through an old-school halftone/error-diffusion process until it looks like it was printed on a two-color press, ripped from a low-bit retro game, or photocopied in a zine. One image in, one image out, and every frame of a batch gets the same treatment. It's the cheapest way to drag a slick generated render into a "physical object" aesthetic, and people mostly use it as a final post-process right before the Save Image node - the thing you reach for when "smooth AI sheen" is exactly the opposite of the mood you want.

It's worth knowing this pack is niche and free, unlike the Patreon-gated dithering nodes that pop up when you search the topic. If all you need is "my image but dithered," this node is the whole job.

How it works

Under the hood it's a small pipeline, and it's refreshingly honest about it. The image is converted to grayscale, then you get pre-adjustment knobs - brightness, contrast, gamma, and a random grain/noise amount - before the actual dithering runs. Then one of two algorithms takes over:

  • Atkinson - classic error diffusion. The code walks every pixel, snaps it to black or white against your threshold, and pushes one-eighth of the error onto six neighboring pixels. That's what gives the soft, stippled, almost-photographic dither you see in old Mac graphics and game art.
  • Halftone - the printed-newsprint look. dot_size controls the grid, and the code builds a circular dot in each cell whose size follows the local darkness. Bigger dot size, chunkier comic-book screen.

Either way the output is snapped to exactly two colors, color1 and color2, so the result is a hard two-tone image - that's the whole point.

The inputs that matter

You'll set maybe five of these. The rest have sane defaults and you can leave them alone:

  • method - atkinson or halftone. This is the look decision, everything else is fine-tuning.
  • threshold - how dark a pixel must be before it flips. 16 is subtle; crank it up for a bolder, more destroyed look.
  • contrast / brightness / gamma / grain - the pre-dither adjusters. Grain is the sneaky one: a little noise before error diffusion keeps flat areas from turning into blobs.
  • color1 / color2 - HEX strings, default black and white. Want a "printed on red" poster? Set #FF0000 and #000000.
  • scale - resizes internally before dithering. Default 2.0 means your output comes back at 200% resolution, which is how you get high-res dithered art without dithering looking like mush.

The single output is IMAGE, so wire it straight into a Save Image node. The full batch passes through unchanged, just at input × scale resolution.

Installing it

If you have ComfyUI Manager, search for ComfyUI-Dither and install it - that's it. Or clone it the old way:

cd ComfyUI/custom_nodes/
git clone https://github.com/jnxmx/ComfyUI-Dither.git
pip install -r ComfyUI-Dither/requirements.txt

Then restart ComfyUI. Note the README's own clone command has a placeholder username in it (YOUR_GITHUB_USERNAME) - use the real URL above. The requirements are light: numpy, opencv-python-headless, and numba. No model files, no API keys, nothing to download.

Where people get burned

Numba isn't optional in practice. The Atkinson algorithm is a pure Python double loop over every pixel, and the code only jit-compiles it if numba is importable; without numba it still works but runs at a crawl on anything bigger than a thumbnail. The README's "100x faster" claim is not marketing - install numba. If your existing environment already has opencv-python (the GUI build), the headless wheel in requirements can fight with it; that's the usual "node installed but errors on load" culprit, and fixing the opencv/numpy versions in your ComfyUI venv is the remedy.

The other classic footgun: color1 and color2 must be valid hex. Feed the node a named color like black or a malformed string and the parse throws, killing the queue. #RRGGBB works, and so does the short 3-digit form.

It's a tiny, focused, does-one-thing node - the rare custom node that works the way its README promises.

Categoryimage/dither

Inputs (11)

NameTypeDefaultDescription
imageIMAGE
methodCOMBO2 options: atkinson, halftone
thresholdFLOAT160–255
contrastFLOAT1500–200
brightnessFLOAT650–200
gammaFLOAT2.00.1–10
grainFLOAT80–100
dot_sizeINT61–100
color1STRING#000000
color2STRING#FFFFFF
scaleFLOAT2.00.1–10

Outputs (1)

NameTypeDescription
IMAGEIMAGE