1use crate::error::{DecodeError, DecodedImage};
16use crate::profile::Profile;
17use crate::yuv;
18use std::sync::atomic::AtomicBool;
19pub fn decode(src: &[u8], profile: &Profile, canceled: &AtomicBool) -> Result<DecodedImage, DecodeError> {
32 let (w, h) = crate::decoder_helpers::validate_dimensions(src, profile, "width and height must be positive", 2)?;
33
34 let mut dst = vec![0u8; w * h * 4];
35
36 if profile.is_interlaced {
37 if h % 2 != 0 {
38 return Err(DecodeError::InvalidFormat(
39 "interlaced UYVY requires an even height".into(),
40 ));
41 }
42 decode_interlaced(src, w, h, &mut dst, canceled)?;
43 } else {
44 decode_progressive(src, w, h, &mut dst, canceled)?;
45 }
46
47 #[allow(clippy::cast_possible_truncation)]
48 Ok(DecodedImage {
49 data: dst,
50 width: w as u32,
51 height: h as u32,
52 })
53}
54
55fn decode_progressive(
57 src: &[u8],
58 w: usize,
59 h: usize,
60 dst: &mut [u8],
61 canceled: &AtomicBool,
62) -> Result<(), DecodeError> {
63 let row_stride = w * 2;
64 for y in 0..h {
65 crate::pixel_utils::check_canceled(canceled, "uyvy decode canceled")?;
66 let src_off = y * row_stride;
67 let dst_off = y * w * 4;
68 decode_row(&src[src_off..], w, &mut dst[dst_off..]);
69 }
70 Ok(())
71}
72
73fn decode_interlaced(src: &[u8], w: usize, h: usize, dst: &mut [u8], canceled: &AtomicBool) -> Result<(), DecodeError> {
79 let half_rows = h / 2;
80 let field_bytes = w * half_rows * 2;
81
82 for i in 0..half_rows {
84 crate::pixel_utils::check_canceled(canceled, "uyvy decode canceled")?;
85 let src_off = i * w * 2;
86 let dst_row = i * 2;
87 decode_row(&src[src_off..], w, &mut dst[dst_row * w * 4..]);
88 }
89
90 for i in 0..half_rows {
92 crate::pixel_utils::check_canceled(canceled, "uyvy decode canceled")?;
93 let src_off = field_bytes + i * w * 2;
94 let dst_row = i * 2 + 1;
95 decode_row(&src[src_off..], w, &mut dst[dst_row * w * 4..]);
96 }
97 Ok(())
98}
99
100#[allow(clippy::similar_names)]
105fn decode_row(row_src: &[u8], w: usize, row_dst: &mut [u8]) {
106 let groups = w / 2;
107
108 #[cfg(feature = "simd")]
109 {
110 let row_bytes = groups * 4;
111 crate::simd::uyvy_row_to_bgra(&row_src[..row_bytes], &mut row_dst[..groups * 8]);
112 }
113 #[cfg(not(feature = "simd"))]
114 {
115 for g in 0..groups {
116 let src_idx = g * 4;
117 let px: [u8; 4] = [
118 row_src[src_idx],
119 row_src[src_idx + 1],
120 row_src[src_idx + 2],
121 row_src[src_idx + 3],
122 ];
123 let out = crate::simd::uyvy_quad_to_bgra(&px);
124 let d0 = g * 8;
125 row_dst[d0..d0 + 8].copy_from_slice(&out);
126 }
127 }
128
129 if w % 2 != 0 {
131 let last_src = groups * 4;
132 let y = row_src[last_src + 1];
133 let u = row_src[last_src];
134 let v = if groups > 0 { row_src[groups * 4 - 2] } else { 128 };
136 let px = yuv::yuv_to_bgra(y, u, v);
137 let d_off = groups * 8;
138 row_dst[d_off..d_off + 4].copy_from_slice(&px);
139 }
140}
141
142#[cfg(test)]
143mod tests {
144 use super::*;
145 use crate::profile::Encoding;
146 use std::sync::atomic::AtomicBool;
147
148 fn make_profile(w: i32, h: i32, interlaced: bool) -> Profile {
150 Profile {
151 prefix: 0,
152 width: w,
153 height: h,
154 encoding: Encoding::Yuv422,
155 frame_byte_length: w * h * 2,
156 is_interlaced: interlaced,
157 ..Default::default()
158 }
159 }
160
161 #[test]
166 fn zero_width() {
167 let p = make_profile(0, 100, false);
168 match decode(&[], &p, &AtomicBool::new(false)) {
169 Err(DecodeError::InvalidFormat(_)) => {}
170 other => panic!("expected InvalidFormat, got {other:?}"),
171 }
172 }
173
174 #[test]
175 fn zero_height() {
176 let p = make_profile(100, 0, false);
177 match decode(&[], &p, &AtomicBool::new(false)) {
178 Err(DecodeError::InvalidFormat(_)) => {}
179 other => panic!("expected InvalidFormat, got {other:?}"),
180 }
181 }
182
183 #[test]
184 fn buffer_too_short() {
185 let p = make_profile(4, 4, false);
186 match decode(&[0u8; 4], &p, &AtomicBool::new(false)) {
187 Err(DecodeError::BufferTooShort { expected, actual }) => {
188 assert_eq!(expected, 4 * 4 * 2);
189 assert_eq!(actual, 4);
190 }
191 other => panic!("expected BufferTooShort, got {other:?}"),
192 }
193 }
194
195 #[test]
196 fn interlaced_odd_height() {
197 let p = make_profile(4, 3, true);
198 match decode(&[0u8; 4 * 3 * 2], &p, &AtomicBool::new(false)) {
199 Err(DecodeError::InvalidFormat(_)) => {}
200 other => panic!("expected InvalidFormat, got {other:?}"),
201 }
202 }
203
204 #[test]
209 fn gray_pair_2x1() {
210 let src = [128u8, 128, 128, 128];
212 let p = make_profile(2, 1, false);
213 let img = decode(&src, &p, &AtomicBool::new(false)).unwrap();
214 assert_eq!(img.height, 1);
215 let expected = [128u8, 128, 128, 255, 128, 128, 128, 255];
216 assert_eq!(img.data, expected);
217 }
218
219 #[test]
220 fn black_pair_2x1() {
221 let src = [128u8, 0, 128, 0];
223 let p = make_profile(2, 1, false);
224 let _img = decode(&src, &p, &AtomicBool::new(false)).unwrap();
225 }
226
227 #[test]
228 fn white_pair_2x1() {
229 let src = [128u8, 255, 128, 255];
231 let p = make_profile(2, 1, false);
232 let _img = decode(&src, &p, &AtomicBool::new(false)).unwrap();
233 }
234
235 #[test]
240 fn four_by_four_pattern() {
241 let src: Vec<u8> = vec![
247 100, 10, 200, 20, 110, 30, 210, 40, 120, 50, 220, 60, 130, 70, 230, 80, 140, 90, 240, 100, 150, 110, 250,
248 120, 160, 130, 10, 140, 170, 150, 20, 160,
249 ];
250
251 let p = make_profile(4, 4, false);
252 let img = decode(&src, &p, &AtomicBool::new(false)).unwrap();
253
254 let px00 = yuv::yuv_to_bgra(10, 100, 200);
256 assert_eq!(img.data[0..4], px00);
257
258 let px01 = yuv::yuv_to_bgra(20, 100, 200);
260 assert_eq!(img.data[4..8], px01);
261
262 let px02 = yuv::yuv_to_bgra(30, 110, 210);
264 assert_eq!(img.data[8..12], px02);
265
266 let px03 = yuv::yuv_to_bgra(40, 110, 210);
268 assert_eq!(img.data[12..16], px03);
269
270 let px30 = yuv::yuv_to_bgra(130, 160, 10);
272 assert_eq!(img.data[48..52], px30);
273
274 let px33 = yuv::yuv_to_bgra(160, 170, 20);
276 assert_eq!(img.data[60..64], px33);
277 }
278
279 #[test]
284 fn interlaced_4x2() {
285 let even_field: Vec<u8> = vec![100, 10, 200, 20, 110, 30, 210, 40];
291 let odd_field: Vec<u8> = vec![150, 50, 250, 60, 160, 70, 10, 80];
292 let src: Vec<u8> = [even_field.as_slice(), odd_field.as_slice()].concat();
293
294 let p = make_profile(4, 2, true);
295 let img = decode(&src, &p, &AtomicBool::new(false)).unwrap();
296
297 let px00 = yuv::yuv_to_bgra(10, 100, 200);
299 let px01 = yuv::yuv_to_bgra(20, 100, 200);
300 let px02 = yuv::yuv_to_bgra(30, 110, 210);
301 let px03 = yuv::yuv_to_bgra(40, 110, 210);
302 assert_eq!(img.data[0..16], [px00, px01, px02, px03].concat());
303
304 let px10 = yuv::yuv_to_bgra(50, 150, 250);
306 let px11 = yuv::yuv_to_bgra(60, 150, 250);
307 let px12 = yuv::yuv_to_bgra(70, 160, 10);
308 let px13 = yuv::yuv_to_bgra(80, 160, 10);
309 assert_eq!(img.data[16..32], [px10, px11, px12, px13].concat());
310 }
311
312 #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss, clippy::cast_possible_wrap)]
313 #[test]
314 fn interlaced_matches_manual_weave() {
315 let w = 6usize;
318 let h = 4usize;
319 let half = h / 2;
320
321 let mut field0 = Vec::with_capacity(w * half * 2);
322 let mut field1 = Vec::with_capacity(w * half * 2);
323 for row in 0..half {
324 for px in 0..w / 2 {
325 let u = (row * 50 + px * 30 + 10) as u8;
326 let y0 = (row * 40 + px * 20 + 5) as u8;
327 let v = (row * 50 + px * 30 + 20) as u8;
328 let y1 = (row * 40 + px * 20 + 15) as u8;
329 field0.extend_from_slice(&[u, y0, v, y1]);
330 }
331 }
332 for row in 0..half {
333 for px in 0..w / 2 {
334 let u = (row * 70 + px * 40 + 100) as u8;
335 let y0 = (row * 60 + px * 30 + 50) as u8;
336 let v = (row * 70 + px * 40 + 120) as u8;
337 let y1 = (row * 60 + px * 30 + 60) as u8;
338 field1.extend_from_slice(&[u, y0, v, y1]);
339 }
340 }
341
342 let interlaced_src: Vec<u8> = [field0.as_slice(), field1.as_slice()].concat();
343 let p = make_profile(w as i32, h as i32, true);
344 let img = decode(&interlaced_src, &p, &AtomicBool::new(false)).unwrap();
345 let mut manual = vec![0u8; w * h * 4];
347 let mut row0_dst = vec![0u8; w * half * 4];
348 let mut row1_dst = vec![0u8; w * half * 4];
349 decode_progressive(&field0, w, half, &mut row0_dst, &AtomicBool::new(false)).unwrap();
350 decode_progressive(&field1, w, half, &mut row1_dst, &AtomicBool::new(false)).unwrap();
351
352 for i in 0..half {
353 let dst_off_even = i * 2 * w * 4;
354 let dst_off_odd = (i * 2 + 1) * w * 4;
355 let src_off = i * w * 4;
356 manual[dst_off_even..dst_off_even + w * 4].copy_from_slice(&row0_dst[src_off..src_off + w * 4]);
357 manual[dst_off_odd..dst_off_odd + w * 4].copy_from_slice(&row1_dst[src_off..src_off + w * 4]);
358 }
359
360 assert_eq!(img.data, manual);
361 }
362
363 #[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
368 #[test]
369 fn bgra_output_alpha_is_always_255() {
370 let mut src: Vec<u8> = Vec::new();
371 for y in 0..2i32 {
373 for x in 0..2 / 2 {
374 src.push(100); src.push((y * 50 + x * 30) as u8); src.push(200); src.push((y * 50 + x * 30 + 20) as u8); }
379 }
380 let p = make_profile(2, 2, false);
381 let img = decode(&src, &p, &AtomicBool::new(false)).unwrap();
382 for (i, chunk) in img.data.chunks_exact(4).enumerate() {
383 assert_eq!(chunk[3], 255, "alpha must be 255 at pixel {i}, got {}", chunk[3]);
384 }
385 }
386}