use crate::mlaf::fmla;
#[allow(unused_imports)]
use num_traits::Float;
const SRGB_LINEAR_THRESHOLD: f64 = 0.04045;
const SRGB_LINEAR_THRESHOLD_F32: f32 = SRGB_LINEAR_THRESHOLD as f32;
const LINEAR_THRESHOLD: f64 = 0.003_130_804_953_560_372;
const LINEAR_THRESHOLD_F32: f32 = LINEAR_THRESHOLD as f32;
const LINEAR_SCALE: f64 = 1.0 / 12.92;
const LINEAR_SCALE_F32: f32 = LINEAR_SCALE as f32;
const SRGB_A: f64 = 0.055;
const SRGB_A_F32: f32 = SRGB_A as f32;
const SRGB_A_PLUS_1: f64 = 1.055;
const SRGB_A_PLUS_1_F32: f32 = SRGB_A_PLUS_1 as f32;
const GAMMA: f64 = 2.4;
const INV_GAMMA: f64 = 1.0 / GAMMA;
const INV_GAMMA_F32: f32 = INV_GAMMA as f32;
#[inline]
pub fn srgb_to_linear_f64(gamma: f64) -> f64 {
if gamma < 0.0 {
0.0
} else if gamma < SRGB_LINEAR_THRESHOLD {
gamma * LINEAR_SCALE
} else if gamma < 1.0 {
((gamma + SRGB_A) / SRGB_A_PLUS_1).powf(GAMMA)
} else {
1.0
}
}
#[inline]
pub fn srgb_to_linear(gamma: f32) -> f32 {
if gamma < 0.0 {
0.0
} else if gamma < SRGB_LINEAR_THRESHOLD_F32 {
gamma * LINEAR_SCALE_F32
} else if gamma < 1.0 {
((gamma + SRGB_A_F32) / SRGB_A_PLUS_1_F32).powf(GAMMA as f32)
} else {
1.0
}
}
#[inline]
pub fn linear_to_srgb_f64(linear: f64) -> f64 {
if linear < 0.0 {
0.0
} else if linear < LINEAR_THRESHOLD {
linear * 12.92
} else if linear < 1.0 {
fmla(SRGB_A_PLUS_1, linear.powf(INV_GAMMA), -SRGB_A)
} else {
1.0
}
}
#[inline]
pub fn linear_to_srgb(linear: f32) -> f32 {
if linear < 0.0 {
0.0
} else if linear < LINEAR_THRESHOLD_F32 {
linear * 12.92
} else if linear < 1.0 {
fmla(SRGB_A_PLUS_1_F32, linear.powf(INV_GAMMA_F32), -SRGB_A_F32)
} else {
1.0
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_iec_boundaries() {
assert_eq!(srgb_to_linear(-0.1), 0.0);
assert_eq!(srgb_to_linear(0.0), 0.0);
assert_eq!(srgb_to_linear(1.0), 1.0);
assert_eq!(srgb_to_linear(1.1), 1.0);
assert_eq!(linear_to_srgb(-0.1), 0.0);
assert_eq!(linear_to_srgb(0.0), 0.0);
assert_eq!(linear_to_srgb(1.0), 1.0);
assert_eq!(linear_to_srgb(1.1), 1.0);
}
#[test]
fn test_iec_roundtrip() {
for i in 0..=255 {
let srgb = i as f32 / 255.0;
let linear = srgb_to_linear(srgb);
let back = linear_to_srgb(linear);
assert!(
(srgb - back).abs() < 1e-5,
"IEC roundtrip failed at {}: {} -> {} -> {}",
i,
srgb,
linear,
back
);
}
}
#[test]
fn test_iec_threshold_is_0_04045() {
let linear_seg = srgb_to_linear(0.04_f32);
let expected = 0.04_f32 * LINEAR_SCALE_F32;
assert_eq!(
linear_seg, expected,
"0.04 should be in linear segment with IEC threshold"
);
}
#[test]
fn test_iec_f64_roundtrip() {
for i in 0..=255 {
let srgb = i as f64 / 255.0;
let linear = srgb_to_linear_f64(srgb);
let back = linear_to_srgb_f64(linear);
assert!(
(srgb - back).abs() < 1e-10,
"IEC f64 roundtrip failed at {}: {} -> {} -> {}",
i,
srgb,
linear,
back
);
}
}
}