1#![cfg(target_os = "linux")]
5
6use crate::{
7 CPUProcessor, Crop, Error, Flip, ImageProcessorTrait, Result, Rotation, TensorImage,
8 TensorImageRef, BGRA, NV12, RGB, RGBA, YUYV,
9};
10use edgefirst_tensor::Tensor;
11use g2d_sys::{G2DFormat, G2DPhysical, G2DSurface, G2D};
12use std::{os::fd::AsRawFd, time::Instant};
13
14#[derive(Debug)]
17pub struct G2DProcessor {
18 g2d: G2D,
19}
20
21unsafe impl Send for G2DProcessor {}
22unsafe impl Sync for G2DProcessor {}
23
24impl G2DProcessor {
25 pub fn new() -> Result<Self> {
27 let mut g2d = G2D::new("libg2d.so.2")?;
28 g2d.set_bt709_colorspace()?;
29
30 log::debug!("G2DConverter created with version {:?}", g2d.version());
31 Ok(Self { g2d })
32 }
33
34 pub fn version(&self) -> g2d_sys::Version {
37 self.g2d.version()
38 }
39
40 fn convert_(
41 &mut self,
42 src: &TensorImage,
43 dst: &mut TensorImage,
44 rotation: Rotation,
45 flip: Flip,
46 crop: Crop,
47 ) -> Result<()> {
48 let mut src_surface: G2DSurface = src.try_into()?;
49 let mut dst_surface: G2DSurface = dst.try_into()?;
50
51 src_surface.rot = match flip {
52 Flip::None => g2d_sys::g2d_rotation_G2D_ROTATION_0,
53 Flip::Vertical => g2d_sys::g2d_rotation_G2D_FLIP_V,
54 Flip::Horizontal => g2d_sys::g2d_rotation_G2D_FLIP_H,
55 };
56
57 dst_surface.rot = match rotation {
58 Rotation::None => g2d_sys::g2d_rotation_G2D_ROTATION_0,
59 Rotation::Clockwise90 => g2d_sys::g2d_rotation_G2D_ROTATION_90,
60 Rotation::Rotate180 => g2d_sys::g2d_rotation_G2D_ROTATION_180,
61 Rotation::CounterClockwise90 => g2d_sys::g2d_rotation_G2D_ROTATION_270,
62 };
63
64 if let Some(crop_rect) = crop.src_rect {
65 src_surface.left = crop_rect.left as i32;
66 src_surface.top = crop_rect.top as i32;
67 src_surface.right = (crop_rect.left + crop_rect.width) as i32;
68 src_surface.bottom = (crop_rect.top + crop_rect.height) as i32;
69 }
70
71 let needs_clear = crop.dst_color.is_some()
77 && crop.dst_rect.is_some_and(|dst_rect| {
78 dst_rect.left != 0
79 || dst_rect.top != 0
80 || dst_rect.width != dst.width()
81 || dst_rect.height != dst.height()
82 });
83
84 if needs_clear && dst.fourcc != RGB {
85 if let Some(dst_color) = crop.dst_color {
86 let start = Instant::now();
87 self.g2d.clear(&mut dst_surface, dst_color)?;
88 log::trace!("g2d clear takes {:?}", start.elapsed());
89 }
90 }
91
92 if let Some(crop_rect) = crop.dst_rect {
93 dst_surface.planes[0] += ((crop_rect.top * dst_surface.width as usize + crop_rect.left)
94 * dst.channels()) as u64;
95
96 dst_surface.right = crop_rect.width as i32;
97 dst_surface.bottom = crop_rect.height as i32;
98 dst_surface.width = crop_rect.width as i32;
99 dst_surface.height = crop_rect.height as i32;
100 }
101
102 log::trace!("Blitting from {src_surface:?} to {dst_surface:?}");
103 self.g2d.blit(&src_surface, &dst_surface)?;
104 self.g2d.finish()?;
105
106 if needs_clear && dst.fourcc == RGB {
108 if let (Some(dst_color), Some(dst_rect)) = (crop.dst_color, crop.dst_rect) {
109 let start = Instant::now();
110 CPUProcessor::fill_image_outside_crop(dst, dst_color, dst_rect)?;
111 log::trace!("cpu fill takes {:?}", start.elapsed());
112 }
113 }
114
115 Ok(())
116 }
117}
118
119impl ImageProcessorTrait for G2DProcessor {
120 fn convert(
135 &mut self,
136 src: &TensorImage,
137 dst: &mut TensorImage,
138 rotation: Rotation,
139 flip: Flip,
140 crop: Crop,
141 ) -> Result<()> {
142 crop.check_crop(src, dst)?;
143 match (src.fourcc(), dst.fourcc()) {
144 (RGBA, RGBA) => {}
145 (RGBA, YUYV) => {}
146 (RGBA, RGB) => {}
147 (YUYV, RGBA) => {}
148 (YUYV, YUYV) => {}
149 (YUYV, RGB) => {}
150 (NV12, RGBA) => {}
153 (NV12, YUYV) => {}
154 (NV12, RGB) => {}
155 (RGBA, BGRA) => {}
156 (YUYV, BGRA) => {}
157 (NV12, BGRA) => {}
158 (BGRA, BGRA) => {}
159 (s, d) => {
160 return Err(Error::NotSupported(format!(
161 "G2D does not support {} to {} conversion",
162 s.display(),
163 d.display()
164 )));
165 }
166 }
167 self.convert_(src, dst, rotation, flip, crop)
168 }
169
170 fn convert_ref(
171 &mut self,
172 src: &TensorImage,
173 dst: &mut TensorImageRef<'_>,
174 rotation: Rotation,
175 flip: Flip,
176 crop: Crop,
177 ) -> Result<()> {
178 let mut cpu = CPUProcessor::new();
180 cpu.convert_ref(src, dst, rotation, flip, crop)
181 }
182
183 fn draw_masks(
184 &mut self,
185 _dst: &mut TensorImage,
186 _detect: &[crate::DetectBox],
187 _segmentation: &[crate::Segmentation],
188 ) -> Result<()> {
189 Err(Error::NotImplemented(
190 "G2D does not support drawing detection or segmentation overlays".to_string(),
191 ))
192 }
193
194 fn draw_masks_proto(
195 &mut self,
196 _dst: &mut TensorImage,
197 _detect: &[crate::DetectBox],
198 _proto_data: &crate::ProtoData,
199 ) -> Result<()> {
200 Err(Error::NotImplemented(
201 "G2D does not support drawing detection or segmentation overlays".to_string(),
202 ))
203 }
204
205 fn decode_masks_atlas(
206 &mut self,
207 _detect: &[crate::DetectBox],
208 _proto_data: crate::ProtoData,
209 _output_width: usize,
210 _output_height: usize,
211 ) -> Result<(Vec<u8>, Vec<crate::MaskRegion>)> {
212 Err(Error::NotImplemented(
213 "G2D does not support decoding mask atlas".to_string(),
214 ))
215 }
216
217 fn set_class_colors(&mut self, _: &[[u8; 4]]) -> Result<()> {
218 Err(Error::NotImplemented(
219 "G2D does not support setting colors for rendering detection or segmentation overlays"
220 .to_string(),
221 ))
222 }
223}
224
225impl TryFrom<&TensorImage> for G2DSurface {
226 type Error = Error;
227
228 fn try_from(img: &TensorImage) -> Result<Self, Self::Error> {
229 let phys: G2DPhysical = match img.tensor() {
230 Tensor::Dma(t) => t.as_raw_fd(),
231 _ => {
232 return Err(Error::NotImplemented(
233 "g2d only supports Dma memory".to_string(),
234 ));
235 }
236 }
237 .try_into()?;
238
239 let base_addr = phys.address();
243 let planes = if img.fourcc() == NV12 {
244 let uv_offset = (img.width() * img.height()) as u64;
245 [base_addr, base_addr + uv_offset, 0]
246 } else {
247 [base_addr, 0, 0]
248 };
249
250 Ok(Self {
251 planes,
252 format: G2DFormat::try_from(img.fourcc())?.format(),
253 left: 0,
254 top: 0,
255 right: img.width() as i32,
256 bottom: img.height() as i32,
257 stride: img.width() as i32,
258 width: img.width() as i32,
259 height: img.height() as i32,
260 blendfunc: 0,
261 clrcolor: 0,
262 rot: 0,
263 global_alpha: 0,
264 })
265 }
266}
267
268impl TryFrom<&mut TensorImage> for G2DSurface {
269 type Error = Error;
270
271 fn try_from(img: &mut TensorImage) -> Result<Self, Self::Error> {
272 let phys: G2DPhysical = match img.tensor() {
273 Tensor::Dma(t) => t.as_raw_fd(),
274 _ => {
275 return Err(Error::NotImplemented(
276 "g2d only supports Dma memory".to_string(),
277 ));
278 }
279 }
280 .try_into()?;
281
282 let base_addr = phys.address();
284 let planes = if img.fourcc() == NV12 {
285 let uv_offset = (img.width() * img.height()) as u64;
286 [base_addr, base_addr + uv_offset, 0]
287 } else {
288 [base_addr, 0, 0]
289 };
290
291 Ok(Self {
292 planes,
293 format: G2DFormat::try_from(img.fourcc())?.format(),
294 left: 0,
295 top: 0,
296 right: img.width() as i32,
297 bottom: img.height() as i32,
298 stride: img.width() as i32,
299 width: img.width() as i32,
300 height: img.height() as i32,
301 blendfunc: 0,
302 clrcolor: 0,
303 rot: 0,
304 global_alpha: 0,
305 })
306 }
307}
308
309#[cfg(feature = "g2d_test_formats")]
310#[cfg(test)]
311mod g2d_tests {
312 use super::*;
313 use crate::{
314 CPUProcessor, Flip, G2DProcessor, ImageProcessorTrait, Rect, Rotation, TensorImage, BGRA,
315 GREY, NV12, RGB, RGBA, YUYV,
316 };
317 use edgefirst_tensor::{is_dma_available, TensorMapTrait, TensorMemory, TensorTrait};
318 use four_char_code::FourCharCode;
319 use image::buffer::ConvertBuffer;
320
321 #[test]
322 #[cfg(target_os = "linux")]
323 fn test_g2d_formats_no_resize() {
324 for i in [RGBA, YUYV, RGB, GREY, NV12] {
325 for o in [RGBA, YUYV, RGB, GREY] {
326 let res = test_g2d_format_no_resize_(i, o);
327 if let Err(e) = res {
328 println!("{} to {} failed: {e:?}", i.display(), o.display());
329 } else {
330 println!("{} to {} success", i.display(), o.display());
331 }
332 }
333 }
334 }
335
336 fn test_g2d_format_no_resize_(
337 g2d_in_fmt: FourCharCode,
338 g2d_out_fmt: FourCharCode,
339 ) -> Result<(), crate::Error> {
340 let dst_width = 1280;
341 let dst_height = 720;
342 let file = include_bytes!("../../../testdata/zidane.jpg").to_vec();
343 let src = TensorImage::load_jpeg(&file, Some(RGB), None)?;
344
345 let mut src2 = TensorImage::new(1280, 720, g2d_in_fmt, Some(TensorMemory::Dma))?;
347
348 let mut cpu_converter = CPUProcessor::new();
349
350 if g2d_in_fmt == NV12 {
352 let nv12_bytes = include_bytes!("../../../testdata/zidane.nv12");
353 src2.tensor()
354 .map()?
355 .as_mut_slice()
356 .copy_from_slice(nv12_bytes);
357 } else {
358 cpu_converter.convert(&src, &mut src2, Rotation::None, Flip::None, Crop::no_crop())?;
359 }
360
361 let mut g2d_dst =
362 TensorImage::new(dst_width, dst_height, g2d_out_fmt, Some(TensorMemory::Dma))?;
363 let mut g2d_converter = G2DProcessor::new()?;
364 g2d_converter.convert_(
365 &src2,
366 &mut g2d_dst,
367 Rotation::None,
368 Flip::None,
369 Crop::no_crop(),
370 )?;
371
372 let mut cpu_dst = TensorImage::new(dst_width, dst_height, RGB, None)?;
373 cpu_converter.convert(
374 &g2d_dst,
375 &mut cpu_dst,
376 Rotation::None,
377 Flip::None,
378 Crop::no_crop(),
379 )?;
380
381 compare_images(
382 &src,
383 &cpu_dst,
384 0.98,
385 &format!("{}_to_{}", g2d_in_fmt.display(), g2d_out_fmt.display()),
386 )
387 }
388
389 #[test]
390 #[cfg(target_os = "linux")]
391 fn test_g2d_formats_with_resize() {
392 for i in [RGBA, YUYV, RGB, GREY, NV12] {
393 for o in [RGBA, YUYV, RGB, GREY] {
394 let res = test_g2d_format_with_resize_(i, o);
395 if let Err(e) = res {
396 println!("{} to {} failed: {e:?}", i.display(), o.display());
397 } else {
398 println!("{} to {} success", i.display(), o.display());
399 }
400 }
401 }
402 }
403
404 #[test]
405 #[cfg(target_os = "linux")]
406 fn test_g2d_formats_with_resize_dst_crop() {
407 for i in [RGBA, YUYV, RGB, GREY, NV12] {
408 for o in [RGBA, YUYV, RGB, GREY] {
409 let res = test_g2d_format_with_resize_dst_crop(i, o);
410 if let Err(e) = res {
411 println!("{} to {} failed: {e:?}", i.display(), o.display());
412 } else {
413 println!("{} to {} success", i.display(), o.display());
414 }
415 }
416 }
417 }
418
419 fn test_g2d_format_with_resize_(
420 g2d_in_fmt: FourCharCode,
421 g2d_out_fmt: FourCharCode,
422 ) -> Result<(), crate::Error> {
423 let dst_width = 600;
424 let dst_height = 400;
425 let file = include_bytes!("../../../testdata/zidane.jpg").to_vec();
426 let src = TensorImage::load_jpeg(&file, Some(RGB), None)?;
427
428 let mut cpu_converter = CPUProcessor::new();
429
430 let mut reference = TensorImage::new(dst_width, dst_height, RGB, Some(TensorMemory::Dma))?;
431 cpu_converter.convert(
432 &src,
433 &mut reference,
434 Rotation::None,
435 Flip::None,
436 Crop::no_crop(),
437 )?;
438
439 let mut src2 = TensorImage::new(1280, 720, g2d_in_fmt, Some(TensorMemory::Dma))?;
441
442 if g2d_in_fmt == NV12 {
444 let nv12_bytes = include_bytes!("../../../testdata/zidane.nv12");
445 src2.tensor()
446 .map()?
447 .as_mut_slice()
448 .copy_from_slice(nv12_bytes);
449 } else {
450 cpu_converter.convert(&src, &mut src2, Rotation::None, Flip::None, Crop::no_crop())?;
451 }
452
453 let mut g2d_dst =
454 TensorImage::new(dst_width, dst_height, g2d_out_fmt, Some(TensorMemory::Dma))?;
455 let mut g2d_converter = G2DProcessor::new()?;
456 g2d_converter.convert_(
457 &src2,
458 &mut g2d_dst,
459 Rotation::None,
460 Flip::None,
461 Crop::no_crop(),
462 )?;
463
464 let mut cpu_dst = TensorImage::new(dst_width, dst_height, RGB, None)?;
465 cpu_converter.convert(
466 &g2d_dst,
467 &mut cpu_dst,
468 Rotation::None,
469 Flip::None,
470 Crop::no_crop(),
471 )?;
472
473 compare_images(
474 &reference,
475 &cpu_dst,
476 0.98,
477 &format!(
478 "{}_to_{}_resized",
479 g2d_in_fmt.display(),
480 g2d_out_fmt.display()
481 ),
482 )
483 }
484
485 fn test_g2d_format_with_resize_dst_crop(
486 g2d_in_fmt: FourCharCode,
487 g2d_out_fmt: FourCharCode,
488 ) -> Result<(), crate::Error> {
489 let dst_width = 600;
490 let dst_height = 400;
491 let crop = Crop {
492 src_rect: None,
493 dst_rect: Some(Rect {
494 top: 100,
495 left: 100,
496 height: 100,
497 width: 200,
498 }),
499 dst_color: None,
500 };
501 let file = include_bytes!("../../../testdata/zidane.jpg").to_vec();
502 let src = TensorImage::load_jpeg(&file, Some(RGB), None)?;
503
504 let mut cpu_converter = CPUProcessor::new();
505
506 let mut reference = TensorImage::new(dst_width, dst_height, RGB, Some(TensorMemory::Dma))?;
507 reference.tensor.map().unwrap().as_mut_slice().fill(128);
508 cpu_converter.convert(&src, &mut reference, Rotation::None, Flip::None, crop)?;
509
510 let mut src2 = TensorImage::new(1280, 720, g2d_in_fmt, Some(TensorMemory::Dma))?;
512
513 if g2d_in_fmt == NV12 {
515 let nv12_bytes = include_bytes!("../../../testdata/zidane.nv12");
516 src2.tensor()
517 .map()?
518 .as_mut_slice()
519 .copy_from_slice(nv12_bytes);
520 } else {
521 cpu_converter.convert(&src, &mut src2, Rotation::None, Flip::None, Crop::no_crop())?;
522 }
523
524 let mut g2d_dst =
525 TensorImage::new(dst_width, dst_height, g2d_out_fmt, Some(TensorMemory::Dma))?;
526 g2d_dst.tensor.map().unwrap().as_mut_slice().fill(128);
527 let mut g2d_converter = G2DProcessor::new()?;
528 g2d_converter.convert_(&src2, &mut g2d_dst, Rotation::None, Flip::None, crop)?;
529
530 let mut cpu_dst = TensorImage::new(dst_width, dst_height, RGB, None)?;
531 cpu_converter.convert(
532 &g2d_dst,
533 &mut cpu_dst,
534 Rotation::None,
535 Flip::None,
536 Crop::no_crop(),
537 )?;
538
539 compare_images(
540 &reference,
541 &cpu_dst,
542 0.98,
543 &format!(
544 "{}_to_{}_resized_dst_crop",
545 g2d_in_fmt.display(),
546 g2d_out_fmt.display()
547 ),
548 )
549 }
550
551 fn compare_images(
552 img1: &TensorImage,
553 img2: &TensorImage,
554 threshold: f64,
555 name: &str,
556 ) -> Result<(), crate::Error> {
557 assert_eq!(img1.height(), img2.height(), "Heights differ");
558 assert_eq!(img1.width(), img2.width(), "Widths differ");
559 assert_eq!(img1.fourcc(), img2.fourcc(), "FourCC differ");
560 assert!(
561 matches!(img1.fourcc(), RGB | RGBA),
562 "FourCC must be RGB or RGBA for comparison"
563 );
564 let image1 = match img1.fourcc() {
565 RGB => image::RgbImage::from_vec(
566 img1.width() as u32,
567 img1.height() as u32,
568 img1.tensor().map().unwrap().to_vec(),
569 )
570 .unwrap(),
571 RGBA => image::RgbaImage::from_vec(
572 img1.width() as u32,
573 img1.height() as u32,
574 img1.tensor().map().unwrap().to_vec(),
575 )
576 .unwrap()
577 .convert(),
578
579 _ => unreachable!(),
580 };
581
582 let image2 = match img2.fourcc() {
583 RGB => image::RgbImage::from_vec(
584 img2.width() as u32,
585 img2.height() as u32,
586 img2.tensor().map().unwrap().to_vec(),
587 )
588 .unwrap(),
589 RGBA => image::RgbaImage::from_vec(
590 img2.width() as u32,
591 img2.height() as u32,
592 img2.tensor().map().unwrap().to_vec(),
593 )
594 .unwrap()
595 .convert(),
596
597 _ => unreachable!(),
598 };
599
600 let similarity = image_compare::rgb_similarity_structure(
601 &image_compare::Algorithm::RootMeanSquared,
602 &image1,
603 &image2,
604 )
605 .expect("Image Comparison failed");
606
607 if similarity.score < threshold {
608 image1.save(format!("{name}_1.png")).unwrap();
609 image2.save(format!("{name}_2.png")).unwrap();
610 return Err(Error::Internal(format!(
616 "{name}: converted image and target image have similarity score too low: {} < {}",
617 similarity.score, threshold
618 )));
619 }
620 Ok(())
621 }
622
623 fn load_raw_image(
629 width: usize,
630 height: usize,
631 fourcc: FourCharCode,
632 memory: Option<TensorMemory>,
633 bytes: &[u8],
634 ) -> Result<TensorImage, crate::Error> {
635 let img = TensorImage::new(width, height, fourcc, memory)?;
636 let mut map = img.tensor().map()?;
637 map.as_mut_slice()[..bytes.len()].copy_from_slice(bytes);
638 Ok(img)
639 }
640
641 #[test]
643 #[cfg(target_os = "linux")]
644 fn test_g2d_nv12_to_rgba_reference() -> Result<(), crate::Error> {
645 if !is_dma_available() {
646 return Ok(());
647 }
648 let src = load_raw_image(
650 1280,
651 720,
652 NV12,
653 Some(TensorMemory::Dma),
654 include_bytes!("../../../testdata/camera720p.nv12"),
655 )?;
656
657 let reference = load_raw_image(
659 1280,
660 720,
661 RGBA,
662 None,
663 include_bytes!("../../../testdata/camera720p.rgba"),
664 )?;
665
666 let mut dst = TensorImage::new(1280, 720, RGBA, Some(TensorMemory::Dma))?;
668 let mut g2d = G2DProcessor::new()?;
669 g2d.convert_(&src, &mut dst, Rotation::None, Flip::None, Crop::no_crop())?;
670
671 let cpu_dst = TensorImage::new(1280, 720, RGBA, None)?;
673 cpu_dst
674 .tensor()
675 .map()?
676 .as_mut_slice()
677 .copy_from_slice(dst.tensor().map()?.as_slice());
678
679 compare_images(&reference, &cpu_dst, 0.98, "g2d_nv12_to_rgba_reference")
680 }
681
682 #[test]
684 #[cfg(target_os = "linux")]
685 fn test_g2d_nv12_to_rgb_reference() -> Result<(), crate::Error> {
686 if !is_dma_available() {
687 return Ok(());
688 }
689 let src = load_raw_image(
691 1280,
692 720,
693 NV12,
694 Some(TensorMemory::Dma),
695 include_bytes!("../../../testdata/camera720p.nv12"),
696 )?;
697
698 let reference = load_raw_image(
700 1280,
701 720,
702 RGB,
703 None,
704 include_bytes!("../../../testdata/camera720p.rgb"),
705 )?;
706
707 let mut dst = TensorImage::new(1280, 720, RGB, Some(TensorMemory::Dma))?;
709 let mut g2d = G2DProcessor::new()?;
710 g2d.convert_(&src, &mut dst, Rotation::None, Flip::None, Crop::no_crop())?;
711
712 let cpu_dst = TensorImage::new(1280, 720, RGB, None)?;
714 cpu_dst
715 .tensor()
716 .map()?
717 .as_mut_slice()
718 .copy_from_slice(dst.tensor().map()?.as_slice());
719
720 compare_images(&reference, &cpu_dst, 0.98, "g2d_nv12_to_rgb_reference")
721 }
722
723 #[test]
725 #[cfg(target_os = "linux")]
726 fn test_g2d_yuyv_to_rgba_reference() -> Result<(), crate::Error> {
727 if !is_dma_available() {
728 return Ok(());
729 }
730 let src = load_raw_image(
732 1280,
733 720,
734 YUYV,
735 Some(TensorMemory::Dma),
736 include_bytes!("../../../testdata/camera720p.yuyv"),
737 )?;
738
739 let reference = load_raw_image(
741 1280,
742 720,
743 RGBA,
744 None,
745 include_bytes!("../../../testdata/camera720p.rgba"),
746 )?;
747
748 let mut dst = TensorImage::new(1280, 720, RGBA, Some(TensorMemory::Dma))?;
750 let mut g2d = G2DProcessor::new()?;
751 g2d.convert_(&src, &mut dst, Rotation::None, Flip::None, Crop::no_crop())?;
752
753 let cpu_dst = TensorImage::new(1280, 720, RGBA, None)?;
755 cpu_dst
756 .tensor()
757 .map()?
758 .as_mut_slice()
759 .copy_from_slice(dst.tensor().map()?.as_slice());
760
761 compare_images(&reference, &cpu_dst, 0.98, "g2d_yuyv_to_rgba_reference")
762 }
763
764 #[test]
766 #[cfg(target_os = "linux")]
767 fn test_g2d_yuyv_to_rgb_reference() -> Result<(), crate::Error> {
768 if !is_dma_available() {
769 return Ok(());
770 }
771 let src = load_raw_image(
773 1280,
774 720,
775 YUYV,
776 Some(TensorMemory::Dma),
777 include_bytes!("../../../testdata/camera720p.yuyv"),
778 )?;
779
780 let reference = load_raw_image(
782 1280,
783 720,
784 RGB,
785 None,
786 include_bytes!("../../../testdata/camera720p.rgb"),
787 )?;
788
789 let mut dst = TensorImage::new(1280, 720, RGB, Some(TensorMemory::Dma))?;
791 let mut g2d = G2DProcessor::new()?;
792 g2d.convert_(&src, &mut dst, Rotation::None, Flip::None, Crop::no_crop())?;
793
794 let cpu_dst = TensorImage::new(1280, 720, RGB, None)?;
796 cpu_dst
797 .tensor()
798 .map()?
799 .as_mut_slice()
800 .copy_from_slice(dst.tensor().map()?.as_slice());
801
802 compare_images(&reference, &cpu_dst, 0.98, "g2d_yuyv_to_rgb_reference")
803 }
804
805 #[test]
808 #[cfg(target_os = "linux")]
809 fn test_g2d_bgra_no_resize() {
810 for src_fmt in [RGBA, YUYV, NV12, BGRA] {
811 test_g2d_bgra_no_resize_(src_fmt).unwrap_or_else(|e| {
812 panic!("{} to BGRA failed: {e:?}", src_fmt.display());
813 });
814 }
815 }
816
817 fn test_g2d_bgra_no_resize_(g2d_in_fmt: FourCharCode) -> Result<(), crate::Error> {
818 let file = include_bytes!("../../../testdata/zidane.jpg").to_vec();
819 let src = TensorImage::load_jpeg(&file, Some(RGB), None)?;
820
821 let mut src2 = TensorImage::new(1280, 720, g2d_in_fmt, Some(TensorMemory::Dma))?;
823 let mut cpu_converter = CPUProcessor::new();
824
825 if g2d_in_fmt == NV12 {
826 let nv12_bytes = include_bytes!("../../../testdata/zidane.nv12");
827 src2.tensor()
828 .map()?
829 .as_mut_slice()
830 .copy_from_slice(nv12_bytes);
831 } else {
832 cpu_converter.convert(&src, &mut src2, Rotation::None, Flip::None, Crop::no_crop())?;
833 }
834
835 let mut g2d = G2DProcessor::new()?;
836
837 let mut bgra_dst = TensorImage::new(1280, 720, BGRA, Some(TensorMemory::Dma))?;
839 g2d.convert_(
840 &src2,
841 &mut bgra_dst,
842 Rotation::None,
843 Flip::None,
844 Crop::no_crop(),
845 )?;
846
847 let mut rgba_dst = TensorImage::new(1280, 720, RGBA, Some(TensorMemory::Dma))?;
849 g2d.convert_(
850 &src2,
851 &mut rgba_dst,
852 Rotation::None,
853 Flip::None,
854 Crop::no_crop(),
855 )?;
856
857 let bgra_cpu = TensorImage::new(1280, 720, BGRA, None)?;
859 bgra_cpu
860 .tensor()
861 .map()?
862 .as_mut_slice()
863 .copy_from_slice(bgra_dst.tensor().map()?.as_slice());
864
865 let rgba_cpu = TensorImage::new(1280, 720, RGBA, None)?;
866 rgba_cpu
867 .tensor()
868 .map()?
869 .as_mut_slice()
870 .copy_from_slice(rgba_dst.tensor().map()?.as_slice());
871
872 let bgra_map = bgra_cpu.tensor().map()?;
874 let rgba_map = rgba_cpu.tensor().map()?;
875 let bgra_buf = bgra_map.as_slice();
876 let rgba_buf = rgba_map.as_slice();
877
878 assert_eq!(bgra_buf.len(), rgba_buf.len());
879 for (i, (bc, rc)) in bgra_buf
880 .chunks_exact(4)
881 .zip(rgba_buf.chunks_exact(4))
882 .enumerate()
883 {
884 assert_eq!(
885 bc[0],
886 rc[2],
887 "{} to BGRA: pixel {i} B mismatch",
888 g2d_in_fmt.display()
889 );
890 assert_eq!(
891 bc[1],
892 rc[1],
893 "{} to BGRA: pixel {i} G mismatch",
894 g2d_in_fmt.display()
895 );
896 assert_eq!(
897 bc[2],
898 rc[0],
899 "{} to BGRA: pixel {i} R mismatch",
900 g2d_in_fmt.display()
901 );
902 assert_eq!(
903 bc[3],
904 rc[3],
905 "{} to BGRA: pixel {i} A mismatch",
906 g2d_in_fmt.display()
907 );
908 }
909 Ok(())
910 }
911}