linear-srgb
Fast linear↔sRGB color space conversion with runtime CPU dispatch.
Quick Start
use *;
// Single values (rational polynomial — fast, ≤14 ULP, perfectly monotonic)
let linear = srgb_to_linear;
let srgb = linear_to_srgb;
// Slices (SIMD-accelerated)
let mut values = vec!;
srgb_to_linear_slice;
linear_to_srgb_slice;
// u8 ↔ f32 (image processing)
let linear = srgb_u8_to_linear;
let srgb_byte = linear_to_srgb_u8;
Which Function Should I Use?
| Your situation | Use this |
|---|---|
| One f32 value (fast) | default::srgb_to_linear(x) / default::linear_to_srgb(x) |
| One f32 value (exact) | precise::srgb_to_linear(x) / precise::linear_to_srgb(x) |
| One u8 value | default::srgb_u8_to_linear(x) (LUT, fastest) |
&mut [f32] slice |
default::srgb_to_linear_slice() / default::linear_to_srgb_slice() |
RGBA &mut [f32] (keep alpha) |
default::srgb_to_linear_rgba_slice() / default::linear_to_srgb_rgba_slice() |
&[u8] → &mut [f32] |
default::srgb_u8_to_linear_slice() |
RGBA &[u8] → &mut [f32] |
default::srgb_u8_to_linear_rgba_slice() / linear_to_srgb_u8_rgba_slice() |
&[u16] ↔ &mut [f32] |
default::srgb_u16_to_linear_slice() / default::linear_to_srgb_u16_slice() |
&[f32] → &mut [u8] |
default::linear_to_srgb_u8_slice() |
Inside #[arcane] fn |
tokens::x8::srgb_to_linear_v3() (inlines, no dispatch) |
API Reference
Single Values
use *;
// f32 conversions — rational polynomial (≤14 ULP max, perfectly monotonic)
let linear = srgb_to_linear;
let srgb = linear_to_srgb;
// u8 conversions (LUT-based, zero math)
let linear = srgb_u8_to_linear;
let srgb_byte = linear_to_srgb_u8;
// u16 conversions (LUT-based)
let linear = srgb_u16_to_linear;
let srgb_u16 = linear_to_srgb_u16;
Precise (powf) Conversions
Uses C0-continuous constants that eliminate the IEC spec's piecewise discontinuity. See the Accuracy section for details on how these differ from the IEC textbook values.
use *;
// f32 — exact powf, C0-continuous (6 ULP max)
let linear = srgb_to_linear;
let srgb = linear_to_srgb;
// f64 high-precision
let linear = srgb_to_linear_f64;
// Extended range (HDR/ICC — no clamping)
use ;
let linear = srgb_to_linear_extended;
let srgb = linear_to_srgb_extended;
Slice Processing (Recommended for Batches)
use *;
// In-place f32 conversion (SIMD-accelerated)
let mut values = vec!;
srgb_to_linear_slice;
linear_to_srgb_slice;
// RGBA slices — alpha channel is preserved, only RGB converted
let mut rgba = vec!;
srgb_to_linear_rgba_slice;
assert_eq!; // alpha untouched
// u8 → f32 (LUT-based, extremely fast)
let srgb_bytes: = .collect;
let mut linear = vec!;
srgb_u8_to_linear_slice;
// RGBA u8 → f32 (alpha passed through as a/255, not sRGB-decoded)
let rgba_bytes = vec!;
let mut rgba_linear = vec!;
srgb_u8_to_linear_rgba_slice;
// f32 → u8 (SIMD-accelerated)
let linear_values: = .map.collect;
let mut srgb_bytes = vec!;
linear_to_srgb_u8_slice;
Custom Gamma (Non-sRGB)
For pure power-law gamma without the sRGB linear segment:
use *;
// gamma 2.2 (common in legacy workflows)
let linear = gamma_to_linear;
let encoded = linear_to_gamma;
// Also available for slices
let mut values = vec!;
gamma_to_linear_slice;
LUT for Custom Bit Depths
use ;
// 16-bit linearization (65536 entries)
let lut = new;
let linear = lut.lookup;
// Interpolated encoding
let encode_lut = new;
let srgb = lut_interp_linear_float;
Advanced: Token-Based #[rite] Functions
For zero-overhead SIMD when embedding inside your own #[arcane] code:
use x8;
use arcane;
Module Organization
default— Recommended API. Rational polynomial for f32, LUT for integers, SIMD for slices.precise— Exactpowf()conversions with C0-continuous constants (not IEC textbook). f32/f64, extended range.tokens— Inlineable#[rite]functions for x4/x8/x16 widths. For use inside#[arcane]code.lut— Lookup tables for custom bit depths.tf— Transfer functions: BT.709, PQ, HLG (feature-gated behindtransfer).iec— IEC 61966-2-1 textbook constants for legacy interop (feature-gated).
Feature Flags
[]
= "0.6" # std enabled by default
# no_std (requires alloc for LUT generation)
= { = "0.6", = false }
# HDR transfer functions (BT.709, PQ, HLG)
= { = "0.6", = ["transfer"] }
std(default): Required for runtime SIMD dispatchtransfer: BT.709, PQ, HLG transfer functionsiec: IEC 61966-2-1 textbook sRGB functions for legacy interopalt: Alternative/experimental implementations for benchmarking
Accuracy
Transfer function constants
All code paths use C0-continuous constants derived from the moxcms reference implementation. These adjust the IEC 61966-2-1 offset from 0.055 to 0.055011 and the threshold from 0.04045 to 0.03929, making the piecewise transfer function mathematically continuous (~2.3e-9 gap eliminated).
At u8 precision the two constant sets produce identical values. At u16, the max difference is ~1 LSB near the threshold. See docs/iec.md for a detailed comparison.
For interop with software that uses the original IEC textbook constants, enable the iec feature for linear_srgb::iec::srgb_to_linear / linear_srgb::iec::linear_to_srgb.
Accuracy summary (exhaustive f32 sweep)
| Path | Max ULP | Avg ULP | Monotonic |
|---|---|---|---|
default s→l (rational poly) |
11 | ~0.5 | yes |
default l→s (rational poly) |
14 | ~0.4 | yes |
precise s→l (powf) |
6 | ~0.1 | yes |
precise l→s (powf) |
3 | ~0.1 | yes |
Reference: C0-continuous f64 powf. The scalar rational polynomial evaluates in f64 intermediate precision, guaranteeing perfect monotonicity (zero reversals across all ~1B f32 values in [0, 1]). SIMD paths use f32 evaluation for throughput and are also monotonic within each segment.
License
MIT OR Apache-2.0
AI-Generated Code Notice
Developed with Claude (Anthropic). All code has been reviewed and benchmarked, but verify critical paths for your use case.