linear-srgb
Fast linear↔sRGB color space conversion with runtime CPU dispatch.
Quick Start
use *;
// Single values
let linear = srgb_to_linear;
let srgb = linear_to_srgb;
// Fast polynomial (~4x faster than powf, 294 ULP max near black, <4 ULP in upper half)
let linear = srgb_to_linear_fast;
let srgb = linear_to_srgb_fast;
// Slices (SIMD-accelerated, polynomial)
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 (exact) | srgb_to_linear(x) / linear_to_srgb(x) |
| One f32 value (fast) | srgb_to_linear_fast(x) / linear_to_srgb_fast(x) |
| One u8 value | srgb_u8_to_linear(x) (LUT, fastest) |
&mut [f32] slice |
srgb_to_linear_slice() / linear_to_srgb_slice() |
&[u8] → &mut [f32] |
srgb_u8_to_linear_slice() |
&[f32] → &mut [u8] |
linear_to_srgb_u8_slice() |
Inside #[arcane] |
default::inline::* (no dispatch) |
| Standalone x8 call | srgb_to_linear_x8() (has dispatch, that's fine) |
API Reference
Single Values
use *;
// f32 conversions — powf (exact reference)
let linear = srgb_to_linear;
let srgb = linear_to_srgb;
// f32 conversions — polynomial (~4x faster, 294 ULP max near black, <4 ULP in upper half)
let linear = srgb_to_linear_fast;
let srgb = linear_to_srgb_fast;
// f64 high-precision
let linear = srgb_to_linear_f64;
// u8 conversions (LUT-based)
let linear = srgb_u8_to_linear; // u8 → f32
let srgb_byte = linear_to_srgb_u8; // f32 → u8
// u16 conversions (LUT-based)
let linear = srgb_u16_to_linear; // u16 → f32
let srgb_u16 = linear_to_srgb_u16; // f32 → u16
Slice Processing (Recommended for Batches)
use *;
// In-place f32 conversion (SIMD-accelerated)
let mut values = vec!;
srgb_to_linear_slice; // Modifies in-place
linear_to_srgb_slice;
// u8 → f32 (LUT-based, extremely fast)
let srgb_bytes: = .collect;
let mut linear = vec!;
srgb_u8_to_linear_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;
Extended Range (HDR / Wide Gamut)
The standard functions clamp to [0, 1]. For cross-gamut pipelines (Rec. 2020 → sRGB, scRGB, HDR):
use ;
let linear = srgb_to_linear_extended; // Preserves negatives
let srgb = linear_to_srgb_extended; // Preserves >1.0
See crate docs for when clamped vs extended is appropriate.
LUT for Custom Bit Depths
use ;
// 16-bit linearization (65536 entries)
let lut = new;
let linear = lut.lookup; // Direct lookup
// Interpolated encoding
let encode_lut = new;
let srgb = lut_interp_linear_float;
Advanced: Token-Based Dispatch (mage feature)
For zero-overhead SIMD when you control the dispatch point:
use mage;
// Obtain a token once, pass to all calls
srgb_to_linear_slice; // Uses archmage incant! internally
Advanced: Inlineable #[rite] Functions (rites feature)
For embedding inside your own #[arcane] code with no dispatch overhead:
use x8;
use arcane;
Module Organization
default— Recommended API. Re-exports optimal implementations.default::inline— Dispatch-freewide::f32x8variants for use inside your own SIMD code.simd— Full SIMD API with_dispatchand_inlinevariants.scalar— Single-value functions. Includes_fast(polynomial) and_extended(unclamped) variants.lut— Lookup tables for custom bit depths.mage— Token-based dispatch via archmage (feature-gated).rites— Inlineable#[rite]functions for x4/x8/x16 widths (feature-gated).
Feature Flags
[]
= "0.5" # std enabled by default
# no_std (requires alloc for LUT generation)
= { = "0.5", = false }
# Token-based dispatch (zero overhead)
= { = "0.5", = ["mage"] }
# Inlineable rites for embedding in #[arcane] code
= { = "0.5", = ["rites"] }
std(default): Required for runtime SIMD dispatchmage: Token-based API using archmagerites: Inlineable#[rite]functions for x4/x8/x16alt: Alternative/experimental implementations for benchmarkingunsafe_simd: Union-based bit manipulation, unchecked indexing
Accuracy
Implements IEC 61966-2-1:1999 sRGB transfer functions with:
- C0-continuous piecewise function (no discontinuity at threshold)
- Constants derived from moxcms reference implementation
- Scalar
powf: exact to f32/f64 precision - Polynomial (
_fast, SIMD): 294 ULP max near threshold, 2-3 ULP in upper half (exhaustive f32 sweep) - f32 roundtrip: ~1e-5 accuracy
- f64 roundtrip: ~1e-10 accuracy
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.