ultrahdr
Pure Rust implementation of Ultra HDR (gain map HDR) encoding and decoding.
Ultra HDR is a backward-compatible HDR image format that embeds a gain map in a standard JPEG, allowing HDR-capable displays to reconstruct the full HDR image while remaining viewable as SDR on legacy displays.
Crates
| Crate | Description |
|---|---|
ultrahdr-rs |
Full encoder/decoder with jpegli-rs JPEG codec |
ultrahdr-core |
Pure math and metadata - no codec dependency, WASM-compatible |
Features
- Encode: Create Ultra HDR JPEGs from HDR images (with optional SDR input)
- Decode: Extract and apply gain maps to reconstruct HDR content
- Tone mapping: Automatic SDR generation from HDR-only input
- Adaptive tonemapping: Learn tone curves from existing HDR/SDR pairs
- Metadata: Full XMP (hdrgm namespace) and ISO 21496-1 support
- Pure Rust: No C dependencies, uses jpegli-rs for JPEG
- WASM:
ultrahdr-corecompiles to WebAssembly
Usage
Encoding
use ;
// Create HDR image (linear float RGB, BT.2020 gamut)
let hdr_image = RawImage ;
// Encode to Ultra HDR JPEG (SDR is auto-generated via tone mapping)
let ultrahdr_jpeg = new
.set_hdr_image
.set_quality // base quality, gainmap quality
.set_gainmap_scale // 1/4 resolution gain map
.set_target_display_peak // nits
.encode?;
write?;
Decoding
use ;
let data = read?;
let decoder = new?;
if decoder.is_ultrahdr
Adaptive Tonemapping (Preserve Artistic Intent)
When editing HDR content, use AdaptiveTonemapper to learn the original tone curve and reproduce it:
use ;
// Learn tone curve from original HDR/SDR pair
let tonemapper = fit?;
// Apply to edited HDR - preserves the original artistic intent
let new_sdr = tonemapper.apply?;
Supported Formats
Input (HDR)
Rgba32F- Linear float RGBARgba16F- Half-float RGBAP010- 10-bit YUV (BT.2020)
Input (SDR)
Rgba8- 8-bit sRGB RGBARgb8- 8-bit sRGB RGB
Output (HDR)
LinearFloat- Linear RGB floatPq1010102- PQ-encoded 10-bit packedSrgb8- Clipped to SDR range
Metadata Formats
Both XMP and ISO 21496-1 metadata are supported for maximum compatibility:
- XMP: Adobe hdrgm namespace, embedded in APP1 marker
- ISO 21496-1: Binary format with fractions, typically in APP2
Transfer Functions
- sRGB (IEC 61966-2-1)
- PQ/ST.2084 (HDR10)
- HLG (ITU-R BT.2100)
Color Gamuts
- BT.709 (sRGB)
- Display P3
- BT.2100/BT.2020
Streaming APIs (Low Memory)
For memory-constrained environments, ultrahdr-core provides streaming APIs that process images row-by-row:
use ;
| Type | Direction | Memory | Use Case |
|---|---|---|---|
RowDecoder |
SDR+gainmap→HDR | Full gainmap in RAM | Gainmap fits in memory |
StreamDecoder |
SDR+gainmap→HDR | 16-row ring buffer | Parallel JPEG decode |
RowEncoder |
HDR+SDR→gainmap | Synchronized batches | Same-rate inputs |
StreamEncoder |
HDR+SDR→gainmap | Independent buffers | Parallel decode sources |
Streaming Decode Example
use ;
use ;
// Load gainmap fully, then stream SDR rows
let mut decoder = new?;
// Process in 16-row batches (JPEG MCU alignment)
for batch_start in .step_by
Memory Savings (4K image)
| API | Peak Memory |
|---|---|
| Full decode | ~166 MB |
| Streaming (16 rows) | ~2 MB |
Using ultrahdr-core with jpegli-rs Directly
For more control, use ultrahdr-core (math + metadata only) with jpegli-rs for JPEG operations:
Encoding UltraHDR
use ;
use ;
// 1. Compute gain map from HDR + SDR
let config = default;
let = compute_gainmap?;
// 2. Encode gain map to JPEG
let gainmap_jpeg = ;
// 3. Generate XMP metadata
let xmp = generate_xmp;
// 4. Encode UltraHDR with embedded gain map
let ultrahdr = ;
Decoding UltraHDR
use ;
use ;
// 1. Decode with metadata preservation
let decoded = new
.preserve
.decode?;
let extras = decoded.extras.expect;
// 2. Parse XMP metadata
let xmp_str = extras.xmp.expect;
let = parse_xmp?;
// 3. Decode gain map JPEG
let gainmap_jpeg = extras.gainmap.expect;
let gainmap_decoded = new.decode?;
// 4. Build RawImage and GainMap structs
let sdr = from_data?;
let gainmap = GainMap ;
// 5. Apply gain map to reconstruct HDR
let hdr = apply_gainmap?;
Lossless Round-Trip (Edit SDR, Preserve Gain Map)
// Decode
let decoded = new.preserve.decode?;
let extras = decoded.extras.unwrap;
// Edit SDR pixels...
let edited_sdr: = /* your edits */;
// Re-encode preserving XMP + gainmap
let encoder_segments = extras.to_encoder_segments;
let cfg = ycbcr
.with_segments; // Preserves XMP + gainmap
let mut enc = cfg.encode_from_bytes?;
enc.push_packed?;
let re_encoded = enc.finish?;
Cooperative Cancellation
Long-running operations accept an impl Stop parameter from the enough crate for cooperative cancellation:
use ;
use AtomicStop;
// Simple usage - no cancellation
let = compute_gainmap?;
// With cancellation support
let stop = new;
let stop_clone = stop.clone;
spawn;
let result = compute_gainmap;
License
Apache-2.0
AI-Generated Code Notice
This library was developed with assistance from Claude (Anthropic). The implementation has been tested against reference Ultra HDR images and passes comprehensive unit tests. Not all code has been manually reviewed - please review critical paths before production use.