Film Grain Pro
Film grain that survives an upscale — and isn't just noise
- image
- image
The complaint you always hear about generated images is "too clean." Slap noise on top and you fix that - until you upscale, and the noise becomes soft blobs, or you shrink, and it vanishes entirely. Film Grain Pro exists because that whole approach is wrong, and the fix is genuinely clever: this is grain rendered from the Newson et al. IPOL 2017 stochastic film grain model, Monte-Carlo'd so that a grain of a given physical size stays the same fraction of the frame no matter what resolution you render at. Upscale all you want; the grain scales with you.
It ships inside ComfyUI-Darkroom, Jeremie Louvaert's film-emulation pack (he's a photographer turned senior CG artist - the pack famously started from parsed Capture One curve data, not eyeballed color grades). This node is the "quality tier" sibling of the pack's plain Film Grain node: slower, physically derived, and the one you reach for when grain is actually part of the look rather than a smudge you half-notice.
How it works
The 2017 model treats grain as a random union of disks - a "Boolean model" - where bright pixels get denser grain than dark ones, exactly like real silver-halide film where dense shadows have coarse clumps and highlights go fine. Each grain has a radius, and the whole thing is rendered via Monte Carlo sampling, so the output is statistically tone-preserving: the grain is variance on top of your image, not a brightness shift. The pack's derivation doc is refreshingly honest about it - this is the published algorithm reimplemented clean-room, not the GPL C++ copied over.
The inputs that matter
- grain_size - radius in pixels at a 1024px reference. Scale your image up and this scales too, which is the entire point of the node.
- strength - blend between original and grainy render. Tone is preserved either way, so this is a dial, not a gamble.
- seed - for reproducible grain. If you're iterating on a look, fix the seed; if you want variation across a batch, don't (each frame in a batch gets its own offset anyway).
- color_grain - 0 is monochrome luminance grain, 1 is independent per-channel chroma grain like color film. Cinestill-style color neg wants a bit of this.
- radius_variation - grain size variability (log-normal); 0 is uniform, higher gives you that mixed coarse/fine character.
- monte_carlo_samples - render quality. Cost is linear, default 64 is the sane spot; crank it when you notice banding, drop it for previews.
Output is a single image you wire into PreviewImage or a Save node like any other.
Install and where it goes
Install the pack via ComfyUI Manager (search "Darkroom") or:
cd ComfyUI/custom_nodes
git clone https://github.com/jeremieLouvaert/ComfyUI-Darkroom
pip install -r ComfyUI-Darkroom/requirements.txt
Restart, and it's under AKURATE/Darkroom/Film. Requirements are just scipy, opensimplex, rawpy, exifread and Pillow - no GPU needed, though this node will use CUDA if it's there.
Where people get burned: grain is a spatial effect, so it can't go inside a LUT bake chain. If you're using Darkroom's LUT Bake Inject/Extract to export a .cube, apply grain to the graded photo after Extract. And the natural home for this node is after upscaling, not before - grain added pre-upscale gets resampled into mush.
One more tip: keep strength modest. Real film grain reads at maybe 0.3–0.5 here; pushing toward 1 makes everything look like a 90s music video, which is a look, just probably not the one you wanted.
Inputs (8)
| Name | Type | Default | Description |
|---|---|---|---|
| image | IMAGE | — | |
| grain_size | FLOAT | 1.200.7–4 | Grain radius in pixels at a 1024px reference. Scaled with image size so grain stays a fixed fraction of the frame at any resolution. |
| strength | FLOAT | 0.500–1 | Grain intensity. Blends the grainy render with the original (tone is preserved either way). |
| radius_variation | FLOAT | 0.000–0.5 | Grain size variability (sigma/mean). 0 = uniform grains, higher = mixed sizes (log-normal). |
| color_grain | FLOAT | 0.000–1 | 0 = monochrome (luminance) grain, 1 = independent per-channel grain (chroma grain, like color film). |
| seed | INT | 00–4294967295 | Random seed for a reproducible grain pattern. |
| monte_carlo_samplesopt | INT | 648–256 | Render quality. Higher = cleaner estimate (grain is preserved), but slower. Cost is linear in this. |
| filter_sigmaopt | FLOAT | 0.80.4–2 | Grain softness (Gaussian filter sigma in output pixels). 0.8 is the physically-motivated default. |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| image | IMAGE | — |