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