Nodes/TJ_ComfyUI_ColorUtility/Image Palette Extractor
ComfyUI Node

Image Palette Extractor

Turn any image into an 8-color palette — without guessing

By TJ16th·Created 10 months ago·Updated 10 months ago· 0
Image Palette Extractor
  • image
  • custom_json
sample_max_pixels100000
merge_delta6.0
seed42
sortfrequency

ComfyUI has a billion nodes for making images and almost none for reading the colors that are already in them. Image Palette Extractor - from the same TJ_ComfyUI_ColorUtility pack as RGB Color Picker - fixes that: feed it an image and it hands you the eight colors that best represent it, as a JSON string you can feed downstream. If you've ever wanted a batch of generations to match the mood of a reference photo, or needed a palette pulled from brand art without opening a separate color tool, this is the node.

How it works - the interesting part

The code does real color science instead of the naive "sample a few pixels" trick. First it converts the IMAGE tensor to numpy RGB in 0–1 space, tolerating both channel layouts ComfyUI uses. If the image has more pixels than sample_max_pixels, it randomly samples down to that cap, seeded so results are repeatable. Then it converts sRGB to CIELAB - the perceptual color space where Euclidean distance roughly matches how different two colors look to a human - and runs k-means clustering with k=8, using k-means++ initialization for 15 iterations.

The merge_delta knob is the one you'll actually tune. After clustering, any two cluster centers closer than that threshold (in Lab distance) get merged, weighted by cluster size, so near-duplicate shades collapse into one representative. Lower it and the palette keeps subtle distinctions; raise it and similar colors blur together. It then guarantees exactly eight clusters, converts the centers back to sRGB, and emits a single STRING in this shape:

{"colors": ["#RRGGBB", "#RRGGBB", "#RRGGBB", "#RRGGBB", "#RRGGBB", "#RRGGBB", "#RRGGBB", "#RRGGBB"]}

The inputs that matter

  • image - any IMAGE. The actual required input.
  • sample_max_pixels (default 100,000) - cap on pixels fed to clustering. With k=8 you rarely need more; a 4K frame downsampled to 100k pixels clusters to basically the same palette far faster. Raise it only if you genuinely need to catch a tiny accent color.
  • merge_delta (default 6.0) - the "how close is too close" threshold in Lab space. The main quality dial.
  • sort - frequency (default), hue, or luminance - controls the order of the eight output slots.
  • seed (default 42) - makes the random sampling and cluster init deterministic.

Where it wires in

The output is a single JSON string, which is the one real beginner trap: this node emits text, not swatches. The natural destination is the same pack's Color Palette node - set its preset to custom and paste in the JSON, and it parses the string into eight separate #RRGGBB outputs you can route anywhere. Show Text (comfyui-custom-scripts) works too if you just want to read the palette. There's no built-in "render the palette as an image" step, so if you want a visual strip you'll pipe the hex strings into a solid-color/fill node yourself.

How to install it

Same as its packmate - no requirements.txt, no models, nothing to download beyond the code itself. ComfyUI Manager, search "TJ_ComfyUI_ColorUtility", or:

cd <ComfyUI>/custom_nodes
git clone https://github.com/TJ16th/TJ_ComfyUI_ColorUtility.git

restart, done. The only real dependency is numpy, which ComfyUI already ships.

Gotchas

  • Output is a string, not an image - see above. The JSON has to be parsed by whatever consumes it; ColorPalette's custom preset handles it, most generic nodes won't.
  • New pack, no community track record. The defaults are sensible (100k samples, delta 6, k=8), so start there and only touch merge_delta and sort until you have a reason.
  • If the same image gives you a different palette run-to-run, check your seed - it drives both the sampling and the k-means initialization, so at a fixed seed the output should be stable.
CategoryTJnodes/color

Inputs (5)

NameTypeDefaultDescription
imageIMAGE
sample_max_pixelsINT1000001000–2000000
merge_deltaFLOAT6.00–50
seedINT420–2147483647
sortCOMBOfrequency

Outputs (1)

NameTypeDescription
custom_jsonSTRING