pub fn srgb_oetf(linear: f32) -> f32 {
if linear <= 0.0031308 {
12.92 * linear
} else {
1.055 * linear.powf(1.0 / 2.4) - 0.055
}
}
pub fn srgb_eotf(encoded: f32) -> f32 {
if encoded <= 0.04045 {
encoded / 12.92
} else {
((encoded + 0.055) / 1.055).powf(2.4)
}
}
#[inline]
pub fn srgb_oetf_rgb(rgb: &[f32; 3]) -> [f32; 3] {
[srgb_oetf(rgb[0]), srgb_oetf(rgb[1]), srgb_oetf(rgb[2])]
}
#[inline]
pub fn srgb_eotf_rgb(rgb: &[f32; 3]) -> [f32; 3] {
[srgb_eotf(rgb[0]), srgb_eotf(rgb[1]), srgb_eotf(rgb[2])]
}
pub fn rec709_oetf(linear: f32) -> f32 {
const ALPHA: f32 = 1.099;
const BETA: f32 = 0.018;
if linear < BETA {
4.5 * linear
} else {
ALPHA * linear.powf(0.45) - (ALPHA - 1.0)
}
}
pub fn rec709_eotf(encoded: f32) -> f32 {
const ALPHA: f32 = 1.099;
const BETA: f32 = 0.018;
const THRESHOLD: f32 = 4.5 * BETA;
if encoded < THRESHOLD {
encoded / 4.5
} else {
((encoded + (ALPHA - 1.0)) / ALPHA).powf(1.0 / 0.45)
}
}
#[inline]
pub fn rec709_oetf_rgb(rgb: &[f32; 3]) -> [f32; 3] {
[
rec709_oetf(rgb[0]),
rec709_oetf(rgb[1]),
rec709_oetf(rgb[2]),
]
}
#[inline]
pub fn rec709_eotf_rgb(rgb: &[f32; 3]) -> [f32; 3] {
[
rec709_eotf(rgb[0]),
rec709_eotf(rgb[1]),
rec709_eotf(rgb[2]),
]
}
#[inline]
pub fn adobe_rgb_oetf(linear: f32) -> f32 {
linear.powf(1. / 2.2)
}
#[inline]
pub fn adobe_rgb_eotf(encoded: f32) -> f32 {
encoded.powf(2.2)
}
#[inline]
pub fn adobe_rgb_oetf_rgb(rgb: &[f32; 3]) -> [f32; 3] {
[
adobe_rgb_oetf(rgb[0]),
adobe_rgb_oetf(rgb[1]),
adobe_rgb_oetf(rgb[2]),
]
}
#[inline]
pub fn adobe_rgb_eotf_rgb(rgb: &[f32; 3]) -> [f32; 3] {
[
adobe_rgb_eotf(rgb[0]),
adobe_rgb_eotf(rgb[1]),
adobe_rgb_eotf(rgb[2]),
]
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_srgb_roundtrip() {
let linear_values = [0.0, 0.001, 0.01, 0.1, 0.5, 0.9, 1.0];
for &linear in &linear_values {
let encoded = srgb_oetf(linear);
let decoded = srgb_eotf(encoded);
assert!(
(linear - decoded).abs() < 1e-6,
"sRGB roundtrip failed for {}: got {}",
linear,
decoded
);
}
}
#[test]
fn test_rec709_roundtrip() {
let linear_values = [0.0, 0.001, 0.01, 0.1, 0.5, 0.9, 1.0];
for &linear in &linear_values {
let encoded = rec709_oetf(linear);
let decoded = rec709_eotf(encoded);
assert!(
(linear - decoded).abs() < 1e-6,
"Rec.709 roundtrip failed for {}: got {}",
linear,
decoded
);
}
}
#[test]
fn test_srgb_known_values() {
assert_eq!(srgb_oetf(0.0), 0.0);
assert_eq!(srgb_eotf(0.0), 0.0);
assert!((srgb_oetf(1.0) - 1.0).abs() < 1e-6);
assert!((srgb_eotf(1.0) - 1.0).abs() < 1e-6);
let encoded = srgb_oetf(0.5);
assert!((encoded - 0.735357).abs() < 1e-5);
}
#[test]
fn test_rec709_known_values() {
assert_eq!(rec709_oetf(0.0), 0.0);
assert_eq!(rec709_eotf(0.0), 0.0);
assert_eq!(rec709_oetf(1.0), 1.0);
assert_eq!(rec709_eotf(1.0), 1.0);
}
#[test]
fn test_srgb_vs_rec709_difference() {
let linear = 0.5;
let srgb_encoded = srgb_oetf(linear);
let rec709_encoded = rec709_oetf(linear);
assert!((srgb_encoded - rec709_encoded).abs() > 0.0001);
assert!((srgb_encoded - rec709_encoded).abs() < 0.1);
}
}