use oxideav_core::frame::VideoPlane;
use oxideav_core::VideoFrame;
use oxideav_prores::alpha::AlphaChannelType;
use oxideav_prores::decoder::{decode_packet_with_depth, BitDepth};
use oxideav_prores::encoder::encode_frame_with_alpha;
use oxideav_prores::frame::{ChromaFormat, Profile};
const W: usize = 32;
const H: usize = 32;
fn alpha_value(x: usize, y: usize) -> u8 {
let v = (x * 255) / (W - 1);
let w = (y * 255) / (H - 1);
((v + w) / 2) as u8
}
fn source_with_8bit_alpha() -> VideoFrame {
let mut y = vec![0u8; W * H];
let mut cb = vec![128u8; W * H];
let mut cr = vec![128u8; W * H];
let mut a = vec![0u8; W * H];
for j in 0..H {
for i in 0..W {
y[j * W + i] = ((i + j) as u16 % 256) as u8;
cb[j * W + i] = 128;
cr[j * W + i] = 128;
a[j * W + i] = alpha_value(i, j);
}
}
VideoFrame {
pts: Some(0),
planes: vec![
VideoPlane { stride: W, data: y },
VideoPlane {
stride: W,
data: cb,
},
VideoPlane {
stride: W,
data: cr,
},
VideoPlane { stride: W, data: a },
],
}
}
fn expected_sample(alpha: u8, out: BitDepth) -> u16 {
let max_out = out.max_value() as u64;
let num = max_out * alpha as u64 * 2 + 255;
(num / (255 * 2)) as u16
}
fn read_alpha_plane(frame: &VideoFrame, out: BitDepth) -> Vec<u16> {
assert_eq!(frame.planes.len(), 4, "expected Y/Cb/Cr/A planes");
let plane = &frame.planes[3];
match out {
BitDepth::Eight => plane.data.iter().map(|&b| b as u16).collect(),
BitDepth::Ten | BitDepth::Twelve | BitDepth::Sixteen => plane
.data
.chunks_exact(2)
.map(|c| u16::from_le_bytes([c[0], c[1]]))
.collect(),
_ => unreachable!("variant not exercised by this test"),
}
}
fn roundtrip_at_depth(out: BitDepth) {
let src = source_with_8bit_alpha();
let pkt = encode_frame_with_alpha(
&src,
W as u32,
H as u32,
ChromaFormat::Y444,
BitDepth::Eight, Profile::Prores4444,
4, Some(AlphaChannelType::Eight),
)
.expect("encode 4444 + 8-bit alpha");
let frame = decode_packet_with_depth(&pkt, Some(0), Some((out, ChromaFormat::Y444)))
.unwrap_or_else(|e| panic!("decode at {out:?} failed: {e:?}"));
let got = read_alpha_plane(&frame, out);
assert_eq!(
got.len(),
W * H,
"alpha plane must be cropped to the {W}x{H} image"
);
for j in 0..H {
for i in 0..W {
let a = alpha_value(i, j);
let want = expected_sample(a, out);
assert_eq!(
got[j * W + i],
want,
"§7.5.2 mismatch at ({i},{j}) out={out:?}: alpha {a} -> got {} want {want}",
got[j * W + i]
);
}
}
}
#[test]
fn alpha8_decodes_identity_at_8bit() {
roundtrip_at_depth(BitDepth::Eight);
}
#[test]
fn alpha8_promotes_to_10bit() {
roundtrip_at_depth(BitDepth::Ten);
}
#[test]
fn alpha8_promotes_to_12bit() {
roundtrip_at_depth(BitDepth::Twelve);
}
#[test]
fn alpha8_promotes_to_16bit() {
roundtrip_at_depth(BitDepth::Sixteen);
}
#[test]
fn alpha8_endpoints_exact_each_depth() {
for out in [
BitDepth::Eight,
BitDepth::Ten,
BitDepth::Twelve,
BitDepth::Sixteen,
] {
assert_eq!(expected_sample(0, out), 0);
assert_eq!(u32::from(expected_sample(255, out)), out.max_value());
}
}
const WP: usize = 32;
const HP: usize = 24;
fn ramp_alpha(x: usize, y: usize) -> u8 {
let v = (x * 255) / (WP - 1);
let w = (y * 255) / (HP - 1);
((v + w) / 2) as u8
}
fn source_partial_height_alpha() -> VideoFrame {
let mut y = vec![0u8; WP * HP];
let mut cb = vec![128u8; WP * HP];
let cr = vec![128u8; WP * HP];
let mut a = vec![0u8; WP * HP];
for j in 0..HP {
for i in 0..WP {
y[j * WP + i] = ((i + j) as u16 % 256) as u8;
cb[j * WP + i] = 128;
a[j * WP + i] = ramp_alpha(i, j);
}
}
VideoFrame {
pts: Some(0),
planes: vec![
VideoPlane {
stride: WP,
data: y,
},
VideoPlane {
stride: WP,
data: cb,
},
VideoPlane {
stride: WP,
data: cr,
},
VideoPlane {
stride: WP,
data: a,
},
],
}
}
fn partial_roundtrip_at_depth(out: BitDepth) {
let src = source_partial_height_alpha();
let pkt = encode_frame_with_alpha(
&src,
WP as u32,
HP as u32,
ChromaFormat::Y444,
BitDepth::Eight,
Profile::Prores4444,
4,
Some(AlphaChannelType::Eight),
)
.expect("encode 4444 + 8-bit alpha at non-MB-aligned height");
let frame = decode_packet_with_depth(&pkt, Some(0), Some((out, ChromaFormat::Y444)))
.unwrap_or_else(|e| panic!("decode at {out:?} failed: {e:?}"));
let got = read_alpha_plane(&frame, out);
assert_eq!(
got.len(),
WP * HP,
"alpha plane must crop to the visible {WP}x{HP} image (including the partial bottom MB row)"
);
for j in 0..HP {
for i in 0..WP {
let a = ramp_alpha(i, j);
let want = expected_sample(a, out);
assert_eq!(
got[j * WP + i],
want,
"§7.5.2/§7.5.3 mismatch at ({i},{j}) out={out:?} \
(partial bottom MB row begins at j=16): alpha {a} -> got {} want {want}",
got[j * WP + i]
);
}
}
}
#[test]
fn alpha8_partial_bottom_row_identity_8bit() {
partial_roundtrip_at_depth(BitDepth::Eight);
}
#[test]
fn alpha8_partial_bottom_row_promotes_10bit() {
partial_roundtrip_at_depth(BitDepth::Ten);
}
#[test]
fn alpha8_partial_bottom_row_promotes_12bit() {
partial_roundtrip_at_depth(BitDepth::Twelve);
}
#[test]
fn alpha16_partial_bottom_row_roundtrips() {
let mut a = vec![0u8; WP * HP * 2];
for j in 0..HP {
for i in 0..WP {
let v = (((i + j * WP) * 65535) / (WP * HP - 1)) as u16;
let off = (j * WP + i) * 2;
a[off] = (v & 0xFF) as u8;
a[off + 1] = (v >> 8) as u8;
}
}
let mut y12 = vec![0u8; WP * HP * 2];
let mut c12 = vec![0u8; WP * HP * 2];
for px in 0..WP * HP {
y12[px * 2] = 0x00;
y12[px * 2 + 1] = 0x08;
c12[px * 2] = 0x00;
c12[px * 2 + 1] = 0x08;
}
let src = VideoFrame {
pts: Some(0),
planes: vec![
VideoPlane {
stride: WP * 2,
data: y12,
},
VideoPlane {
stride: WP * 2,
data: c12.clone(),
},
VideoPlane {
stride: WP * 2,
data: c12,
},
VideoPlane {
stride: WP * 2,
data: a.clone(),
},
],
};
let pkt = encode_frame_with_alpha(
&src,
WP as u32,
HP as u32,
ChromaFormat::Y444,
BitDepth::Twelve,
Profile::Prores4444,
2,
Some(AlphaChannelType::Sixteen),
)
.expect("encode 4444 + 16-bit alpha at non-MB-aligned height");
let frame =
decode_packet_with_depth(&pkt, Some(0), Some((BitDepth::Twelve, ChromaFormat::Y444)))
.expect("decode 16-bit alpha");
let got = read_alpha_plane(&frame, BitDepth::Twelve);
assert_eq!(got.len(), WP * HP);
for j in 0..HP {
for i in 0..WP {
let off = (j * WP + i) * 2;
let alpha = u16::from_le_bytes([a[off], a[off + 1]]) as u64;
let want = ((4095u64 * alpha * 2 + 65535) / (65535 * 2)) as u16;
assert_eq!(
got[j * WP + i],
want,
"16-bit alpha §7.5.2 demotion mismatch at ({i},{j})"
);
}
}
}