1#![cfg(target_os = "linux")]
5
6use crate::{CPUProcessor, Crop, Error, Flip, ImageProcessorTrait, Result, Rotation};
7use edgefirst_tensor::{DType, PixelFormat, Tensor, TensorDyn, TensorMapTrait, TensorTrait};
8use four_char_code::FourCharCode;
9use g2d_sys::{G2DFormat, G2DPhysical, G2DSurface, G2D};
10use std::{os::fd::AsRawFd, time::Instant};
11
12fn pixelfmt_to_fourcc(fmt: PixelFormat) -> FourCharCode {
14 use four_char_code::four_char_code;
15 match fmt {
16 PixelFormat::Rgb => four_char_code!("RGB "),
17 PixelFormat::Rgba => four_char_code!("RGBA"),
18 PixelFormat::Bgra => four_char_code!("BGRA"),
19 PixelFormat::Grey => four_char_code!("Y800"),
20 PixelFormat::Yuyv => four_char_code!("YUYV"),
21 PixelFormat::Vyuy => four_char_code!("VYUY"),
22 PixelFormat::Nv12 => four_char_code!("NV12"),
23 PixelFormat::Nv16 => four_char_code!("NV16"),
24 _ => four_char_code!("RGBA"),
26 }
27}
28
29#[derive(Debug)]
32pub struct G2DProcessor {
33 g2d: G2D,
34}
35
36unsafe impl Send for G2DProcessor {}
37unsafe impl Sync for G2DProcessor {}
38
39impl G2DProcessor {
40 pub fn new() -> Result<Self> {
42 let mut g2d = G2D::new("libg2d.so.2")?;
43 g2d.set_bt709_colorspace()?;
44
45 log::debug!("G2DConverter created with version {:?}", g2d.version());
46 Ok(Self { g2d })
47 }
48
49 pub fn version(&self) -> g2d_sys::Version {
52 self.g2d.version()
53 }
54
55 fn convert_impl(
56 &mut self,
57 src_dyn: &TensorDyn,
58 dst_dyn: &mut TensorDyn,
59 rotation: Rotation,
60 flip: Flip,
61 crop: Crop,
62 ) -> Result<()> {
63 if log::log_enabled!(log::Level::Trace) {
64 log::trace!(
65 "G2D convert: {:?}({:?}/{:?}) → {:?}({:?}/{:?})",
66 src_dyn.format(),
67 src_dyn.dtype(),
68 src_dyn.memory(),
69 dst_dyn.format(),
70 dst_dyn.dtype(),
71 dst_dyn.memory(),
72 );
73 }
74
75 if src_dyn.dtype() != DType::U8 {
76 return Err(Error::NotSupported(
77 "G2D only supports u8 source tensors".to_string(),
78 ));
79 }
80 let is_int8_dst = dst_dyn.dtype() == DType::I8;
81 if dst_dyn.dtype() != DType::U8 && !is_int8_dst {
82 return Err(Error::NotSupported(
83 "G2D only supports u8 or i8 destination tensors".to_string(),
84 ));
85 }
86
87 let src_fmt = src_dyn.format().ok_or(Error::NotAnImage)?;
88 let dst_fmt = dst_dyn.format().ok_or(Error::NotAnImage)?;
89
90 use PixelFormat::*;
92 match (src_fmt, dst_fmt) {
93 (Rgba, Rgba) => {}
94 (Rgba, Yuyv) => {}
95 (Rgba, Rgb) => {}
96 (Yuyv, Rgba) => {}
97 (Yuyv, Yuyv) => {}
98 (Yuyv, Rgb) => {}
99 (Nv12, Rgba) => {}
102 (Nv12, Yuyv) => {}
103 (Nv12, Rgb) => {}
104 (Rgba, Bgra) => {}
105 (Yuyv, Bgra) => {}
106 (Nv12, Bgra) => {}
107 (Bgra, Bgra) => {}
108 (s, d) => {
109 return Err(Error::NotSupported(format!(
110 "G2D does not support {} to {} conversion",
111 s, d
112 )));
113 }
114 }
115
116 crop.check_crop_dyn(src_dyn, dst_dyn)?;
117
118 let src = src_dyn.as_u8().unwrap();
119 let dst = if is_int8_dst {
122 let i8_tensor = dst_dyn.as_i8_mut().unwrap();
130 unsafe { &mut *(i8_tensor as *mut Tensor<i8> as *mut Tensor<u8>) }
131 } else {
132 dst_dyn.as_u8_mut().unwrap()
133 };
134
135 let mut src_surface = tensor_to_g2d_surface(src)?;
136 let mut dst_surface = tensor_to_g2d_surface(dst)?;
137
138 src_surface.rot = match flip {
139 Flip::None => g2d_sys::g2d_rotation_G2D_ROTATION_0,
140 Flip::Vertical => g2d_sys::g2d_rotation_G2D_FLIP_V,
141 Flip::Horizontal => g2d_sys::g2d_rotation_G2D_FLIP_H,
142 };
143
144 dst_surface.rot = match rotation {
145 Rotation::None => g2d_sys::g2d_rotation_G2D_ROTATION_0,
146 Rotation::Clockwise90 => g2d_sys::g2d_rotation_G2D_ROTATION_90,
147 Rotation::Rotate180 => g2d_sys::g2d_rotation_G2D_ROTATION_180,
148 Rotation::CounterClockwise90 => g2d_sys::g2d_rotation_G2D_ROTATION_270,
149 };
150
151 if let Some(crop_rect) = crop.src_rect {
152 src_surface.left = crop_rect.left as i32;
153 src_surface.top = crop_rect.top as i32;
154 src_surface.right = (crop_rect.left + crop_rect.width) as i32;
155 src_surface.bottom = (crop_rect.top + crop_rect.height) as i32;
156 }
157
158 let dst_w = dst.width().unwrap();
159 let dst_h = dst.height().unwrap();
160
161 let needs_clear = crop.dst_color.is_some()
167 && crop.dst_rect.is_some_and(|dst_rect| {
168 dst_rect.left != 0
169 || dst_rect.top != 0
170 || dst_rect.width != dst_w
171 || dst_rect.height != dst_h
172 });
173
174 if needs_clear && dst_fmt != Rgb {
175 if let Some(dst_color) = crop.dst_color {
176 let start = Instant::now();
177 self.g2d.clear(&mut dst_surface, dst_color)?;
178 log::trace!("g2d clear takes {:?}", start.elapsed());
179 }
180 }
181
182 if let Some(crop_rect) = crop.dst_rect {
183 dst_surface.planes[0] += ((crop_rect.top * dst_surface.width as usize + crop_rect.left)
184 * dst_fmt.channels()) as u64;
185
186 dst_surface.right = crop_rect.width as i32;
187 dst_surface.bottom = crop_rect.height as i32;
188 dst_surface.width = crop_rect.width as i32;
189 dst_surface.height = crop_rect.height as i32;
190 }
191
192 log::trace!("G2D blit: {src_fmt}→{dst_fmt} int8={is_int8_dst}");
193 self.g2d.blit(&src_surface, &dst_surface)?;
194 self.g2d.finish()?;
195 log::trace!("G2D blit complete");
196
197 if needs_clear && dst_fmt == Rgb {
199 if let (Some(dst_color), Some(dst_rect)) = (crop.dst_color, crop.dst_rect) {
200 let start = Instant::now();
201 CPUProcessor::fill_image_outside_crop_u8(dst, dst_color, dst_rect)?;
202 log::trace!("cpu fill takes {:?}", start.elapsed());
203 }
204 }
205
206 if is_int8_dst {
211 let start = Instant::now();
212 let mut map = dst.map()?;
213 crate::cpu::apply_int8_xor_bias(map.as_mut_slice(), dst_fmt);
214 log::trace!("g2d int8 XOR 0x80 post-pass takes {:?}", start.elapsed());
215 }
216
217 Ok(())
218 }
219}
220
221impl ImageProcessorTrait for G2DProcessor {
222 fn convert(
223 &mut self,
224 src: &TensorDyn,
225 dst: &mut TensorDyn,
226 rotation: Rotation,
227 flip: Flip,
228 crop: Crop,
229 ) -> Result<()> {
230 self.convert_impl(src, dst, rotation, flip, crop)
231 }
232
233 fn draw_masks(
234 &mut self,
235 _dst: &mut TensorDyn,
236 _detect: &[crate::DetectBox],
237 _segmentation: &[crate::Segmentation],
238 ) -> Result<()> {
239 Err(Error::NotImplemented(
240 "G2D does not support drawing detection or segmentation overlays".to_string(),
241 ))
242 }
243
244 fn draw_masks_proto(
245 &mut self,
246 _dst: &mut TensorDyn,
247 _detect: &[crate::DetectBox],
248 _proto_data: &crate::ProtoData,
249 ) -> Result<()> {
250 Err(Error::NotImplemented(
251 "G2D does not support drawing detection or segmentation overlays".to_string(),
252 ))
253 }
254
255 fn decode_masks_atlas(
256 &mut self,
257 _detect: &[crate::DetectBox],
258 _proto_data: crate::ProtoData,
259 _output_width: usize,
260 _output_height: usize,
261 ) -> Result<(Vec<u8>, Vec<crate::MaskRegion>)> {
262 Err(Error::NotImplemented(
263 "G2D does not support decoding mask atlas".to_string(),
264 ))
265 }
266
267 fn set_class_colors(&mut self, _: &[[u8; 4]]) -> Result<()> {
268 Err(Error::NotImplemented(
269 "G2D does not support setting colors for rendering detection or segmentation overlays"
270 .to_string(),
271 ))
272 }
273}
274
275fn tensor_to_g2d_surface(img: &Tensor<u8>) -> Result<G2DSurface> {
279 let fmt = img.format().ok_or(Error::NotAnImage)?;
280 let dma = img
281 .as_dma()
282 .ok_or_else(|| Error::NotImplemented("g2d only supports Dma memory".to_string()))?;
283 let phys: G2DPhysical = dma.fd.as_raw_fd().try_into()?;
284
285 let base_addr = phys.address();
288 let planes = if fmt == PixelFormat::Nv12 {
289 if img.is_multiplane() {
290 let chroma = img.chroma().unwrap();
292 let chroma_dma = chroma.as_dma().ok_or_else(|| {
293 Error::NotImplemented("g2d multiplane chroma must be DMA-backed".to_string())
294 })?;
295 let uv_phys: G2DPhysical = chroma_dma.fd.as_raw_fd().try_into()?;
296 [base_addr, uv_phys.address(), 0]
297 } else {
298 let w = img.width().unwrap();
299 let h = img.height().unwrap();
300 let stride = img.effective_row_stride().unwrap_or(w);
301 let offset = img.plane_offset().unwrap_or(0);
302 let uv_offset = (offset + stride * h) as u64;
303 [base_addr, base_addr + uv_offset, 0]
304 }
305 } else {
306 [base_addr, 0, 0]
307 };
308
309 let w = img.width().unwrap();
310 let h = img.height().unwrap();
311 let fourcc = pixelfmt_to_fourcc(fmt);
312
313 Ok(G2DSurface {
314 planes,
315 format: G2DFormat::try_from(fourcc)?.format(),
316 left: 0,
317 top: 0,
318 right: w as i32,
319 bottom: h as i32,
320 stride: w as i32,
321 width: w as i32,
322 height: h as i32,
323 blendfunc: 0,
324 clrcolor: 0,
325 rot: 0,
326 global_alpha: 0,
327 })
328}
329
330#[cfg(feature = "g2d_test_formats")]
331#[cfg(test)]
332mod g2d_tests {
333 use super::*;
334 use crate::{CPUProcessor, Flip, G2DProcessor, ImageProcessorTrait, Rect, Rotation};
335 use edgefirst_tensor::{
336 is_dma_available, DType, PixelFormat, TensorDyn, TensorMapTrait, TensorMemory, TensorTrait,
337 };
338 use image::buffer::ConvertBuffer;
339
340 #[test]
341 #[cfg(target_os = "linux")]
342 fn test_g2d_formats_no_resize() {
343 for i in [
344 PixelFormat::Rgba,
345 PixelFormat::Yuyv,
346 PixelFormat::Rgb,
347 PixelFormat::Grey,
348 PixelFormat::Nv12,
349 ] {
350 for o in [
351 PixelFormat::Rgba,
352 PixelFormat::Yuyv,
353 PixelFormat::Rgb,
354 PixelFormat::Grey,
355 ] {
356 let res = test_g2d_format_no_resize_(i, o);
357 if let Err(e) = res {
358 println!("{i} to {o} failed: {e:?}");
359 } else {
360 println!("{i} to {o} success");
361 }
362 }
363 }
364 }
365
366 fn test_g2d_format_no_resize_(
367 g2d_in_fmt: PixelFormat,
368 g2d_out_fmt: PixelFormat,
369 ) -> Result<(), crate::Error> {
370 let dst_width = 1280;
371 let dst_height = 720;
372 let file = include_bytes!(concat!(
373 env!("CARGO_MANIFEST_DIR"),
374 "/../../testdata/zidane.jpg"
375 ))
376 .to_vec();
377 let src = crate::load_image(&file, Some(PixelFormat::Rgb), None)?;
378
379 let mut src2 = TensorDyn::image(1280, 720, g2d_in_fmt, DType::U8, Some(TensorMemory::Dma))?;
381
382 let mut cpu_converter = CPUProcessor::new();
383
384 if g2d_in_fmt == PixelFormat::Nv12 {
386 let nv12_bytes = include_bytes!(concat!(
387 env!("CARGO_MANIFEST_DIR"),
388 "/../../testdata/zidane.nv12"
389 ));
390 src2.as_u8()
391 .unwrap()
392 .map()?
393 .as_mut_slice()
394 .copy_from_slice(nv12_bytes);
395 } else {
396 cpu_converter.convert(&src, &mut src2, Rotation::None, Flip::None, Crop::no_crop())?;
397 }
398
399 let mut g2d_dst = TensorDyn::image(
400 dst_width,
401 dst_height,
402 g2d_out_fmt,
403 DType::U8,
404 Some(TensorMemory::Dma),
405 )?;
406 let mut g2d_converter = G2DProcessor::new()?;
407 let src2_dyn = src2;
408 let mut g2d_dst_dyn = g2d_dst;
409 g2d_converter.convert(
410 &src2_dyn,
411 &mut g2d_dst_dyn,
412 Rotation::None,
413 Flip::None,
414 Crop::no_crop(),
415 )?;
416 g2d_dst = {
417 let mut __t = g2d_dst_dyn.into_u8().unwrap();
418 __t.set_format(g2d_out_fmt)
419 .map_err(|e| crate::Error::Internal(e.to_string()))?;
420 TensorDyn::from(__t)
421 };
422
423 let mut cpu_dst =
424 TensorDyn::image(dst_width, dst_height, PixelFormat::Rgb, DType::U8, None)?;
425 cpu_converter.convert(
426 &g2d_dst,
427 &mut cpu_dst,
428 Rotation::None,
429 Flip::None,
430 Crop::no_crop(),
431 )?;
432
433 compare_images(
434 &src,
435 &cpu_dst,
436 0.98,
437 &format!("{g2d_in_fmt}_to_{g2d_out_fmt}"),
438 )
439 }
440
441 #[test]
442 #[cfg(target_os = "linux")]
443 fn test_g2d_formats_with_resize() {
444 for i in [
445 PixelFormat::Rgba,
446 PixelFormat::Yuyv,
447 PixelFormat::Rgb,
448 PixelFormat::Grey,
449 PixelFormat::Nv12,
450 ] {
451 for o in [
452 PixelFormat::Rgba,
453 PixelFormat::Yuyv,
454 PixelFormat::Rgb,
455 PixelFormat::Grey,
456 ] {
457 let res = test_g2d_format_with_resize_(i, o);
458 if let Err(e) = res {
459 println!("{i} to {o} failed: {e:?}");
460 } else {
461 println!("{i} to {o} success");
462 }
463 }
464 }
465 }
466
467 #[test]
468 #[cfg(target_os = "linux")]
469 fn test_g2d_formats_with_resize_dst_crop() {
470 for i in [
471 PixelFormat::Rgba,
472 PixelFormat::Yuyv,
473 PixelFormat::Rgb,
474 PixelFormat::Grey,
475 PixelFormat::Nv12,
476 ] {
477 for o in [
478 PixelFormat::Rgba,
479 PixelFormat::Yuyv,
480 PixelFormat::Rgb,
481 PixelFormat::Grey,
482 ] {
483 let res = test_g2d_format_with_resize_dst_crop(i, o);
484 if let Err(e) = res {
485 println!("{i} to {o} failed: {e:?}");
486 } else {
487 println!("{i} to {o} success");
488 }
489 }
490 }
491 }
492
493 fn test_g2d_format_with_resize_(
494 g2d_in_fmt: PixelFormat,
495 g2d_out_fmt: PixelFormat,
496 ) -> Result<(), crate::Error> {
497 let dst_width = 600;
498 let dst_height = 400;
499 let file = include_bytes!(concat!(
500 env!("CARGO_MANIFEST_DIR"),
501 "/../../testdata/zidane.jpg"
502 ))
503 .to_vec();
504 let src = crate::load_image(&file, Some(PixelFormat::Rgb), None)?;
505
506 let mut cpu_converter = CPUProcessor::new();
507
508 let mut reference = TensorDyn::image(
509 dst_width,
510 dst_height,
511 PixelFormat::Rgb,
512 DType::U8,
513 Some(TensorMemory::Dma),
514 )?;
515 cpu_converter.convert(
516 &src,
517 &mut reference,
518 Rotation::None,
519 Flip::None,
520 Crop::no_crop(),
521 )?;
522
523 let mut src2 = TensorDyn::image(1280, 720, g2d_in_fmt, DType::U8, Some(TensorMemory::Dma))?;
525
526 if g2d_in_fmt == PixelFormat::Nv12 {
528 let nv12_bytes = include_bytes!(concat!(
529 env!("CARGO_MANIFEST_DIR"),
530 "/../../testdata/zidane.nv12"
531 ));
532 src2.as_u8()
533 .unwrap()
534 .map()?
535 .as_mut_slice()
536 .copy_from_slice(nv12_bytes);
537 } else {
538 cpu_converter.convert(&src, &mut src2, Rotation::None, Flip::None, Crop::no_crop())?;
539 }
540
541 let mut g2d_dst = TensorDyn::image(
542 dst_width,
543 dst_height,
544 g2d_out_fmt,
545 DType::U8,
546 Some(TensorMemory::Dma),
547 )?;
548 let mut g2d_converter = G2DProcessor::new()?;
549 let src2_dyn = src2;
550 let mut g2d_dst_dyn = g2d_dst;
551 g2d_converter.convert(
552 &src2_dyn,
553 &mut g2d_dst_dyn,
554 Rotation::None,
555 Flip::None,
556 Crop::no_crop(),
557 )?;
558 g2d_dst = {
559 let mut __t = g2d_dst_dyn.into_u8().unwrap();
560 __t.set_format(g2d_out_fmt)
561 .map_err(|e| crate::Error::Internal(e.to_string()))?;
562 TensorDyn::from(__t)
563 };
564
565 let mut cpu_dst =
566 TensorDyn::image(dst_width, dst_height, PixelFormat::Rgb, DType::U8, None)?;
567 cpu_converter.convert(
568 &g2d_dst,
569 &mut cpu_dst,
570 Rotation::None,
571 Flip::None,
572 Crop::no_crop(),
573 )?;
574
575 compare_images(
576 &reference,
577 &cpu_dst,
578 0.98,
579 &format!("{g2d_in_fmt}_to_{g2d_out_fmt}_resized"),
580 )
581 }
582
583 fn test_g2d_format_with_resize_dst_crop(
584 g2d_in_fmt: PixelFormat,
585 g2d_out_fmt: PixelFormat,
586 ) -> Result<(), crate::Error> {
587 let dst_width = 600;
588 let dst_height = 400;
589 let crop = Crop {
590 src_rect: None,
591 dst_rect: Some(Rect {
592 top: 100,
593 left: 100,
594 height: 100,
595 width: 200,
596 }),
597 dst_color: None,
598 };
599 let file = include_bytes!(concat!(
600 env!("CARGO_MANIFEST_DIR"),
601 "/../../testdata/zidane.jpg"
602 ))
603 .to_vec();
604 let src = crate::load_image(&file, Some(PixelFormat::Rgb), None)?;
605
606 let mut cpu_converter = CPUProcessor::new();
607
608 let mut reference = TensorDyn::image(
609 dst_width,
610 dst_height,
611 PixelFormat::Rgb,
612 DType::U8,
613 Some(TensorMemory::Dma),
614 )?;
615 reference
616 .as_u8()
617 .unwrap()
618 .map()
619 .unwrap()
620 .as_mut_slice()
621 .fill(128);
622 cpu_converter.convert(&src, &mut reference, Rotation::None, Flip::None, crop)?;
623
624 let mut src2 = TensorDyn::image(1280, 720, g2d_in_fmt, DType::U8, Some(TensorMemory::Dma))?;
626
627 if g2d_in_fmt == PixelFormat::Nv12 {
629 let nv12_bytes = include_bytes!(concat!(
630 env!("CARGO_MANIFEST_DIR"),
631 "/../../testdata/zidane.nv12"
632 ));
633 src2.as_u8()
634 .unwrap()
635 .map()?
636 .as_mut_slice()
637 .copy_from_slice(nv12_bytes);
638 } else {
639 cpu_converter.convert(&src, &mut src2, Rotation::None, Flip::None, Crop::no_crop())?;
640 }
641
642 let mut g2d_dst = TensorDyn::image(
643 dst_width,
644 dst_height,
645 g2d_out_fmt,
646 DType::U8,
647 Some(TensorMemory::Dma),
648 )?;
649 g2d_dst
650 .as_u8()
651 .unwrap()
652 .map()
653 .unwrap()
654 .as_mut_slice()
655 .fill(128);
656 let mut g2d_converter = G2DProcessor::new()?;
657 let src2_dyn = src2;
658 let mut g2d_dst_dyn = g2d_dst;
659 g2d_converter.convert(
660 &src2_dyn,
661 &mut g2d_dst_dyn,
662 Rotation::None,
663 Flip::None,
664 crop,
665 )?;
666 g2d_dst = {
667 let mut __t = g2d_dst_dyn.into_u8().unwrap();
668 __t.set_format(g2d_out_fmt)
669 .map_err(|e| crate::Error::Internal(e.to_string()))?;
670 TensorDyn::from(__t)
671 };
672
673 let mut cpu_dst =
674 TensorDyn::image(dst_width, dst_height, PixelFormat::Rgb, DType::U8, None)?;
675 cpu_converter.convert(
676 &g2d_dst,
677 &mut cpu_dst,
678 Rotation::None,
679 Flip::None,
680 Crop::no_crop(),
681 )?;
682
683 compare_images(
684 &reference,
685 &cpu_dst,
686 0.98,
687 &format!("{g2d_in_fmt}_to_{g2d_out_fmt}_resized_dst_crop"),
688 )
689 }
690
691 fn compare_images(
692 img1: &TensorDyn,
693 img2: &TensorDyn,
694 threshold: f64,
695 name: &str,
696 ) -> Result<(), crate::Error> {
697 assert_eq!(img1.height(), img2.height(), "Heights differ");
698 assert_eq!(img1.width(), img2.width(), "Widths differ");
699 assert_eq!(
700 img1.format().unwrap(),
701 img2.format().unwrap(),
702 "PixelFormat differ"
703 );
704 assert!(
705 matches!(img1.format().unwrap(), PixelFormat::Rgb | PixelFormat::Rgba),
706 "format must be Rgb or Rgba for comparison"
707 );
708 let image1 = match img1.format().unwrap() {
709 PixelFormat::Rgb => image::RgbImage::from_vec(
710 img1.width().unwrap() as u32,
711 img1.height().unwrap() as u32,
712 img1.as_u8().unwrap().map().unwrap().to_vec(),
713 )
714 .unwrap(),
715 PixelFormat::Rgba => image::RgbaImage::from_vec(
716 img1.width().unwrap() as u32,
717 img1.height().unwrap() as u32,
718 img1.as_u8().unwrap().map().unwrap().to_vec(),
719 )
720 .unwrap()
721 .convert(),
722
723 _ => unreachable!(),
724 };
725
726 let image2 = match img2.format().unwrap() {
727 PixelFormat::Rgb => image::RgbImage::from_vec(
728 img2.width().unwrap() as u32,
729 img2.height().unwrap() as u32,
730 img2.as_u8().unwrap().map().unwrap().to_vec(),
731 )
732 .unwrap(),
733 PixelFormat::Rgba => image::RgbaImage::from_vec(
734 img2.width().unwrap() as u32,
735 img2.height().unwrap() as u32,
736 img2.as_u8().unwrap().map().unwrap().to_vec(),
737 )
738 .unwrap()
739 .convert(),
740
741 _ => unreachable!(),
742 };
743
744 let similarity = image_compare::rgb_similarity_structure(
745 &image_compare::Algorithm::RootMeanSquared,
746 &image1,
747 &image2,
748 )
749 .expect("Image Comparison failed");
750
751 if similarity.score < threshold {
752 image1.save(format!("{name}_1.png")).unwrap();
753 image2.save(format!("{name}_2.png")).unwrap();
754 return Err(Error::Internal(format!(
755 "{name}: converted image and target image have similarity score too low: {} < {}",
756 similarity.score, threshold
757 )));
758 }
759 Ok(())
760 }
761
762 fn load_raw_image(
768 width: usize,
769 height: usize,
770 format: PixelFormat,
771 memory: Option<TensorMemory>,
772 bytes: &[u8],
773 ) -> Result<TensorDyn, crate::Error> {
774 let img = TensorDyn::image(width, height, format, DType::U8, memory)?;
775 let mut map = img.as_u8().unwrap().map()?;
776 map.as_mut_slice()[..bytes.len()].copy_from_slice(bytes);
777 Ok(img)
778 }
779
780 #[test]
782 #[cfg(target_os = "linux")]
783 fn test_g2d_nv12_to_rgba_reference() -> Result<(), crate::Error> {
784 if !is_dma_available() {
785 return Ok(());
786 }
787 let src = load_raw_image(
789 1280,
790 720,
791 PixelFormat::Nv12,
792 Some(TensorMemory::Dma),
793 include_bytes!(concat!(
794 env!("CARGO_MANIFEST_DIR"),
795 "/../../testdata/camera720p.nv12"
796 )),
797 )?;
798
799 let reference = load_raw_image(
801 1280,
802 720,
803 PixelFormat::Rgba,
804 None,
805 include_bytes!(concat!(
806 env!("CARGO_MANIFEST_DIR"),
807 "/../../testdata/camera720p.rgba"
808 )),
809 )?;
810
811 let mut dst = TensorDyn::image(
813 1280,
814 720,
815 PixelFormat::Rgba,
816 DType::U8,
817 Some(TensorMemory::Dma),
818 )?;
819 let mut g2d = G2DProcessor::new()?;
820 let src_dyn = src;
821 let mut dst_dyn = dst;
822 g2d.convert(
823 &src_dyn,
824 &mut dst_dyn,
825 Rotation::None,
826 Flip::None,
827 Crop::no_crop(),
828 )?;
829 dst = {
830 let mut __t = dst_dyn.into_u8().unwrap();
831 __t.set_format(PixelFormat::Rgba)
832 .map_err(|e| crate::Error::Internal(e.to_string()))?;
833 TensorDyn::from(__t)
834 };
835
836 let cpu_dst = TensorDyn::image(1280, 720, PixelFormat::Rgba, DType::U8, None)?;
838 cpu_dst
839 .as_u8()
840 .unwrap()
841 .map()?
842 .as_mut_slice()
843 .copy_from_slice(dst.as_u8().unwrap().map()?.as_slice());
844
845 compare_images(&reference, &cpu_dst, 0.98, "g2d_nv12_to_rgba_reference")
846 }
847
848 #[test]
850 #[cfg(target_os = "linux")]
851 fn test_g2d_nv12_to_rgb_reference() -> Result<(), crate::Error> {
852 if !is_dma_available() {
853 return Ok(());
854 }
855 let src = load_raw_image(
857 1280,
858 720,
859 PixelFormat::Nv12,
860 Some(TensorMemory::Dma),
861 include_bytes!(concat!(
862 env!("CARGO_MANIFEST_DIR"),
863 "/../../testdata/camera720p.nv12"
864 )),
865 )?;
866
867 let reference = load_raw_image(
869 1280,
870 720,
871 PixelFormat::Rgb,
872 None,
873 include_bytes!(concat!(
874 env!("CARGO_MANIFEST_DIR"),
875 "/../../testdata/camera720p.rgb"
876 )),
877 )?;
878
879 let mut dst = TensorDyn::image(
881 1280,
882 720,
883 PixelFormat::Rgb,
884 DType::U8,
885 Some(TensorMemory::Dma),
886 )?;
887 let mut g2d = G2DProcessor::new()?;
888 let src_dyn = src;
889 let mut dst_dyn = dst;
890 g2d.convert(
891 &src_dyn,
892 &mut dst_dyn,
893 Rotation::None,
894 Flip::None,
895 Crop::no_crop(),
896 )?;
897 dst = {
898 let mut __t = dst_dyn.into_u8().unwrap();
899 __t.set_format(PixelFormat::Rgb)
900 .map_err(|e| crate::Error::Internal(e.to_string()))?;
901 TensorDyn::from(__t)
902 };
903
904 let cpu_dst = TensorDyn::image(1280, 720, PixelFormat::Rgb, DType::U8, None)?;
906 cpu_dst
907 .as_u8()
908 .unwrap()
909 .map()?
910 .as_mut_slice()
911 .copy_from_slice(dst.as_u8().unwrap().map()?.as_slice());
912
913 compare_images(&reference, &cpu_dst, 0.98, "g2d_nv12_to_rgb_reference")
914 }
915
916 #[test]
918 #[cfg(target_os = "linux")]
919 fn test_g2d_yuyv_to_rgba_reference() -> Result<(), crate::Error> {
920 if !is_dma_available() {
921 return Ok(());
922 }
923 let src = load_raw_image(
925 1280,
926 720,
927 PixelFormat::Yuyv,
928 Some(TensorMemory::Dma),
929 include_bytes!(concat!(
930 env!("CARGO_MANIFEST_DIR"),
931 "/../../testdata/camera720p.yuyv"
932 )),
933 )?;
934
935 let reference = load_raw_image(
937 1280,
938 720,
939 PixelFormat::Rgba,
940 None,
941 include_bytes!(concat!(
942 env!("CARGO_MANIFEST_DIR"),
943 "/../../testdata/camera720p.rgba"
944 )),
945 )?;
946
947 let mut dst = TensorDyn::image(
949 1280,
950 720,
951 PixelFormat::Rgba,
952 DType::U8,
953 Some(TensorMemory::Dma),
954 )?;
955 let mut g2d = G2DProcessor::new()?;
956 let src_dyn = src;
957 let mut dst_dyn = dst;
958 g2d.convert(
959 &src_dyn,
960 &mut dst_dyn,
961 Rotation::None,
962 Flip::None,
963 Crop::no_crop(),
964 )?;
965 dst = {
966 let mut __t = dst_dyn.into_u8().unwrap();
967 __t.set_format(PixelFormat::Rgba)
968 .map_err(|e| crate::Error::Internal(e.to_string()))?;
969 TensorDyn::from(__t)
970 };
971
972 let cpu_dst = TensorDyn::image(1280, 720, PixelFormat::Rgba, DType::U8, None)?;
974 cpu_dst
975 .as_u8()
976 .unwrap()
977 .map()?
978 .as_mut_slice()
979 .copy_from_slice(dst.as_u8().unwrap().map()?.as_slice());
980
981 compare_images(&reference, &cpu_dst, 0.98, "g2d_yuyv_to_rgba_reference")
982 }
983
984 #[test]
986 #[cfg(target_os = "linux")]
987 fn test_g2d_yuyv_to_rgb_reference() -> Result<(), crate::Error> {
988 if !is_dma_available() {
989 return Ok(());
990 }
991 let src = load_raw_image(
993 1280,
994 720,
995 PixelFormat::Yuyv,
996 Some(TensorMemory::Dma),
997 include_bytes!(concat!(
998 env!("CARGO_MANIFEST_DIR"),
999 "/../../testdata/camera720p.yuyv"
1000 )),
1001 )?;
1002
1003 let reference = load_raw_image(
1005 1280,
1006 720,
1007 PixelFormat::Rgb,
1008 None,
1009 include_bytes!(concat!(
1010 env!("CARGO_MANIFEST_DIR"),
1011 "/../../testdata/camera720p.rgb"
1012 )),
1013 )?;
1014
1015 let mut dst = TensorDyn::image(
1017 1280,
1018 720,
1019 PixelFormat::Rgb,
1020 DType::U8,
1021 Some(TensorMemory::Dma),
1022 )?;
1023 let mut g2d = G2DProcessor::new()?;
1024 let src_dyn = src;
1025 let mut dst_dyn = dst;
1026 g2d.convert(
1027 &src_dyn,
1028 &mut dst_dyn,
1029 Rotation::None,
1030 Flip::None,
1031 Crop::no_crop(),
1032 )?;
1033 dst = {
1034 let mut __t = dst_dyn.into_u8().unwrap();
1035 __t.set_format(PixelFormat::Rgb)
1036 .map_err(|e| crate::Error::Internal(e.to_string()))?;
1037 TensorDyn::from(__t)
1038 };
1039
1040 let cpu_dst = TensorDyn::image(1280, 720, PixelFormat::Rgb, DType::U8, None)?;
1042 cpu_dst
1043 .as_u8()
1044 .unwrap()
1045 .map()?
1046 .as_mut_slice()
1047 .copy_from_slice(dst.as_u8().unwrap().map()?.as_slice());
1048
1049 compare_images(&reference, &cpu_dst, 0.98, "g2d_yuyv_to_rgb_reference")
1050 }
1051
1052 #[test]
1055 #[cfg(target_os = "linux")]
1056 #[ignore = "G2D on i.MX 8MP rejects BGRA as destination format; re-enable when supported"]
1057 fn test_g2d_bgra_no_resize() {
1058 for src_fmt in [
1059 PixelFormat::Rgba,
1060 PixelFormat::Yuyv,
1061 PixelFormat::Nv12,
1062 PixelFormat::Bgra,
1063 ] {
1064 test_g2d_bgra_no_resize_(src_fmt).unwrap_or_else(|e| {
1065 panic!("{src_fmt} to PixelFormat::Bgra failed: {e:?}");
1066 });
1067 }
1068 }
1069
1070 fn test_g2d_bgra_no_resize_(g2d_in_fmt: PixelFormat) -> Result<(), crate::Error> {
1071 let file = include_bytes!(concat!(
1072 env!("CARGO_MANIFEST_DIR"),
1073 "/../../testdata/zidane.jpg"
1074 ))
1075 .to_vec();
1076 let src = crate::load_image(&file, Some(PixelFormat::Rgb), None)?;
1077
1078 let mut src2 = TensorDyn::image(1280, 720, g2d_in_fmt, DType::U8, Some(TensorMemory::Dma))?;
1080 let mut cpu_converter = CPUProcessor::new();
1081
1082 if g2d_in_fmt == PixelFormat::Nv12 {
1083 let nv12_bytes = include_bytes!(concat!(
1084 env!("CARGO_MANIFEST_DIR"),
1085 "/../../testdata/zidane.nv12"
1086 ));
1087 src2.as_u8()
1088 .unwrap()
1089 .map()?
1090 .as_mut_slice()
1091 .copy_from_slice(nv12_bytes);
1092 } else {
1093 cpu_converter.convert(&src, &mut src2, Rotation::None, Flip::None, Crop::no_crop())?;
1094 }
1095
1096 let mut g2d = G2DProcessor::new()?;
1097
1098 let mut bgra_dst = TensorDyn::image(
1100 1280,
1101 720,
1102 PixelFormat::Bgra,
1103 DType::U8,
1104 Some(TensorMemory::Dma),
1105 )?;
1106 let src2_dyn = src2;
1107 let mut bgra_dst_dyn = bgra_dst;
1108 g2d.convert(
1109 &src2_dyn,
1110 &mut bgra_dst_dyn,
1111 Rotation::None,
1112 Flip::None,
1113 Crop::no_crop(),
1114 )?;
1115 bgra_dst = {
1116 let mut __t = bgra_dst_dyn.into_u8().unwrap();
1117 __t.set_format(PixelFormat::Bgra)
1118 .map_err(|e| crate::Error::Internal(e.to_string()))?;
1119 TensorDyn::from(__t)
1120 };
1121
1122 let src2 = {
1124 let mut __t = src2_dyn.into_u8().unwrap();
1125 __t.set_format(g2d_in_fmt)
1126 .map_err(|e| crate::Error::Internal(e.to_string()))?;
1127 TensorDyn::from(__t)
1128 };
1129
1130 let mut rgba_dst = TensorDyn::image(
1132 1280,
1133 720,
1134 PixelFormat::Rgba,
1135 DType::U8,
1136 Some(TensorMemory::Dma),
1137 )?;
1138 let src2_dyn2 = src2;
1139 let mut rgba_dst_dyn = rgba_dst;
1140 g2d.convert(
1141 &src2_dyn2,
1142 &mut rgba_dst_dyn,
1143 Rotation::None,
1144 Flip::None,
1145 Crop::no_crop(),
1146 )?;
1147 rgba_dst = {
1148 let mut __t = rgba_dst_dyn.into_u8().unwrap();
1149 __t.set_format(PixelFormat::Rgba)
1150 .map_err(|e| crate::Error::Internal(e.to_string()))?;
1151 TensorDyn::from(__t)
1152 };
1153
1154 let bgra_cpu = TensorDyn::image(1280, 720, PixelFormat::Bgra, DType::U8, None)?;
1156 bgra_cpu
1157 .as_u8()
1158 .unwrap()
1159 .map()?
1160 .as_mut_slice()
1161 .copy_from_slice(bgra_dst.as_u8().unwrap().map()?.as_slice());
1162
1163 let rgba_cpu = TensorDyn::image(1280, 720, PixelFormat::Rgba, DType::U8, None)?;
1164 rgba_cpu
1165 .as_u8()
1166 .unwrap()
1167 .map()?
1168 .as_mut_slice()
1169 .copy_from_slice(rgba_dst.as_u8().unwrap().map()?.as_slice());
1170
1171 let bgra_map = bgra_cpu.as_u8().unwrap().map()?;
1173 let rgba_map = rgba_cpu.as_u8().unwrap().map()?;
1174 let bgra_buf = bgra_map.as_slice();
1175 let rgba_buf = rgba_map.as_slice();
1176
1177 assert_eq!(bgra_buf.len(), rgba_buf.len());
1178 for (i, (bc, rc)) in bgra_buf
1179 .chunks_exact(4)
1180 .zip(rgba_buf.chunks_exact(4))
1181 .enumerate()
1182 {
1183 assert_eq!(
1184 bc[0], rc[2],
1185 "{g2d_in_fmt} to PixelFormat::Bgra: pixel {i} B mismatch",
1186 );
1187 assert_eq!(
1188 bc[1], rc[1],
1189 "{g2d_in_fmt} to PixelFormat::Bgra: pixel {i} G mismatch",
1190 );
1191 assert_eq!(
1192 bc[2], rc[0],
1193 "{g2d_in_fmt} to PixelFormat::Bgra: pixel {i} R mismatch",
1194 );
1195 assert_eq!(
1196 bc[3], rc[3],
1197 "{g2d_in_fmt} to PixelFormat::Bgra: pixel {i} A mismatch",
1198 );
1199 }
1200 Ok(())
1201 }
1202}