Gear · Color Grade (exr-viewer)
A full color page inside ComfyUI — grade your HDR EXRs without leaving the graph
- hdr_linear
- sdr_reference
- graded_display
- graded_linear
This is the pack's showpiece, and the reason to install ComfyUI_Gear in the first place. The decode nodes hand you scene-linear HDR, and the natural next step is to grade it - but poking numeric sliders on a node to "grade" footage is a miserable way to work. Gear · Color Grade embeds the exr-viewer grading UI as a modal pop-up in your browser: color wheels, lift/gamma/gain/offset, scopes, A|B compare, a batch frame scrubber. It's a slice of DaVinci's color page running inside ComfyUI, driven by WebGL2.
The workflow
Wire hdr_linear (typically from Gear · LogC3/LogC4/ACEScct Decode) into the node and run the graph once. Then click Open grade panel on the node. The modal pops up with the full exr-viewer interface, and every slider and wheel moves the WebGL canvas instantly - no graph re-runs while you work.
How it works
The grade math lives in two places that stay in sync: a GLSL fragment shader in exr-viewer/src/renderer.ts drives the live preview, and a torch port in gear/grading.py runs on backend execution when you queue. The pipeline is: exposure → white balance → ACEScct → lift/gamma/gain/offset → contrast → shadows/highlights → vibrance → saturation → hue shift → tone map → soft clip → sRGB OETF.
The node technically exposes 26 inputs, but you won't hand-type most of them - the panel writes your grade to the node's hidden widgets. The ones you might set directly are:
- tone_map -
None,Reinhard,ACES Fitted(default),AgX,Hable. Your pick of look; ACES Fitted is the safe default. - exposure - global EV before the grade stack.
- soft_clip - rolls off highlights instead of hard-clipping them.
- sdr_reference - the one optional input. Give it any display-referred IMAGE (like the decode node's
tonemapped_preview) and the panel's A|B wipe compare has something to grade against.
The important bits about the panel
- Persistence: changes are ephemeral until you hit the blue Save button. Cancel, ×, Esc, or clicking outside all discard. Saved state survives modal reopen and workflow save/load - including exact dot + master positions on the color wheels.
- Batches: pass an
[N, H, W, C]IMAGE and the panel shows a frame scrubber at the bottom. A|B compare works across the batch, swapping both EXR and SDR as you scrub. - LUTs: the LUT button loads any
.cubefile (1D LUTs unsupported). Bundled film LUTs ship inweb/vendor/exr-viewer/test-luts/.
Outputs
- graded_display - display-referred sRGB, post tone-map and OETF,
0..1. What you actually look at. - graded_linear - scene-linear HDR after grading, pre-tonemap. Pipe this back into a Gear decode/save node's EXR writer to bake the grade into your EXRs.
Gotchas worth knowing
End users never run npm - the prebuilt exr-viewer is committed in web/vendor/, so Manager install just works. You need a browser with WebGL2 (any modern one).
One thing that surprises people: this node deliberately never caches. Its IS_CHANGED returns a random value every call, because the panel previews an EXR written to ComfyUI's temp folder, and ComfyUI wipes temp on every restart. A cached node would leave the panel pointing at a deleted file. The cost is that the node re-executes every queue even if nothing changed - it's cheap (one small grade pass plus one EXR write), but you'll see it run. And after a restart, just re-run the graph once and reopen the panel to refresh the source EXR.
Install via ComfyUI Manager (search ComfyUI_Gear) or git clone https://github.com/oumad/ComfyUI_Gear into ComfyUI/custom_nodes, then pip install -r requirements.txt and restart. Deps stay at opencv-python, numpy, Pillow - the panel itself is pure browser code.
Inputs (27)
| Name | Type | Default | Description |
|---|---|---|---|
| hdr_linear | IMAGE | — | |
| exposure | FLOAT | 0.0-10–10 | — |
| tone_map | COMBO | ACES Fitted | 5 options: None, Reinhard, ACES Fitted, AgX, Hable |
| soft_clip | FLOAT | 0.000–1 | — |
| temperature | FLOAT | 0.00-1–1 | — |
| tint | FLOAT | 0.00-1–1 | — |
| lift_r | FLOAT | 0.00-1–1 | — |
| lift_g | FLOAT | 0.00-1–1 | — |
| lift_b | FLOAT | 0.00-1–1 | — |
| gamma_r | FLOAT | 1.000.1–4 | — |
| gamma_g | FLOAT | 1.000.1–4 | — |
| gamma_b | FLOAT | 1.000.1–4 | — |
| gain_r | FLOAT | 1.000–4 | — |
| gain_g | FLOAT | 1.000–4 | — |
| gain_b | FLOAT | 1.000–4 | — |
| offset_r | FLOAT | 0.00-1–1 | — |
| offset_g | FLOAT | 0.00-1–1 | — |
| offset_b | FLOAT | 0.00-1–1 | — |
| contrast | FLOAT | 1.000–4 | — |
| pivot | FLOAT | 0.1800.001–1 | — |
| shadows | FLOAT | 0.00-2–2 | — |
| highlights | FLOAT | 0.00-2–2 | — |
| saturation | FLOAT | 1.000–3 | — |
| vibrance | FLOAT | 0.00-2–2 | — |
| hue_shift | FLOAT | 0-180–180 | — |
| false_color | BOOLEAN | false | — |
| sdr_referenceopt | IMAGE | — |
Outputs (2)
| Name | Type | Description |
|---|---|---|
| graded_display | IMAGE | — |
| graded_linear | IMAGE | — |