linear_srgb/lib.rs
1//! Fast linear↔sRGB color space conversion.
2//!
3//! This crate provides efficient conversion between linear light values and
4//! sRGB gamma-encoded values following the IEC 61966-2-1:1999 standard.
5//!
6//! # Module Organization
7//!
8//! - [`default`] - **Recommended API** with optimal implementations for each use case
9//! - [`simd`] - SIMD-accelerated functions with full control over dispatch
10//! - [`scalar`] - Single-value conversion functions (f32/f64)
11//! - [`lut`] - Lookup table types for custom bit depths
12//!
13//! # Quick Start
14//!
15//! ```rust
16//! use linear_srgb::default::{srgb_to_linear, linear_to_srgb};
17//!
18//! // Convert sRGB 0.5 to linear
19//! let linear = srgb_to_linear(0.5);
20//! assert!((linear - 0.214).abs() < 0.001);
21//!
22//! // Convert back to sRGB
23//! let srgb = linear_to_srgb(linear);
24//! assert!((srgb - 0.5).abs() < 0.001);
25//! ```
26//!
27//! # Batch Processing (SIMD)
28//!
29//! For maximum throughput on slices:
30//!
31//! ```rust
32//! use linear_srgb::default::{srgb_to_linear_slice, linear_to_srgb_slice};
33//!
34//! let mut values = vec![0.5f32; 10000];
35//! srgb_to_linear_slice(&mut values); // SIMD-accelerated
36//! linear_to_srgb_slice(&mut values);
37//! ```
38//!
39//! # Custom Gamma
40//!
41//! For non-sRGB gamma (pure power function without linear segment):
42//!
43//! ```rust
44//! use linear_srgb::default::{gamma_to_linear, linear_to_gamma};
45//!
46//! let linear = gamma_to_linear(0.5, 2.2); // gamma 2.2
47//! let encoded = linear_to_gamma(linear, 2.2);
48//! ```
49//!
50//! # LUT-based Conversion
51//!
52//! For batch processing with pre-computed lookup tables:
53//!
54//! ```rust
55//! use linear_srgb::default::SrgbConverter;
56//!
57//! let conv = SrgbConverter::new(); // Zero-cost, const tables
58//!
59//! // Fast 8-bit conversions
60//! let linear = conv.srgb_u8_to_linear(128);
61//! let srgb = conv.linear_to_srgb_u8(linear);
62//! ```
63//!
64//! # Choosing the Right API
65//!
66//! | Use Case | Recommended Function |
67//! |----------|---------------------|
68//! | Single f32 value | [`default::srgb_to_linear`] |
69//! | Single u8 value | [`default::srgb_u8_to_linear`] |
70//! | f32 slice (in-place) | [`default::srgb_to_linear_slice`] |
71//! | u8 slice → f32 slice | [`default::srgb_u8_to_linear_slice`] |
72//! | Manual SIMD (8 values) | [`default::srgb_to_linear_x8`] |
73//! | Inside `#[multiversed]` | [`default::inline::srgb_to_linear_x8`] |
74//! | Custom bit depth LUT | [`lut::LinearTable16`] |
75//!
76//! # Feature Flags
77//!
78//! - `std` (default): Enable std library support
79//! - `unsafe_simd`: Enable unsafe optimizations for maximum performance
80//!
81//! # `no_std` Support
82//!
83//! This crate is `no_std` compatible. Disable the `std` feature:
84//!
85//! ```toml
86//! linear-srgb = { version = "0.2", default-features = false }
87//! ```
88
89#![cfg_attr(not(feature = "std"), no_std)]
90#![cfg_attr(not(feature = "unsafe_simd"), deny(unsafe_code))]
91#![warn(missing_docs)]
92
93#[cfg(not(feature = "std"))]
94extern crate alloc;
95
96#[cfg(all(test, not(feature = "std")))]
97extern crate std;
98
99// ============================================================================
100// Public modules
101// ============================================================================
102
103/// Recommended API with optimal implementations for each use case.
104///
105/// See module documentation for details.
106pub mod default;
107
108/// Lookup table types for sRGB conversion.
109///
110/// Provides both build-time const tables ([`SrgbConverter`](lut::SrgbConverter))
111/// and runtime-generated tables for custom bit depths.
112pub mod lut;
113
114/// SIMD-accelerated conversion functions.
115///
116/// Provides full control over CPU dispatch with `_dispatch` and `_inline` variants.
117pub mod simd;
118
119/// Scalar (single-value) conversion functions.
120///
121/// Direct computation without SIMD. Best for individual value conversions.
122pub mod scalar;
123
124/// Token-based API using archmage for zero dispatch overhead.
125///
126/// This module provides an alternative API using archmage tokens for users who
127/// want to avoid per-call dispatch overhead. Obtain a token once at startup,
128/// then pass it to all conversion functions.
129///
130/// Requires the `mage` feature.
131#[cfg(feature = "mage")]
132pub mod mage;
133
134// ============================================================================
135// Internal modules
136// ============================================================================
137
138mod mlaf;
139mod targets;
140
141// Internal fast math for SIMD (not public API)
142pub(crate) mod fast_math;
143
144// Pre-computed const lookup tables (embedded in binary)
145mod const_luts;
146
147// Alternative/experimental implementations (for benchmarking)
148#[cfg(feature = "alt")]
149pub mod alt;
150
151// ============================================================================
152// Tests
153// ============================================================================
154
155#[cfg(test)]
156mod tests {
157 use crate::default::*;
158
159 #[cfg(not(feature = "std"))]
160 use alloc::vec::Vec;
161
162 #[test]
163 fn test_api_consistency() {
164 // Ensure direct and LUT-based conversions are consistent
165 let conv = SrgbConverter::new();
166
167 for i in 0..=255u8 {
168 let direct = srgb_u8_to_linear(i);
169 let lut = conv.srgb_u8_to_linear(i);
170 assert!(
171 (direct - lut).abs() < 1e-5,
172 "Mismatch at {}: direct={}, lut={}",
173 i,
174 direct,
175 lut
176 );
177 }
178 }
179
180 #[test]
181 fn test_slice_conversion() {
182 let mut values: Vec<f32> = (0..=10).map(|i| i as f32 / 10.0).collect();
183 let original = values.clone();
184
185 srgb_to_linear_slice(&mut values);
186 linear_to_srgb_slice(&mut values);
187
188 for (i, (orig, conv)) in original.iter().zip(values.iter()).enumerate() {
189 assert!(
190 (orig - conv).abs() < 1e-5,
191 "Slice roundtrip failed at {}: {} -> {}",
192 i,
193 orig,
194 conv
195 );
196 }
197 }
198}