use crate::ToneMap;
pub struct Bt2446B {
breakpoint: f32,
gain: f32,
log_scale: f32,
log_offset: f32,
}
impl Bt2446B {
pub fn new(hdr_peak_nits: f32, sdr_peak_nits: f32) -> Self {
let sdr_bp_pct = 78.0;
let sdr_bp = sdr_bp_pct / 100.0;
let hdr_bp_nits = sdr_bp * sdr_peak_nits;
let breakpoint = hdr_bp_nits / hdr_peak_nits;
let gain = sdr_bp / breakpoint;
let log_scale = (1.0 - sdr_bp) / libm::logf(1.0 / breakpoint);
let log_offset = sdr_bp;
Self {
breakpoint,
gain,
log_scale,
log_offset,
}
}
}
impl ToneMap for Bt2446B {
fn map_rgb(&self, rgb: [f32; 3]) -> [f32; 3] {
let y = 0.2627 * rgb[0] + 0.6780 * rgb[1] + 0.0593 * rgb[2];
if y <= 0.0 {
return [0.0, 0.0, 0.0];
}
let y_sdr = if y < self.breakpoint {
self.gain * y
} else {
self.log_scale * libm::logf(y / self.breakpoint) + self.log_offset
};
let ratio = y_sdr / y;
[
(rgb[0] * ratio).clamp(0.0, 1.0),
(rgb[1] * ratio).clamp(0.0, 1.0),
(rgb[2] * ratio).clamp(0.0, 1.0),
]
}
fn map_strip_simd(&self, strip: &mut [[f32; 3]]) {
archmage::incant!(
crate::simd::curves::bt2446b_tier(
strip,
self.breakpoint,
self.gain,
self.log_scale,
self.log_offset,
),
[v3, neon, wasm128, scalar]
);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn black_to_black() {
let tm = Bt2446B::new(1000.0, 100.0);
assert_eq!(tm.map_rgb([0.0, 0.0, 0.0]), [0.0, 0.0, 0.0]);
}
#[test]
fn monotonic_neutral_ramp() {
let tm = Bt2446B::new(1000.0, 100.0);
let mut last = -1.0_f32;
for i in 0..=100 {
let v = i as f32 / 100.0;
let out = tm.map_rgb([v, v, v]);
assert!(out[0] >= last - 1e-5, "mono at {v}: {} < {last}", out[0]);
last = out[0];
}
}
#[test]
fn peak_maps_near_one() {
let tm = Bt2446B::new(1000.0, 100.0);
let out = tm.map_rgb([1.0, 1.0, 1.0]);
assert!(
out[0] > 0.9 && out[0] <= 1.0,
"peak should map near 1.0: {}",
out[0]
);
}
#[test]
fn below_breakpoint_is_linear() {
let tm = Bt2446B::new(1000.0, 100.0);
let a = tm.map_rgb([0.01, 0.01, 0.01]);
let b = tm.map_rgb([0.02, 0.02, 0.02]);
let ratio = b[0] / a[0];
assert!(
(ratio - 2.0).abs() < 0.01,
"below breakpoint should be linear: ratio {}",
ratio
);
}
}