ComfyUI Node

LUT Master

Stack Four .cube LUTs Without Leaving ComfyUI

By Rimor-dev·Created a day ago·Updated a day ago· 0
LUT Master
  • image
  • IMAGE
◄lut_config[]►
◄exposure0.00►
◄offset0.00►
◄gamma1.00►
◄curve_a_x0.25►
◄curve_a_y0.25►
◄curve_b_x0.75►
◄curve_b_y0.75►
◄photo_filtercooling_lbb►
◄photo_filter_density0.0►
◄photo_filter_preserve_lumfalse►
◄photo_filter_after_lutfalse►

The image is done except for the color. It's a little flat, a little clean, and it doesn't look like it came off a camera - so you're about to open Resolve, or worse, re-roll the seed and hope. LUT Master is the shortcut: up to four .cube LUTs on a finished frame, each dialled down to whatever strength you want, plus exposure and gamma while you're there. No model pass, no seed lottery.

That's the pitch of this whole layer: a lookup table maps every RGB triplet in to a new one out, deterministically, and it does "Fuji print stock colors" better than a prompt ever will.

What it actually does

The order of operations is fixed in the source (lut_master_nodes.py), not configurable:

  1. Exposure - a linear multiply by 2^exposure, so 1.0 is one stop brighter.
  2. Offset - pure addition to every channel, then a clamp to 0–1.
  3. Gamma - pow(img, 1/gamma), so above 1 lifts midtones, below 1 crushes them.
  4. RGB curve - piecewise-linear, pinned at black (0,0) and white (1,1) with two movable knots, curve_a_* and curve_b_*. Identity at the defaults.
  5. Photo filter - optional, and by default it runs before the LUTs.
  6. The LUTs, in slot order, each a linear crossfade toward the LUT result at its own strength.
  7. Photo filter again, if you ticked photo_filter_after_lut.

The README's "Exposure → Offset → Gamma → LUTs" is honest but incomplete: the curve and the filter sit between gamma and the first LUT.

The LUT side is where the pack earns its keep. A tiny route, /lut_master/list, fills the dropdown from the pack's own luts/ directory. Click + LUT, pick a .cube, set a strength - what that button writes into the lut_config field is a JSON list of {"file": ..., "strength": ...} objects you never type into yourself.

The inputs you'll actually touch

image goes in; a single IMAGE comes out, so it wires straight into Save Image, a preview, or another post-process node.

exposure (-5 to 5) and gamma (0.1 to 3) are the two worth learning: gamma when the image looks flat, exposure when it's dark overall. offset adds a constant and clips, so it flattens contrast fast - leave it at 0 unless you know why you're there.

curve_a_x/curve_a_y is your shadow knot, curve_b_x/curve_b_y your highlight knot. Lift curve_a_y for filmic blacks, pull curve_b_y down for a highlight roll-off. The node quietly swaps the two if a_x passes b_x, and skips the curve entirely if the two x values are equal - a silent no-op that will confuse you for ten minutes.

The photo_filter trio is a tint: it multiplies the image by a fixed color vector per preset (deep_blue is (0, 0, 0.5), which is why 100% looks nearly black). Live at 10–30%; photo_filter_preserve_lum puts the lightness back so you get the hue shift without the darkening.

Install

ComfyUI Manager: search "LUT Master" (the registry display name; the repo is ComfyUI-LUT-Master). Or by hand:

cd ComfyUI/custom_nodes
git clone https://github.com/Rimor-dev/ComfyUI-LUT-Master.git
cd ComfyUI-LUT-Master
pip install -r requirements.txt

Then restart ComfyUI. The only dependencies are pillow-lut>=0.2.0 and numpy - no torch extras, no downloads. Eight film-emulation cubes ship in the repo (Fuji ETERNA 250D, Kodak 5218 against 2383 and 2395 print emulations).

Where people get burned

The LUT dropdown is empty. The list is built from custom_nodes/ComfyUI-LUT-Master/luts/ and nothing else - not models/luts, not Downloads. Put the files there, reload the page, and check the console for [LUT Master] Endpoint registered: /lut_master/list on startup. No green line, no dropdown.

The image comes out identical and nothing errors. Missing LUT files print a yellow LUT not found and pass the image through untouched; same for a .cube that won't parse. If your output looks suspiciously like your input, read the terminal before touching a slider. A hand-built workflow carrying the default lut_config of "[]" is zero LUTs applied and zero complaints.

Both nodes vanish after a restart. That's pillow-lut failing to import - the pack's __init__.py imports everything eagerly, so one bad import takes both nodes down. Install with the Python ComfyUI actually runs: on a portable Windows build that's python_embeded\python.exe -m pip install -r requirements.txt, not whatever pip is on your PATH.

Banding, and slow video. Each LUT slot converts your float tensor to 8-bit PIL, filters it, and comes back - four slots is four 8-bit round trips through the same pixels, looped per frame in Python. Invisible on a still; on a Wan or LTX batch you'll notice the time and, on smooth skies, the steps. One good LUT beats four stacked ones anyway; chained print emulations mostly crush contrast.

Last thing to know rather than fix: the node always re-executes (IS_CHANGED returns NaN), so it never caches. Great while you're auditioning .cube files - swap the file, hit queue, see it. Wrong place for anything expensive in a long graph.

If you'd rather have one LUT and a strength slider, ComfyUI-OlmLUT is the community default. If you want a graded copy blended back over the original at 40%, that's Layer Blend from this same pack.

CategoryRimor_dev/postprocess

Inputs (13)

NameTypeDefaultDescription
imageIMAGE—
lut_configSTRING[]—
exposureFLOAT0.00-5–5—
offsetFLOAT0.00-1–1—
gammaFLOAT1.000.1–3—
curve_a_xFLOAT0.250–1—
curve_a_yFLOAT0.250–1—
curve_b_xFLOAT0.750–1—
curve_b_yFLOAT0.750–1—
photo_filterCOMBOcooling_lbb9 options: cooling_lbb, cooling_80, warming_85, warming_lba, sepia, deep_red, +3
photo_filter_densityFLOAT0.00–100—
photo_filter_preserve_lumBOOLEANfalse—
photo_filter_after_lutBOOLEANfalse—

Outputs (1)

NameTypeDescription
IMAGEIMAGE—