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// ============================================================================
125// Internal modules
126// ============================================================================
127
128mod mlaf;
129mod targets;
130
131// Internal fast math for SIMD (not public API)
132pub(crate) mod fast_math;
133
134// Pre-computed const lookup tables (embedded in binary)
135mod const_luts;
136
137// Alternative/experimental implementations (for benchmarking)
138#[cfg(feature = "alt")]
139pub mod alt;
140
141// ============================================================================
142// Tests
143// ============================================================================
144
145#[cfg(test)]
146mod tests {
147 use crate::default::*;
148
149 #[cfg(not(feature = "std"))]
150 use alloc::vec::Vec;
151
152 #[test]
153 fn test_api_consistency() {
154 // Ensure direct and LUT-based conversions are consistent
155 let conv = SrgbConverter::new();
156
157 for i in 0..=255u8 {
158 let direct = srgb_u8_to_linear(i);
159 let lut = conv.srgb_u8_to_linear(i);
160 assert!(
161 (direct - lut).abs() < 1e-5,
162 "Mismatch at {}: direct={}, lut={}",
163 i,
164 direct,
165 lut
166 );
167 }
168 }
169
170 #[test]
171 fn test_slice_conversion() {
172 let mut values: Vec<f32> = (0..=10).map(|i| i as f32 / 10.0).collect();
173 let original = values.clone();
174
175 srgb_to_linear_slice(&mut values);
176 linear_to_srgb_slice(&mut values);
177
178 for (i, (orig, conv)) in original.iter().zip(values.iter()).enumerate() {
179 assert!(
180 (orig - conv).abs() < 1e-5,
181 "Slice roundtrip failed at {}: {} -> {}",
182 i,
183 orig,
184 conv
185 );
186 }
187 }
188}