use crate::error::{DecodeError, DecodedImage};
use crate::profile::Profile;
use crate::yuv;
use std::sync::atomic::AtomicBool;
pub fn decode(src: &[u8], profile: &Profile, canceled: &AtomicBool) -> Result<DecodedImage, DecodeError> {
let (w, h) = crate::decoder_helpers::validate_dimensions(src, profile, "width and height must be positive", 2)?;
let mut dst = vec![0u8; w * h * 4];
if profile.is_interlaced {
if h % 2 != 0 {
return Err(DecodeError::InvalidFormat(
"interlaced UYVY requires an even height".into(),
));
}
decode_interlaced(src, w, h, &mut dst, canceled)?;
} else {
decode_progressive(src, w, h, &mut dst, canceled)?;
}
#[allow(clippy::cast_possible_truncation)]
Ok(DecodedImage {
data: dst,
width: w as u32,
height: h as u32,
})
}
fn decode_progressive(
src: &[u8],
w: usize,
h: usize,
dst: &mut [u8],
canceled: &AtomicBool,
) -> Result<(), DecodeError> {
let row_stride = w * 2;
for y in 0..h {
crate::pixel_utils::check_canceled(canceled, "uyvy decode canceled")?;
let src_off = y * row_stride;
let dst_off = y * w * 4;
decode_row(&src[src_off..], w, &mut dst[dst_off..]);
}
Ok(())
}
fn decode_interlaced(src: &[u8], w: usize, h: usize, dst: &mut [u8], canceled: &AtomicBool) -> Result<(), DecodeError> {
let half_rows = h / 2;
let field_bytes = w * half_rows * 2;
for i in 0..half_rows {
crate::pixel_utils::check_canceled(canceled, "uyvy decode canceled")?;
let src_off = i * w * 2;
let dst_row = i * 2;
decode_row(&src[src_off..], w, &mut dst[dst_row * w * 4..]);
}
for i in 0..half_rows {
crate::pixel_utils::check_canceled(canceled, "uyvy decode canceled")?;
let src_off = field_bytes + i * w * 2;
let dst_row = i * 2 + 1;
decode_row(&src[src_off..], w, &mut dst[dst_row * w * 4..]);
}
Ok(())
}
#[allow(clippy::similar_names)]
fn decode_row(row_src: &[u8], w: usize, row_dst: &mut [u8]) {
let groups = w / 2;
#[cfg(feature = "simd")]
{
let row_bytes = groups * 4;
crate::simd::uyvy_row_to_bgra(&row_src[..row_bytes], &mut row_dst[..groups * 8]);
}
#[cfg(not(feature = "simd"))]
{
for g in 0..groups {
let src_idx = g * 4;
let px: [u8; 4] = [
row_src[src_idx],
row_src[src_idx + 1],
row_src[src_idx + 2],
row_src[src_idx + 3],
];
let out = crate::simd::uyvy_quad_to_bgra(&px);
let d0 = g * 8;
row_dst[d0..d0 + 8].copy_from_slice(&out);
}
}
if w % 2 != 0 {
let last_src = groups * 4;
let y = row_src[last_src + 1];
let u = row_src[last_src];
let v = if groups > 0 { row_src[groups * 4 - 2] } else { 128 };
let px = yuv::yuv_to_bgra(y, u, v);
let d_off = groups * 8;
row_dst[d_off..d_off + 4].copy_from_slice(&px);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::profile::Encoding;
use std::sync::atomic::AtomicBool;
fn make_profile(w: i32, h: i32, interlaced: bool) -> Profile {
Profile {
prefix: 0,
width: w,
height: h,
encoding: Encoding::Yuv422,
frame_byte_length: w * h * 2,
is_interlaced: interlaced,
..Default::default()
}
}
#[test]
fn zero_width() {
let p = make_profile(0, 100, false);
match decode(&[], &p, &AtomicBool::new(false)) {
Err(DecodeError::InvalidFormat(_)) => {}
other => panic!("expected InvalidFormat, got {other:?}"),
}
}
#[test]
fn zero_height() {
let p = make_profile(100, 0, false);
match decode(&[], &p, &AtomicBool::new(false)) {
Err(DecodeError::InvalidFormat(_)) => {}
other => panic!("expected InvalidFormat, got {other:?}"),
}
}
#[test]
fn buffer_too_short() {
let p = make_profile(4, 4, false);
match decode(&[0u8; 4], &p, &AtomicBool::new(false)) {
Err(DecodeError::BufferTooShort { expected, actual }) => {
assert_eq!(expected, 4 * 4 * 2);
assert_eq!(actual, 4);
}
other => panic!("expected BufferTooShort, got {other:?}"),
}
}
#[test]
fn interlaced_odd_height() {
let p = make_profile(4, 3, true);
match decode(&[0u8; 4 * 3 * 2], &p, &AtomicBool::new(false)) {
Err(DecodeError::InvalidFormat(_)) => {}
other => panic!("expected InvalidFormat, got {other:?}"),
}
}
#[test]
fn gray_pair_2x1() {
let src = [128u8, 128, 128, 128];
let p = make_profile(2, 1, false);
let img = decode(&src, &p, &AtomicBool::new(false)).unwrap();
assert_eq!(img.height, 1);
let expected = [128u8, 128, 128, 255, 128, 128, 128, 255];
assert_eq!(img.data, expected);
}
#[test]
fn black_pair_2x1() {
let src = [128u8, 0, 128, 0];
let p = make_profile(2, 1, false);
let _img = decode(&src, &p, &AtomicBool::new(false)).unwrap();
}
#[test]
fn white_pair_2x1() {
let src = [128u8, 255, 128, 255];
let p = make_profile(2, 1, false);
let _img = decode(&src, &p, &AtomicBool::new(false)).unwrap();
}
#[test]
fn four_by_four_pattern() {
let src: Vec<u8> = vec![
100, 10, 200, 20, 110, 30, 210, 40, 120, 50, 220, 60, 130, 70, 230, 80, 140, 90, 240, 100, 150, 110, 250,
120, 160, 130, 10, 140, 170, 150, 20, 160,
];
let p = make_profile(4, 4, false);
let img = decode(&src, &p, &AtomicBool::new(false)).unwrap();
let px00 = yuv::yuv_to_bgra(10, 100, 200);
assert_eq!(img.data[0..4], px00);
let px01 = yuv::yuv_to_bgra(20, 100, 200);
assert_eq!(img.data[4..8], px01);
let px02 = yuv::yuv_to_bgra(30, 110, 210);
assert_eq!(img.data[8..12], px02);
let px03 = yuv::yuv_to_bgra(40, 110, 210);
assert_eq!(img.data[12..16], px03);
let px30 = yuv::yuv_to_bgra(130, 160, 10);
assert_eq!(img.data[48..52], px30);
let px33 = yuv::yuv_to_bgra(160, 170, 20);
assert_eq!(img.data[60..64], px33);
}
#[test]
fn interlaced_4x2() {
let even_field: Vec<u8> = vec![100, 10, 200, 20, 110, 30, 210, 40];
let odd_field: Vec<u8> = vec![150, 50, 250, 60, 160, 70, 10, 80];
let src: Vec<u8> = [even_field.as_slice(), odd_field.as_slice()].concat();
let p = make_profile(4, 2, true);
let img = decode(&src, &p, &AtomicBool::new(false)).unwrap();
let px00 = yuv::yuv_to_bgra(10, 100, 200);
let px01 = yuv::yuv_to_bgra(20, 100, 200);
let px02 = yuv::yuv_to_bgra(30, 110, 210);
let px03 = yuv::yuv_to_bgra(40, 110, 210);
assert_eq!(img.data[0..16], [px00, px01, px02, px03].concat());
let px10 = yuv::yuv_to_bgra(50, 150, 250);
let px11 = yuv::yuv_to_bgra(60, 150, 250);
let px12 = yuv::yuv_to_bgra(70, 160, 10);
let px13 = yuv::yuv_to_bgra(80, 160, 10);
assert_eq!(img.data[16..32], [px10, px11, px12, px13].concat());
}
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss, clippy::cast_possible_wrap)]
#[test]
fn interlaced_matches_manual_weave() {
let w = 6usize;
let h = 4usize;
let half = h / 2;
let mut field0 = Vec::with_capacity(w * half * 2);
let mut field1 = Vec::with_capacity(w * half * 2);
for row in 0..half {
for px in 0..w / 2 {
let u = (row * 50 + px * 30 + 10) as u8;
let y0 = (row * 40 + px * 20 + 5) as u8;
let v = (row * 50 + px * 30 + 20) as u8;
let y1 = (row * 40 + px * 20 + 15) as u8;
field0.extend_from_slice(&[u, y0, v, y1]);
}
}
for row in 0..half {
for px in 0..w / 2 {
let u = (row * 70 + px * 40 + 100) as u8;
let y0 = (row * 60 + px * 30 + 50) as u8;
let v = (row * 70 + px * 40 + 120) as u8;
let y1 = (row * 60 + px * 30 + 60) as u8;
field1.extend_from_slice(&[u, y0, v, y1]);
}
}
let interlaced_src: Vec<u8> = [field0.as_slice(), field1.as_slice()].concat();
let p = make_profile(w as i32, h as i32, true);
let img = decode(&interlaced_src, &p, &AtomicBool::new(false)).unwrap();
let mut manual = vec![0u8; w * h * 4];
let mut row0_dst = vec![0u8; w * half * 4];
let mut row1_dst = vec![0u8; w * half * 4];
decode_progressive(&field0, w, half, &mut row0_dst, &AtomicBool::new(false)).unwrap();
decode_progressive(&field1, w, half, &mut row1_dst, &AtomicBool::new(false)).unwrap();
for i in 0..half {
let dst_off_even = i * 2 * w * 4;
let dst_off_odd = (i * 2 + 1) * w * 4;
let src_off = i * w * 4;
manual[dst_off_even..dst_off_even + w * 4].copy_from_slice(&row0_dst[src_off..src_off + w * 4]);
manual[dst_off_odd..dst_off_odd + w * 4].copy_from_slice(&row1_dst[src_off..src_off + w * 4]);
}
assert_eq!(img.data, manual);
}
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
#[test]
fn bgra_output_alpha_is_always_255() {
let mut src: Vec<u8> = Vec::new();
for y in 0..2i32 {
for x in 0..2 / 2 {
src.push(100); src.push((y * 50 + x * 30) as u8); src.push(200); src.push((y * 50 + x * 30 + 20) as u8); }
}
let p = make_profile(2, 2, false);
let img = decode(&src, &p, &AtomicBool::new(false)).unwrap();
for (i, chunk) in img.data.chunks_exact(4).enumerate() {
assert_eq!(chunk[3], 255, "alpha must be 255 at pixel {i}, got {}", chunk[3]);
}
}
}