7.9.7. STBIMAGE-07 — HDR Images

This tutorial covers high-dynamic-range images: float pixel buffers whose values are not clamped to [0, 1], saved and loaded through the Radiance .hdr format.

7.9.7.1. Creating a Float Image

HDR images store float pixels (bpc = 4), so a channel can be brighter than full white. make_image(w, h, channels, 4) allocates the float buffer; the float overload of with_pixels exposes it as array<float>#:

require stbimage/stbimage_boost

var img = make_image(32, 32, 3, 4)
img |> with_pixels() $(var px : array<float>#) {
    for (i in range(32 * 32)) {
        px[i * 3 + 0] = 2.5        // red brighter than full white
        px[i * 3 + 1] = 1.0
        px[i * 3 + 2] = 0.5
    }
}

7.9.7.2. Saving and Loading

save() selects the encoder from the file extension; .hdr writes Radiance RGBE. load_hdr() reads it back as float and sets bpc = 4. is_hdr() reports whether a file on disk is HDR:

let (ok, err) = img.save("out.hdr")
let detected = is_hdr("out.hdr")        // true

var reloaded : Image
let (ok2, err2) = reloaded.load_hdr("out.hdr")
// reloaded.bpc == 4, reloaded.channels == 3

7.9.7.3. Round-Trip Accuracy

Radiance .hdr is RGBE: an 8-bit mantissa per channel plus one shared exponent. It is therefore slightly lossy — per-channel precision is about max_channel / 256 — but HDR magnitudes (values above 1.0) survive:

var maxerr = 0.0
img |> with_pixels() $(var a : array<float>#) {
    reloaded |> with_pixels() $(var b : array<float>#) {
        for (i in range(length(a))) {
            maxerr = max(maxerr, abs(a[i] - b[i]))
        }
    }
}
// maxerr stays within the RGBE tolerance

Values that are exact multiples of a power of two (like the 2.5 / 1.0 / 0.5 above) reconstruct exactly; arbitrary values pick up a small RGBE rounding error.

See also

Full source: tutorials/dasStbImage/07_hdr.das

Previous tutorial: STBIMAGE-06 — TrueType Fonts