CIE LCH Noise (GEGL-like)
Perceptual film grain, straight from GIMP's math
- image
- image
Most noise filters just add random pixels and call it a day. CIE LCH Noise (GEGL-like) is the thoughtful version: it adds noise in the CIE LCh color space, which is designed to match how human perception actually works. That means the grain it produces looks like film grain rather than digital static - the kind of organic texture that makes a clean AI render feel like it was shot on stock instead of generated. It's a port of GIMP's gegl:noise-cie-lch filter, and it's one of the more quietly useful nodes in the AnotherUtils pack for the final-finish crowd.
The key idea: LCh splits color into Lightness (L), Chroma (C, how saturated), and Hue (h, the actual color angle). Noise in RGB lands wherever it lands. Noise in LCh lands where your eye is least annoyed by it - you can jitter lightness for grain, chroma for "chunky" color, or hue for subtle shimmer, each independently.
How it works
The node does a full round trip: sRGB → linear RGB → XYZ (D65) → Lab → LCh, all in vectorized torch, then perturbs the three channels, then converts back. Two details make it feel authentic:
- Hue wraps. Hue is a circle, so hue noise wraps around 0–360° instead of clamping - no hard color cutoffs. It also only jitters hue where chroma is above zero (random hue on a gray pixel is meaningless).
- Holdness (default 2) replicates GEGL's "dulling" trick: instead of one uniform draw, it takes the minimum of
holdnessuniforms, which skews the noise toward small values - most pixels move a little, few move a lot. Bump it higher for subtler, clumpier grain.
Everything is seeded via the seed input, so a fixed seed gives reproducible grain - important when you're iterating on a grade and want to compare settings pixel-for-pixel.
The inputs that matter
lightness_distance(0–100, default 40) - how far L can jump. This is your grain amount.chroma_distance(0–100, default 40) - color saturation jitter. Higher = noisier, more "electronic" color.hue_distance(0–180, default 3) - keep this small. Hue noise past ~10 gets ugly fast, and the default 3 is already visible.holdness(1–8, default 2) - the dulling factor above.seed- determinism.
One output, image. Alpha is preserved unchanged.
Where it fits
Use it in the finishing chain: after upscaling or right before a final light sharpen, on photographic or cinematic renders. It's also a cheap way to add texture that survives video - a consistent seeded grain across frames is exactly what "film grain" needs. Pair it with the pack's RGB Noise for heavier, channel-split grain, or Mean Curvature Blur to smooth first and grain after.
Install
ComfyUI Manager → search AnotherUtils, or:
cd ComfyUI/custom_nodes
git clone https://github.com/marcoc2/ComfyUI-AnotherUtils.git
Restart ComfyUI. Pure torch - no weights, no pip extras.
Common issues
If grain is invisible, you're likely on small lightness_distance values or the image is very flat - LCh noise is most obvious in midtones. If it looks like rainbow static, your hue_distance is way too high; keep it under ~10. On very large batches the vectorized conversion churns GPU memory, so process big batches in chunks. And remember the seed: two runs with the same seed produce identical grain, which is either a feature or a trap depending on whether you remembered to vary it.
Inputs (6)
| Name | Type | Default | Description |
|---|---|---|---|
| image | IMAGE | — | |
| lightness_distance | FLOAT | 40.00–100 | — |
| chroma_distance | FLOAT | 40.00–100 | — |
| hue_distance | FLOAT | 3.00–180 | — |
| holdness | INT | 21–8 | — |
| seed | INT | 0-2147483648–2147483647 | — |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| image | IMAGE | — |