use crate::yuv::YuvMatrix;
#[inline]
pub fn read_f32le(buf: &[u8], i: usize) -> f32 {
f32::from_le_bytes([buf[i * 4], buf[i * 4 + 1], buf[i * 4 + 2], buf[i * 4 + 3]])
}
#[inline]
pub fn write_f32le(buf: &mut [u8], i: usize, v: f32) {
buf[i * 4..i * 4 + 4].copy_from_slice(&v.to_le_bytes());
}
#[inline]
pub fn unorm_to_f32(code: u32, bits: u32) -> f32 {
let full = ((1u64 << bits) - 1) as f32;
code as f32 / full
}
#[inline]
pub fn f32_to_unorm(v: f32, bits: u32) -> u32 {
let full = ((1u64 << bits) - 1) as f32;
let c = if v >= 1.0 {
1.0
} else if v > 0.0 {
v
} else {
0.0
};
(c * full).round() as u32
}
#[inline]
pub fn luminance_linear(r: f32, g: f32, b: f32, matrix: YuvMatrix) -> f32 {
if r == g && g == b {
return g;
}
let kg = 1.0 - matrix.kr - matrix.kb;
matrix.kr * r + kg * g + matrix.kb * b
}
pub fn plane_u8_to_f32le(src: &[u8], dst: &mut [u8], count: usize) {
for (i, &code) in src.iter().enumerate().take(count) {
write_f32le(dst, i, unorm_to_f32(code as u32, 8));
}
}
pub fn plane_le16_to_f32le(src: &[u8], dst: &mut [u8], count: usize, bits: u32) {
let mask = ((1u32 << bits) - 1) as u16;
for i in 0..count {
let code = u16::from_le_bytes([src[i * 2], src[i * 2 + 1]]) & mask;
write_f32le(dst, i, unorm_to_f32(code as u32, bits));
}
}
pub fn plane_f32le_to_u8(src: &[u8], dst: &mut [u8], count: usize) {
for (i, out) in dst.iter_mut().enumerate().take(count) {
*out = f32_to_unorm(read_f32le(src, i), 8) as u8;
}
}
pub fn plane_f32le_to_le16(src: &[u8], dst: &mut [u8], count: usize, bits: u32) {
for i in 0..count {
let code = f32_to_unorm(read_f32le(src, i), bits) as u16;
dst[i * 2..i * 2 + 2].copy_from_slice(&code.to_le_bytes());
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn quantise_rules() {
assert_eq!(f32_to_unorm(0.0, 8), 0);
assert_eq!(f32_to_unorm(1.0, 8), 255);
assert_eq!(f32_to_unorm(1.5, 8), 255);
assert_eq!(f32_to_unorm(-0.25, 8), 0);
assert_eq!(f32_to_unorm(f32::NAN, 16), 0);
assert_eq!(f32_to_unorm(f32::INFINITY, 16), 65535);
assert_eq!(f32_to_unorm(f32::NEG_INFINITY, 10), 0);
assert_eq!(f32_to_unorm(0.5, 8), 128); assert_eq!(f32_to_unorm(0.5, 16), 32768);
}
#[test]
fn every_code_round_trips_at_every_depth() {
for bits in [8u32, 10, 12, 14, 16] {
for code in 0..(1u32 << bits) {
assert_eq!(f32_to_unorm(unorm_to_f32(code, bits), bits), code);
}
}
}
#[test]
fn luminance_weights_sum_to_one() {
for m in [YuvMatrix::BT601, YuvMatrix::BT709, YuvMatrix::BT2020] {
assert_eq!(luminance_linear(1.0, 1.0, 1.0, m), 1.0);
assert_eq!(luminance_linear(0.5, 0.5, 0.5, m), 0.5);
assert_eq!(luminance_linear(2.0, 2.0, 2.0, m), 2.0);
let inf = f32::INFINITY;
assert_eq!(luminance_linear(inf, inf, inf, m), inf);
let y = luminance_linear(1.0, 0.0, 0.0, m);
assert!((y - m.kr).abs() < 1e-6);
let y = luminance_linear(0.0, 0.0, 1.0, m);
assert!((y - m.kb).abs() < 1e-6);
let y = luminance_linear(0.0, 1.0, 0.0, m);
assert!((y - (1.0 - m.kr - m.kb)).abs() < 1e-6);
}
}
}