HDR Color Pipeline
Linearise, adapt, convert, compress — in one node
- image
- vae_image
- scene_linear
- peak_linear
- colorspace_json
Wiring a proper scene-linear handoff usually looks like five nodes in a row: decode the transfer curve, adapt the white point, swap primaries, compress the highlights so a VAE can survive them, then output. HDR Color Pipeline is that whole chain in one box, and it emits both answers you need - a VAE-ready image and the untouched scene-linear one.
This is the node for the seam between "I have an image in some encoding" and "I have something a diffusion model can eat without banding." It's the colours department's version of a prep node.
How it works
Four stages, in order:
- Linearise.
encodingnames the input's transfer curve and gets decoded to linear light. PQ is decoded absolutely - 203 nits maps to 1.0 - because that's what ST.2084 is. Log curves decode to camera scene-linear.Linear (none)just clamps negatives. - Adapt.
chromatic_adaptationapplies a Bradford white-point matrix (D65_to_D60,D60_to_D65,D65_to_D50,D50_to_D65). This step is skipped with a warning when the primaries conversion already changes the white point - i.e. to or from ACEScg, which is D60. That guard exists so you never apply the same adaptation twice, and it's the kind of thing most nodes make you discover the hard way. - Primaries.
source_primaries→target_primaries, any pair from Rec.709 (sRGB), BT.2020, ACEScg, DCI-P3 (D65), XYZ (D65). Same on both ends means no change. - Compress. A soft-knee highlight compressor, strength set by
compression_ratio(default 0.5). This is the part that keeps a VAE from choking on 60.0-valued sunsets.
Inputs and outputs
The four required inputs are image, encoding, compression_ratio and - practically - nothing else. Ten encodings are offered: sRGB, Rec.709 (OETF), BT.1886 (TV γ2.4), Gamma 2.2, Gamma 2.4, PQ (ST.2084), HLG, ARRI LogC4, Sony S-Log3, and Linear (none).
Then source_primaries and target_primaries (default both Rec.709, i.e. no primaries work), chromatic_adaptation (default None), and pq_peak_nits - which the author's own tooltip confirms has no effect on the decode, because PQ is absolute. It's there for compatibility.
Four outputs, and the split is the point:
vae_image- the compressed, VAE-ready result. This is what goes into your encoder.scene_linear- the uncompressed linear version, for grading, analysis or EXR delivery.peak_linear(FLOAT) - how hot the scene got before compression. Watch this while you tunecompression_ratio.colorspace_json(STRING) - a record of the conversion, for logging or metadata.
Install
Manager → search Radiance → Install → restart → refresh.
cd ComfyUI/custom_nodes
git clone https://github.com/fxtd-studios/radiance.git
cd radiance
python -m pip install -r requirements.txt
Windows portable users: swap python for python_embeded\python.exe. No models needed here.
Where people get burned
The double-adaptation skip is correct behaviour, but it means your graph can contain a chromatic_adaptation setting that silently does nothing because you're converting to or from ACEScg. Read the console warning instead of assuming the widget bit.
Second, don't treat vae_image as your deliverable. It's compressed on purpose - that's a loss the diffusion stack is designed to tolerate, not a look you want in an EXR. Save scene_linear and let the encode node do delivery.
Third, watch peak_linear. If it's enormous (hundreds, not tens), a 0.5 compression_ratio is doing heavy lifting and you'll see it as flat, milky highlights downstream. That's a signal that your HDR expansion upstream is too aggressive, not that this node is broken.
Inputs (7)
| Name | Type | Default | Description |
|---|---|---|---|
| image | IMAGE | Encoded input image. Negatives are clamped to 0; alpha is passed through. | |
| encoding | COMBO | sRGB | Transfer curve of the input, decoded to linear. PQ decodes so 203 nits = 1.0; log curves decode to camera scene-linear. Linear (none) only clamps negatives. |
| compression_ratio | FLOAT | 0.500–1 | Compression ratio for soft-knee HDR highlight compression. |
| source_primariesopt | COMBO | Rec.709 (sRGB) | Primaries of the input. Any pair of the listed primaries converts, with a Bradford white-point adaptation where the whites differ (ACEScg is D60). |
| target_primariesopt | COMBO | Rec.709 (sRGB) | Primaries to convert to. Same as source leaves colours unchanged. |
| chromatic_adaptationopt | COMBO | None | Bradford white-point matrix applied to the linear RGB before the primaries step. Skipped, with a warning, when the primaries conversion already changes the white point (to or from ACEScg), so it is never applied twice. |
| pq_peak_nitsopt | FLOAT | 1000100–10000 | Has no effect on the decode: PQ is absolute, so it always decodes with 203 nits = 1.0 whatever this is set to. |
Outputs (4)
| Name | Type | Description |
|---|---|---|
| vae_image | IMAGE | — |
| scene_linear | IMAGE | — |
| peak_linear | FLOAT | — |
| colorspace_json | STRING | — |