styx_codec/decoder/raw/
bayer.rs

1use std::sync::Arc;
2use styx_core::prelude::*;
3use rayon::prelude::*;
4
5use crate::{Codec, CodecDescriptor, CodecError, CodecKind};
6
7#[cfg(feature = "image")]
8use crate::decoder::{ImageDecode, process_to_dynamic};
9
10#[derive(Clone, Copy, PartialEq, Eq)]
11#[allow(clippy::upper_case_acronyms)]
12enum BayerPattern {
13    RGGB,
14    BGGR,
15    GBRG,
16    GRBG,
17}
18
19#[derive(Clone, Copy)]
20pub struct BayerInfo {
21    pattern: BayerPattern,
22    bit_depth: u8,
23    bytes_per_sample: usize,
24}
25
26pub fn bayer_info(fourcc: FourCc) -> Option<BayerInfo> {
27    let code = fourcc.to_u32().to_le_bytes();
28    let info = match &code {
29        b"BA81" => BayerInfo {
30            pattern: BayerPattern::BGGR,
31            bit_depth: 8,
32            bytes_per_sample: 1,
33        },
34        b"BA10" => BayerInfo {
35            // V4L2_PIX_FMT_SGRBG10 (BA10): GRGR.. BGBG..
36            pattern: BayerPattern::GRBG,
37            bit_depth: 10,
38            bytes_per_sample: 2,
39        },
40        b"BA12" => BayerInfo {
41            // V4L2_PIX_FMT_SGRBG12 (BA12): GRGR.. BGBG..
42            pattern: BayerPattern::GRBG,
43            bit_depth: 12,
44            bytes_per_sample: 2,
45        },
46        b"BA14" => BayerInfo {
47            pattern: BayerPattern::GRBG,
48            bit_depth: 14,
49            bytes_per_sample: 2,
50        },
51        b"BG10" => BayerInfo {
52            pattern: BayerPattern::BGGR,
53            bit_depth: 10,
54            bytes_per_sample: 2,
55        },
56        b"BG12" => BayerInfo {
57            pattern: BayerPattern::BGGR,
58            bit_depth: 12,
59            bytes_per_sample: 2,
60        },
61        b"BG14" => BayerInfo {
62            pattern: BayerPattern::BGGR,
63            bit_depth: 14,
64            bytes_per_sample: 2,
65        },
66        b"BG16" => BayerInfo {
67            pattern: BayerPattern::BGGR,
68            bit_depth: 16,
69            bytes_per_sample: 2,
70        },
71        b"GB10" => BayerInfo {
72            pattern: BayerPattern::GBRG,
73            bit_depth: 10,
74            bytes_per_sample: 2,
75        },
76        b"GB12" => BayerInfo {
77            pattern: BayerPattern::GBRG,
78            bit_depth: 12,
79            bytes_per_sample: 2,
80        },
81        b"GB14" => BayerInfo {
82            pattern: BayerPattern::GBRG,
83            bit_depth: 14,
84            bytes_per_sample: 2,
85        },
86        b"GB16" => BayerInfo {
87            pattern: BayerPattern::GBRG,
88            bit_depth: 16,
89            bytes_per_sample: 2,
90        },
91        b"RG10" => BayerInfo {
92            pattern: BayerPattern::RGGB,
93            bit_depth: 10,
94            bytes_per_sample: 2,
95        },
96        b"RG12" => BayerInfo {
97            pattern: BayerPattern::RGGB,
98            bit_depth: 12,
99            bytes_per_sample: 2,
100        },
101        b"RG14" => BayerInfo {
102            pattern: BayerPattern::RGGB,
103            bit_depth: 14,
104            bytes_per_sample: 2,
105        },
106        b"RG16" => BayerInfo {
107            pattern: BayerPattern::RGGB,
108            bit_depth: 16,
109            bytes_per_sample: 2,
110        },
111        b"GR10" => BayerInfo {
112            pattern: BayerPattern::GRBG,
113            bit_depth: 10,
114            bytes_per_sample: 2,
115        },
116        b"GR12" => BayerInfo {
117            pattern: BayerPattern::GRBG,
118            bit_depth: 12,
119            bytes_per_sample: 2,
120        },
121        b"GR14" => BayerInfo {
122            pattern: BayerPattern::GRBG,
123            bit_depth: 14,
124            bytes_per_sample: 2,
125        },
126        b"GR16" => BayerInfo {
127            pattern: BayerPattern::GRBG,
128            bit_depth: 16,
129            bytes_per_sample: 2,
130        },
131        b"BYR2" => BayerInfo {
132            pattern: BayerPattern::BGGR,
133            bit_depth: 16,
134            bytes_per_sample: 2,
135        },
136        b"RGGB" => BayerInfo {
137            pattern: BayerPattern::RGGB,
138            bit_depth: 8,
139            bytes_per_sample: 1,
140        },
141        b"GRBG" => BayerInfo {
142            pattern: BayerPattern::GRBG,
143            bit_depth: 8,
144            bytes_per_sample: 1,
145        },
146        b"GBRG" => BayerInfo {
147            pattern: BayerPattern::GBRG,
148            bit_depth: 8,
149            bytes_per_sample: 1,
150        },
151        b"BGGR" => BayerInfo {
152            pattern: BayerPattern::BGGR,
153            bit_depth: 8,
154            bytes_per_sample: 1,
155        },
156        // V4L2_PIX_FMT_S*10P / V4L2_PIX_FMT_S*12P (MIPI packed RAW10/RAW12).
157        // bytes_per_sample = 0 signals packed bitstream.
158        b"pBAA" => BayerInfo {
159            pattern: BayerPattern::BGGR,
160            bit_depth: 10,
161            bytes_per_sample: 0,
162        },
163        b"pGAA" => BayerInfo {
164            pattern: BayerPattern::GBRG,
165            bit_depth: 10,
166            bytes_per_sample: 0,
167        },
168        b"pgAA" => BayerInfo {
169            pattern: BayerPattern::GRBG,
170            bit_depth: 10,
171            bytes_per_sample: 0,
172        },
173        b"pRAA" => BayerInfo {
174            pattern: BayerPattern::RGGB,
175            bit_depth: 10,
176            bytes_per_sample: 0,
177        },
178        b"pBCC" => BayerInfo {
179            pattern: BayerPattern::BGGR,
180            bit_depth: 12,
181            bytes_per_sample: 0,
182        },
183        b"pGCC" => BayerInfo {
184            pattern: BayerPattern::GBRG,
185            bit_depth: 12,
186            bytes_per_sample: 0,
187        },
188        b"pgCC" => BayerInfo {
189            pattern: BayerPattern::GRBG,
190            bit_depth: 12,
191            bytes_per_sample: 0,
192        },
193        b"pRCC" => BayerInfo {
194            pattern: BayerPattern::RGGB,
195            bit_depth: 12,
196            bytes_per_sample: 0,
197        },
198        _ => return None,
199    };
200    Some(info)
201}
202
203fn color_at(pattern: BayerPattern, x: usize, y: usize) -> (bool, bool, bool) {
204    match pattern {
205        BayerPattern::RGGB => match (y & 1, x & 1) {
206            (0, 0) => (true, false, false),
207            (0, 1) => (false, true, false),
208            (1, 0) => (false, true, false),
209            _ => (false, false, true),
210        },
211        BayerPattern::BGGR => match (y & 1, x & 1) {
212            (0, 0) => (false, false, true),
213            (0, 1) => (false, true, false),
214            (1, 0) => (false, true, false),
215            _ => (true, false, false),
216        },
217        BayerPattern::GBRG => match (y & 1, x & 1) {
218            (0, 0) => (false, true, false),
219            (0, 1) => (false, false, true),
220            (1, 0) => (true, false, false),
221            _ => (false, true, false),
222        },
223        BayerPattern::GRBG => match (y & 1, x & 1) {
224            (0, 0) => (false, true, false),
225            (0, 1) => (true, false, false),
226            (1, 0) => (false, false, true),
227            _ => (false, true, false),
228        },
229    }
230}
231
232fn sample_to_u8(data: &[u8], offset: usize, bps: usize, bit_depth: u8) -> u8 {
233    if bps == 1 {
234        data[offset]
235    } else {
236        let lo = data[offset];
237        let hi = data[offset + 1];
238        let v = u16::from_le_bytes([lo, hi]);
239        let shift = (bit_depth.saturating_sub(8)) as u32;
240        (v >> shift) as u8
241    }
242}
243
244fn min_stride(width: usize, bit_depth: u8, bps: usize) -> usize {
245    if bps > 0 {
246        width.saturating_mul(bps)
247    } else {
248        match bit_depth {
249            10 => width.div_ceil(4) * 5,
250            12 => width.div_ceil(2) * 3,
251            _ => 0,
252        }
253    }
254}
255
256#[allow(clippy::too_many_arguments)]
257fn sample_at(
258    data: &[u8],
259    stride: usize,
260    bps: usize,
261    bit_depth: u8,
262    x: usize,
263    y: usize,
264    width: usize,
265    height: usize,
266) -> u8 {
267    let xs = x.min(width.saturating_sub(1));
268    let ys = y.min(height.saturating_sub(1));
269    let row_off = ys.saturating_mul(stride);
270
271    if bps > 0 {
272        let offset = row_off.saturating_add(xs.saturating_mul(bps));
273        return sample_to_u8(data, offset, bps, bit_depth);
274    }
275
276    // Packed MIPI RAW10/RAW12.
277    let v = match bit_depth {
278        10 => {
279            // 4 pixels -> 5 bytes: [b0 b1 b2 b3 b4], where b4 carries 2 MSBs per pixel.
280            let group = xs / 4;
281            let idx = xs % 4;
282            let base = row_off.saturating_add(group.saturating_mul(5));
283            let b = data.get(base + idx).copied().unwrap_or(0) as u16;
284            let b4 = data.get(base + 4).copied().unwrap_or(0) as u16;
285            let msb = (b4 >> (idx * 2)) & 0x3;
286            b | (msb << 8)
287        }
288        12 => {
289            // 2 pixels -> 3 bytes: [b0 b1 b2], where b2 carries 4 MSBs per pixel.
290            let pair = xs / 2;
291            let idx = xs % 2;
292            let base = row_off.saturating_add(pair.saturating_mul(3));
293            let b0 = data.get(base).copied().unwrap_or(0) as u16;
294            let b1 = data.get(base + 1).copied().unwrap_or(0) as u16;
295            let b2 = data.get(base + 2).copied().unwrap_or(0) as u16;
296            if idx == 0 {
297                b0 | ((b2 & 0x0f) << 8)
298            } else {
299                b1 | (((b2 >> 4) & 0x0f) << 8)
300            }
301        }
302        _ => 0,
303    };
304    let shift = (bit_depth.saturating_sub(8)) as u32;
305    (v >> shift) as u8
306}
307
308/// Naive bilinear demosaic to RG24.
309pub struct BayerToRgbDecoder {
310    descriptor: CodecDescriptor,
311    pool: BufferPool,
312    packed_pool: BufferPool,
313    info: BayerInfo,
314}
315
316impl BayerToRgbDecoder {
317    pub fn new(input: FourCc, info: BayerInfo, max_width: u32, max_height: u32) -> Self {
318        let bytes = max_width as usize * max_height as usize * 3;
319        let packed_bytes = max_width as usize * max_height as usize * 2;
320        Self {
321            descriptor: CodecDescriptor {
322                kind: CodecKind::Decoder,
323                input,
324                output: FourCc::new(*b"RG24"),
325                name: "bayer2rgb",
326                impl_name: "bayer-bilinear",
327            },
328            pool: BufferPool::with_limits(2, bytes, 4),
329            packed_pool: BufferPool::with_limits(2, packed_bytes, 4),
330            info,
331        }
332    }
333
334    /// Decode into a caller-provided tightly-packed RGB24 buffer.
335    ///
336    /// `dst` must be at least `width * height * 3` bytes.
337    pub fn decode_into(&self, input: &FrameLease, dst: &mut [u8]) -> Result<FrameMeta, CodecError> {
338        let meta = input.meta();
339        if meta.format.code != self.descriptor.input {
340            return Err(CodecError::FormatMismatch {
341                expected: self.descriptor.input,
342                actual: meta.format.code,
343            });
344        }
345        let plane = input
346            .planes()
347            .into_iter()
348            .next()
349            .ok_or_else(|| CodecError::Codec("bayer frame missing plane".into()))?;
350
351        let width = meta.format.resolution.width.get() as usize;
352        let height = meta.format.resolution.height.get() as usize;
353        if width < 2 || height < 2 {
354            return Err(CodecError::Codec("bayer frame too small".into()));
355        }
356
357        let stride = plane.stride().max(min_stride(
358            width,
359            self.info.bit_depth,
360            self.info.bytes_per_sample,
361        ));
362        let required = stride
363            .checked_mul(height)
364            .ok_or_else(|| CodecError::Codec("bayer stride overflow".into()))?;
365        if plane.data().len() < required {
366            return Err(CodecError::Codec("bayer plane buffer too short".into()));
367        }
368
369        let row_bytes = width
370            .checked_mul(3)
371            .ok_or_else(|| CodecError::Codec("bayer output overflow".into()))?;
372        let out_len = row_bytes
373            .checked_mul(height)
374            .ok_or_else(|| CodecError::Codec("bayer output overflow".into()))?;
375        if dst.len() < out_len {
376            return Err(CodecError::Codec("bayer dst buffer too short".into()));
377        }
378
379        let dst = &mut dst[..out_len];
380        let data = plane.data();
381        if self.info.bytes_per_sample == 0 {
382            let mut packed = self.packed_pool.lease();
383            let packed_len = width
384                .checked_mul(height)
385                .and_then(|px| px.checked_mul(2))
386                .ok_or_else(|| CodecError::Codec("bayer packed buffer overflow".into()))?;
387            unsafe { packed.resize_uninit(packed_len) };
388            let packed_u16 = unsafe {
389                std::slice::from_raw_parts_mut(
390                    packed.as_mut_slice().as_mut_ptr() as *mut u16,
391                    width * height,
392                )
393            };
394            unpack_mipi_packed_to_u16_le(
395                packed_u16,
396                data,
397                stride,
398                width,
399                height,
400                self.info.bit_depth,
401            );
402            demosaic_bilinear_u16_le(
403                dst,
404                packed.as_slice(),
405                width,
406                width,
407                height,
408                self.info.pattern,
409                self.info.bit_depth,
410            );
411        } else {
412            demosaic_bilinear_to_rg24(
413                dst,
414                data,
415                stride,
416                width,
417                height,
418                self.info.pattern,
419                self.info.bit_depth,
420                self.info.bytes_per_sample,
421            );
422        }
423
424        Ok(FrameMeta::new(
425            MediaFormat::new(
426                self.descriptor.output,
427                meta.format.resolution,
428                meta.format.color,
429            ),
430            meta.timestamp,
431        ))
432    }
433}
434
435impl Codec for BayerToRgbDecoder {
436    fn descriptor(&self) -> &CodecDescriptor {
437        &self.descriptor
438    }
439
440    fn process(&self, input: FrameLease) -> Result<FrameLease, CodecError> {
441        let layout = plane_layout_from_dims(
442            input.meta().format.resolution.width,
443            input.meta().format.resolution.height,
444            3,
445        );
446        let mut buf = self.pool.lease();
447        unsafe { buf.resize_uninit(layout.len) };
448        let meta = self.decode_into(&input, buf.as_mut_slice())?;
449
450        Ok(unsafe {
451            FrameLease::single_plane_uninit(
452                meta,
453                buf,
454                layout.len,
455                layout.stride,
456            )
457        })
458    }
459}
460
461fn unpack_mipi_packed_to_u16_le(
462    dst: &mut [u16],
463    data: &[u8],
464    stride: usize,
465    width: usize,
466    height: usize,
467    bit_depth: u8,
468) {
469    debug_assert!(dst.len() >= width.saturating_mul(height));
470    dst.par_chunks_mut(width)
471        .enumerate()
472        .for_each(|(y, dst_row)| {
473            let src_row = &data[y * stride..][..stride];
474            match bit_depth {
475                10 => unpack_raw10_row(dst_row, src_row, width),
476                12 => unpack_raw12_row(dst_row, src_row, width),
477                _ => {
478                    for (x, dst_px) in dst_row.iter_mut().enumerate().take(width) {
479                        let v =
480                            sample_at(data, stride, 0, bit_depth, x, y, width, height) as u16;
481                        *dst_px = v.to_le();
482                    }
483                }
484            }
485        });
486}
487
488#[inline(always)]
489fn unpack_raw10_row(dst: &mut [u16], src: &[u8], width: usize) {
490    let mut x = 0usize;
491    let mut off = 0usize;
492    while x + 4 <= width {
493        // 4 pixels -> 5 bytes: [b0 b1 b2 b3 b4], where b4 carries 2 MSBs per pixel.
494        let b0 = unsafe { *src.get_unchecked(off) } as u16;
495        let b1 = unsafe { *src.get_unchecked(off + 1) } as u16;
496        let b2 = unsafe { *src.get_unchecked(off + 2) } as u16;
497        let b3 = unsafe { *src.get_unchecked(off + 3) } as u16;
498        let b4 = unsafe { *src.get_unchecked(off + 4) } as u16;
499        unsafe {
500            *dst.get_unchecked_mut(x) = (b0 | ((b4 & 0x03) << 8)).to_le();
501            *dst.get_unchecked_mut(x + 1) = (b1 | (((b4 >> 2) & 0x03) << 8)).to_le();
502            *dst.get_unchecked_mut(x + 2) = (b2 | (((b4 >> 4) & 0x03) << 8)).to_le();
503            *dst.get_unchecked_mut(x + 3) = (b3 | (((b4 >> 6) & 0x03) << 8)).to_le();
504        }
505        x += 4;
506        off += 5;
507    }
508    if x < width {
509        // Tail: fall back to the generic sampler for the last 1-3 pixels.
510        for (xs, dst_px) in dst.iter_mut().enumerate().take(width).skip(x) {
511            let group = xs / 4;
512            let idx = xs % 4;
513            let base = group.saturating_mul(5);
514            let b = src.get(base + idx).copied().unwrap_or(0) as u16;
515            let b4 = src.get(base + 4).copied().unwrap_or(0) as u16;
516            let msb = (b4 >> (idx * 2)) & 0x3;
517            *dst_px = (b | (msb << 8)).to_le();
518        }
519    }
520}
521
522#[inline(always)]
523fn unpack_raw12_row(dst: &mut [u16], src: &[u8], width: usize) {
524    let mut x = 0usize;
525    let mut off = 0usize;
526    while x + 2 <= width {
527        // 2 pixels -> 3 bytes: [b0 b1 b2], where b2 carries 4 MSBs per pixel.
528        let b0 = unsafe { *src.get_unchecked(off) } as u16;
529        let b1 = unsafe { *src.get_unchecked(off + 1) } as u16;
530        let b2 = unsafe { *src.get_unchecked(off + 2) } as u16;
531        unsafe {
532            *dst.get_unchecked_mut(x) = (b0 | ((b2 & 0x0f) << 8)).to_le();
533            *dst.get_unchecked_mut(x + 1) = (b1 | (((b2 >> 4) & 0x0f) << 8)).to_le();
534        }
535        x += 2;
536        off += 3;
537    }
538    if x < width {
539        // Tail: fall back to the generic sampler for the last pixel.
540        let pair = x / 2;
541        let idx = x % 2;
542        let base = pair.saturating_mul(3);
543        let b0 = src.get(base).copied().unwrap_or(0) as u16;
544        let b1 = src.get(base + 1).copied().unwrap_or(0) as u16;
545        let b2 = src.get(base + 2).copied().unwrap_or(0) as u16;
546        dst[x] = (if idx == 0 {
547            b0 | ((b2 & 0x0f) << 8)
548        } else {
549            b1 | (((b2 >> 4) & 0x0f) << 8)
550        })
551        .to_le();
552    }
553}
554
555#[allow(clippy::too_many_arguments, clippy::needless_range_loop)]
556fn demosaic_bilinear_to_rg24(
557    dst: &mut [u8],
558    data: &[u8],
559    stride: usize,
560    width: usize,
561    height: usize,
562    pattern: BayerPattern,
563    bit_depth: u8,
564    bytes_per_sample: usize,
565) {
566    if bytes_per_sample == 2 && stride % 2 == 0 {
567        demosaic_bilinear_u16_le(dst, data, stride / 2, width, height, pattern, bit_depth);
568        return;
569    }
570
571    // Slow fallback: use the generic sampler (supports packed RAW10/RAW12).
572    for y in 0..height {
573        for x in 0..width {
574            let (is_r, _is_g, is_b) = color_at(pattern, x, y);
575            let center = sample_at(
576                data,
577                stride,
578                bytes_per_sample,
579                bit_depth,
580                x,
581                y,
582                width,
583                height,
584            ) as u16;
585
586            let r;
587            let g;
588            let b;
589
590            if is_r {
591                r = center;
592                let g_sum = sample_at(
593                    data,
594                    stride,
595                    bytes_per_sample,
596                    bit_depth,
597                    x + 1,
598                    y,
599                    width,
600                    height,
601                ) as u16
602                    + sample_at(
603                        data,
604                        stride,
605                        bytes_per_sample,
606                        bit_depth,
607                        x,
608                        y + 1,
609                        width,
610                        height,
611                    ) as u16
612                    + sample_at(
613                        data,
614                        stride,
615                        bytes_per_sample,
616                        bit_depth,
617                        x.saturating_sub(1),
618                        y,
619                        width,
620                        height,
621                    ) as u16
622                    + sample_at(
623                        data,
624                        stride,
625                        bytes_per_sample,
626                        bit_depth,
627                        x,
628                        y.saturating_sub(1),
629                        width,
630                        height,
631                    ) as u16;
632                g = (g_sum / 4) as u16;
633                let b_sum = sample_at(
634                    data,
635                    stride,
636                    bytes_per_sample,
637                    bit_depth,
638                    x + 1,
639                    y + 1,
640                    width,
641                    height,
642                ) as u16
643                    + sample_at(
644                        data,
645                        stride,
646                        bytes_per_sample,
647                        bit_depth,
648                        x.saturating_sub(1),
649                        y + 1,
650                        width,
651                        height,
652                    ) as u16
653                    + sample_at(
654                        data,
655                        stride,
656                        bytes_per_sample,
657                        bit_depth,
658                        x + 1,
659                        y.saturating_sub(1),
660                        width,
661                        height,
662                    ) as u16
663                    + sample_at(
664                        data,
665                        stride,
666                        bytes_per_sample,
667                        bit_depth,
668                        x.saturating_sub(1),
669                        y.saturating_sub(1),
670                        width,
671                        height,
672                    ) as u16;
673                b = (b_sum / 4) as u16;
674            } else if is_b {
675                b = center;
676                let g_sum = sample_at(
677                    data,
678                    stride,
679                    bytes_per_sample,
680                    bit_depth,
681                    x + 1,
682                    y,
683                    width,
684                    height,
685                ) as u16
686                    + sample_at(
687                        data,
688                        stride,
689                        bytes_per_sample,
690                        bit_depth,
691                        x,
692                        y + 1,
693                        width,
694                        height,
695                    ) as u16
696                    + sample_at(
697                        data,
698                        stride,
699                        bytes_per_sample,
700                        bit_depth,
701                        x.saturating_sub(1),
702                        y,
703                        width,
704                        height,
705                    ) as u16
706                    + sample_at(
707                        data,
708                        stride,
709                        bytes_per_sample,
710                        bit_depth,
711                        x,
712                        y.saturating_sub(1),
713                        width,
714                        height,
715                    ) as u16;
716                g = (g_sum / 4) as u16;
717                let r_sum = sample_at(
718                    data,
719                    stride,
720                    bytes_per_sample,
721                    bit_depth,
722                    x + 1,
723                    y + 1,
724                    width,
725                    height,
726                ) as u16
727                    + sample_at(
728                        data,
729                        stride,
730                        bytes_per_sample,
731                        bit_depth,
732                        x.saturating_sub(1),
733                        y + 1,
734                        width,
735                        height,
736                    ) as u16
737                    + sample_at(
738                        data,
739                        stride,
740                        bytes_per_sample,
741                        bit_depth,
742                        x + 1,
743                        y.saturating_sub(1),
744                        width,
745                        height,
746                    ) as u16
747                    + sample_at(
748                        data,
749                        stride,
750                        bytes_per_sample,
751                        bit_depth,
752                        x.saturating_sub(1),
753                        y.saturating_sub(1),
754                        width,
755                        height,
756                    ) as u16;
757                r = (r_sum / 4) as u16;
758            } else {
759                g = center;
760                let on_red_row = match pattern {
761                    BayerPattern::RGGB | BayerPattern::GRBG => (y & 1) == 0,
762                    BayerPattern::BGGR | BayerPattern::GBRG => (y & 1) == 1,
763                };
764                let on_red_col = match pattern {
765                    BayerPattern::RGGB | BayerPattern::GBRG => (x & 1) == 0,
766                    BayerPattern::BGGR | BayerPattern::GRBG => (x & 1) == 1,
767                };
768                if on_red_row == on_red_col {
769                    r = ((sample_at(
770                        data,
771                        stride,
772                        bytes_per_sample,
773                        bit_depth,
774                        x.saturating_sub(1),
775                        y,
776                        width,
777                        height,
778                    ) as u16
779                        + sample_at(
780                            data,
781                            stride,
782                            bytes_per_sample,
783                            bit_depth,
784                            x + 1,
785                            y,
786                            width,
787                            height,
788                        ) as u16)
789                        / 2) as u16;
790                    b = ((sample_at(
791                        data,
792                        stride,
793                        bytes_per_sample,
794                        bit_depth,
795                        x,
796                        y.saturating_sub(1),
797                        width,
798                        height,
799                    ) as u16
800                        + sample_at(
801                            data,
802                            stride,
803                            bytes_per_sample,
804                            bit_depth,
805                            x,
806                            y + 1,
807                            width,
808                            height,
809                        ) as u16)
810                        / 2) as u16;
811                } else {
812                    r = ((sample_at(
813                        data,
814                        stride,
815                        bytes_per_sample,
816                        bit_depth,
817                        x,
818                        y.saturating_sub(1),
819                        width,
820                        height,
821                    ) as u16
822                        + sample_at(
823                            data,
824                            stride,
825                            bytes_per_sample,
826                            bit_depth,
827                            x,
828                            y + 1,
829                            width,
830                            height,
831                        ) as u16)
832                        / 2) as u16;
833                    b = ((sample_at(
834                        data,
835                        stride,
836                        bytes_per_sample,
837                        bit_depth,
838                        x.saturating_sub(1),
839                        y,
840                        width,
841                        height,
842                    ) as u16
843                        + sample_at(
844                            data,
845                            stride,
846                            bytes_per_sample,
847                            bit_depth,
848                            x + 1,
849                            y,
850                            width,
851                            height,
852                        ) as u16)
853                        / 2) as u16;
854                }
855            }
856
857            let dst_idx = (y * width + x) * 3;
858            dst[dst_idx] = r as u8;
859            dst[dst_idx + 1] = g as u8;
860            dst[dst_idx + 2] = b as u8;
861        }
862    }
863}
864
865#[allow(clippy::needless_range_loop)]
866fn demosaic_bilinear_u16_le(
867    dst: &mut [u8],
868    data: &[u8],
869    stride_px: usize,
870    width: usize,
871    height: usize,
872    pattern: BayerPattern,
873    bit_depth: u8,
874) {
875    let shift = (bit_depth.saturating_sub(8)) as u32;
876    let src_u16 = unsafe {
877        std::slice::from_raw_parts(
878            data.as_ptr() as *const u16,
879            stride_px.saturating_mul(height),
880        )
881    };
882
883    #[inline(always)]
884    fn read(src: &[u16], stride_px: usize, x: usize, y: usize) -> u16 {
885        u16::from_le(src[y * stride_px + x])
886    }
887
888    #[inline(always)]
889    fn to_u8(v: u16, shift: u32) -> u8 {
890        (v >> shift) as u8
891    }
892
893    #[cfg(target_arch = "aarch64")]
894    #[inline(always)]
895    unsafe fn avg2_u16(a: std::arch::aarch64::uint16x8_t, b: std::arch::aarch64::uint16x8_t) -> std::arch::aarch64::uint16x8_t {
896        use std::arch::aarch64::{
897            vaddq_u32, vcombine_u16, vget_high_u16, vget_low_u16, vmovn_u32, vmovl_u16,
898            vshrq_n_u32,
899        };
900        unsafe {
901            let a0 = vmovl_u16(vget_low_u16(a));
902            let a1 = vmovl_u16(vget_high_u16(a));
903            let b0 = vmovl_u16(vget_low_u16(b));
904            let b1 = vmovl_u16(vget_high_u16(b));
905            let lo = vshrq_n_u32(vaddq_u32(a0, b0), 1);
906            let hi = vshrq_n_u32(vaddq_u32(a1, b1), 1);
907            vcombine_u16(vmovn_u32(lo), vmovn_u32(hi))
908        }
909    }
910
911    #[cfg(target_arch = "aarch64")]
912    #[inline(always)]
913    unsafe fn avg4_u16(
914        a: std::arch::aarch64::uint16x8_t,
915        b: std::arch::aarch64::uint16x8_t,
916        c: std::arch::aarch64::uint16x8_t,
917        d: std::arch::aarch64::uint16x8_t,
918    ) -> std::arch::aarch64::uint16x8_t {
919        use std::arch::aarch64::{
920            vaddq_u32, vcombine_u16, vget_high_u16, vget_low_u16, vmovn_u32, vmovl_u16,
921            vshrq_n_u32,
922        };
923
924        unsafe {
925            let a0 = vmovl_u16(vget_low_u16(a));
926            let a1 = vmovl_u16(vget_high_u16(a));
927            let b0 = vmovl_u16(vget_low_u16(b));
928            let b1 = vmovl_u16(vget_high_u16(b));
929            let c0 = vmovl_u16(vget_low_u16(c));
930            let c1 = vmovl_u16(vget_high_u16(c));
931            let d0 = vmovl_u16(vget_low_u16(d));
932            let d1 = vmovl_u16(vget_high_u16(d));
933
934            let lo = vshrq_n_u32(vaddq_u32(vaddq_u32(a0, b0), vaddq_u32(c0, d0)), 2);
935            let hi = vshrq_n_u32(vaddq_u32(vaddq_u32(a1, b1), vaddq_u32(c1, d1)), 2);
936            vcombine_u16(vmovn_u32(lo), vmovn_u32(hi))
937        }
938    }
939
940    #[cfg(target_arch = "aarch64")]
941    #[inline(always)]
942    unsafe fn shift_u16x8_to_u8(v: std::arch::aarch64::uint16x8_t, shift: u32) -> std::arch::aarch64::uint8x8_t {
943        use std::arch::aarch64::{vmovn_u16, vshrn_n_u16};
944        unsafe {
945            match shift {
946                0 => vmovn_u16(v),
947                1 => vshrn_n_u16(v, 1),
948                2 => vshrn_n_u16(v, 2),
949                3 => vshrn_n_u16(v, 3),
950                4 => vshrn_n_u16(v, 4),
951                5 => vshrn_n_u16(v, 5),
952                6 => vshrn_n_u16(v, 6),
953                7 => vshrn_n_u16(v, 7),
954                8 => vshrn_n_u16(v, 8),
955                // Defensive: shifts > 8 shouldn't happen for 16-bit inputs.
956                _ => vshrn_n_u16(v, 8),
957            }
958        }
959    }
960
961    // Borders: grayscale (center value replicated).
962    for x in 0..width {
963        for y in [0usize, height - 1] {
964            let c = to_u8(read(src_u16, stride_px, x, y), shift);
965            let o = (y * width + x) * 3;
966            dst[o] = c;
967            dst[o + 1] = c;
968            dst[o + 2] = c;
969        }
970    }
971    for y in 1..(height - 1) {
972        for x in [0usize, width - 1] {
973            let c = to_u8(read(src_u16, stride_px, x, y), shift);
974            let o = (y * width + x) * 3;
975            dst[o] = c;
976            dst[o + 1] = c;
977            dst[o + 2] = c;
978        }
979    }
980
981    let row_bytes = width * 3;
982    let dst_inner = &mut dst[row_bytes..(height - 1) * row_bytes];
983    dst_inner
984        .par_chunks_mut(row_bytes)
985        .enumerate()
986        .for_each(|(row_idx, out_row)| {
987            let y = row_idx + 1;
988            let ym1 = y - 1;
989            let yp1 = y + 1;
990
991            #[cfg(target_arch = "aarch64")]
992            unsafe {
993                        use std::arch::aarch64::{
994                            uint16x8_t, uint8x8x3_t, vbslq_u16, vld1q_u16, vmvnq_u16, vst3_u8,
995                        };
996
997                        // For the vector loop we always start at x=1; x parity does not change as we step by 8.
998                        const MASK_START_EVEN: [u16; 8] = [0xFFFF, 0x0000, 0xFFFF, 0x0000, 0xFFFF, 0x0000, 0xFFFF, 0x0000];
999                        let mask_start_even: uint16x8_t = vld1q_u16(MASK_START_EVEN.as_ptr());
1000                        let mask_x_is_even: uint16x8_t = if (1usize & 1) == 0 {
1001                            mask_start_even
1002                        } else {
1003                            vmvnq_u16(mask_start_even)
1004                        };
1005
1006                        let row_up = src_u16.as_ptr().add(ym1 * stride_px);
1007                        let row = src_u16.as_ptr().add(y * stride_px);
1008                        let row_dn = src_u16.as_ptr().add(yp1 * stride_px);
1009
1010                        let mut x = 1usize;
1011                        while x + 8 <= width - 1 {
1012                            let c = vld1q_u16(row.add(x));
1013                            let l = vld1q_u16(row.add(x - 1));
1014                            let r = vld1q_u16(row.add(x + 1));
1015                            let u = vld1q_u16(row_up.add(x));
1016                            let d = vld1q_u16(row_dn.add(x));
1017                            let ul = vld1q_u16(row_up.add(x - 1));
1018                            let ur = vld1q_u16(row_up.add(x + 1));
1019                            let dl = vld1q_u16(row_dn.add(x - 1));
1020                            let dr = vld1q_u16(row_dn.add(x + 1));
1021
1022                            let g_lrud = avg4_u16(l, r, u, d);
1023                            let diag = avg4_u16(ul, ur, dl, dr);
1024                            let lr2 = avg2_u16(l, r);
1025                            let ud2 = avg2_u16(u, d);
1026
1027                            let y_is_even = (y & 1) == 0;
1028                            let (r_even, g_even, b_even, r_odd, g_odd, b_odd) = match pattern {
1029                                BayerPattern::RGGB => {
1030                                    if y_is_even {
1031                                        // even x: R, odd x: G (on red row)
1032                                        (c, g_lrud, diag, lr2, c, ud2)
1033                                    } else {
1034                                        // even x: G (on blue row), odd x: B
1035                                        (ud2, c, lr2, diag, g_lrud, c)
1036                                    }
1037                                }
1038                                BayerPattern::BGGR => {
1039                                    if y_is_even {
1040                                        // even x: B, odd x: G (on blue row)
1041                                        (diag, g_lrud, c, ud2, c, lr2)
1042                                    } else {
1043                                        // even x: G (on red row), odd x: R
1044                                        (lr2, c, ud2, c, g_lrud, diag)
1045                                    }
1046                                }
1047                                BayerPattern::GBRG => {
1048                                    if y_is_even {
1049                                        // even x: G (on blue row), odd x: B
1050                                        (ud2, c, lr2, diag, g_lrud, c)
1051                                    } else {
1052                                        // even x: R, odd x: G (on red row)
1053                                        (c, g_lrud, diag, lr2, c, ud2)
1054                                    }
1055                                }
1056                                BayerPattern::GRBG => {
1057                                    if y_is_even {
1058                                        // even x: G (on red row), odd x: R
1059                                        (lr2, c, ud2, c, g_lrud, diag)
1060                                    } else {
1061                                        // even x: B, odd x: G (on blue row)
1062                                        (diag, g_lrud, c, ud2, c, lr2)
1063                                    }
1064                                }
1065                            };
1066
1067                            let r16 = vbslq_u16(mask_x_is_even, r_even, r_odd);
1068                            let g16 = vbslq_u16(mask_x_is_even, g_even, g_odd);
1069                            let b16 = vbslq_u16(mask_x_is_even, b_even, b_odd);
1070
1071                            let r8 = shift_u16x8_to_u8(r16, shift);
1072                            let g8 = shift_u16x8_to_u8(g16, shift);
1073                            let b8 = shift_u16x8_to_u8(b16, shift);
1074                            let rgb = uint8x8x3_t(r8, g8, b8);
1075                            vst3_u8(out_row.as_mut_ptr().add(x * 3), rgb);
1076
1077                            x += 8;
1078                        }
1079
1080                        // Tail.
1081                        for x in x..(width - 1) {
1082                            let xm1 = x - 1;
1083                            let xp1 = x + 1;
1084                            let c = read(src_u16, stride_px, x, y);
1085                            let l = read(src_u16, stride_px, xm1, y);
1086                            let r = read(src_u16, stride_px, xp1, y);
1087                            let u = read(src_u16, stride_px, x, ym1);
1088                            let d = read(src_u16, stride_px, x, yp1);
1089                            let ul = read(src_u16, stride_px, xm1, ym1);
1090                            let ur = read(src_u16, stride_px, xp1, ym1);
1091                            let dl = read(src_u16, stride_px, xm1, yp1);
1092                            let dr = read(src_u16, stride_px, xp1, yp1);
1093
1094                            let (r16, g16, b16) = match pattern {
1095                                BayerPattern::BGGR => match ((y & 1) == 0, (x & 1) == 0) {
1096                                    (true, true) => {
1097                                        let g = (l as u32 + r as u32 + u as u32 + d as u32) / 4;
1098                                        let r = (ul as u32 + ur as u32 + dl as u32 + dr as u32) / 4;
1099                                        (r as u16, g as u16, c)
1100                                    }
1101                                    (true, false) => {
1102                                        let b = (l as u32 + r as u32) / 2;
1103                                        let r = (u as u32 + d as u32) / 2;
1104                                        (r as u16, c, b as u16)
1105                                    }
1106                                    (false, true) => {
1107                                        let r = (l as u32 + r as u32) / 2;
1108                                        let b = (u as u32 + d as u32) / 2;
1109                                        (r as u16, c, b as u16)
1110                                    }
1111                                    (false, false) => {
1112                                        let g = (l as u32 + r as u32 + u as u32 + d as u32) / 4;
1113                                        let b = (ul as u32 + ur as u32 + dl as u32 + dr as u32) / 4;
1114                                        (c, g as u16, b as u16)
1115                                    }
1116                                },
1117                                BayerPattern::RGGB => match ((y & 1) == 0, (x & 1) == 0) {
1118                                    (true, true) => {
1119                                        let g = (l as u32 + r as u32 + u as u32 + d as u32) / 4;
1120                                        let b = (ul as u32 + ur as u32 + dl as u32 + dr as u32) / 4;
1121                                        (c, g as u16, b as u16)
1122                                    }
1123                                    (true, false) => {
1124                                        let r = (l as u32 + r as u32) / 2;
1125                                        let b = (u as u32 + d as u32) / 2;
1126                                        (r as u16, c, b as u16)
1127                                    }
1128                                    (false, true) => {
1129                                        let b = (l as u32 + r as u32) / 2;
1130                                        let r = (u as u32 + d as u32) / 2;
1131                                        (r as u16, c, b as u16)
1132                                    }
1133                                    (false, false) => {
1134                                        let g = (l as u32 + r as u32 + u as u32 + d as u32) / 4;
1135                                        let r = (ul as u32 + ur as u32 + dl as u32 + dr as u32) / 4;
1136                                        (r as u16, g as u16, c)
1137                                    }
1138                                },
1139                                BayerPattern::GBRG => match ((y & 1) == 0, (x & 1) == 0) {
1140                                    (true, true) => {
1141                                        let r = (u as u32 + d as u32) / 2;
1142                                        let b = (l as u32 + r as u32) / 2;
1143                                        (r as u16, c, b as u16)
1144                                    }
1145                                    (true, false) => {
1146                                        let g = (l as u32 + r as u32 + u as u32 + d as u32) / 4;
1147                                        let r = (ul as u32 + ur as u32 + dl as u32 + dr as u32) / 4;
1148                                        (r as u16, g as u16, c)
1149                                    }
1150                                    (false, true) => {
1151                                        let g = (l as u32 + r as u32 + u as u32 + d as u32) / 4;
1152                                        let b = (ul as u32 + ur as u32 + dl as u32 + dr as u32) / 4;
1153                                        (c, g as u16, b as u16)
1154                                    }
1155                                    (false, false) => {
1156                                        let r = (l as u32 + r as u32) / 2;
1157                                        let b = (u as u32 + d as u32) / 2;
1158                                        (r as u16, c, b as u16)
1159                                    }
1160                                },
1161                                BayerPattern::GRBG => match ((y & 1) == 0, (x & 1) == 0) {
1162                                    (true, true) => {
1163                                        let r = (l as u32 + r as u32) / 2;
1164                                        let b = (u as u32 + d as u32) / 2;
1165                                        (r as u16, c, b as u16)
1166                                    }
1167                                    (true, false) => {
1168                                        let g = (l as u32 + r as u32 + u as u32 + d as u32) / 4;
1169                                        let b = (ul as u32 + ur as u32 + dl as u32 + dr as u32) / 4;
1170                                        (c, g as u16, b as u16)
1171                                    }
1172                                    (false, true) => {
1173                                        let g = (l as u32 + r as u32 + u as u32 + d as u32) / 4;
1174                                        let r = (ul as u32 + ur as u32 + dl as u32 + dr as u32) / 4;
1175                                        (r as u16, g as u16, c)
1176                                    }
1177                                    (false, false) => {
1178                                        let b = (l as u32 + r as u32) / 2;
1179                                        let r = (u as u32 + d as u32) / 2;
1180                                        (r as u16, c, b as u16)
1181                                    }
1182                                },
1183                            };
1184
1185                            let off = x * 3;
1186                            out_row[off] = to_u8(r16, shift);
1187                            out_row[off + 1] = to_u8(g16, shift);
1188                            out_row[off + 2] = to_u8(b16, shift);
1189                        }
1190                        return;
1191            }
1192
1193            #[cfg(not(target_arch = "aarch64"))]
1194            for x in 1..(width - 1) {
1195                        let xm1 = x - 1;
1196                        let xp1 = x + 1;
1197                        let c = read(src_u16, stride_px, x, y);
1198                        let l = read(src_u16, stride_px, xm1, y);
1199                        let r = read(src_u16, stride_px, xp1, y);
1200                        let u = read(src_u16, stride_px, x, ym1);
1201                        let d = read(src_u16, stride_px, x, yp1);
1202                        let ul = read(src_u16, stride_px, xm1, ym1);
1203                        let ur = read(src_u16, stride_px, xp1, ym1);
1204                        let dl = read(src_u16, stride_px, xm1, yp1);
1205                        let dr = read(src_u16, stride_px, xp1, yp1);
1206
1207                        let (r16, g16, b16) = match pattern {
1208                            BayerPattern::BGGR => match ((y & 1) == 0, (x & 1) == 0) {
1209                                (true, true) => {
1210                                    let g = (l as u32 + r as u32 + u as u32 + d as u32) / 4;
1211                                    let r = (ul as u32 + ur as u32 + dl as u32 + dr as u32) / 4;
1212                                    (r as u16, g as u16, c)
1213                                }
1214                                (true, false) => {
1215                                    let b = (l as u32 + r as u32) / 2;
1216                                    let r = (u as u32 + d as u32) / 2;
1217                                    (r as u16, c, b as u16)
1218                                }
1219                                (false, true) => {
1220                                    let r = (l as u32 + r as u32) / 2;
1221                                    let b = (u as u32 + d as u32) / 2;
1222                                    (r as u16, c, b as u16)
1223                                }
1224                                (false, false) => {
1225                                    let g = (l as u32 + r as u32 + u as u32 + d as u32) / 4;
1226                                    let b = (ul as u32 + ur as u32 + dl as u32 + dr as u32) / 4;
1227                                    (c, g as u16, b as u16)
1228                                }
1229                            },
1230                            BayerPattern::RGGB => match ((y & 1) == 0, (x & 1) == 0) {
1231                                (true, true) => {
1232                                    let g = (l as u32 + r as u32 + u as u32 + d as u32) / 4;
1233                                    let b = (ul as u32 + ur as u32 + dl as u32 + dr as u32) / 4;
1234                                    (c, g as u16, b as u16)
1235                                }
1236                                (true, false) => {
1237                                    let r = (l as u32 + r as u32) / 2;
1238                                    let b = (u as u32 + d as u32) / 2;
1239                                    (r as u16, c, b as u16)
1240                                }
1241                                (false, true) => {
1242                                    let b = (l as u32 + r as u32) / 2;
1243                                    let r = (u as u32 + d as u32) / 2;
1244                                    (r as u16, c, b as u16)
1245                                }
1246                                (false, false) => {
1247                                    let g = (l as u32 + r as u32 + u as u32 + d as u32) / 4;
1248                                    let r = (ul as u32 + ur as u32 + dl as u32 + dr as u32) / 4;
1249                                    (r as u16, g as u16, c)
1250                                }
1251                            },
1252                            BayerPattern::GBRG => match ((y & 1) == 0, (x & 1) == 0) {
1253                                (true, true) => {
1254                                    let r = (u as u32 + d as u32) / 2;
1255                                    let b = (l as u32 + r as u32) / 2;
1256                                    (r as u16, c, b as u16)
1257                                }
1258                                (true, false) => {
1259                                    let g = (l as u32 + r as u32 + u as u32 + d as u32) / 4;
1260                                    let r = (ul as u32 + ur as u32 + dl as u32 + dr as u32) / 4;
1261                                    (r as u16, g as u16, c)
1262                                }
1263                                (false, true) => {
1264                                    let g = (l as u32 + r as u32 + u as u32 + d as u32) / 4;
1265                                    let b = (ul as u32 + ur as u32 + dl as u32 + dr as u32) / 4;
1266                                    (c, g as u16, b as u16)
1267                                }
1268                                (false, false) => {
1269                                    let r = (l as u32 + r as u32) / 2;
1270                                    let b = (u as u32 + d as u32) / 2;
1271                                    (r as u16, c, b as u16)
1272                                }
1273                            },
1274                            BayerPattern::GRBG => match ((y & 1) == 0, (x & 1) == 0) {
1275                                (true, true) => {
1276                                    let r = (l as u32 + r as u32) / 2;
1277                                    let b = (u as u32 + d as u32) / 2;
1278                                    (r as u16, c, b as u16)
1279                                }
1280                                (true, false) => {
1281                                    let g = (l as u32 + r as u32 + u as u32 + d as u32) / 4;
1282                                    let b = (ul as u32 + ur as u32 + dl as u32 + dr as u32) / 4;
1283                                    (c, g as u16, b as u16)
1284                                }
1285                                (false, true) => {
1286                                    let g = (l as u32 + r as u32 + u as u32 + d as u32) / 4;
1287                                    let r = (ul as u32 + ur as u32 + dl as u32 + dr as u32) / 4;
1288                                    (r as u16, g as u16, c)
1289                                }
1290                                (false, false) => {
1291                                    let b = (l as u32 + r as u32) / 2;
1292                                    let r = (u as u32 + d as u32) / 2;
1293                                    (r as u16, c, b as u16)
1294                                }
1295                            },
1296                        };
1297
1298                        let off = x * 3;
1299                        out_row[off] = to_u8(r16, shift);
1300                        out_row[off + 1] = to_u8(g16, shift);
1301                        out_row[off + 2] = to_u8(b16, shift);
1302                    }
1303        });
1304}
1305
1306#[cfg(feature = "image")]
1307impl ImageDecode for BayerToRgbDecoder {
1308    fn decode_image(&self, frame: FrameLease) -> Result<image::DynamicImage, CodecError> {
1309        process_to_dynamic(self, frame)
1310    }
1311}
1312
1313pub fn bayer_decoder_for(
1314    fourcc: FourCc,
1315    info: BayerInfo,
1316    max_width: u32,
1317    max_height: u32,
1318) -> Arc<dyn Codec> {
1319    Arc::new(BayerToRgbDecoder::new(fourcc, info, max_width, max_height))
1320}
1321
1322#[cfg(test)]
1323mod tests {
1324    use super::*;
1325
1326    fn pack_raw10_4px(p0: u16, p1: u16, p2: u16, p3: u16) -> [u8; 5] {
1327        let b0 = (p0 & 0xff) as u8;
1328        let b1 = (p1 & 0xff) as u8;
1329        let b2 = (p2 & 0xff) as u8;
1330        let b3 = (p3 & 0xff) as u8;
1331        let b4 = ((p0 >> 8) as u8 & 0x03)
1332            | (((p1 >> 8) as u8 & 0x03) << 2)
1333            | (((p2 >> 8) as u8 & 0x03) << 4)
1334            | (((p3 >> 8) as u8 & 0x03) << 6);
1335        [b0, b1, b2, b3, b4]
1336    }
1337
1338    #[test]
1339    fn raw10_packed_sampling_matches_values() {
1340        let row = pack_raw10_4px(0x000, 0x155, 0x2aa, 0x3ff);
1341        let stride = row.len();
1342        let data = row.as_slice();
1343        let w = 4;
1344        let h = 1;
1345        assert_eq!(sample_at(data, stride, 0, 10, 0, 0, w, h), 0x00);
1346        assert_eq!(sample_at(data, stride, 0, 10, 1, 0, w, h), 0x55);
1347        assert_eq!(sample_at(data, stride, 0, 10, 2, 0, w, h), 0xaa);
1348        assert_eq!(sample_at(data, stride, 0, 10, 3, 0, w, h), 0xff);
1349    }
1350
1351    #[test]
1352    fn raw10_unpack_matches_values() {
1353        let row = pack_raw10_4px(0x000, 0x155, 0x2aa, 0x3ff);
1354        let mut out = [0u16; 4];
1355        unpack_raw10_row(&mut out, &row, 4);
1356        assert_eq!(u16::from_le(out[0]), 0x000);
1357        assert_eq!(u16::from_le(out[1]), 0x155);
1358        assert_eq!(u16::from_le(out[2]), 0x2aa);
1359        assert_eq!(u16::from_le(out[3]), 0x3ff);
1360    }
1361
1362    #[test]
1363    fn packed_raw10_decode_matches_unpacked() {
1364        let w = 4usize;
1365        let h = 4usize;
1366        let res = Resolution::new(w as u32, h as u32).unwrap();
1367
1368        let mut raw = vec![0u16; w * h];
1369        for y in 0..h {
1370            for x in 0..w {
1371                raw[y * w + x] = (((y * w + x) * 77) & 0x3ff) as u16;
1372            }
1373        }
1374
1375        // Packed RAW10: 4 pixels -> 5 bytes, no padding for w=4.
1376        let packed_stride = 5usize;
1377        let mut packed = Vec::with_capacity(packed_stride * h);
1378        for y in 0..h {
1379            let row = &raw[y * w..(y + 1) * w];
1380            packed.extend_from_slice(&pack_raw10_4px(row[0], row[1], row[2], row[3]));
1381        }
1382
1383        // Unpacked u16 little-endian: w * 2 bytes per row.
1384        let unpacked_stride = w * 2;
1385        let mut unpacked = vec![0u8; unpacked_stride * h];
1386        for y in 0..h {
1387            for x in 0..w {
1388                let v = raw[y * w + x].to_le_bytes();
1389                let o = y * unpacked_stride + x * 2;
1390                unpacked[o] = v[0];
1391                unpacked[o + 1] = v[1];
1392            }
1393        }
1394
1395        let packed_fourcc = FourCc::new(*b"pRAA");
1396        let unpacked_fourcc = FourCc::new(*b"RG10");
1397        let packed_info = bayer_info(packed_fourcc).unwrap();
1398        let unpacked_info = bayer_info(unpacked_fourcc).unwrap();
1399        let packed_dec = BayerToRgbDecoder::new(packed_fourcc, packed_info, res.width.get(), res.height.get());
1400        let unpacked_dec =
1401            BayerToRgbDecoder::new(unpacked_fourcc, unpacked_info, res.width.get(), res.height.get());
1402
1403        let pool = BufferPool::with_limits(2, packed.len().max(unpacked.len()), 4);
1404
1405        let mut packed_buf = pool.lease();
1406        packed_buf.resize(packed.len());
1407        packed_buf.as_mut_slice().copy_from_slice(&packed);
1408        let packed_frame = FrameLease::single_plane(
1409            FrameMeta::new(MediaFormat::new(packed_fourcc, res, ColorSpace::Unknown), 0),
1410            packed_buf,
1411            packed.len(),
1412            packed_stride,
1413        );
1414
1415        let mut unpacked_buf = pool.lease();
1416        unpacked_buf.resize(unpacked.len());
1417        unpacked_buf.as_mut_slice().copy_from_slice(&unpacked);
1418        let unpacked_frame = FrameLease::single_plane(
1419            FrameMeta::new(
1420                MediaFormat::new(unpacked_fourcc, res, ColorSpace::Unknown),
1421                0,
1422            ),
1423            unpacked_buf,
1424            unpacked.len(),
1425            unpacked_stride,
1426        );
1427
1428        let a = packed_dec.process(packed_frame).unwrap();
1429        let b = unpacked_dec.process(unpacked_frame).unwrap();
1430        let a_plane = a.planes();
1431        let b_plane = b.planes();
1432        assert_eq!(a_plane.len(), 1);
1433        assert_eq!(b_plane.len(), 1);
1434        assert_eq!(a_plane[0].data(), b_plane[0].data());
1435    }
1436}