hdr
stableParse Radiance HDR (.hdr) file headers, convert between RGBE and float color, and apply tone mapping operators to HDR pixel data.
use plugin hdr::{parse_rgbe_header, rgbe_to_float, float_to_rgbe, …} Functions (11)
- parse_rgbe_header Parse width, height, format from .hdr text header
- rgbe_to_float Convert RGBE bytes to linear float RGB
- float_to_rgbe Convert linear float RGB to RGBE bytes
- tonemap_reinhard Reinhard tone mapping to 8-bit RGB
- tonemap_aces ACES filmic tone mapping to 8-bit RGB
- gamma_correct Apply gamma correction to a float value
- exposure_adjust Multiply HDR values by an exposure factor
- luminance Compute relative luminance (BT.709)
- build_rgbe_header Build a minimal Radiance .hdr text header
- tonemap_uncharted2 Uncharted 2 filmic tone mapping to 8-bit RGB
- clamp_hdr Clamp linear HDR channel values to a range
Overview
hdr is a small, stateless toolkit for working with high-dynamic-range color and the Radiance .hdr (RGBE) file format. There are no handles or objects to manage: every function is a plain call that takes numbers (or a header string) and returns a number, a string, or a {r, g, b, ...} table. The core idea is that HDR pixels carry linear light values that can exceed 1.0, so you typically decode RGBE bytes into floats, adjust exposure, then tone-map and gamma-correct the result back down into the displayable 0–255 range.
Reach for hdr when you need to read or write .hdr headers, round-trip pixels between the compact RGBE byte encoding and linear floats, or compress bright HDR light into an 8-bit image using a tone-mapping operator. The three tone-mappers — tonemap_reinhard, tonemap_aces, and tonemap_uncharted2 — share the same (r, g, b, exposure) signature, so you can swap operators freely to compare their looks.
Common patterns
Decode an RGBE pixel and tone-map it for display:
use plugin hdr::{rgbe_to_float, tonemap_aces}
let lin = rgbe_to_float(200, 180, 160, 140)
let out = tonemap_aces(lin["rf"], lin["gf"], lin["bf"], 1.0)
print("display rgb: {out["r"]}, {out["g"]}, {out["b"]}")
Compare three tone-mapping operators on the same bright pixel:
use plugin hdr::{tonemap_reinhard, tonemap_aces, tonemap_uncharted2}
let r = 4.0
let g = 2.5
let b = 1.0
print("reinhard: {tonemap_reinhard(r, g, b, 1.0)["r"]}")
print("aces: {tonemap_aces(r, g, b, 1.0)["r"]}")
print("uncharted2: {tonemap_uncharted2(r, g, b, 1.0)["r"]}")
Build a header and encode a clamped float pixel for writing a .hdr file:
use plugin hdr::{build_rgbe_header, clamp_hdr, float_to_rgbe}
let header = build_rgbe_header(1920, 1080)
let safe = clamp_hdr(2.5, -0.1, 1.8, 0.0, 16.0)
let enc = float_to_rgbe(safe["r"], safe["g"], safe["b"])
print(header)
print("rgbe: {enc["r"]}, {enc["g"]}, {enc["b"]}, {enc["e"]}")
Parse width, height, format from .hdr text header
Parses the text header of a Radiance .hdr file and returns {width, height, format}. The resolution line (-Y <h> +X <w>) and FORMAT= line are recognized.
use plugin hdr::{build_rgbe_header, parse_rgbe_header}
let info = parse_rgbe_header(build_rgbe_header(640, 480))
print("{info["width"]}x{info["height"]}")
print(info["format"])
build_rgbe_header and parse_rgbe_header round-trip, so you can read back the dimensions you just wrote:
use plugin hdr::{build_rgbe_header, parse_rgbe_header}
let info = parse_rgbe_header(build_rgbe_header(800, 600))
print("{info["width"]}x{info["height"]} ({info["format"]})")
Convert RGBE bytes to linear float RGB
Converts a single RGBE-encoded pixel (four 8-bit integers) to linear floating-point {rf, gf, bf} using the standard RGBE scale formula.
use plugin hdr::{rgbe_to_float}
let px = rgbe_to_float(200, 180, 160, 140)
print(px["rf"])
print(px["gf"])
A zero exponent encodes black, so the decoded float color is all zeros:
use plugin hdr::{rgbe_to_float}
let black = rgbe_to_float(0, 0, 0, 0)
print("{black["rf"]}, {black["gf"]}, {black["bf"]}")
Convert linear float RGB to RGBE bytes
Converts linear float RGB values to RGBE encoding {r, g, b, e} (all integers 0–255). Returns all zeros if the color is below 1e-32.
use plugin hdr::{float_to_rgbe}
let enc = float_to_rgbe(1.5, 0.8, 0.3)
print(enc["e"])
float_to_rgbe and rgbe_to_float are inverses, so you can round-trip a float color through the byte encoding:
use plugin hdr::{float_to_rgbe, rgbe_to_float}
let enc = float_to_rgbe(1.5, 0.8, 0.3)
let back = rgbe_to_float(enc["r"], enc["g"], enc["b"], enc["e"])
print("{back["rf"]}, {back["gf"]}, {back["bf"]}")
Reinhard tone mapping to 8-bit RGB
Applies Reinhard tone mapping (c / (1 + c)) followed by sRGB gamma to linear HDR RGB values scaled by exposure. Returns {r, g, b} as integers 0–255.
use plugin hdr::{tonemap_reinhard}
let px = tonemap_reinhard(2.5, 1.2, 0.8, 1.0)
print("{px["r"]}, {px["g"]}, {px["b"]}")
Raising exposure brightens the result before the curve compresses it back into range:
use plugin hdr::{tonemap_reinhard}
let dim = tonemap_reinhard(0.4, 0.3, 0.2, 1.0)
let bright = tonemap_reinhard(0.4, 0.3, 0.2, 4.0)
print("dim r: {dim["r"]}, bright r: {bright["r"]}")
ACES filmic tone mapping to 8-bit RGB
Applies the ACES filmic approximation tone mapping curve to HDR values scaled by exposure. Returns {r, g, b} as integers 0–255.
use plugin hdr::{tonemap_aces}
let px = tonemap_aces(3.0, 2.0, 1.0, 0.8)
print("{px["r"]}, {px["g"]}, {px["b"]}")
Apply gamma correction to a float value
Applies gamma correction: value ^ (1 / gamma). Use gamma = 2.2 for standard sRGB output.
use plugin hdr::{gamma_correct}
let corrected = gamma_correct(0.5, 2.2)
print(corrected)
Multiply HDR values by an exposure factor
Multiplies linear HDR channel values by the exposure factor without tone mapping. Returns {r, g, b} as floats.
use plugin hdr::{exposure_adjust}
let boosted = exposure_adjust(0.4, 0.3, 0.2, 2.0)
print(boosted["r"])
Compute relative luminance (BT.709)
Computes relative luminance using ITU-R BT.709 coefficients: 0.2126*r + 0.7152*g + 0.0722*b.
use plugin hdr::{luminance}
let lum = luminance(0.8, 0.6, 0.2)
print(lum)
Because green is weighted most heavily, a green pixel reads brighter than an equally-valued blue one:
use plugin hdr::{luminance}
print("green: {luminance(0.0, 1.0, 0.0)}")
print("blue: {luminance(0.0, 0.0, 1.0)}")
Build a minimal Radiance .hdr text header
Builds a minimal Radiance .hdr text header string with FORMAT=32-bit_rle_rgbe and the standard resolution line.
use plugin hdr::{build_rgbe_header}
let hdr_header = build_rgbe_header(1920, 1080)
print(hdr_header)
Uncharted 2 filmic tone mapping to 8-bit RGB
Applies the Uncharted 2 filmic tone mapping curve (John Hable) to HDR values scaled by exposure. Returns {r, g, b} as integers 0–255.
use plugin hdr::{tonemap_uncharted2}
let px = tonemap_uncharted2(4.0, 2.5, 1.0, 1.0)
print("{px["r"]}, {px["g"]}, {px["b"]}")
Clamp linear HDR channel values to a range
Clamps each linear HDR channel to the range [min, max]. Returns {r, g, b} as floats. Useful before encoding to prevent out-of-range values.
use plugin hdr::{clamp_hdr}
let clamped = clamp_hdr(2.5, -0.1, 1.8, 0.0, 1.0)
print(clamped["r"])