1#![cfg(target_os = "linux")]
5
6use crate::{
7 CPUProcessor, Crop, Error, Flip, ImageProcessorTrait, Result, Rotation, TensorImage,
8 TensorImageRef, 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) => {}
151 (NV12, YUYV) => {}
152 (NV12, RGB) => {}
153 (s, d) => {
154 return Err(Error::NotSupported(format!(
155 "G2D does not support {} to {} conversion",
156 s.display(),
157 d.display()
158 )));
159 }
160 }
161 self.convert_(src, dst, rotation, flip, crop)
162 }
163
164 fn convert_ref(
165 &mut self,
166 src: &TensorImage,
167 dst: &mut TensorImageRef<'_>,
168 rotation: Rotation,
169 flip: Flip,
170 crop: Crop,
171 ) -> Result<()> {
172 let mut cpu = CPUProcessor::new();
174 cpu.convert_ref(src, dst, rotation, flip, crop)
175 }
176
177 #[cfg(feature = "decoder")]
178 fn render_to_image(
179 &mut self,
180 _dst: &mut TensorImage,
181 _detect: &[crate::DetectBox],
182 _segmentation: &[crate::Segmentation],
183 ) -> Result<()> {
184 Err(Error::NotImplemented(
185 "G2D does not support rendering detection or segmentation overlays".to_string(),
186 ))
187 }
188
189 #[cfg(feature = "decoder")]
190 fn render_from_protos(
191 &mut self,
192 _dst: &mut TensorImage,
193 _detect: &[crate::DetectBox],
194 _proto_data: &crate::ProtoData,
195 ) -> Result<()> {
196 Err(Error::NotImplemented(
197 "G2D does not support rendering detection or segmentation overlays".to_string(),
198 ))
199 }
200
201 #[cfg(feature = "decoder")]
202 fn render_masks_from_protos(
203 &mut self,
204 _detect: &[crate::DetectBox],
205 _proto_data: crate::ProtoData,
206 _output_width: usize,
207 _output_height: usize,
208 ) -> Result<Vec<crate::MaskResult>> {
209 Err(Error::NotImplemented(
210 "G2D does not support rendering per-instance masks".to_string(),
211 ))
212 }
213
214 #[cfg(feature = "decoder")]
215 fn set_class_colors(&mut self, _: &[[u8; 4]]) -> Result<()> {
216 Err(Error::NotImplemented(
217 "G2D does not support setting colors for rendering detection or segmentation overlays"
218 .to_string(),
219 ))
220 }
221}
222
223impl TryFrom<&TensorImage> for G2DSurface {
224 type Error = Error;
225
226 fn try_from(img: &TensorImage) -> Result<Self, Self::Error> {
227 let phys: G2DPhysical = match img.tensor() {
228 Tensor::Dma(t) => t.as_raw_fd(),
229 _ => {
230 return Err(Error::NotImplemented(
231 "g2d only supports Dma memory".to_string(),
232 ));
233 }
234 }
235 .try_into()?;
236
237 let base_addr = phys.address();
241 let planes = if img.fourcc() == NV12 {
242 let uv_offset = (img.width() * img.height()) as u64;
243 [base_addr, base_addr + uv_offset, 0]
244 } else {
245 [base_addr, 0, 0]
246 };
247
248 Ok(Self {
249 planes,
250 format: G2DFormat::try_from(img.fourcc())?.format(),
251 left: 0,
252 top: 0,
253 right: img.width() as i32,
254 bottom: img.height() as i32,
255 stride: img.width() as i32,
256 width: img.width() as i32,
257 height: img.height() as i32,
258 blendfunc: 0,
259 clrcolor: 0,
260 rot: 0,
261 global_alpha: 0,
262 })
263 }
264}
265
266impl TryFrom<&mut TensorImage> for G2DSurface {
267 type Error = Error;
268
269 fn try_from(img: &mut TensorImage) -> Result<Self, Self::Error> {
270 let phys: G2DPhysical = match img.tensor() {
271 Tensor::Dma(t) => t.as_raw_fd(),
272 _ => {
273 return Err(Error::NotImplemented(
274 "g2d only supports Dma memory".to_string(),
275 ));
276 }
277 }
278 .try_into()?;
279
280 let base_addr = phys.address();
282 let planes = if img.fourcc() == NV12 {
283 let uv_offset = (img.width() * img.height()) as u64;
284 [base_addr, base_addr + uv_offset, 0]
285 } else {
286 [base_addr, 0, 0]
287 };
288
289 Ok(Self {
290 planes,
291 format: G2DFormat::try_from(img.fourcc())?.format(),
292 left: 0,
293 top: 0,
294 right: img.width() as i32,
295 bottom: img.height() as i32,
296 stride: img.width() as i32,
297 width: img.width() as i32,
298 height: img.height() as i32,
299 blendfunc: 0,
300 clrcolor: 0,
301 rot: 0,
302 global_alpha: 0,
303 })
304 }
305}
306
307#[cfg(feature = "g2d_test_formats")]
308#[cfg(test)]
309mod g2d_tests {
310 use super::*;
311 use crate::{
312 CPUProcessor, Flip, G2DProcessor, ImageProcessorTrait, Rect, Rotation, TensorImage, GREY,
313 NV12, RGB, RGBA, YUYV,
314 };
315 use edgefirst_tensor::{is_dma_available, TensorMapTrait, TensorMemory, TensorTrait};
316 use four_char_code::FourCharCode;
317 use image::buffer::ConvertBuffer;
318
319 #[test]
320 #[cfg(target_os = "linux")]
321 fn test_g2d_formats_no_resize() {
322 for i in [RGBA, YUYV, RGB, GREY, NV12] {
323 for o in [RGBA, YUYV, RGB, GREY] {
324 let res = test_g2d_format_no_resize_(i, o);
325 if let Err(e) = res {
326 println!("{} to {} failed: {e:?}", i.display(), o.display());
327 } else {
328 println!("{} to {} success", i.display(), o.display());
329 }
330 }
331 }
332 }
333
334 fn test_g2d_format_no_resize_(
335 g2d_in_fmt: FourCharCode,
336 g2d_out_fmt: FourCharCode,
337 ) -> Result<(), crate::Error> {
338 let dst_width = 1280;
339 let dst_height = 720;
340 let file = include_bytes!("../../../testdata/zidane.jpg").to_vec();
341 let src = TensorImage::load_jpeg(&file, Some(RGB), None)?;
342
343 let mut src2 = TensorImage::new(1280, 720, g2d_in_fmt, Some(TensorMemory::Dma))?;
345
346 let mut cpu_converter = CPUProcessor::new();
347
348 if g2d_in_fmt == NV12 {
350 let nv12_bytes = include_bytes!("../../../testdata/zidane.nv12");
351 src2.tensor()
352 .map()?
353 .as_mut_slice()
354 .copy_from_slice(nv12_bytes);
355 } else {
356 cpu_converter.convert(&src, &mut src2, Rotation::None, Flip::None, Crop::no_crop())?;
357 }
358
359 let mut g2d_dst =
360 TensorImage::new(dst_width, dst_height, g2d_out_fmt, Some(TensorMemory::Dma))?;
361 let mut g2d_converter = G2DProcessor::new()?;
362 g2d_converter.convert_(
363 &src2,
364 &mut g2d_dst,
365 Rotation::None,
366 Flip::None,
367 Crop::no_crop(),
368 )?;
369
370 let mut cpu_dst = TensorImage::new(dst_width, dst_height, RGB, None)?;
371 cpu_converter.convert(
372 &g2d_dst,
373 &mut cpu_dst,
374 Rotation::None,
375 Flip::None,
376 Crop::no_crop(),
377 )?;
378
379 compare_images(
380 &src,
381 &cpu_dst,
382 0.98,
383 &format!("{}_to_{}", g2d_in_fmt.display(), g2d_out_fmt.display()),
384 )
385 }
386
387 #[test]
388 #[cfg(target_os = "linux")]
389 fn test_g2d_formats_with_resize() {
390 for i in [RGBA, YUYV, RGB, GREY, NV12] {
391 for o in [RGBA, YUYV, RGB, GREY] {
392 let res = test_g2d_format_with_resize_(i, o);
393 if let Err(e) = res {
394 println!("{} to {} failed: {e:?}", i.display(), o.display());
395 } else {
396 println!("{} to {} success", i.display(), o.display());
397 }
398 }
399 }
400 }
401
402 #[test]
403 #[cfg(target_os = "linux")]
404 fn test_g2d_formats_with_resize_dst_crop() {
405 for i in [RGBA, YUYV, RGB, GREY, NV12] {
406 for o in [RGBA, YUYV, RGB, GREY] {
407 let res = test_g2d_format_with_resize_dst_crop(i, o);
408 if let Err(e) = res {
409 println!("{} to {} failed: {e:?}", i.display(), o.display());
410 } else {
411 println!("{} to {} success", i.display(), o.display());
412 }
413 }
414 }
415 }
416
417 fn test_g2d_format_with_resize_(
418 g2d_in_fmt: FourCharCode,
419 g2d_out_fmt: FourCharCode,
420 ) -> Result<(), crate::Error> {
421 let dst_width = 600;
422 let dst_height = 400;
423 let file = include_bytes!("../../../testdata/zidane.jpg").to_vec();
424 let src = TensorImage::load_jpeg(&file, Some(RGB), None)?;
425
426 let mut cpu_converter = CPUProcessor::new();
427
428 let mut reference = TensorImage::new(dst_width, dst_height, RGB, Some(TensorMemory::Dma))?;
429 cpu_converter.convert(
430 &src,
431 &mut reference,
432 Rotation::None,
433 Flip::None,
434 Crop::no_crop(),
435 )?;
436
437 let mut src2 = TensorImage::new(1280, 720, g2d_in_fmt, Some(TensorMemory::Dma))?;
439
440 if g2d_in_fmt == NV12 {
442 let nv12_bytes = include_bytes!("../../../testdata/zidane.nv12");
443 src2.tensor()
444 .map()?
445 .as_mut_slice()
446 .copy_from_slice(nv12_bytes);
447 } else {
448 cpu_converter.convert(&src, &mut src2, Rotation::None, Flip::None, Crop::no_crop())?;
449 }
450
451 let mut g2d_dst =
452 TensorImage::new(dst_width, dst_height, g2d_out_fmt, Some(TensorMemory::Dma))?;
453 let mut g2d_converter = G2DProcessor::new()?;
454 g2d_converter.convert_(
455 &src2,
456 &mut g2d_dst,
457 Rotation::None,
458 Flip::None,
459 Crop::no_crop(),
460 )?;
461
462 let mut cpu_dst = TensorImage::new(dst_width, dst_height, RGB, None)?;
463 cpu_converter.convert(
464 &g2d_dst,
465 &mut cpu_dst,
466 Rotation::None,
467 Flip::None,
468 Crop::no_crop(),
469 )?;
470
471 compare_images(
472 &reference,
473 &cpu_dst,
474 0.98,
475 &format!(
476 "{}_to_{}_resized",
477 g2d_in_fmt.display(),
478 g2d_out_fmt.display()
479 ),
480 )
481 }
482
483 fn test_g2d_format_with_resize_dst_crop(
484 g2d_in_fmt: FourCharCode,
485 g2d_out_fmt: FourCharCode,
486 ) -> Result<(), crate::Error> {
487 let dst_width = 600;
488 let dst_height = 400;
489 let crop = Crop {
490 src_rect: None,
491 dst_rect: Some(Rect {
492 top: 100,
493 left: 100,
494 height: 100,
495 width: 200,
496 }),
497 dst_color: None,
498 };
499 let file = include_bytes!("../../../testdata/zidane.jpg").to_vec();
500 let src = TensorImage::load_jpeg(&file, Some(RGB), None)?;
501
502 let mut cpu_converter = CPUProcessor::new();
503
504 let mut reference = TensorImage::new(dst_width, dst_height, RGB, Some(TensorMemory::Dma))?;
505 reference.tensor.map().unwrap().as_mut_slice().fill(128);
506 cpu_converter.convert(&src, &mut reference, Rotation::None, Flip::None, crop)?;
507
508 let mut src2 = TensorImage::new(1280, 720, g2d_in_fmt, Some(TensorMemory::Dma))?;
510
511 if g2d_in_fmt == NV12 {
513 let nv12_bytes = include_bytes!("../../../testdata/zidane.nv12");
514 src2.tensor()
515 .map()?
516 .as_mut_slice()
517 .copy_from_slice(nv12_bytes);
518 } else {
519 cpu_converter.convert(&src, &mut src2, Rotation::None, Flip::None, Crop::no_crop())?;
520 }
521
522 let mut g2d_dst =
523 TensorImage::new(dst_width, dst_height, g2d_out_fmt, Some(TensorMemory::Dma))?;
524 g2d_dst.tensor.map().unwrap().as_mut_slice().fill(128);
525 let mut g2d_converter = G2DProcessor::new()?;
526 g2d_converter.convert_(&src2, &mut g2d_dst, Rotation::None, Flip::None, crop)?;
527
528 let mut cpu_dst = TensorImage::new(dst_width, dst_height, RGB, None)?;
529 cpu_converter.convert(
530 &g2d_dst,
531 &mut cpu_dst,
532 Rotation::None,
533 Flip::None,
534 Crop::no_crop(),
535 )?;
536
537 compare_images(
538 &reference,
539 &cpu_dst,
540 0.98,
541 &format!(
542 "{}_to_{}_resized_dst_crop",
543 g2d_in_fmt.display(),
544 g2d_out_fmt.display()
545 ),
546 )
547 }
548
549 fn compare_images(
550 img1: &TensorImage,
551 img2: &TensorImage,
552 threshold: f64,
553 name: &str,
554 ) -> Result<(), crate::Error> {
555 assert_eq!(img1.height(), img2.height(), "Heights differ");
556 assert_eq!(img1.width(), img2.width(), "Widths differ");
557 assert_eq!(img1.fourcc(), img2.fourcc(), "FourCC differ");
558 assert!(
559 matches!(img1.fourcc(), RGB | RGBA),
560 "FourCC must be RGB or RGBA for comparison"
561 );
562 let image1 = match img1.fourcc() {
563 RGB => image::RgbImage::from_vec(
564 img1.width() as u32,
565 img1.height() as u32,
566 img1.tensor().map().unwrap().to_vec(),
567 )
568 .unwrap(),
569 RGBA => image::RgbaImage::from_vec(
570 img1.width() as u32,
571 img1.height() as u32,
572 img1.tensor().map().unwrap().to_vec(),
573 )
574 .unwrap()
575 .convert(),
576
577 _ => unreachable!(),
578 };
579
580 let image2 = match img2.fourcc() {
581 RGB => image::RgbImage::from_vec(
582 img2.width() as u32,
583 img2.height() as u32,
584 img2.tensor().map().unwrap().to_vec(),
585 )
586 .unwrap(),
587 RGBA => image::RgbaImage::from_vec(
588 img2.width() as u32,
589 img2.height() as u32,
590 img2.tensor().map().unwrap().to_vec(),
591 )
592 .unwrap()
593 .convert(),
594
595 _ => unreachable!(),
596 };
597
598 let similarity = image_compare::rgb_similarity_structure(
599 &image_compare::Algorithm::RootMeanSquared,
600 &image1,
601 &image2,
602 )
603 .expect("Image Comparison failed");
604
605 if similarity.score < threshold {
606 image1.save(format!("{name}_1.png")).unwrap();
607 image2.save(format!("{name}_2.png")).unwrap();
608 return Err(Error::Internal(format!(
614 "{name}: converted image and target image have similarity score too low: {} < {}",
615 similarity.score, threshold
616 )));
617 }
618 Ok(())
619 }
620
621 fn load_raw_image(
627 width: usize,
628 height: usize,
629 fourcc: FourCharCode,
630 memory: Option<TensorMemory>,
631 bytes: &[u8],
632 ) -> Result<TensorImage, crate::Error> {
633 let img = TensorImage::new(width, height, fourcc, memory)?;
634 let mut map = img.tensor().map()?;
635 map.as_mut_slice()[..bytes.len()].copy_from_slice(bytes);
636 Ok(img)
637 }
638
639 #[test]
641 #[cfg(target_os = "linux")]
642 fn test_g2d_nv12_to_rgba_reference() -> Result<(), crate::Error> {
643 if !is_dma_available() {
644 return Ok(());
645 }
646 let src = load_raw_image(
648 1280,
649 720,
650 NV12,
651 Some(TensorMemory::Dma),
652 include_bytes!("../../../testdata/camera720p.nv12"),
653 )?;
654
655 let reference = load_raw_image(
657 1280,
658 720,
659 RGBA,
660 None,
661 include_bytes!("../../../testdata/camera720p.rgba"),
662 )?;
663
664 let mut dst = TensorImage::new(1280, 720, RGBA, Some(TensorMemory::Dma))?;
666 let mut g2d = G2DProcessor::new()?;
667 g2d.convert_(&src, &mut dst, Rotation::None, Flip::None, Crop::no_crop())?;
668
669 let cpu_dst = TensorImage::new(1280, 720, RGBA, None)?;
671 cpu_dst
672 .tensor()
673 .map()?
674 .as_mut_slice()
675 .copy_from_slice(dst.tensor().map()?.as_slice());
676
677 compare_images(&reference, &cpu_dst, 0.98, "g2d_nv12_to_rgba_reference")
678 }
679
680 #[test]
682 #[cfg(target_os = "linux")]
683 fn test_g2d_nv12_to_rgb_reference() -> Result<(), crate::Error> {
684 if !is_dma_available() {
685 return Ok(());
686 }
687 let src = load_raw_image(
689 1280,
690 720,
691 NV12,
692 Some(TensorMemory::Dma),
693 include_bytes!("../../../testdata/camera720p.nv12"),
694 )?;
695
696 let reference = load_raw_image(
698 1280,
699 720,
700 RGB,
701 None,
702 include_bytes!("../../../testdata/camera720p.rgb"),
703 )?;
704
705 let mut dst = TensorImage::new(1280, 720, RGB, Some(TensorMemory::Dma))?;
707 let mut g2d = G2DProcessor::new()?;
708 g2d.convert_(&src, &mut dst, Rotation::None, Flip::None, Crop::no_crop())?;
709
710 let cpu_dst = TensorImage::new(1280, 720, RGB, None)?;
712 cpu_dst
713 .tensor()
714 .map()?
715 .as_mut_slice()
716 .copy_from_slice(dst.tensor().map()?.as_slice());
717
718 compare_images(&reference, &cpu_dst, 0.98, "g2d_nv12_to_rgb_reference")
719 }
720
721 #[test]
723 #[cfg(target_os = "linux")]
724 fn test_g2d_yuyv_to_rgba_reference() -> Result<(), crate::Error> {
725 if !is_dma_available() {
726 return Ok(());
727 }
728 let src = load_raw_image(
730 1280,
731 720,
732 YUYV,
733 Some(TensorMemory::Dma),
734 include_bytes!("../../../testdata/camera720p.yuyv"),
735 )?;
736
737 let reference = load_raw_image(
739 1280,
740 720,
741 RGBA,
742 None,
743 include_bytes!("../../../testdata/camera720p.rgba"),
744 )?;
745
746 let mut dst = TensorImage::new(1280, 720, RGBA, Some(TensorMemory::Dma))?;
748 let mut g2d = G2DProcessor::new()?;
749 g2d.convert_(&src, &mut dst, Rotation::None, Flip::None, Crop::no_crop())?;
750
751 let cpu_dst = TensorImage::new(1280, 720, RGBA, None)?;
753 cpu_dst
754 .tensor()
755 .map()?
756 .as_mut_slice()
757 .copy_from_slice(dst.tensor().map()?.as_slice());
758
759 compare_images(&reference, &cpu_dst, 0.98, "g2d_yuyv_to_rgba_reference")
760 }
761
762 #[test]
764 #[cfg(target_os = "linux")]
765 fn test_g2d_yuyv_to_rgb_reference() -> Result<(), crate::Error> {
766 if !is_dma_available() {
767 return Ok(());
768 }
769 let src = load_raw_image(
771 1280,
772 720,
773 YUYV,
774 Some(TensorMemory::Dma),
775 include_bytes!("../../../testdata/camera720p.yuyv"),
776 )?;
777
778 let reference = load_raw_image(
780 1280,
781 720,
782 RGB,
783 None,
784 include_bytes!("../../../testdata/camera720p.rgb"),
785 )?;
786
787 let mut dst = TensorImage::new(1280, 720, RGB, Some(TensorMemory::Dma))?;
789 let mut g2d = G2DProcessor::new()?;
790 g2d.convert_(&src, &mut dst, Rotation::None, Flip::None, Crop::no_crop())?;
791
792 let cpu_dst = TensorImage::new(1280, 720, RGB, None)?;
794 cpu_dst
795 .tensor()
796 .map()?
797 .as_mut_slice()
798 .copy_from_slice(dst.tensor().map()?.as_slice());
799
800 compare_images(&reference, &cpu_dst, 0.98, "g2d_yuyv_to_rgb_reference")
801 }
802}