#[inline]
fn pow(base: f32, exp: f32) -> f32 {
libm::powf(base, exp)
}
pub fn transfer_oe_bt709(val: f32) -> f32 {
if val >= 0.018 {
1.099 * pow(val, 0.45) - 0.099
} else {
4.500 * val
}
}
pub fn transfer_eo_bt709(val: f32) -> f32 {
if val >= transfer_oe_bt709(0.018) {
pow((val + 0.099) / 1.099, 1.0 / 0.45)
} else {
val / 4.500
}
}
pub fn transfer_oe_bt470m(val: f32) -> f32 {
pow(val, 1.0 / 2.200)
}
pub fn transfer_eo_bt470m(val: f32) -> f32 {
pow(val, 2.200)
}
pub fn transfer_oe_bt470(val: f32) -> f32 {
pow(val.abs(), 1.0 / 2.800).copysign(val)
}
pub fn transfer_eo_bt470(val: f32) -> f32 {
pow(val.abs(), 2.800).copysign(val)
}
pub fn transfer_oe_bt601(val: f32) -> f32 {
transfer_oe_bt709(val)
}
pub fn transfer_eo_bt601(val: f32) -> f32 {
transfer_eo_bt709(val)
}
pub fn transfer_oe_smpte240(val: f32) -> f32 {
if val < 0.0228 {
4.0 * val
} else {
1.1115 * pow(val, 0.45) - 0.1115
}
}
pub fn transfer_eo_smpte240(val: f32) -> f32 {
if val < 0.0913 {
val / 4.0
} else {
pow((val + 0.1115) / 1.1115, 1.0 / 0.45)
}
}
pub fn transfer_oe_srgb(val: f32) -> f32 {
if val < -0.0031308 {
-1.055 * pow(-val, 1.0 / 2.4) + 0.055
} else if val <= 0.0031308 {
val * 12.92
} else {
1.055 * pow(val, 1.0 / 2.4) - 0.055
}
}
pub fn transfer_eo_srgb(val: f32) -> f32 {
if val < -0.04045 {
-pow((-val + 0.055) / 1.055, 2.4)
} else if val <= 0.04045 {
return val / 12.92;
} else {
pow((val + 0.055) / 1.055, 2.4)
}
}
pub fn transfer_oe_bt2020_10b(val: f32) -> f32 {
transfer_oe_bt709(val)
}
pub fn transfer_eo_bt2020_10b(val: f32) -> f32 {
transfer_eo_bt709(val)
}
const SMPTE2084_M1: f32 = 2610.0 / 16384.0;
const SMPTE2084_M2: f32 = 2523.0 / 4096.0 * 128.;
const SMPTE2084_C1: f32 = 3424.0 / 4096.0;
const SMPTE2084_C2: f32 = 2413.0 / 128.0;
const SMPTE2084_C3: f32 = 2392.0 / 128.0;
#[allow(non_snake_case)] pub fn transfer_eo_smpte2084(val: f32) -> f32 {
let N = pow(val, 1.0 / SMPTE2084_M2);
let nom = (N - SMPTE2084_C1).max(0.0);
let denom = SMPTE2084_C2 - SMPTE2084_C3 * N;
pow(nom / denom, 1.0 / SMPTE2084_M1)
}
#[allow(non_snake_case)] pub fn transfer_eo_inv_smpte2084(val: f32) -> f32 {
let fraction = pow(val, SMPTE2084_M1);
let nom = SMPTE2084_C1 + SMPTE2084_C2 * fraction;
let denom = 1.0 + SMPTE2084_C3 * fraction;
pow(nom / denom, SMPTE2084_M2)
}
pub fn transfer_scene_display_smpte2084(val: f32) -> f32 {
let e_prime = transfer_oe_bt709(59.5208 * val);
pow(e_prime, 2.4) / 100.0
}
pub fn transfer_display_scene_smpte2084(val: f32) -> f32 {
let e_prime = pow(val * 100.0, 1.0 / 2.4);
transfer_eo_bt709(e_prime) / 59.5208
}
pub fn transfer_oe_smpte2084(val: f32) -> f32 {
transfer_eo_inv_smpte2084(transfer_scene_display_smpte2084(val))
}
#[expect(dead_code)]
pub fn transfer_oe_inv_smpte2084(val: f32) -> f32 {
transfer_display_scene_smpte2084(transfer_eo_smpte2084(val))
}
#[test]
fn colour_test_vectors() {
struct TestVector {
name: &'static str,
eotf: fn(f32) -> f32,
oetf: fn(f32) -> f32,
data: &'static [(f32, f32)],
abs_diff_oetf: f32,
abs_diff_eotf: f32,
}
const DEFAULTS: TestVector = TestVector {
name: "none",
eotf: |x| x,
oetf: |x| x,
data: &[],
abs_diff_eotf: 1e-6,
abs_diff_oetf: 1e-6,
};
const VECTORS: &[TestVector] = &[
TestVector {
name: "sRGB",
eotf: transfer_eo_srgb,
oetf: transfer_oe_srgb,
data: &[
(0.0, 0.0),
(1.0, 1.0),
(0.5, 0.21404114048223255),
(0.25, 0.050876088171556789),
(0.75, 0.52252155396839206),
(0.0031308, 0.00024232198142414861),
],
..DEFAULTS
},
TestVector {
name: "BT.709",
eotf: transfer_eo_bt709,
oetf: transfer_oe_bt709,
data: &[
(0.0, 0.0),
(1.0, 1.0),
(0.5, 0.25958940050628576),
(0.25, 0.07815387594543223),
(0.75, 0.56352229924287789),
(0.01, 0.0022222222222222222),
],
..DEFAULTS
},
TestVector {
name: "BT.601",
eotf: transfer_eo_bt601,
oetf: transfer_oe_bt601,
data: &[
(0.0, 0.0),
(1.0, 1.0),
(0.5, 0.25958940050628576),
(0.25, 0.07815387594543223),
(0.75, 0.56352229924287789),
(0.01, 0.0022222222222222222),
],
..DEFAULTS
},
TestVector {
name: "SMPTE 240M",
eotf: transfer_eo_smpte240,
oetf: transfer_oe_smpte240,
data: &[
(0.0, 0.0),
(1.0, 1.0),
(0.5, 0.26503573357867721),
(0.25, 0.082413320052187017),
(0.75, 0.56767766904658656),
],
..DEFAULTS
},
TestVector {
name: "BT470",
eotf: transfer_eo_bt470,
oetf: transfer_oe_bt470,
data: &[
(0.0, 0.0),
(1.0, 1.0),
(0.5, 0.14358729437462939),
(0.25, 0.020617311105826479),
(0.75, 0.44686005794246769),
(0.90235831092596908, 0.75),
(0.78070918215571006, 0.5),
(0.60950682710223769, 0.25),
],
..DEFAULTS
},
TestVector {
name: "BT2020",
eotf: transfer_eo_bt2020_10b,
oetf: transfer_oe_bt2020_10b,
data: &[
(0.0, 0.0),
(1.0, 1.0),
(0.5, 0.25958940050628576),
(0.25, 0.07815387594543223),
(0.75, 0.56352229924287789),
(0.8665510955460799, 0.75),
(0.7055150899221212, 0.5),
(0.48993951766369304, 0.25),
],
..DEFAULTS
},
TestVector {
name: "ST 2084",
eotf: transfer_eo_smpte2084,
oetf: transfer_eo_inv_smpte2084,
data: &[
(0.0, 0.0 / 10_000.0),
(1.0, 10_000.0 / 10_000.0),
(0.5, 92.245708994065268 / 10_000.0),
(0.25, 5.1541760098330069 / 10_000.0),
(0.75, 983.37785558702751 / 10_000.0),
(0.9, 3905.6446528344222 / 10_000.0),
],
abs_diff_oetf: 1e-5,
..DEFAULTS
},
];
for vector in VECTORS {
for (a, b) in vector.data {
let eotf_result = (vector.eotf)(*a);
let oetf_result = (vector.oetf)(*b);
assert!(
(eotf_result - *b).abs() < vector.abs_diff_eotf,
"{} failed for eotf {}: expected {}, got {}",
vector.name,
a,
b,
eotf_result
);
assert!(
(oetf_result - *a).abs() < vector.abs_diff_oetf,
"{} failed for oetf {}: expected {}, got {}",
vector.name,
b,
a,
oetf_result
);
}
}
}