HDR Shadow / Highlight Recovery
Pull detail back without clipping anything
- image
- recovered_image
You have a linear HDR image where the shadows are down in the basement and the highlights are somewhere up past 5.0. You could tone map it, but tone mapping is a display transform - it bakes in a look and throws away the range you wanted to grade. Shadow / Highlight Recovery is the opposite move: it redistributes detail inside the HDR range so you have something workable to grade, and it never clamps the top end.
It's the node you reach for before the grading chain, not after the render.
How it works
Two separate operations, driven by luma.
The shadow side is a lift. Pixels are multiplied by up to 1 + shadow_amount, and that gain fades out exponentially with luma so black gets the full lift and midtones get almost none. shadow_tone sets the width of that falloff - by the time luma reaches shadow_tone, the lift is down to about 5%. The lift is neutral-ish, which is why there's a color_correction control: pushing luminance around in the deep shadows tends to amplify whatever colour is sitting there, and this desaturates the lifted region to counter it, up to 30% at full strength, weighted toward the deepest pixels.
The highlight side is a soft compression. Above highlight_tone, values are scaled down progressively - at the default, luma 1.0 gets multiplied by 0.8 and brighter values get squeezed harder. Critically it never clips: 20.0 in is still a big number out, just a smaller one. Super-whites keep working.
local_contrast is a separate door: a large-radius (around 50 px) unsharp on luma. Positive punches, negative flattens. Leave it at 0 unless you want it - it's the one control here that can look "AI-processed" fast.
Inputs and outputs
Five things to set, in rough order of how much you'll touch them:
- image - linear HDR, values above 1.0 welcome.
- shadow_amount (0–2, default 0.5) and highlight_amount (0–2, default 0.5) - the two gains. At 0 each side is off, so you can use it as a pure highlighter or a pure shadow lifter.
- shadow_tone (default 0.25) and highlight_tone (default 0.75) - where each operation lives in luma. These are the controls that decide whether the effect touches only the extremes or creeps into the body of the image.
- color_correction (default 0.5) and local_contrast (default 0) - the two finishing touches described above.
One output: recovered_image. From there, straight into a grade, a Write EXR, or into Tonemap when you finally need a display version.
One batch gotcha worth knowing before you wire it
Only the first frame of a batch is processed. That's in the author's own tooltip, and it's a real constraint, not a bug you'll see fixed by wiggling a widget. If you're working with video frames, run it per frame or accept that frames 2 onward are untouched - mixing recovered and unrecovered frames in a sequence is a flicker source, so don't.
Installing Radiance
ComfyUI Manager, search Radiance, install, restart, refresh. Or:
cd ComfyUI/custom_nodes
git clone https://github.com/fxtd-studios/radiance.git
cd radiance
python -m pip install -r requirements.txt
On the Windows portable build, use the bundled interpreter:
python_embeded\python.exe -m pip install -r requirements.txt
This is a big pack - 147 visible nodes, GPL-3.0, with OpenEXR, OpenImageIO and OpenColorIO as real dependencies because EXR and colour management are the point of it. No model downloads needed for this node.
Troubleshooting
"Nothing happened." Check your shadow_tone and highlight_tone. If your image's luma never gets below 0.25 or above 0.75, both operations are working on regions your image doesn't have. Widen the tone values toward the middle to see the effect, then dial back.
Shadows went muddy and coloured. That's the lift amplifying chroma. Raise color_correction toward 1. If it still looks wrong, your blacks aren't at zero - fix the levels first and the lift becomes clean.
Highlights look grey rather than brighter. You're compressing into a range that was already mid. Drop highlight_amount; this node can't invent headroom that isn't in the source, only redistribute what's there.
The output preview looks blown out. If you piped the result into ComfyUI's stock preview with values still above 1.0, that's expected - the preview is SDR. Send it to Radiance's Viewer or a tonemap node instead, and stop trusting the default preview for anything HDR.
If you want an estimate of what clipped detail should have been, this is the wrong tool. Radiance's learned SDR → HDR path (the RUDRA pixel model) guesses highlights from an SDR plate; this node only moves numbers that genuinely exist.
Inputs (7)
| Name | Type | Default | Description |
|---|---|---|---|
| image | IMAGE | Linear HDR image (values above 1.0 allowed). Only the first frame of a batch is processed. | |
| shadow_amount | FLOAT | 0.500–2 | Shadow gain at black: pixels are multiplied by up to 1 + amount, fading out with luma (see shadow_tone). 0 = off. |
| highlight_amount | FLOAT | 0.500–2 | Highlight compression above highlight_tone, never clipping. At the default, luma 1.0 is scaled by 0.8 and brighter values more. 0 = off. |
| shadow_toneopt | FLOAT | 0.250–0.5 | Linear-luma width of the shadow lift. It decays exponentially and is down to about 5% at this luma. |
| highlight_toneopt | FLOAT | 0.750.5–1 | Linear luma above which highlight compression starts; it keeps increasing past 1.0 for super-whites. |
| color_correctionopt | FLOAT | 0.50–1 | Desaturates lifted shadows to counter the colour boost of the lift (up to 30% at 1.0, deepest shadows most). 0 = off. |
| local_contrastopt | FLOAT | 0.0-1–1 | Large-radius local contrast (about 50 px blur of luma). Positive adds, negative flattens, 0 = off. |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| recovered_image | IMAGE | — |