Expand description
Fast linear↔sRGB color space conversion.
This crate provides efficient conversion between linear light values and sRGB gamma-encoded values, with multiple implementation strategies for different accuracy/performance tradeoffs.
§Module Organization
default— Start here. Rational polynomial for f32, LUT for integers, SIMD for slices.precise— Exactpowf()with C0-continuous constants. f32/f64, extended range. Slower.tokens— Inlineable#[rite]functions for embedding in your own#[arcane]SIMD code.lut— Lookup tables for custom bit depths (10-bit, 12-bit, 16-bit).tf— Transfer functions beyond sRGB: BT.709, PQ, HLG. Requirestransferfeature.iec— IEC 61966-2-1 textbook constants (legacy interop). Requiresiecfeature.
§Quick Start
use linear_srgb::default::{srgb_to_linear, linear_to_srgb};
// Convert sRGB 0.5 to linear
let linear = srgb_to_linear(0.5);
assert!((linear - 0.214).abs() < 0.001);
// Convert back to sRGB
let srgb = linear_to_srgb(linear);
assert!((srgb - 0.5).abs() < 0.001);§Batch Processing (SIMD)
For maximum throughput on slices:
use linear_srgb::default::{srgb_to_linear_slice, linear_to_srgb_slice};
let mut values = vec![0.5f32; 10000];
srgb_to_linear_slice(&mut values); // SIMD-accelerated
linear_to_srgb_slice(&mut values);§Custom Gamma
For non-sRGB gamma (pure power function without linear segment):
use linear_srgb::default::{gamma_to_linear, linear_to_gamma};
let linear = gamma_to_linear(0.5, 2.2); // gamma 2.2
let encoded = linear_to_gamma(linear, 2.2);§LUT-based Conversion
For batch processing with pre-computed lookup tables:
use linear_srgb::default::SrgbConverter;
let conv = SrgbConverter::new(); // Zero-cost, const tables
// Fast 8-bit conversions
let linear = conv.srgb_u8_to_linear(128);
let srgb = conv.linear_to_srgb_u8(linear);§Choosing the Right API
| Use Case | Recommended Function |
|---|---|
| Single f32 value | default::srgb_to_linear |
| Single u8 value | default::srgb_u8_to_linear |
| f32 slice (in-place) | default::srgb_to_linear_slice |
| RGBA f32 slice (alpha-preserving) | default::srgb_to_linear_rgba_slice |
| u8 slice → f32 slice | default::srgb_u8_to_linear_slice |
| RGBA u8 → f32 (alpha-preserving) | default::srgb_u8_to_linear_rgba_slice |
| u16 slice → f32 slice | default::srgb_u16_to_linear_slice |
| Exact f32/f64 (powf) | precise::srgb_to_linear |
| Extended range (HDR) | precise::srgb_to_linear_extended |
Inside #[arcane] | tokens::x8::srgb_to_linear_v3 |
| Custom bit depth LUT | lut::LinearTable16 |
§Clamping and Extended Range
The f32↔f32 conversion functions come in two flavors: clamped (default) and extended (unclamped). Integer paths (u8, u16) always clamp since out-of-range values can’t be represented in the output format.
§Clamped (default) — use for same-gamut pipelines
All functions except the _extended variants clamp inputs to [0, 1]:
negatives become 0, values above 1 become 1.
This is correct whenever the source and destination share the same color space (gamut + transfer function). The typical pipeline:
- Decode sRGB image (u8 → linear f32 via LUT, or f32 via TRC)
- Process in linear light (resize, blur, blend, composite)
- Re-encode to sRGB (linear f32 → sRGB f32 or u8)
In this pipeline, out-of-range values only come from processing artifacts: resize filters with negative lobes (Lanczos, Mitchell, etc.) produce small negatives near dark edges and values slightly above 1.0 near bright edges. These are ringing artifacts, not real colors — clamping is correct.
Float decoders like jpegli can also produce small out-of-range values from YCbCr quantization noise. When the image is sRGB, these are compression artifacts and clamping is correct — gives the same result as decoding to u8 first.
§Extended (unclamped) — use for cross-gamut pipelines
precise::srgb_to_linear_extended and precise::linear_to_srgb_extended
do not clamp. They follow the mathematical sRGB transfer function for all
inputs: negatives pass through the linear segment, values above 1.0 pass
through the power segment.
Use these when the sRGB transfer function is applied to values from a different, wider gamut. A 3×3 matrix converting Rec. 2020 linear or Display P3 linear to sRGB linear can produce values well outside [0, 1]: a saturated Rec. 2020 green maps to deeply negative sRGB red and blue. These are real out-of-gamut colors, not artifacts — clamping destroys information that downstream gamut mapping or compositing may need.
This matters in practice: JPEG and JPEG XL images can carry Rec. 2020 or Display P3 ICC profiles. Phones shoot Rec. 2020 HLG, cameras embed wide-gamut profiles. Decoding such an image and converting to sRGB for display produces out-of-gamut values that should survive until final output.
If a float decoder (jpegli, libjxl) outputs wide-gamut data directly to
f32, the output contains both small compression artifacts and real
out-of-gamut values. The artifacts are tiny; the gamut excursions
dominate. Using _extended preserves both — the artifacts are harmless
noise that vanishes at quantization.
The _extended variants also cover scRGB (float sRGB with values
outside [0, 1] for HDR and wide color) and any pipeline where
intermediate f32 values are not yet at the final output stage.
§Summary
| Function | Range | Pipeline |
|---|---|---|
All default::*_slice, tokens::*, lut::* | [0, 1] | Same-gamut batch processing |
default::srgb_to_linear | [0, 1] | Same-gamut single values |
default::linear_to_srgb | [0, 1] | Same-gamut single values |
precise::srgb_to_linear_extended | Unbounded | Cross-gamut, scRGB, HDR |
precise::linear_to_srgb_extended | Unbounded | Cross-gamut, scRGB, HDR |
| All u8/u16 paths | [0, 1] | Final quantization (clamp inherent) |
No SIMD extended-range variants exist yet. The fast polynomial
approximation is fitted to [0, 1] and produces garbage outside that
domain. Extended-range SIMD would use pow instead of the polynomial
(~3× slower, still faster than scalar for linear_to_srgb). For batch
extended-range conversion today, loop over the precise _extended
functions.
§Feature Flags
std(default) — Enable runtime SIMD dispatch. Required for slice functions.avx512(default) — Enable AVX-512 code paths andtokens::x16module.transfer— BT.709, PQ, and HLG transfer functions intfandtokens.iec— IEC 61966-2-1 textbook sRGB functions for legacy interop.alt— Alternative implementations for benchmarking (not stable API).unsafe_simd— No-op (kept for backward compatibility, will be removed in 0.7).
§no_std Support
This crate is no_std compatible (requires alloc for LUT generation).
Disable the std feature:
linear-srgb = { version = "0.6", default-features = false }Modules§
- default
- Recommended API with optimal implementations for each use case.
- iec
- IEC 61966-2-1:1999 textbook sRGB transfer functions.
- lut
- Lookup table types for sRGB conversion.
- precise
- Exact
powf()-based conversions with C0-continuous constants. - tf
- Transfer functions: sRGB, BT.709, PQ (ST 2084), HLG (ARIB STD-B67).
- tokens
- Inlineable
#[rite]functions for embedding in your own#[arcane]code.