Skip to content

hdr

stable

Parse 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, …}
11 functions Graphics
/ filter jk navigate Esc clear
Functions (11)
  1. parse_rgbe_header Parse width, height, format from .hdr text header
  2. rgbe_to_float Convert RGBE bytes to linear float RGB
  3. float_to_rgbe Convert linear float RGB to RGBE bytes
  4. tonemap_reinhard Reinhard tone mapping to 8-bit RGB
  5. tonemap_aces ACES filmic tone mapping to 8-bit RGB
  6. gamma_correct Apply gamma correction to a float value
  7. exposure_adjust Multiply HDR values by an exposure factor
  8. luminance Compute relative luminance (BT.709)
  9. build_rgbe_header Build a minimal Radiance .hdr text header
  10. tonemap_uncharted2 Uncharted 2 filmic tone mapping to 8-bit RGB
  11. 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"])
enespt-br