Linear → sRGB
Make a linear plate look like a picture again
- images
- IMAGE
This is the smallest node in the pack: one IMAGE in, one IMAGE out, no widgets. It takes scene-linear values and applies the sRGB transfer function to them, which is the difference between "my plate" and "my plate as your monitor would show it."
Why you'd want that mid-graph, when Load EXR Plate already has a colorspace dropdown, is the whole article. Three real cases:
- Something upstream is linear and the model is next. Diffusion and video models were trained on display-referred 0–1 images. Hand them linear and you get a washed-out, low-contrast generation that no amount of CFG fixes.
- You want to look at a linear tensor. ComfyUI has no display transform - Preview Image and Save Image encode the tensor's values straight out. A linear plate previewed that way is the dark, flat thing people post about. Wrapping it in this node is the correct fix, and it's why the pack's own loader does this for its preview.
- The linear data didn't come from an EXR loader. A VAE decode you're choosing to treat as linear, a depth or normal pass, an HDR image from an OpenImageIO-based pack - the dropdown on the loader can't help you there.
How it works
It's the exact sRGB transfer function, applied per pixel in float32 numpy: for values above 0.0031308, 1.055 * x^(1/2.4) - 0.055; below that, the linear segment 12.92 * x. That's the same curve Nuke's sRGB colorspace uses, which matters if you're roundtripping - no LUT, no approximation, and it inverts exactly with sRGB → Linear up to float32 precision.
Two mechanical details worth knowing:
- It clamps to 0–1 on the way out. Even though the transfer function itself happily returns >1 for input >1. So this node is not an HDR path: superwhites become white here. If your plate has values above 1 and they need to survive, use
linear -> Reinhard (HDR-safe)on the loader instead - that maps[0, ∞)into[0,1)and the inverse recovers the original values exactly. - It runs on the CPU. The tensor is copied to numpy, transformed, and copied back, so it's a sync point in a GPU-heavy graph. Irrelevant for a few frames, measurable if you're doing this on thousands.
It's genuinely cheap otherwise - the numbers that make the whole pack make sense are in this operation: linear mid-grey 0.18 lands on sRGB 0.46. If your midtones are darker than you expect after a generation, you double-decoded somewhere. If they're washed out and hazy, you probably skipped a decode.
Wiring it
Input is images; output is a plain IMAGE. Sandwiched properly it looks like: Load EXR Plate (keep linear - you're doing the conversion yourself now) → Linear → sRGB → model/sampler → sRGB → Linear → Save Plate (keep linear). Notice that's the manual version of what the loader and saver dropdowns do automatically, which is why most pipelines never drop these two nodes - and also why the classic failure mode is having both. If the loader is already set to linear -> sRGB and you add this node after it, you've gamma-encoded twice: soft, grey, low-contrast, and it looks like a model problem.
Installing it
Same pack - ComfyUI Manager → search EXR Plate (Samuelsujan's EXR Plate) → Install, or:
cd ComfyUI/custom_nodes
git clone https://github.com/Samuelsujan/comfyui-exr-plate
python -m pip install -r comfyui-exr-plate/requirements.txt # opencv-python>=4.6, numpy
Restart and hard-refresh the browser. Nodes show up under Add Node → EXR Plate.
Where people get burned
- Nothing visible happens because you're viewing it in a node that already transforms. Image Comparer and the built-in previews just show tensor values, so the change is visible there - but a video node that expects sRGB output and gets linear, or vice versa, will look wrong downstream while the preview looks fine.
- Expected it to fix a crushed plate. This is a transfer-function conversion, not a grade. If the data was clipped to 0–1 on the way in, encoding it to sRGB doesn't bring anything back - it just redistributes what's left.
- Treating it as "color management." It's one transform, hardcoded to sRGB. If your shot is managed by an OCIO/ACES config with a different working space, you want the OCIO-node packs, not a fixed curve - otherwise your element will still be the odd one out in the comp.
Inputs (1)
| Name | Type | Default | Description |
|---|---|---|---|
| images | IMAGE | — |
Outputs (1)
| Name | Type | Description |
|---|---|---|
| IMAGE | IMAGE | — |