use super::*;
#[cfg_attr(not(tarpaulin), inline(always))]
pub(crate) fn y216_to_rgb_or_rgba_row<const ALPHA: bool, const BE: bool>(
packed: &[u16],
out: &mut [u8],
width: usize,
matrix: ColorMatrix,
full_range: bool,
) {
assert!(width.is_multiple_of(2), "Y216 requires even width");
assert!(packed.len() >= width * 2, "packed row too short");
let bpp: usize = if ALPHA { 4 } else { 3 };
debug_assert!(out.len() >= width * bpp, "out row too short");
let coeffs = Coefficients::for_matrix(matrix);
let (y_off, y_scale, c_scale) = range_params_n::<16, 8>(full_range);
let bias = chroma_bias::<16>();
let pairs = width / 2;
let base = packed.as_ptr().cast::<u8>();
for p in 0..pairs {
let off4 = p * 4 * 2;
let y0 = unsafe { load_endian_u16::<BE>(base.add(off4)) } as i32;
let u = unsafe { load_endian_u16::<BE>(base.add(off4 + 2)) } as i32;
let y1 = unsafe { load_endian_u16::<BE>(base.add(off4 + 4)) } as i32;
let v = unsafe { load_endian_u16::<BE>(base.add(off4 + 6)) } as i32;
let u_d = q15_scale(u - bias, c_scale);
let v_d = q15_scale(v - bias, c_scale);
let r_chroma = q15_chroma(coeffs.r_u(), u_d, coeffs.r_v(), v_d);
let g_chroma = q15_chroma(coeffs.g_u(), u_d, coeffs.g_v(), v_d);
let b_chroma = q15_chroma(coeffs.b_u(), u_d, coeffs.b_v(), v_d);
for (k, &y) in [y0, y1].iter().enumerate() {
let y_s = q15_scale(y - y_off, y_scale);
let off = (p * 2 + k) * bpp;
out[off] = clamp_u8(y_s + r_chroma);
out[off + 1] = clamp_u8(y_s + g_chroma);
out[off + 2] = clamp_u8(y_s + b_chroma);
if ALPHA {
out[off + 3] = 0xFF;
}
}
}
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub(crate) fn y216_to_rgb_u16_or_rgba_u16_row<const ALPHA: bool, const BE: bool>(
packed: &[u16],
out: &mut [u16],
width: usize,
matrix: ColorMatrix,
full_range: bool,
) {
assert!(width.is_multiple_of(2), "Y216 requires even width");
assert!(packed.len() >= width * 2, "packed row too short");
let bpp: usize = if ALPHA { 4 } else { 3 };
debug_assert!(out.len() >= width * bpp, "out row too short");
let coeffs = Coefficients::for_matrix(matrix);
let (y_off, y_scale, c_scale) = range_params_n::<16, 16>(full_range);
let bias = chroma_bias::<16>();
let out_max: i32 = 0xFFFF;
let pairs = width / 2;
let base = packed.as_ptr().cast::<u8>();
for p in 0..pairs {
let off4 = p * 4 * 2;
let y0 = unsafe { load_endian_u16::<BE>(base.add(off4)) } as i32;
let u = unsafe { load_endian_u16::<BE>(base.add(off4 + 2)) } as i32;
let y1 = unsafe { load_endian_u16::<BE>(base.add(off4 + 4)) } as i32;
let v = unsafe { load_endian_u16::<BE>(base.add(off4 + 6)) } as i32;
let u_d = q15_scale(u - bias, c_scale);
let v_d = q15_scale(v - bias, c_scale);
let r_chroma = q15_chroma64(coeffs.r_u(), u_d, coeffs.r_v(), v_d);
let g_chroma = q15_chroma64(coeffs.g_u(), u_d, coeffs.g_v(), v_d);
let b_chroma = q15_chroma64(coeffs.b_u(), u_d, coeffs.b_v(), v_d);
for (k, &y) in [y0, y1].iter().enumerate() {
let y_s = q15_scale64(y - y_off, y_scale);
let off = (p * 2 + k) * bpp;
out[off] = (y_s + r_chroma).clamp(0, out_max) as u16;
out[off + 1] = (y_s + g_chroma).clamp(0, out_max) as u16;
out[off + 2] = (y_s + b_chroma).clamp(0, out_max) as u16;
if ALPHA {
out[off + 3] = 0xFFFF;
}
}
}
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub(crate) fn y216_to_luma_row<const BE: bool>(packed: &[u16], out: &mut [u8], width: usize) {
assert!(width.is_multiple_of(2));
assert!(packed.len() >= width * 2);
debug_assert!(out.len() >= width);
let pairs = width / 2;
let base = packed.as_ptr().cast::<u8>();
for p in 0..pairs {
let off4 = p * 4 * 2;
let y0 = unsafe { load_endian_u16::<BE>(base.add(off4)) };
let y1 = unsafe { load_endian_u16::<BE>(base.add(off4 + 4)) };
out[p * 2] = (y0 >> 8) as u8;
out[p * 2 + 1] = (y1 >> 8) as u8;
}
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub(crate) fn y216_to_luma_u16_row<const BE: bool>(packed: &[u16], out: &mut [u16], width: usize) {
assert!(width.is_multiple_of(2));
assert!(packed.len() >= width * 2);
debug_assert!(out.len() >= width);
let pairs = width / 2;
let base = packed.as_ptr().cast::<u8>();
for p in 0..pairs {
let off4 = p * 4 * 2;
out[p * 2] = unsafe { load_endian_u16::<BE>(base.add(off4)) };
out[p * 2 + 1] = unsafe { load_endian_u16::<BE>(base.add(off4 + 4)) };
}
}
#[cfg(all(test, feature = "std"))]
mod tests {
use super::*;
use crate::ColorMatrix;
fn as_le_u16(host: &[u16]) -> std::vec::Vec<u16> {
host
.iter()
.map(|v| u16::from_ne_bytes(v.to_le_bytes()))
.collect()
}
fn test_input() -> std::vec::Vec<u16> {
as_le_u16(&[4096, 32768, 32000, 32768, 0, 16384, 65535, 49152])
}
fn as_be_u16(host: &[u16]) -> std::vec::Vec<u16> {
host
.iter()
.map(|v| u16::from_ne_bytes(v.to_be_bytes()))
.collect()
}
fn ref_y216_to_rgb_u8(
intended: &[u16],
width: usize,
matrix: ColorMatrix,
full_range: bool,
) -> std::vec::Vec<u8> {
let coeffs = Coefficients::for_matrix(matrix);
let (y_off, y_scale, c_scale) = range_params_n::<16, 8>(full_range);
let bias = chroma_bias::<16>();
let pairs = width / 2;
let mut out = std::vec![0u8; width * 3];
for p in 0..pairs {
let off4 = p * 4;
let y0 = intended[off4] as i32;
let u = intended[off4 + 1] as i32;
let y1 = intended[off4 + 2] as i32;
let v = intended[off4 + 3] as i32;
let u_d = q15_scale(u - bias, c_scale);
let v_d = q15_scale(v - bias, c_scale);
let r_chroma = q15_chroma(coeffs.r_u(), u_d, coeffs.r_v(), v_d);
let g_chroma = q15_chroma(coeffs.g_u(), u_d, coeffs.g_v(), v_d);
let b_chroma = q15_chroma(coeffs.b_u(), u_d, coeffs.b_v(), v_d);
for (k, &y) in [y0, y1].iter().enumerate() {
let y_s = q15_scale(y - y_off, y_scale);
let off = (p * 2 + k) * 3;
out[off] = clamp_u8(y_s + r_chroma);
out[off + 1] = clamp_u8(y_s + g_chroma);
out[off + 2] = clamp_u8(y_s + b_chroma);
}
}
out
}
fn ref_y216_to_rgb_u16(
intended: &[u16],
width: usize,
matrix: ColorMatrix,
full_range: bool,
) -> std::vec::Vec<u16> {
let coeffs = Coefficients::for_matrix(matrix);
let (y_off, y_scale, c_scale) = range_params_n::<16, 16>(full_range);
let bias = chroma_bias::<16>();
let out_max: i32 = 0xFFFF;
let pairs = width / 2;
let mut out = std::vec![0u16; width * 3];
for p in 0..pairs {
let off4 = p * 4;
let y0 = intended[off4] as i32;
let u = intended[off4 + 1] as i32;
let y1 = intended[off4 + 2] as i32;
let v = intended[off4 + 3] as i32;
let u_d = q15_scale(u - bias, c_scale);
let v_d = q15_scale(v - bias, c_scale);
let r_chroma = q15_chroma64(coeffs.r_u(), u_d, coeffs.r_v(), v_d);
let g_chroma = q15_chroma64(coeffs.g_u(), u_d, coeffs.g_v(), v_d);
let b_chroma = q15_chroma64(coeffs.b_u(), u_d, coeffs.b_v(), v_d);
for (k, &y) in [y0, y1].iter().enumerate() {
let y_s = q15_scale64(y - y_off, y_scale);
let off = (p * 2 + k) * 3;
out[off] = (y_s + r_chroma).clamp(0, out_max) as u16;
out[off + 1] = (y_s + g_chroma).clamp(0, out_max) as u16;
out[off + 2] = (y_s + b_chroma).clamp(0, out_max) as u16;
}
}
out
}
fn ref_y216_to_luma(intended: &[u16], width: usize) -> std::vec::Vec<u8> {
let pairs = width / 2;
let mut out = std::vec![0u8; width];
for p in 0..pairs {
let off4 = p * 4;
out[p * 2] = (intended[off4] >> 8) as u8;
out[p * 2 + 1] = (intended[off4 + 2] >> 8) as u8;
}
out
}
fn ref_y216_to_luma_u16(intended: &[u16], width: usize) -> std::vec::Vec<u16> {
let pairs = width / 2;
let mut out = std::vec![0u16; width];
for p in 0..pairs {
let off4 = p * 4;
out[p * 2] = intended[off4];
out[p * 2 + 1] = intended[off4 + 2];
}
out
}
fn intended_test_input() -> std::vec::Vec<u16> {
std::vec![4096, 32768, 32000, 32768, 0, 16384, 65535, 49152]
}
#[test]
fn y216_known_pattern_rgb() {
let packed = test_input();
let mut out = [0u8; 4 * 3];
y216_to_rgb_or_rgba_row::<false, false>(&packed, &mut out, 4, ColorMatrix::Bt709, false);
assert_eq!(&out[0..3], &[0, 0, 0], "pixel 0 (Y=4096, neutral chroma)");
assert_eq!(
&out[3..6],
&[127, 127, 127],
"pixel 1 (Y=32000, neutral chroma)"
);
assert_eq!(&out[6..9], &[96, 0, 0], "pixel 2 (Y=0, non-neutral chroma)");
assert_eq!(
&out[9..12],
&[255, 255, 144],
"pixel 3 (Y=65535, non-neutral chroma)"
);
}
#[test]
fn y216_known_pattern_rgba() {
let packed = test_input();
let mut out = [0u8; 4 * 4];
y216_to_rgb_or_rgba_row::<true, false>(&packed, &mut out, 4, ColorMatrix::Bt709, false);
assert_eq!(&out[0..4], &[0, 0, 0, 0xFF]);
assert_eq!(&out[4..8], &[127, 127, 127, 0xFF]);
assert_eq!(&out[8..12], &[96, 0, 0, 0xFF]);
assert_eq!(&out[12..16], &[255, 255, 144, 0xFF]);
}
#[test]
fn y216_known_pattern_rgb_u16() {
let packed = test_input();
let mut out = [0u16; 4 * 3];
y216_to_rgb_u16_or_rgba_u16_row::<false, false>(
&packed,
&mut out,
4,
ColorMatrix::Bt709,
false,
);
assert_eq!(
&out[0..3],
&[0, 0, 0],
"pixel 0 (Y=4096, neutral chroma, u16)"
);
assert_eq!(
&out[3..6],
&[32618, 32618, 32618],
"pixel 1 (Y=32000, neutral chroma, u16)"
);
assert_eq!(&out[6..9], &[24702_u16, 0, 0]);
assert_eq!(&out[9..12], &[65535_u16, 65535, 37073]);
}
#[test]
fn y216_known_pattern_rgba_u16() {
let packed = test_input();
let mut out = [0u16; 4 * 4];
y216_to_rgb_u16_or_rgba_u16_row::<true, false>(&packed, &mut out, 4, ColorMatrix::Bt709, false);
assert_eq!(&out[0..4], &[0, 0, 0, 0xFFFF]);
assert_eq!(&out[4..8], &[32618, 32618, 32618, 0xFFFF]);
assert_eq!(&out[8..12], &[24702_u16, 0, 0, 0xFFFF]);
assert_eq!(&out[12..16], &[65535_u16, 65535, 37073, 0xFFFF]);
}
#[test]
fn y216_luma_extract() {
let packed = as_le_u16(&[0xAB12u16, 0x4444, 0xCD34, 0x5555]);
let mut out = [0u8; 2];
y216_to_luma_row::<false>(&packed, &mut out, 2);
assert_eq!(out[0], 0xAB, "Y0 luma u8");
assert_eq!(out[1], 0xCD, "Y1 luma u8");
}
#[test]
fn y216_luma_u16_extract() {
let packed = as_le_u16(&[0xAB12u16, 0x4444, 0xCD34, 0x5555]);
let mut out = [0u16; 2];
y216_to_luma_u16_row::<false>(&packed, &mut out, 2);
assert_eq!(out[0], 0xAB12, "Y0 luma u16");
assert_eq!(out[1], 0xCD34, "Y1 luma u16");
}
#[test]
fn y216_be_rgb_matches_le() {
let intended = intended_test_input();
let le = as_le_u16(&intended);
let be = as_be_u16(&intended);
let mut out_le = [0u8; 4 * 3];
let mut out_be = [0u8; 4 * 3];
y216_to_rgb_or_rgba_row::<false, false>(&le, &mut out_le, 4, ColorMatrix::Bt709, false);
y216_to_rgb_or_rgba_row::<false, true>(&be, &mut out_be, 4, ColorMatrix::Bt709, false);
let expected = ref_y216_to_rgb_u8(&intended, 4, ColorMatrix::Bt709, false);
assert_eq!(
out_le.as_slice(),
expected,
"LE path must match scalar reference"
);
assert_eq!(
out_be.as_slice(),
expected,
"BE path must match scalar reference"
);
assert_eq!(out_le, out_be, "BE and LE RGB outputs must agree");
}
#[test]
fn y216_be_rgb_u16_matches_le() {
let intended = intended_test_input();
let le = as_le_u16(&intended);
let be = as_be_u16(&intended);
let mut out_le = [0u16; 4 * 3];
let mut out_be = [0u16; 4 * 3];
y216_to_rgb_u16_or_rgba_u16_row::<false, false>(&le, &mut out_le, 4, ColorMatrix::Bt709, false);
y216_to_rgb_u16_or_rgba_u16_row::<false, true>(&be, &mut out_be, 4, ColorMatrix::Bt709, false);
let expected = ref_y216_to_rgb_u16(&intended, 4, ColorMatrix::Bt709, false);
assert_eq!(
out_le.as_slice(),
expected,
"LE path must match scalar reference"
);
assert_eq!(
out_be.as_slice(),
expected,
"BE path must match scalar reference"
);
assert_eq!(out_le, out_be, "BE and LE RGB u16 outputs must agree");
}
#[test]
fn y216_be_luma_matches_le() {
let intended = intended_test_input();
let le = as_le_u16(&intended);
let be = as_be_u16(&intended);
let mut luma_le = [0u8; 4];
let mut luma_be = [0u8; 4];
y216_to_luma_row::<false>(&le, &mut luma_le, 4);
y216_to_luma_row::<true>(&be, &mut luma_be, 4);
let expected = ref_y216_to_luma(&intended, 4);
assert_eq!(
luma_le.as_slice(),
expected,
"LE path must match scalar reference"
);
assert_eq!(
luma_be.as_slice(),
expected,
"BE path must match scalar reference"
);
assert_eq!(luma_le, luma_be, "BE and LE luma outputs must agree");
}
#[test]
fn y216_be_luma_u16_matches_le() {
let intended = intended_test_input();
let le = as_le_u16(&intended);
let be = as_be_u16(&intended);
let mut luma_le = [0u16; 4];
let mut luma_be = [0u16; 4];
y216_to_luma_u16_row::<false>(&le, &mut luma_le, 4);
y216_to_luma_u16_row::<true>(&be, &mut luma_be, 4);
let expected = ref_y216_to_luma_u16(&intended, 4);
assert_eq!(
luma_le.as_slice(),
expected,
"LE path must match scalar reference"
);
assert_eq!(
luma_be.as_slice(),
expected,
"BE path must match scalar reference"
);
assert_eq!(luma_le, luma_be, "BE and LE luma_u16 outputs must agree");
}
}