use super::*;
use crate::sinker::MixedSinker;
fn planes_from_packed_rgb(rgb: &[u8], width: usize, height: usize) -> (Vec<u8>, Vec<u8>, Vec<u8>) {
let n = width * height;
let mut g = std::vec![0u8; n];
let mut b = std::vec![0u8; n];
let mut r = std::vec![0u8; n];
for i in 0..n {
r[i] = rgb[i * 3];
g[i] = rgb[i * 3 + 1];
b[i] = rgb[i * 3 + 2];
}
(g, b, r)
}
fn random_rgb(width: usize, height: usize, seed: u32) -> Vec<u8> {
let mut buf = std::vec![0u8; width * height * 3];
pseudo_random_u8(&mut buf, seed);
buf
}
fn random_alpha(width: usize, height: usize, seed: u32) -> Vec<u8> {
let mut buf = std::vec![0u8; width * height];
pseudo_random_u8(&mut buf, seed);
buf
}
#[test]
#[cfg_attr(
miri,
ignore = "SIMD-dispatched row kernels use intrinsics unsupported by Miri"
)]
fn gbrp_with_rgb_reorders_planes_to_packed_rgb() {
let w = 16usize;
let h = 4usize;
let mut g = std::vec![100u8; w * h];
let mut b = std::vec![50u8; w * h];
let mut r = std::vec![200u8; w * h];
g[7] = 33;
b[7] = 44;
r[7] = 55;
let src =
GbrpFrame::try_new(&g, &b, &r, w as u32, h as u32, w as u32, w as u32, w as u32).unwrap();
let mut rgb_out = std::vec![0u8; w * h * 3];
let mut sink = MixedSinker::<Gbrp>::new(w, h)
.with_rgb(&mut rgb_out)
.unwrap();
gbrp_to(&src, true, ColorMatrix::Bt709, &mut sink).unwrap();
for i in 0..w * h {
assert_eq!(rgb_out[i * 3], r[i], "R px {i}");
assert_eq!(rgb_out[i * 3 + 1], g[i], "G px {i}");
assert_eq!(rgb_out[i * 3 + 2], b[i], "B px {i}");
}
}
#[test]
#[cfg_attr(
miri,
ignore = "SIMD-dispatched row kernels use intrinsics unsupported by Miri"
)]
fn gbrp_with_rgba_appends_opaque_alpha() {
let w = 16usize;
let h = 4usize;
let g = std::vec![100u8; w * h];
let b = std::vec![50u8; w * h];
let r = std::vec![200u8; w * h];
let src =
GbrpFrame::try_new(&g, &b, &r, w as u32, h as u32, w as u32, w as u32, w as u32).unwrap();
let mut rgba = std::vec![0u8; w * h * 4];
let mut sink = MixedSinker::<Gbrp>::new(w, h).with_rgba(&mut rgba).unwrap();
gbrp_to(&src, true, ColorMatrix::Bt709, &mut sink).unwrap();
for px in rgba.chunks(4) {
assert_eq!(px, [200, 100, 50, 0xFF]);
}
}
#[test]
#[cfg_attr(
miri,
ignore = "SIMD-dispatched row kernels use intrinsics unsupported by Miri"
)]
fn gbrp_planar_parity_with_rgb24() {
let w = 16usize;
let h = 8usize;
let rgb_seed = random_rgb(w, h, 0x1234_5678);
let (g, b, r) = planes_from_packed_rgb(&rgb_seed, w, h);
let rgb24 = Rgb24Frame::try_new(&rgb_seed, w as u32, h as u32, (w * 3) as u32).unwrap();
let mut rgb_ref = std::vec![0u8; w * h * 3];
let mut rgba_ref = std::vec![0u8; w * h * 4];
let mut luma_ref = std::vec![0u8; w * h];
let mut h_ref = std::vec![0u8; w * h];
let mut s_ref = std::vec![0u8; w * h];
let mut v_ref = std::vec![0u8; w * h];
{
let mut sink = MixedSinker::<Rgb24>::new(w, h)
.with_rgb(&mut rgb_ref)
.unwrap()
.with_rgba(&mut rgba_ref)
.unwrap()
.with_luma(&mut luma_ref)
.unwrap()
.with_hsv(&mut h_ref, &mut s_ref, &mut v_ref)
.unwrap();
rgb24_to(&rgb24, true, ColorMatrix::Bt709, &mut sink).unwrap();
}
let gbrp =
GbrpFrame::try_new(&g, &b, &r, w as u32, h as u32, w as u32, w as u32, w as u32).unwrap();
let mut rgb_g = std::vec![0u8; w * h * 3];
let mut rgba_g = std::vec![0u8; w * h * 4];
let mut luma_g = std::vec![0u8; w * h];
let mut h_g = std::vec![0u8; w * h];
let mut s_g = std::vec![0u8; w * h];
let mut v_g = std::vec![0u8; w * h];
{
let mut sink = MixedSinker::<Gbrp>::new(w, h)
.with_rgb(&mut rgb_g)
.unwrap()
.with_rgba(&mut rgba_g)
.unwrap()
.with_luma(&mut luma_g)
.unwrap()
.with_hsv(&mut h_g, &mut s_g, &mut v_g)
.unwrap();
gbrp_to(&gbrp, true, ColorMatrix::Bt709, &mut sink).unwrap();
}
assert_eq!(rgb_g, rgb_ref, "RGB mismatch Gbrp vs Rgb24");
assert_eq!(rgba_g, rgba_ref, "RGBA mismatch Gbrp vs Rgb24");
assert_eq!(luma_g, luma_ref, "luma mismatch Gbrp vs Rgb24");
assert_eq!(h_g, h_ref, "H mismatch Gbrp vs Rgb24");
assert_eq!(s_g, s_ref, "S mismatch Gbrp vs Rgb24");
assert_eq!(v_g, v_ref, "V mismatch Gbrp vs Rgb24");
}
#[test]
#[cfg_attr(
miri,
ignore = "SIMD-dispatched row kernels use intrinsics unsupported by Miri"
)]
fn gbrp_with_luma_u16_zero_extends_u8_luma() {
let w = 16usize;
let h = 4usize;
let rgb_seed = random_rgb(w, h, 0xABCD_EF01);
let (g, b, r) = planes_from_packed_rgb(&rgb_seed, w, h);
let src =
GbrpFrame::try_new(&g, &b, &r, w as u32, h as u32, w as u32, w as u32, w as u32).unwrap();
let mut luma_u8 = std::vec![0u8; w * h];
let mut luma_u16 = std::vec![0u16; w * h];
let mut sink = MixedSinker::<Gbrp>::new(w, h)
.with_luma(&mut luma_u8)
.unwrap()
.with_luma_u16(&mut luma_u16)
.unwrap();
gbrp_to(&src, true, ColorMatrix::Bt709, &mut sink).unwrap();
for i in 0..w * h {
assert_eq!(luma_u16[i], luma_u8[i] as u16, "px {i}");
}
}
#[test]
#[cfg_attr(
miri,
ignore = "SIMD-dispatched row kernels use intrinsics unsupported by Miri"
)]
fn gbrp_with_luma_u16_wide_row_no_alloc_regression() {
let w = 9000usize;
let h = 1usize;
let rgb_seed = random_rgb(w, h, 0x1234_5678);
let (g, b, r) = planes_from_packed_rgb(&rgb_seed, w, h);
let src =
GbrpFrame::try_new(&g, &b, &r, w as u32, h as u32, w as u32, w as u32, w as u32).unwrap();
let mut luma_u8 = std::vec![0u8; w * h];
let mut luma_u16 = std::vec![0u16; w * h];
let mut sink = MixedSinker::<Gbrp>::new(w, h)
.with_luma(&mut luma_u8)
.unwrap()
.with_luma_u16(&mut luma_u16)
.unwrap();
gbrp_to(&src, true, ColorMatrix::Bt709, &mut sink).unwrap();
for i in 0..w * h {
assert_eq!(luma_u16[i], luma_u8[i] as u16, "px {i}");
}
}
#[test]
#[cfg_attr(
miri,
ignore = "SIMD-dispatched row kernels use intrinsics unsupported by Miri"
)]
fn gbrap_with_rgba_passes_source_alpha() {
let w = 16usize;
let h = 4usize;
let g = std::vec![100u8; w * h];
let b = std::vec![50u8; w * h];
let r = std::vec![200u8; w * h];
let a = random_alpha(w, h, 0xDEAD_BEEF);
let src = GbrapFrame::try_new(
&g, &b, &r, &a, w as u32, h as u32, w as u32, w as u32, w as u32, w as u32,
)
.unwrap();
let mut rgba = std::vec![0u8; w * h * 4];
let mut sink = MixedSinker::<Gbrap>::new(w, h)
.with_rgba(&mut rgba)
.unwrap();
gbrap_to(&src, true, ColorMatrix::Bt709, &mut sink).unwrap();
for i in 0..w * h {
assert_eq!(rgba[i * 4], r[i], "R px {i}");
assert_eq!(rgba[i * 4 + 1], g[i], "G px {i}");
assert_eq!(rgba[i * 4 + 2], b[i], "B px {i}");
assert_eq!(rgba[i * 4 + 3], a[i], "A px {i}");
}
}
#[test]
#[cfg_attr(
miri,
ignore = "SIMD-dispatched row kernels use intrinsics unsupported by Miri"
)]
fn gbrap_with_rgb_drops_alpha() {
let w = 16usize;
let h = 4usize;
let rgb_seed = random_rgb(w, h, 0xBEEF_F00D);
let (g, b, r) = planes_from_packed_rgb(&rgb_seed, w, h);
let a = random_alpha(w, h, 0x1357_9BDF);
let src = GbrapFrame::try_new(
&g, &b, &r, &a, w as u32, h as u32, w as u32, w as u32, w as u32, w as u32,
)
.unwrap();
let mut rgb_out = std::vec![0u8; w * h * 3];
let mut sink = MixedSinker::<Gbrap>::new(w, h)
.with_rgb(&mut rgb_out)
.unwrap();
gbrap_to(&src, true, ColorMatrix::Bt709, &mut sink).unwrap();
let (rg, rb, rr) = planes_from_packed_rgb(&rgb_out, w, h);
assert_eq!(rg, g, "G");
assert_eq!(rb, b, "B");
assert_eq!(rr, r, "R");
}
#[test]
#[cfg_attr(
miri,
ignore = "SIMD-dispatched row kernels use intrinsics unsupported by Miri"
)]
fn gbrap_with_rgb_and_with_rgba_strategy_a_plus_matches_independent() {
let w = 32usize;
let h = 8usize;
let rgb_seed = random_rgb(w, h, 0xABBA_BABE);
let (g, b, r) = planes_from_packed_rgb(&rgb_seed, w, h);
let a = random_alpha(w, h, 0xCAFE_BABE);
let src = GbrapFrame::try_new(
&g, &b, &r, &a, w as u32, h as u32, w as u32, w as u32, w as u32, w as u32,
)
.unwrap();
let mut rgb_ref = std::vec![0u8; w * h * 3];
let mut rgba_ref = std::vec![0u8; w * h * 4];
{
let mut sink = MixedSinker::<Gbrap>::new(w, h)
.with_rgb(&mut rgb_ref)
.unwrap();
gbrap_to(&src, true, ColorMatrix::Bt709, &mut sink).unwrap();
}
{
let mut sink = MixedSinker::<Gbrap>::new(w, h)
.with_rgba(&mut rgba_ref)
.unwrap();
gbrap_to(&src, true, ColorMatrix::Bt709, &mut sink).unwrap();
}
let mut rgb_combo = std::vec![0u8; w * h * 3];
let mut rgba_combo = std::vec![0u8; w * h * 4];
{
let mut sink = MixedSinker::<Gbrap>::new(w, h)
.with_rgb(&mut rgb_combo)
.unwrap()
.with_rgba(&mut rgba_combo)
.unwrap();
gbrap_to(&src, true, ColorMatrix::Bt709, &mut sink).unwrap();
}
assert_eq!(rgb_combo, rgb_ref, "RGB mismatch combo vs independent");
assert_eq!(rgba_combo, rgba_ref, "RGBA mismatch combo vs independent");
}
#[test]
#[cfg_attr(
miri,
ignore = "SIMD-dispatched row kernels use intrinsics unsupported by Miri"
)]
fn gbrap_planar_parity_with_rgb24_when_alpha_drop() {
let w = 16usize;
let h = 8usize;
let rgb_seed = random_rgb(w, h, 0xC001_F00D);
let (g, b, r) = planes_from_packed_rgb(&rgb_seed, w, h);
let a = random_alpha(w, h, 0xDEAD_FA11);
let rgb24 = Rgb24Frame::try_new(&rgb_seed, w as u32, h as u32, (w * 3) as u32).unwrap();
let gbrap = GbrapFrame::try_new(
&g, &b, &r, &a, w as u32, h as u32, w as u32, w as u32, w as u32, w as u32,
)
.unwrap();
let mut rgb_ref = std::vec![0u8; w * h * 3];
let mut luma_ref = std::vec![0u8; w * h];
{
let mut sink = MixedSinker::<Rgb24>::new(w, h)
.with_rgb(&mut rgb_ref)
.unwrap()
.with_luma(&mut luma_ref)
.unwrap();
rgb24_to(&rgb24, true, ColorMatrix::Bt709, &mut sink).unwrap();
}
let mut rgb_g = std::vec![0u8; w * h * 3];
let mut luma_g = std::vec![0u8; w * h];
{
let mut sink = MixedSinker::<Gbrap>::new(w, h)
.with_rgb(&mut rgb_g)
.unwrap()
.with_luma(&mut luma_g)
.unwrap();
gbrap_to(&gbrap, true, ColorMatrix::Bt709, &mut sink).unwrap();
}
assert_eq!(rgb_g, rgb_ref, "RGB mismatch Gbrap vs Rgb24");
assert_eq!(luma_g, luma_ref, "luma mismatch Gbrap vs Rgb24");
}
#[test]
#[cfg_attr(
miri,
ignore = "SIMD-dispatched row kernels use intrinsics unsupported by Miri"
)]
fn gbrp_simd_matches_scalar() {
let w = 64usize;
let h = 8usize;
let rgb_seed = random_rgb(w, h, 0xFEED_FACE);
let (g, b, r) = planes_from_packed_rgb(&rgb_seed, w, h);
let src =
GbrpFrame::try_new(&g, &b, &r, w as u32, h as u32, w as u32, w as u32, w as u32).unwrap();
let make_buffers = || {
(
std::vec![0u8; w * h * 3], std::vec![0u8; w * h * 4], std::vec![0u8; w * h], std::vec![0u16; w * h], std::vec![0u8; w * h], std::vec![0u8; w * h], std::vec![0u8; w * h], )
};
let (mut r1, mut a1, mut l1, mut lu1, mut h1, mut s1, mut v1) = make_buffers();
let (mut r2, mut a2, mut l2, mut lu2, mut h2, mut s2, mut v2) = make_buffers();
{
let mut sink = MixedSinker::<Gbrp>::new(w, h)
.with_rgb(&mut r1)
.unwrap()
.with_rgba(&mut a1)
.unwrap()
.with_luma(&mut l1)
.unwrap()
.with_luma_u16(&mut lu1)
.unwrap()
.with_hsv(&mut h1, &mut s1, &mut v1)
.unwrap()
.with_simd(true);
gbrp_to(&src, true, ColorMatrix::Bt709, &mut sink).unwrap();
}
{
let mut sink = MixedSinker::<Gbrp>::new(w, h)
.with_rgb(&mut r2)
.unwrap()
.with_rgba(&mut a2)
.unwrap()
.with_luma(&mut l2)
.unwrap()
.with_luma_u16(&mut lu2)
.unwrap()
.with_hsv(&mut h2, &mut s2, &mut v2)
.unwrap()
.with_simd(false);
gbrp_to(&src, true, ColorMatrix::Bt709, &mut sink).unwrap();
}
assert_eq!(r1, r2, "rgb SIMD vs scalar");
assert_eq!(a1, a2, "rgba SIMD vs scalar");
assert_eq!(l1, l2, "luma SIMD vs scalar");
assert_eq!(lu1, lu2, "luma_u16 SIMD vs scalar");
assert_hsv_within_one_lsb(&h1, &s1, &v1, &h2, &s2, &v2);
}
#[test]
#[cfg_attr(
miri,
ignore = "SIMD-dispatched row kernels use intrinsics unsupported by Miri"
)]
fn gbrap_simd_matches_scalar() {
let w = 64usize;
let h = 8usize;
let rgb_seed = random_rgb(w, h, 0xFACE_F00D);
let (g, b, r) = planes_from_packed_rgb(&rgb_seed, w, h);
let a = random_alpha(w, h, 0xC0DE_BABE);
let src = GbrapFrame::try_new(
&g, &b, &r, &a, w as u32, h as u32, w as u32, w as u32, w as u32, w as u32,
)
.unwrap();
let make_buffers = || {
(
std::vec![0u8; w * h * 3],
std::vec![0u8; w * h * 4],
std::vec![0u8; w * h],
std::vec![0u16; w * h],
std::vec![0u8; w * h],
std::vec![0u8; w * h],
std::vec![0u8; w * h],
)
};
let (mut r1, mut a1, mut l1, mut lu1, mut h1, mut s1, mut v1) = make_buffers();
let (mut r2, mut a2, mut l2, mut lu2, mut h2, mut s2, mut v2) = make_buffers();
{
let mut sink = MixedSinker::<Gbrap>::new(w, h)
.with_rgb(&mut r1)
.unwrap()
.with_rgba(&mut a1)
.unwrap()
.with_luma(&mut l1)
.unwrap()
.with_luma_u16(&mut lu1)
.unwrap()
.with_hsv(&mut h1, &mut s1, &mut v1)
.unwrap()
.with_simd(true);
gbrap_to(&src, true, ColorMatrix::Bt709, &mut sink).unwrap();
}
{
let mut sink = MixedSinker::<Gbrap>::new(w, h)
.with_rgb(&mut r2)
.unwrap()
.with_rgba(&mut a2)
.unwrap()
.with_luma(&mut l2)
.unwrap()
.with_luma_u16(&mut lu2)
.unwrap()
.with_hsv(&mut h2, &mut s2, &mut v2)
.unwrap()
.with_simd(false);
gbrap_to(&src, true, ColorMatrix::Bt709, &mut sink).unwrap();
}
assert_eq!(r1, r2, "rgb SIMD vs scalar");
assert_eq!(a1, a2, "rgba SIMD vs scalar");
assert_eq!(l1, l2, "luma SIMD vs scalar");
assert_eq!(lu1, lu2, "luma_u16 SIMD vs scalar");
assert_hsv_within_one_lsb(&h1, &s1, &v1, &h2, &s2, &v2);
}
fn assert_hsv_within_one_lsb(h1: &[u8], s1: &[u8], v1: &[u8], h2: &[u8], s2: &[u8], v2: &[u8]) {
for (i, (&a, &b)) in h1.iter().zip(h2.iter()).enumerate() {
let d = a.abs_diff(b);
let circ = d.min(180 - d);
assert!(circ <= 1, "H divergence at pixel {i}: simd={a} scalar={b}");
}
for (i, (&a, &b)) in s1.iter().zip(s2.iter()).enumerate() {
assert!(
a.abs_diff(b) <= 1,
"S divergence at pixel {i}: simd={a} scalar={b}"
);
}
for (i, (&a, &b)) in v1.iter().zip(v2.iter()).enumerate() {
assert!(
a.abs_diff(b) <= 1,
"V divergence at pixel {i}: simd={a} scalar={b}"
);
}
}
#[test]
#[cfg_attr(
miri,
ignore = "SIMD-dispatched row kernels use intrinsics unsupported by Miri"
)]
fn gbrp_with_luma_bt709_pure_red() {
let w = 16usize;
let h = 4usize;
let g = std::vec![0u8; w * h];
let b = std::vec![0u8; w * h];
let r = std::vec![255u8; w * h];
let src =
GbrpFrame::try_new(&g, &b, &r, w as u32, h as u32, w as u32, w as u32, w as u32).unwrap();
let mut luma = std::vec![0u8; w * h];
let mut sink = MixedSinker::<Gbrp>::new(w, h).with_luma(&mut luma).unwrap();
gbrp_to(&src, true, ColorMatrix::Bt709, &mut sink).unwrap();
for &y in &luma {
assert!(y.abs_diff(54) <= 1, "got Y={y}");
}
}