use super::*;
#[cfg_attr(not(tarpaulin), inline(always))]
const fn extract_xv36(quad: &[u16]) -> (i32, i32, i32) {
let u = (quad[0] >> 4) as i32;
let y = (quad[1] >> 4) as i32;
let v = (quad[2] >> 4) as i32;
(u, y, v)
}
#[cfg_attr(not(tarpaulin), inline(always))]
fn load_xv36_u16<const BE: bool>(v: u16) -> u16 {
if BE { u16::from_be(v) } else { u16::from_le(v) }
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub(crate) fn xv36_to_rgb_or_rgba_row<const ALPHA: bool, const BE: bool>(
packed: &[u16],
out: &mut [u8],
width: usize,
matrix: ColorMatrix,
full_range: bool,
) {
debug_assert!(packed.len() >= width * 4, "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::<12, 8>(full_range);
let bias = chroma_bias::<12>();
for x in 0..width {
let base = x * 4;
let quad = [
load_xv36_u16::<BE>(packed[base]),
load_xv36_u16::<BE>(packed[base + 1]),
load_xv36_u16::<BE>(packed[base + 2]),
load_xv36_u16::<BE>(packed[base + 3]),
];
let (u, y, v) = extract_xv36(&quad);
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);
let y_s = q15_scale(y - y_off, y_scale);
let off = x * 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 xv36_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,
) {
debug_assert!(packed.len() >= width * 4, "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::<12, 12>(full_range);
let bias = chroma_bias::<12>();
let alpha_max: u16 = 0x0FFF;
let out_max: i32 = 0x0FFF;
for x in 0..width {
let base = x * 4;
let quad = [
load_xv36_u16::<BE>(packed[base]),
load_xv36_u16::<BE>(packed[base + 1]),
load_xv36_u16::<BE>(packed[base + 2]),
load_xv36_u16::<BE>(packed[base + 3]),
];
let (u, y, v) = extract_xv36(&quad);
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);
let y_s = q15_scale(y - y_off, y_scale);
let off = x * 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] = alpha_max;
}
}
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub(crate) fn xv36_to_luma_row<const BE: bool>(packed: &[u16], out: &mut [u8], width: usize) {
debug_assert!(packed.len() >= width * 4);
debug_assert!(out.len() >= width);
for x in 0..width {
let y = load_xv36_u16::<BE>(packed[x * 4 + 1]) >> 8;
out[x] = y as u8;
}
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub(crate) fn xv36_to_luma_u16_row<const BE: bool>(packed: &[u16], out: &mut [u16], width: usize) {
debug_assert!(packed.len() >= width * 4);
debug_assert!(out.len() >= width);
for x in 0..width {
let y = load_xv36_u16::<BE>(packed[x * 4 + 1]) >> 4;
out[x] = y;
}
}
#[cfg(all(test, feature = "std"))]
mod tests {
use super::*;
use crate::ColorMatrix;
fn pack_xv36(u: u16, y: u16, v: u16, a: u16) -> [u16; 4] {
debug_assert!(u <= 0xFFF && y <= 0xFFF && v <= 0xFFF && a <= 0xFFF);
[u << 4, y << 4, v << 4, a << 4]
}
fn as_le_u16(host: &[u16]) -> Vec<u16> {
host
.iter()
.map(|v| u16::from_ne_bytes(v.to_le_bytes()))
.collect()
}
#[test]
fn xv36_known_pattern_rgb() {
let p0 = pack_xv36(2048, 256, 2048, 0);
let p1 = pack_xv36(2048, 256, 2048, 0);
let p2 = pack_xv36(2048, 3760, 2048, 0);
let p3 = pack_xv36(2048, 3760, 2048, 0);
let intended: Vec<u16> = [p0, p1, p2, p3].iter().flatten().copied().collect();
let packed = as_le_u16(&intended);
let mut out = vec![0u8; 4 * 3];
xv36_to_rgb_or_rgba_row::<false, false>(&packed, &mut out, 4, ColorMatrix::Bt709, false);
assert_eq!(&out[0..3], &[0u8, 0, 0]);
assert_eq!(&out[3..6], &[0u8, 0, 0]);
assert_eq!(&out[6..9], &[255u8, 255, 255]);
assert_eq!(&out[9..12], &[255u8, 255, 255]);
}
#[test]
fn xv36_known_pattern_rgba_alpha_max() {
let p = pack_xv36(2048, 3760, 2048, 0);
let packed = as_le_u16(&p);
let mut out = vec![0u8; 4];
xv36_to_rgb_or_rgba_row::<true, false>(&packed, &mut out, 1, ColorMatrix::Bt709, false);
assert_eq!(out[3], 0xFF);
}
#[test]
fn xv36_known_pattern_rgba_ignores_source_alpha_bits() {
let p = pack_xv36(2048, 3760, 2048, 0xFFF);
let packed = as_le_u16(&p);
let mut out = vec![0u8; 4];
xv36_to_rgb_or_rgba_row::<true, false>(&packed, &mut out, 1, ColorMatrix::Bt709, false);
assert_eq!(out[3], 0xFF);
}
#[test]
fn xv36_luma_extract_u8() {
let intended: Vec<u16> = [pack_xv36(0, 0xFFF, 0, 0), pack_xv36(0, 0x100, 0, 0)]
.iter()
.flatten()
.copied()
.collect();
let packed = as_le_u16(&intended);
let mut out = vec![0u8; 2];
xv36_to_luma_row::<false>(&packed, &mut out, 2);
assert_eq!(&out[..], &[0xFFu8, 0x10]);
}
#[test]
fn xv36_luma_extract_u16_low_bit_packed() {
let intended: Vec<u16> = [pack_xv36(0, 0xFFF, 0, 0), pack_xv36(0, 0x123, 0, 0)]
.iter()
.flatten()
.copied()
.collect();
let packed = as_le_u16(&intended);
let mut out = vec![0u16; 2];
xv36_to_luma_u16_row::<false>(&packed, &mut out, 2);
assert_eq!(&out[..], &[0xFFFu16, 0x123]);
}
#[test]
fn xv36_known_pattern_rgba_u16_alpha_max() {
let p = pack_xv36(2048, 3760, 2048, 0xFFF);
let packed = as_le_u16(&p);
let mut out = vec![0u16; 4];
xv36_to_rgb_u16_or_rgba_u16_row::<true, false>(&packed, &mut out, 1, ColorMatrix::Bt709, false);
assert_eq!(out[3], 0x0FFF);
}
#[test]
fn xv36_be_roundtrip_matches_byte_swapped_le() {
let intended = pack_xv36(1024, 2048, 512, 0);
let le_bytes: Vec<u8> = intended.iter().flat_map(|v| v.to_le_bytes()).collect();
let be_bytes: Vec<u8> = intended.iter().flat_map(|v| v.to_be_bytes()).collect();
let le_buf: Vec<u16> = le_bytes
.chunks_exact(2)
.map(|b| u16::from_ne_bytes([b[0], b[1]]))
.collect();
let be_buf: Vec<u16> = be_bytes
.chunks_exact(2)
.map(|b| u16::from_ne_bytes([b[0], b[1]]))
.collect();
let mut out_le = vec![0u8; 3];
let mut out_be = vec![0u8; 3];
xv36_to_rgb_or_rgba_row::<false, false>(&le_buf, &mut out_le, 1, ColorMatrix::Bt709, false);
xv36_to_rgb_or_rgba_row::<false, true>(&be_buf, &mut out_be, 1, ColorMatrix::Bt709, false);
assert_eq!(out_le, out_be, "XV36 BE scalar must match byte-swapped LE");
}
}