DiffHDR Save EXR
Writing float HDR that a compositor won't throw back at you
- images
This is the delivery node, and it's the reason the rest of the pack exists. The hdr output of DiffHDR is float linear Rec.709 with values in the tens. ComfyUI's SaveImage will quietly quantise that to 8 bits and clip everything above 1.0 - you'd be paying for an A100-minute of radiance reconstruction and throwing it away in the last node of the graph. DiffHDR Save EXR writes OpenEXR (16- or 32-bit float, with real compression choices) or Radiance .hdr, converts primaries if you need to, and stamps the correct chromaticities into the header so the next tool reads it correctly.
It's an output node: no outputs, just files on disk, plus a tonemapped preview in the UI so you can see what you're writing.
The settings that matter
filename_prefix- defaultDiffHDR/hdr, relative to ComfyUI's output folder. A batch doesn't go into one file: it becomes<prefix>_<counter>/frame_####.exr. A single image is written directly as<prefix>_<counter>.exr. That asymmetry is worth knowing before you point a comp at a folder.format-exrorhdr(Radiance RGBE). The Radiance path is deliberately dumb: always linear Rec.709, andbit_depth,compressionandcolorspaceare ignored. Fine for a quick handoff to a phone wallpaper generator; use EXR for anything serious.bit_depth-half(16-bit) orfloat(32-bit).halfis what most compositing work actually uses and it's half the size; values are clamped to ±65504, which is far beyond anything DiffHDR produces. Pickfloatwhen you're feeding something that insists on it.compression-zip(default) andpizare lossless;dwaa/dwabare lossy and very effective on this kind of content, withdwa_compression_level(default 45, the OpenEXR default) trading size against quality.b44/b44aare the half-float-only compressors, so pairing them withbit_depth = floatwill get you an error from OpenEXR itself - that's a format rule, not a bug in the node.noneon a 99-frame 720p sequence is a good way to fill a disk.colorspace-linear_rec709(default),acescgoraces2065_1. The node converts the pixels with a Bradford-adapted matrix and writes the matching chromaticities into the header, so downstream tools interpret the file correctly.acescgis the one a Nuke comp generally wants. Note theaces2065_1path also setsacesImageContainerFlag, which is exactly the kind of detail that separates "it opens" from "it's right".start_frame- the first frame number of a sequence. Padding is at least four digits and widens for the whole sequence when the last frame needs it, soframe_0998.exrdoesn't sort afterframe_1000.exr.preview_exposure- stops, for the UI thumbnail only. It does not affect the file. Set it to −4 if you want the thumbnail to show you what the pack's README shows you.
Frames, not just pictures
A batch of frames gets a counter bump if the target folder already exists, so re-running a graph doesn't silently overwrite yesterday's render - you get a fresh hdr_00002/. Small thing, saves a lot of swearing. And saving is cheap next to sampling: 33 frames as float32 ZIP ran about 7.7 s including the preview, so don't let file size scare you off float.
Install
cd ComfyUI/custom_nodes
git clone https://github.com/claussteinmassl/ComfyUI-DiffHDR.git
pip install -r ComfyUI-DiffHDR/requirements.txt # OpenEXR>=3.3 + huggingface_hub
This is the node that actually needs the OpenEXR dependency, so don't skip the pip step - or use ComfyUI Manager, which installs requirements for you. Restart afterwards; the node appears under DiffHDR.
Gotchas
- OpenEXR too old. The pack pins
OpenEXR>=3.3because the writer uses the 3.xFileAPI. An older binding installed in your ComfyUI environment produces an import or attribute error at save time, not at load time.pip install -U "OpenEXR>=3.3"in the ComfyUI venv. - A dark thumbnail. The preview is tonemapped with Reinhard at
preview_exposure, and 0 EV on a scene-linear frame looks dim. Push it to −4 and it'll look like the demo. The file on disk is unaffected. - EXR that "looks wrong" in an 8-bit viewer. Most consumer image viewers apply a gamma curve they have no business applying to linear data. Check in something that knows what chromaticities are - Nuke, Blender's compositor, Affinity,
oiiotooling - before you diagnose a colour problem. - Reading it back into ComfyUI as an 8-bit image. There are EXR-reading nodes out there (the OIIO-style packs, and the ones people reach for when a depth map needs more than 8 bits). If a loader hands you an 8-bit tensor, you've lost the HDR before a single node has run. The classic complaint in that corner is exactly this: someone's depth values come back quantised to 8 bits and they can't tell whether it was the model or the pipeline. Don't be that thread.
- Not a DiffHDR-specific problem, but the pack's problem too: writing a 99-frame 720p float32 EXR sequence is real disk I/O.
zipordwaais your friend, anddwaaat level 45 is visually free on this content.
Inputs (9)
| Name | Type | Default | Description |
|---|---|---|---|
| images | IMAGE | Linear Rec.709 HDR image or frame batch. | |
| filename_prefix | STRING | DiffHDR/hdr | Prefix inside the ComfyUI output folder. Batches are written to <prefix>_<counter>/frame_####.exr. |
| format | COMBO | exr | exr: OpenEXR. hdr: Radiance RGBE (always linear Rec.709; bit depth, compression and colorspace are ignored). |
| bit_depth | COMBO | half | half (16-bit float) or float (32-bit). |
| compression | COMBO | zip | EXR compression. dwaa/dwab are lossy; zip/piz are lossless. |
| dwa_compression_level | FLOAT | 450–500 | DWAA/DWAB compression level (dwaCompressionLevel). Higher = smaller and lossier. 45 is the OpenEXR default. |
| colorspace | COMBO | linear_rec709 | Output primaries. Pixels are converted and chromaticities are written to the header. |
| start_frame | INT | 10–9999999 | Frame number of the first file in a sequence. Numbers are padded to at least four digits, widened for the whole sequence when the last frame needs more. |
| preview_exposure | FLOAT | 0.00-16–16 | Exposure in stops for the tonemapped UI preview. |
Outputs (0)
No outputs