use super::*;
#[cfg_attr(not(tarpaulin), inline(always))]
const fn extract_ayuv64(quad: &[u16]) -> (i32, i32, i32, u16) {
let a = quad[0]; let y = quad[1] as i32; let u = quad[2] as i32; let v = quad[3] as i32; (u, y, v, a) }
#[cfg_attr(not(tarpaulin), inline(always))]
fn load_ayuv64_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 ayuv64_to_rgb_or_rgba_row<
const ALPHA: bool,
const ALPHA_SRC: bool,
const BE: bool,
>(
packed: &[u16],
out: &mut [u8],
width: usize,
matrix: ColorMatrix,
full_range: bool,
) {
const { assert!(!ALPHA_SRC || ALPHA) };
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::<16, 8>(full_range);
let bias = chroma_bias::<16>();
for x in 0..width {
let pix_off = x * 4;
let quad = [
load_ayuv64_u16::<BE>(packed[pix_off]),
load_ayuv64_u16::<BE>(packed[pix_off + 1]),
load_ayuv64_u16::<BE>(packed[pix_off + 2]),
load_ayuv64_u16::<BE>(packed[pix_off + 3]),
];
let (u, y, v, a) = extract_ayuv64(&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] = if ALPHA_SRC { (a >> 8) as u8 } else { 0xFF };
}
}
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub(crate) fn ayuv64_to_rgb_row<const BE: bool>(
packed: &[u16],
rgb_out: &mut [u8],
width: usize,
matrix: ColorMatrix,
full_range: bool,
) {
ayuv64_to_rgb_or_rgba_row::<false, false, BE>(packed, rgb_out, width, matrix, full_range);
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub(crate) fn ayuv64_to_rgba_row<const BE: bool>(
packed: &[u16],
rgba_out: &mut [u8],
width: usize,
matrix: ColorMatrix,
full_range: bool,
) {
ayuv64_to_rgb_or_rgba_row::<true, true, BE>(packed, rgba_out, width, matrix, full_range);
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub(crate) fn ayuv64_to_rgb_u16_or_rgba_u16_row<
const ALPHA: bool,
const ALPHA_SRC: bool,
const BE: bool,
>(
packed: &[u16],
out: &mut [u16],
width: usize,
matrix: ColorMatrix,
full_range: bool,
) {
const { assert!(!ALPHA_SRC || ALPHA) };
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::<16, 16>(full_range);
let bias = chroma_bias::<16>();
for x in 0..width {
let pix_off = x * 4;
let quad = [
load_ayuv64_u16::<BE>(packed[pix_off]),
load_ayuv64_u16::<BE>(packed[pix_off + 1]),
load_ayuv64_u16::<BE>(packed[pix_off + 2]),
load_ayuv64_u16::<BE>(packed[pix_off + 3]),
];
let (u, y, v, a) = extract_ayuv64(&quad);
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);
let y_s = q15_scale64(y - y_off, y_scale);
let off = x * bpp;
out[off] = (y_s + r_chroma).clamp(0, 0xFFFF) as u16;
out[off + 1] = (y_s + g_chroma).clamp(0, 0xFFFF) as u16;
out[off + 2] = (y_s + b_chroma).clamp(0, 0xFFFF) as u16;
if ALPHA {
out[off + 3] = if ALPHA_SRC { a } else { 0xFFFF };
}
}
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub(crate) fn ayuv64_to_rgb_u16_row<const BE: bool>(
packed: &[u16],
rgb_out: &mut [u16],
width: usize,
matrix: ColorMatrix,
full_range: bool,
) {
ayuv64_to_rgb_u16_or_rgba_u16_row::<false, false, BE>(packed, rgb_out, width, matrix, full_range);
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub(crate) fn ayuv64_to_rgba_u16_row<const BE: bool>(
packed: &[u16],
rgba_out: &mut [u16],
width: usize,
matrix: ColorMatrix,
full_range: bool,
) {
ayuv64_to_rgb_u16_or_rgba_u16_row::<true, true, BE>(packed, rgba_out, width, matrix, full_range);
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub(crate) fn ayuv64_to_luma_row<const BE: bool>(
packed: &[u16],
luma_out: &mut [u8],
width: usize,
) {
debug_assert!(packed.len() >= width * 4, "packed row too short");
debug_assert!(luma_out.len() >= width, "luma row too short");
for x in 0..width {
luma_out[x] = (load_ayuv64_u16::<BE>(packed[x * 4 + 1]) >> 8) as u8;
}
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub(crate) fn ayuv64_to_luma_u16_row<const BE: bool>(
packed: &[u16],
luma_out: &mut [u16],
width: usize,
) {
debug_assert!(packed.len() >= width * 4, "packed row too short");
debug_assert!(luma_out.len() >= width, "luma row too short");
for x in 0..width {
luma_out[x] = load_ayuv64_u16::<BE>(packed[x * 4 + 1]);
}
}
#[cfg(all(test, feature = "std"))]
mod tests {
use super::*;
use crate::ColorMatrix;
fn pack_ayuv64(a: u16, y: u16, u: u16, v: u16) -> [u16; 4] {
[a, y, u, v]
}
fn as_le_u16(host: &[u16]) -> Vec<u16> {
host
.iter()
.map(|v| u16::from_ne_bytes(v.to_le_bytes()))
.collect()
}
#[test]
fn ayuv64_known_pattern_rgb_limited_range() {
let p_black = pack_ayuv64(0xFFFF, 4096, 32768, 32768);
let p_white = pack_ayuv64(0xFFFF, 60160, 32768, 32768);
let intended: Vec<u16> = [p_black, p_black, p_white, p_white]
.iter()
.flatten()
.copied()
.collect();
let packed = as_le_u16(&intended);
let mut out = vec![0u8; 4 * 3];
ayuv64_to_rgb_row::<false>(&packed, &mut out, 4, ColorMatrix::Bt709, false);
assert_eq!(&out[0..3], &[0u8, 0, 0], "black pixel 0");
assert_eq!(&out[3..6], &[0u8, 0, 0], "black pixel 1");
assert_eq!(&out[6..9], &[255u8, 255, 255], "white pixel 2");
assert_eq!(&out[9..12], &[255u8, 255, 255], "white pixel 3");
}
#[test]
fn ayuv64_rgba_passes_source_alpha_depth_converted() {
let p0 = pack_ayuv64(0x42AB, 60160, 32768, 32768);
let p1 = pack_ayuv64(0x99CD, 60160, 32768, 32768);
let intended: Vec<u16> = [p0, p1].iter().flatten().copied().collect();
let packed = as_le_u16(&intended);
let mut out = vec![0u8; 2 * 4];
ayuv64_to_rgba_row::<false>(&packed, &mut out, 2, ColorMatrix::Bt709, false);
assert_eq!(out[3], 0x42, "pixel 0 alpha (0x42AB >> 8 = 0x42)");
assert_eq!(out[7], 0x99, "pixel 1 alpha (0x99CD >> 8 = 0x99)");
}
#[test]
fn ayuv64_rgba_u16_passes_source_alpha_direct() {
let p0 = pack_ayuv64(0x42AB, 60160, 32768, 32768);
let p1 = pack_ayuv64(0x99CD, 60160, 32768, 32768);
let intended: Vec<u16> = [p0, p1].iter().flatten().copied().collect();
let packed = as_le_u16(&intended);
let mut out = vec![0u16; 2 * 4];
ayuv64_to_rgba_u16_row::<false>(&packed, &mut out, 2, ColorMatrix::Bt709, false);
assert_eq!(out[3], 0x42AB, "pixel 0 alpha u16 direct");
assert_eq!(out[7], 0x99CD, "pixel 1 alpha u16 direct");
}
#[test]
fn ayuv64_luma_extract_u8_high_byte() {
let p0 = pack_ayuv64(0, 0xFFFF, 0, 0);
let p1 = pack_ayuv64(0, 0x4000, 0, 0);
let intended: Vec<u16> = [p0, p1].iter().flatten().copied().collect();
let packed = as_le_u16(&intended);
let mut out = vec![0u8; 2];
ayuv64_to_luma_row::<false>(&packed, &mut out, 2);
assert_eq!(&out[..], &[0xFFu8, 0x40], "luma u8 high-byte extract");
}
#[test]
fn ayuv64_luma_extract_u16_direct() {
let p0 = pack_ayuv64(0, 0xABCD, 0, 0);
let p1 = pack_ayuv64(0, 0x1234, 0, 0);
let intended: Vec<u16> = [p0, p1].iter().flatten().copied().collect();
let packed = as_le_u16(&intended);
let mut out = vec![0u16; 2];
ayuv64_to_luma_u16_row::<false>(&packed, &mut out, 2);
assert_eq!(&out[..], &[0xABCDu16, 0x1234], "luma u16 direct extract");
}
#[test]
fn ayuv64_be_roundtrip_matches_byte_swapped_le() {
let intended = pack_ayuv64(0xFFFF, 60160, 32768, 32768);
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];
ayuv64_to_rgb_row::<false>(&le_buf, &mut out_le, 1, ColorMatrix::Bt709, false);
ayuv64_to_rgb_row::<true>(&be_buf, &mut out_be, 1, ColorMatrix::Bt709, false);
assert_eq!(
out_le, out_be,
"AYUV64 BE scalar must match byte-swapped LE"
);
}
}