1use crate::error::{DecodeError, DecodedImage};
27use crate::profile::Profile;
28use std::sync::atomic::AtomicBool;
29
30pub fn decode(src: &[u8], profile: &Profile, canceled: &AtomicBool) -> Result<DecodedImage, DecodeError> {
50 let (w, h) = crate::decoder_helpers::validate_dimensions(src, profile, "CL dimensions must be positive", 2)?;
51 let pixel_count = w * h;
52 let expected = pixel_count * 2;
53 let mut dst = vec![0u8; pixel_count * 4];
54
55 crate::pixel_utils::check_canceled(canceled, "cl decode canceled")?;
60 crate::simd::cl_row_to_bgra(&src[..expected], &mut dst);
61
62 #[allow(clippy::cast_possible_truncation)]
63 let out_w = w as u32;
64 #[allow(clippy::cast_possible_truncation)]
65 let out_h = h as u32;
66
67 Ok(DecodedImage {
68 data: dst,
69 width: out_w,
70 height: out_h,
71 })
72}
73
74#[cfg(test)]
79mod tests {
80 use super::*;
81 use crate::profile::Encoding;
82 use std::sync::atomic::AtomicBool;
83
84 fn make_profile(w: i32, h: i32) -> Profile {
85 Profile {
86 prefix: 0,
87 width: w,
88 height: h,
89 encoding: Encoding::Rgb565,
90 frame_byte_length: w * h * 2,
91 cl_chroma: true,
92 ..Default::default()
93 }
94 }
95
96 #[test]
99 fn zero_width_returns_invalid_format() {
100 let profile = make_profile(0, 100);
101 let result = decode(b"", &profile, &AtomicBool::new(false));
102 assert!(matches!(result, Err(DecodeError::InvalidFormat(..))));
103 }
104
105 #[test]
106 fn zero_height_returns_invalid_format() {
107 let profile = make_profile(100, 0);
108 let result = decode(b"", &profile, &AtomicBool::new(false));
109 assert!(matches!(result, Err(DecodeError::InvalidFormat(..))));
110 }
111
112 #[test]
113 fn negative_width_returns_invalid_format() {
114 let profile = make_profile(-1, 100);
115 let result = decode(b"", &profile, &AtomicBool::new(false));
116 assert!(matches!(result, Err(DecodeError::InvalidFormat(..))));
117 }
118
119 #[test]
120 fn buffer_too_short_returns_error() {
121 let profile = make_profile(10, 10);
122 let result = decode(&[0u8; 10], &profile, &AtomicBool::new(false));
124 assert!(matches!(
125 result,
126 Err(DecodeError::BufferTooShort {
127 expected: 200,
128 actual: 10
129 })
130 ));
131 }
132
133 #[test]
136 fn gray_pixel_neutral_chroma() {
137 let profile = make_profile(1, 1);
140 let img = decode(&[128, 0x88], &profile, &AtomicBool::new(false)).unwrap();
141 assert_eq!(img.data, [128, 128, 128, 255]);
142 assert_eq!(img.width, 1);
143 assert_eq!(img.height, 1);
144 }
145
146 #[test]
147 fn black_with_neutral_chroma() {
148 let profile = make_profile(1, 1);
150 let img = decode(&[0, 0x88], &profile, &AtomicBool::new(false)).unwrap();
151 assert_eq!(img.data, [0, 0, 0, 255]);
152 }
153
154 #[test]
155 fn white_with_neutral_chroma() {
156 let profile = make_profile(1, 1);
158 let img = decode(&[255, 0x88], &profile, &AtomicBool::new(false)).unwrap();
159 assert_eq!(img.data, [255, 255, 255, 255]);
160 }
161
162 #[test]
165 fn chroma_nibble_high_is_cr_low_is_cb() {
166 let profile = make_profile(1, 1);
175 let img = decode(&[128, 0xF0], &profile, &AtomicBool::new(false)).unwrap();
176 assert_eq!(img.data, [0, 92, 255, 255], "high nibble CB=0, CR=15");
177 }
178
179 #[test]
180 fn chroma_nibble_low_is_cb_high_is_cr() {
181 let profile = make_profile(1, 1);
190 let img = decode(&[128, 0x0F], &profile, &AtomicBool::new(false)).unwrap();
191 assert_eq!(img.data, [255, 182, 0, 255], "low nibble CB=15, CR=0");
192 }
193
194 #[test]
197 fn two_pixels_planar_layout() {
198 let profile = make_profile(2, 1);
202 let img = decode(&[128, 0, 0x88, 0x88], &profile, &AtomicBool::new(false)).unwrap();
203 assert_eq!(img.width, 2);
204 assert_eq!(img.height, 1);
205 assert_eq!(
206 img.data,
207 [
208 128, 128, 128, 255, 0, 0, 0, 255, ]
211 );
212 }
213
214 #[test]
215 fn two_by_two_grid() {
216 let profile = make_profile(2, 2);
219 let img = decode(
220 &[128, 128, 128, 128, 0x88, 0x88, 0x88, 0x88],
221 &profile,
222 &AtomicBool::new(false),
223 )
224 .unwrap();
225 assert_eq!(img.width, 2);
226 assert_eq!(img.height, 2);
227 let expected = vec![128u8, 128, 128, 255]; for y in 0..2 {
229 for x in 0..2 {
230 let off = (y * 2 + x) * 4;
231 assert_eq!(img.data[off..off + 4], expected, "pixel ({x},{y}) mismatch");
232 }
233 }
234 }
235
236 #[test]
239 fn chroma_nibbles_at_extremes() {
240 let profile = make_profile(1, 1);
246 let img = decode(&[255, 0xFF], &profile, &AtomicBool::new(false)).unwrap();
247 assert_eq!(img.data, [255, 137, 255, 255]);
248 }
249
250 #[test]
253 fn different_y_and_chroma_planes() {
254 let profile = make_profile(2, 1);
258 let img = decode(&[100, 200, 0x88, 0x88], &profile, &AtomicBool::new(false)).unwrap();
259 assert_eq!(img.data[0..4], [100, 100, 100, 255], "pixel 0 uses Y=100");
260 assert_eq!(img.data[4..8], [200, 200, 200, 255], "pixel 1 uses Y=200");
261 }
262
263 #[test]
266 #[allow(clippy::cast_possible_truncation)]
267 fn odd_width_3x3_decode_correct() {
268 let mut state: u32 = 0x9ABC_DEF0;
271 let n = 9;
272 let mut src = vec![0u8; n * 2];
273 for b in &mut src {
274 state = state.wrapping_mul(1_103_515_245).wrapping_add(12_345);
275 *b = (state >> 16) as u8;
276 }
277 let profile = make_profile(3, 3);
278 let img = decode(&src, &profile, &AtomicBool::new(false)).unwrap();
279 let (y, chroma) = src.split_at(n);
281 let mut expected = vec![0u8; n * 4];
282 for i in 0..n {
283 let cr = chroma[i] & 0xF0;
284 let cb = (chroma[i] & 0x0F) << 4;
285 let px = crate::yuv::yuv_to_bgra(y[i], cb, cr);
286 let o = i * 4;
287 expected[o..o + 4].copy_from_slice(&px);
288 }
289 assert_eq!(img.data, expected, "3×3 CL decode mismatch");
290 assert_eq!(img.width, 3);
291 assert_eq!(img.height, 3);
292 }
293
294 #[test]
295 #[allow(clippy::cast_possible_truncation)]
296 fn odd_width_7x3_decode_correct() {
297 let mut state: u32 = 0xFEDC_BA09;
301 let n = 21;
302 let mut src = vec![0u8; n * 2];
303 for b in &mut src {
304 state = state.wrapping_mul(1_103_515_245).wrapping_add(12_345);
305 *b = (state >> 16) as u8;
306 }
307 let profile = make_profile(7, 3);
308 let img = decode(&src, &profile, &AtomicBool::new(false)).unwrap();
309 let (y, chroma) = src.split_at(n);
310 let mut expected = vec![0u8; n * 4];
311 for i in 0..n {
312 let cr = chroma[i] & 0xF0;
313 let cb = (chroma[i] & 0x0F) << 4;
314 let px = crate::yuv::yuv_to_bgra(y[i], cb, cr);
315 let o = i * 4;
316 expected[o..o + 4].copy_from_slice(&px);
317 }
318 assert_eq!(img.data, expected, "7×3 CL decode mismatch");
319 }
320}