Nodes/Chye ComfyUI Toolset/🎬 CYH Post Process | Film Grain
ComfyUI Node

🎬 CYH Post Process | Film Grain

Film grain that clumps like the real thing, not static

By chyerΒ·Created about a year agoΒ·Updated 6 months agoΒ· 1
🎬 CYH Post Process | Film Grain
  • image
  • IMAGE
β—„strength0.50β–Ί
β—„iso400β–Ί
β—„grain_size2.0β–Ί
β—„coloredfalseβ–Ί

Most "film grain" filters in image editors are just uniform white noise - it reads as static on a screen, not grain on film. Real grain clumps: it's spatially correlated, it's worse in the shadows, and it scales with how sensitive your "film" was. This node does all three, which puts it a cut above the default noise overlay.

It's one of four post-process nodes in the Chye ComfyUI Toolset, sitting right after VAE Decode in a classic workflow: KSampler β†’ VAE Decode β†’ CYH Post Process | Film Grain β†’ Save. And it's the one people in the film-emulation corner of the community tend to respect, because it's actually modeling grain rather than slapping a texture on.

How it works

The grain isn't generated per-pixel - it's filtered noise. The pack generates a random noise field, then runs it through a Gaussian filter (scipy.ndimage.gaussian_filter) so the particles clump into visible grain structure instead of staying as independent pixels. Then three things modulate it:

  • ISO scales grain logarithmically - log10(iso / 100). ISO 400 is modest, 6400 is heavy. That's how real film responds: grain grows with sensitivity, but not linearly.
  • shadow_boost multiplies grain in dark areas, because shadows on real film show grain first.
  • strength is the master 0–1 dial that sets how visible the whole thing is.

There's also a luminance-based pass so grain lands on the shadows and highlights appropriately rather than uniformly across the frame.

The inputs that matter

Four inputs, one IMAGE:

  • strength (0–1, default 0.5) - overall intensity. Start at 0.3–0.4 and creep up.
  • iso (100–6400, default 400) - the character knob. 400 is subtle, 1600+ starts to look like pushed film.
  • grain_size (1–10, default 2) - particle size. 1–2 is fine grain, 8–10 is chunky and dreamy.
  • colored (default off) - off gives monochrome grain (the classic look); on generates a separate grain pattern per RGB channel, which reads as color noise / chroma grain.

Output is IMAGE, straight through to a save node or further processing.

Where it bites

Two things, both real:

First, only the first image in a batch gets processed. The code grabs image_np[0] and processes one frame. Feed it a batch of 4 frames expecting a grainy grid and you'll get one grainy frame plus three untouched ones. For single images this is a non-issue; for latent-video or batch pipelines it will silently do the wrong thing.

Second, it's a CPU numpy pass, not a GPU one - the node runs its math in numpy after moving the tensor off the GPU. On a 1024Γ—1024 image that's a noticeable-but-fine hiccup (a second or two); on a 4K frame it adds up. It's not a real-time filter.

Install

ComfyUI Manager (search "Chye ComfyUI Toolset"), or:

cd ComfyUI/custom_nodes
git clone https://github.com/chyer/Chye-ComfyUI-Toolset
cd Chye-ComfyUI-Toolset
pip install -r requirements.txt

Restart ComfyUI. This node is why scipy is in the pack's requirements.txt - the Gaussian-filtered noise needs it. opencv-python also installs with the pack. Zip installs need .git/.cnr-id (Chye-ComfyUI-Toolset) per the README.

Verdict

For a "vibe" filter this is a surprisingly good one - correlated noise is the difference between "looks like a filter" and "looks shot on film." The single-frame limitation is the only real complaint. If you're batch-generating, process images one at a time or pick a different tool.

Categorypost_process

Inputs (5)

NameTypeDefaultDescription
imageIMAGEβ€”
strengthFLOAT0.500–1β€”
isoINT400100–6400β€”
grain_sizeFLOAT2.01–10β€”
coloredBOOLEANfalseβ€”

Outputs (1)

NameTypeDescription
IMAGEIMAGEβ€”