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 pattern: BayerPattern::GRBG,
37 bit_depth: 10,
38 bytes_per_sample: 2,
39 },
40 b"BA12" => BayerInfo {
41 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 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 let v = match bit_depth {
278 10 => {
279 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 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
308pub 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 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 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 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 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 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 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 _ => vshrn_n_u16(v, 8),
957 }
958 }
959 }
960
961 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 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 (c, g_lrud, diag, lr2, c, ud2)
1033 } else {
1034 (ud2, c, lr2, diag, g_lrud, c)
1036 }
1037 }
1038 BayerPattern::BGGR => {
1039 if y_is_even {
1040 (diag, g_lrud, c, ud2, c, lr2)
1042 } else {
1043 (lr2, c, ud2, c, g_lrud, diag)
1045 }
1046 }
1047 BayerPattern::GBRG => {
1048 if y_is_even {
1049 (ud2, c, lr2, diag, g_lrud, c)
1051 } else {
1052 (c, g_lrud, diag, lr2, c, ud2)
1054 }
1055 }
1056 BayerPattern::GRBG => {
1057 if y_is_even {
1058 (lr2, c, ud2, c, g_lrud, diag)
1060 } else {
1061 (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 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 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 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}