X2HDR Color Grade
A full HDR colorist's panel that lives inside ComfyUI — no external app needed
- hdr_image
- graded_display
- graded_linear
Every X2HDR workflow ends the same way: you've got a linear float HDR tensor that looks flat and alien on a normal monitor, and you need to turn it into something you can actually look at - while keeping an HDR master for anyone who needs one. X2HDR Color Grade does both, and it's the pack's headline act.
The headline feature is the interactive viewer. Run the node once, then click Open X2HDR color grade on the node itself. This isn't a third-party plugin bolted on - it's implemented inside the pack (a bit of Python routes plus a JS frontend). The mechanism is that the node caches the HDR tensor on the ComfyUI server, and the viewer requests tone-mapped preview frames over the pack's own HTTP endpoints as you drag sliders. You're grading live, not waiting on full graph re-runs.
The viewer gives you: canvas pan/zoom/fit/1:1, live exposure plus auto exposure, tone mapping, soft clip, white balance, contrast, lift/gamma/gain/offset, shadows/highlights, saturation/vibrance, hue shift, density, black lift, split toning, a 3×3 color matrix, false color, source/graded/split comparison, an RGB histogram, HDR and display pixel sampling, frame navigation for batches, and factory plus user presets. Hit Save and it writes your values back to the node's widgets; Save PNG and Save EXR export the current frame through the pack's backend.
Inputs a beginner actually sets
- tone_map -
None/Reinhard/ACES Fitted/AgX/Hable.ACES Fittedis the default and the right starting point: it's the classic filmic curve and handles the bright end far better than Reinhard's gentle squash. - exposure (EV) and soft_clip - the two dials that rescue a blown-out or dim grade.
- temperature / tint for white balance, saturation / vibrance for color, contrast with its pivot (default 0.18, i.e. mid-gray).
- false_color - a boolean that shows you clipping instead of making you guess.
Everything else in the required list is the standard per-channel lift/gamma/gain/offset trio, and the optional section hides auto-exposure, the color matrix, density, black lift and split-toning controls. Ignore those until you feel limited; the viewer exposes all of it anyway.
Outputs
- graded_display (
IMAGE) - the LDR display render. Wire it to a normalSave ImageorPreview Image. - graded_linear (
IMAGE) - the same grade, still linear HDR. This is your HDR master; wire it toX2HDR Save EXRif you want a graded EXR rather than the raw decode.
Feed it the hdr_image output of any X2HDR decode (PU21, LogC3 or LogC4). The full pipeline is decode → (QA) → save raw EXR → color grade → save display PNG plus graded EXR.
Installing it
ComfyUI Manager → search comfyui-x2hdr → install, restart, nodes under image/HDR/X2HDR. Or:
cd ComfyUI/custom_nodes
git clone https://github.com/facok/comfyui-x2hdr
then restart. The one extra dependency is PyAV (av), declared in pyproject.toml; it's imported at load time, so if the pack fails to import, pip install av in ComfyUI's Python env fixes it.
Gotchas
- The viewer only shows content after you've run the node once - that's when the tensor gets cached on the server.
- Don't save
graded_displayand call it your HDR. It's an LDR preview, gamma-encoded; the HDR lives ingraded_linear/ the EXR. This is the classic sRGB-vs-scene-linear trap from the color-management world, and it's what trips up people handing AI output to a compositor.
Inputs (47)
| Name | Type | Default | Description |
|---|---|---|---|
| hdr_image | 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–4 | — |
| 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 | — |
| auto_exposureopt | BOOLEAN | false | — |
| auto_exposure_lockopt | BOOLEAN | false | — |
| auto_exposure_evopt | FLOAT | 0.00-10–10 | — |
| matrix_rropt | FLOAT | 1.00-2–2 | — |
| matrix_rgopt | FLOAT | 0.00-2–2 | — |
| matrix_rbopt | FLOAT | 0.00-2–2 | — |
| matrix_gropt | FLOAT | 0.00-2–2 | — |
| matrix_ggopt | FLOAT | 1.00-2–2 | — |
| matrix_gbopt | FLOAT | 0.00-2–2 | — |
| matrix_bropt | FLOAT | 0.00-2–2 | — |
| matrix_bgopt | FLOAT | 0.00-2–2 | — |
| matrix_bbopt | FLOAT | 1.00-2–2 | — |
| densityopt | FLOAT | 0.00-2–2 | — |
| black_liftopt | FLOAT | 0.000-0.25–0.25 | — |
| shadow_tone_ropt | FLOAT | 0.000-0.25–0.25 | — |
| shadow_tone_gopt | FLOAT | 0.000-0.25–0.25 | — |
| shadow_tone_bopt | FLOAT | 0.000-0.25–0.25 | — |
| highlight_tone_ropt | FLOAT | 0.000-0.25–0.25 | — |
| highlight_tone_gopt | FLOAT | 0.000-0.25–0.25 | — |
| highlight_tone_bopt | FLOAT | 0.000-0.25–0.25 | — |
| tone_balanceopt | FLOAT | 0.500–1 | — |
Outputs (2)
| Name | Type | Description |
|---|---|---|
| graded_display | IMAGE | — |
| graded_linear | IMAGE | — |