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 uv_offset = (w * h) as u64;
301 [base_addr, base_addr + uv_offset, 0]
302 }
303 } else {
304 [base_addr, 0, 0]
305 };
306
307 let w = img.width().unwrap();
308 let h = img.height().unwrap();
309 let fourcc = pixelfmt_to_fourcc(fmt);
310
311 Ok(G2DSurface {
312 planes,
313 format: G2DFormat::try_from(fourcc)?.format(),
314 left: 0,
315 top: 0,
316 right: w as i32,
317 bottom: h as i32,
318 stride: w as i32,
319 width: w as i32,
320 height: h as i32,
321 blendfunc: 0,
322 clrcolor: 0,
323 rot: 0,
324 global_alpha: 0,
325 })
326}
327
328#[cfg(feature = "g2d_test_formats")]
329#[cfg(test)]
330mod g2d_tests {
331 use super::*;
332 use crate::{CPUProcessor, Flip, G2DProcessor, ImageProcessorTrait, Rect, Rotation};
333 use edgefirst_tensor::{
334 is_dma_available, DType, PixelFormat, TensorDyn, TensorMapTrait, TensorMemory, TensorTrait,
335 };
336 use image::buffer::ConvertBuffer;
337
338 #[test]
339 #[cfg(target_os = "linux")]
340 fn test_g2d_formats_no_resize() {
341 for i in [
342 PixelFormat::Rgba,
343 PixelFormat::Yuyv,
344 PixelFormat::Rgb,
345 PixelFormat::Grey,
346 PixelFormat::Nv12,
347 ] {
348 for o in [
349 PixelFormat::Rgba,
350 PixelFormat::Yuyv,
351 PixelFormat::Rgb,
352 PixelFormat::Grey,
353 ] {
354 let res = test_g2d_format_no_resize_(i, o);
355 if let Err(e) = res {
356 println!("{i} to {o} failed: {e:?}");
357 } else {
358 println!("{i} to {o} success");
359 }
360 }
361 }
362 }
363
364 fn test_g2d_format_no_resize_(
365 g2d_in_fmt: PixelFormat,
366 g2d_out_fmt: PixelFormat,
367 ) -> Result<(), crate::Error> {
368 let dst_width = 1280;
369 let dst_height = 720;
370 let file = include_bytes!(concat!(
371 env!("CARGO_MANIFEST_DIR"),
372 "/../../testdata/zidane.jpg"
373 ))
374 .to_vec();
375 let src = crate::load_image(&file, Some(PixelFormat::Rgb), None)?;
376
377 let mut src2 = TensorDyn::image(1280, 720, g2d_in_fmt, DType::U8, Some(TensorMemory::Dma))?;
379
380 let mut cpu_converter = CPUProcessor::new();
381
382 if g2d_in_fmt == PixelFormat::Nv12 {
384 let nv12_bytes = include_bytes!(concat!(
385 env!("CARGO_MANIFEST_DIR"),
386 "/../../testdata/zidane.nv12"
387 ));
388 src2.as_u8()
389 .unwrap()
390 .map()?
391 .as_mut_slice()
392 .copy_from_slice(nv12_bytes);
393 } else {
394 cpu_converter.convert(&src, &mut src2, Rotation::None, Flip::None, Crop::no_crop())?;
395 }
396
397 let mut g2d_dst = TensorDyn::image(
398 dst_width,
399 dst_height,
400 g2d_out_fmt,
401 DType::U8,
402 Some(TensorMemory::Dma),
403 )?;
404 let mut g2d_converter = G2DProcessor::new()?;
405 let src2_dyn = src2;
406 let mut g2d_dst_dyn = g2d_dst;
407 g2d_converter.convert(
408 &src2_dyn,
409 &mut g2d_dst_dyn,
410 Rotation::None,
411 Flip::None,
412 Crop::no_crop(),
413 )?;
414 g2d_dst = {
415 let mut __t = g2d_dst_dyn.into_u8().unwrap();
416 __t.set_format(g2d_out_fmt)
417 .map_err(|e| crate::Error::Internal(e.to_string()))?;
418 TensorDyn::from(__t)
419 };
420
421 let mut cpu_dst =
422 TensorDyn::image(dst_width, dst_height, PixelFormat::Rgb, DType::U8, None)?;
423 cpu_converter.convert(
424 &g2d_dst,
425 &mut cpu_dst,
426 Rotation::None,
427 Flip::None,
428 Crop::no_crop(),
429 )?;
430
431 compare_images(
432 &src,
433 &cpu_dst,
434 0.98,
435 &format!("{g2d_in_fmt}_to_{g2d_out_fmt}"),
436 )
437 }
438
439 #[test]
440 #[cfg(target_os = "linux")]
441 fn test_g2d_formats_with_resize() {
442 for i in [
443 PixelFormat::Rgba,
444 PixelFormat::Yuyv,
445 PixelFormat::Rgb,
446 PixelFormat::Grey,
447 PixelFormat::Nv12,
448 ] {
449 for o in [
450 PixelFormat::Rgba,
451 PixelFormat::Yuyv,
452 PixelFormat::Rgb,
453 PixelFormat::Grey,
454 ] {
455 let res = test_g2d_format_with_resize_(i, o);
456 if let Err(e) = res {
457 println!("{i} to {o} failed: {e:?}");
458 } else {
459 println!("{i} to {o} success");
460 }
461 }
462 }
463 }
464
465 #[test]
466 #[cfg(target_os = "linux")]
467 fn test_g2d_formats_with_resize_dst_crop() {
468 for i in [
469 PixelFormat::Rgba,
470 PixelFormat::Yuyv,
471 PixelFormat::Rgb,
472 PixelFormat::Grey,
473 PixelFormat::Nv12,
474 ] {
475 for o in [
476 PixelFormat::Rgba,
477 PixelFormat::Yuyv,
478 PixelFormat::Rgb,
479 PixelFormat::Grey,
480 ] {
481 let res = test_g2d_format_with_resize_dst_crop(i, o);
482 if let Err(e) = res {
483 println!("{i} to {o} failed: {e:?}");
484 } else {
485 println!("{i} to {o} success");
486 }
487 }
488 }
489 }
490
491 fn test_g2d_format_with_resize_(
492 g2d_in_fmt: PixelFormat,
493 g2d_out_fmt: PixelFormat,
494 ) -> Result<(), crate::Error> {
495 let dst_width = 600;
496 let dst_height = 400;
497 let file = include_bytes!(concat!(
498 env!("CARGO_MANIFEST_DIR"),
499 "/../../testdata/zidane.jpg"
500 ))
501 .to_vec();
502 let src = crate::load_image(&file, Some(PixelFormat::Rgb), None)?;
503
504 let mut cpu_converter = CPUProcessor::new();
505
506 let mut reference = TensorDyn::image(
507 dst_width,
508 dst_height,
509 PixelFormat::Rgb,
510 DType::U8,
511 Some(TensorMemory::Dma),
512 )?;
513 cpu_converter.convert(
514 &src,
515 &mut reference,
516 Rotation::None,
517 Flip::None,
518 Crop::no_crop(),
519 )?;
520
521 let mut src2 = TensorDyn::image(1280, 720, g2d_in_fmt, DType::U8, Some(TensorMemory::Dma))?;
523
524 if g2d_in_fmt == PixelFormat::Nv12 {
526 let nv12_bytes = include_bytes!(concat!(
527 env!("CARGO_MANIFEST_DIR"),
528 "/../../testdata/zidane.nv12"
529 ));
530 src2.as_u8()
531 .unwrap()
532 .map()?
533 .as_mut_slice()
534 .copy_from_slice(nv12_bytes);
535 } else {
536 cpu_converter.convert(&src, &mut src2, Rotation::None, Flip::None, Crop::no_crop())?;
537 }
538
539 let mut g2d_dst = TensorDyn::image(
540 dst_width,
541 dst_height,
542 g2d_out_fmt,
543 DType::U8,
544 Some(TensorMemory::Dma),
545 )?;
546 let mut g2d_converter = G2DProcessor::new()?;
547 let src2_dyn = src2;
548 let mut g2d_dst_dyn = g2d_dst;
549 g2d_converter.convert(
550 &src2_dyn,
551 &mut g2d_dst_dyn,
552 Rotation::None,
553 Flip::None,
554 Crop::no_crop(),
555 )?;
556 g2d_dst = {
557 let mut __t = g2d_dst_dyn.into_u8().unwrap();
558 __t.set_format(g2d_out_fmt)
559 .map_err(|e| crate::Error::Internal(e.to_string()))?;
560 TensorDyn::from(__t)
561 };
562
563 let mut cpu_dst =
564 TensorDyn::image(dst_width, dst_height, PixelFormat::Rgb, DType::U8, None)?;
565 cpu_converter.convert(
566 &g2d_dst,
567 &mut cpu_dst,
568 Rotation::None,
569 Flip::None,
570 Crop::no_crop(),
571 )?;
572
573 compare_images(
574 &reference,
575 &cpu_dst,
576 0.98,
577 &format!("{g2d_in_fmt}_to_{g2d_out_fmt}_resized"),
578 )
579 }
580
581 fn test_g2d_format_with_resize_dst_crop(
582 g2d_in_fmt: PixelFormat,
583 g2d_out_fmt: PixelFormat,
584 ) -> Result<(), crate::Error> {
585 let dst_width = 600;
586 let dst_height = 400;
587 let crop = Crop {
588 src_rect: None,
589 dst_rect: Some(Rect {
590 top: 100,
591 left: 100,
592 height: 100,
593 width: 200,
594 }),
595 dst_color: None,
596 };
597 let file = include_bytes!(concat!(
598 env!("CARGO_MANIFEST_DIR"),
599 "/../../testdata/zidane.jpg"
600 ))
601 .to_vec();
602 let src = crate::load_image(&file, Some(PixelFormat::Rgb), None)?;
603
604 let mut cpu_converter = CPUProcessor::new();
605
606 let mut reference = TensorDyn::image(
607 dst_width,
608 dst_height,
609 PixelFormat::Rgb,
610 DType::U8,
611 Some(TensorMemory::Dma),
612 )?;
613 reference
614 .as_u8()
615 .unwrap()
616 .map()
617 .unwrap()
618 .as_mut_slice()
619 .fill(128);
620 cpu_converter.convert(&src, &mut reference, Rotation::None, Flip::None, crop)?;
621
622 let mut src2 = TensorDyn::image(1280, 720, g2d_in_fmt, DType::U8, Some(TensorMemory::Dma))?;
624
625 if g2d_in_fmt == PixelFormat::Nv12 {
627 let nv12_bytes = include_bytes!(concat!(
628 env!("CARGO_MANIFEST_DIR"),
629 "/../../testdata/zidane.nv12"
630 ));
631 src2.as_u8()
632 .unwrap()
633 .map()?
634 .as_mut_slice()
635 .copy_from_slice(nv12_bytes);
636 } else {
637 cpu_converter.convert(&src, &mut src2, Rotation::None, Flip::None, Crop::no_crop())?;
638 }
639
640 let mut g2d_dst = TensorDyn::image(
641 dst_width,
642 dst_height,
643 g2d_out_fmt,
644 DType::U8,
645 Some(TensorMemory::Dma),
646 )?;
647 g2d_dst
648 .as_u8()
649 .unwrap()
650 .map()
651 .unwrap()
652 .as_mut_slice()
653 .fill(128);
654 let mut g2d_converter = G2DProcessor::new()?;
655 let src2_dyn = src2;
656 let mut g2d_dst_dyn = g2d_dst;
657 g2d_converter.convert(
658 &src2_dyn,
659 &mut g2d_dst_dyn,
660 Rotation::None,
661 Flip::None,
662 crop,
663 )?;
664 g2d_dst = {
665 let mut __t = g2d_dst_dyn.into_u8().unwrap();
666 __t.set_format(g2d_out_fmt)
667 .map_err(|e| crate::Error::Internal(e.to_string()))?;
668 TensorDyn::from(__t)
669 };
670
671 let mut cpu_dst =
672 TensorDyn::image(dst_width, dst_height, PixelFormat::Rgb, DType::U8, None)?;
673 cpu_converter.convert(
674 &g2d_dst,
675 &mut cpu_dst,
676 Rotation::None,
677 Flip::None,
678 Crop::no_crop(),
679 )?;
680
681 compare_images(
682 &reference,
683 &cpu_dst,
684 0.98,
685 &format!("{g2d_in_fmt}_to_{g2d_out_fmt}_resized_dst_crop"),
686 )
687 }
688
689 fn compare_images(
690 img1: &TensorDyn,
691 img2: &TensorDyn,
692 threshold: f64,
693 name: &str,
694 ) -> Result<(), crate::Error> {
695 assert_eq!(img1.height(), img2.height(), "Heights differ");
696 assert_eq!(img1.width(), img2.width(), "Widths differ");
697 assert_eq!(
698 img1.format().unwrap(),
699 img2.format().unwrap(),
700 "PixelFormat differ"
701 );
702 assert!(
703 matches!(img1.format().unwrap(), PixelFormat::Rgb | PixelFormat::Rgba),
704 "format must be Rgb or Rgba for comparison"
705 );
706 let image1 = match img1.format().unwrap() {
707 PixelFormat::Rgb => image::RgbImage::from_vec(
708 img1.width().unwrap() as u32,
709 img1.height().unwrap() as u32,
710 img1.as_u8().unwrap().map().unwrap().to_vec(),
711 )
712 .unwrap(),
713 PixelFormat::Rgba => image::RgbaImage::from_vec(
714 img1.width().unwrap() as u32,
715 img1.height().unwrap() as u32,
716 img1.as_u8().unwrap().map().unwrap().to_vec(),
717 )
718 .unwrap()
719 .convert(),
720
721 _ => unreachable!(),
722 };
723
724 let image2 = match img2.format().unwrap() {
725 PixelFormat::Rgb => image::RgbImage::from_vec(
726 img2.width().unwrap() as u32,
727 img2.height().unwrap() as u32,
728 img2.as_u8().unwrap().map().unwrap().to_vec(),
729 )
730 .unwrap(),
731 PixelFormat::Rgba => image::RgbaImage::from_vec(
732 img2.width().unwrap() as u32,
733 img2.height().unwrap() as u32,
734 img2.as_u8().unwrap().map().unwrap().to_vec(),
735 )
736 .unwrap()
737 .convert(),
738
739 _ => unreachable!(),
740 };
741
742 let similarity = image_compare::rgb_similarity_structure(
743 &image_compare::Algorithm::RootMeanSquared,
744 &image1,
745 &image2,
746 )
747 .expect("Image Comparison failed");
748
749 if similarity.score < threshold {
750 image1.save(format!("{name}_1.png")).unwrap();
751 image2.save(format!("{name}_2.png")).unwrap();
752 return Err(Error::Internal(format!(
753 "{name}: converted image and target image have similarity score too low: {} < {}",
754 similarity.score, threshold
755 )));
756 }
757 Ok(())
758 }
759
760 fn load_raw_image(
766 width: usize,
767 height: usize,
768 format: PixelFormat,
769 memory: Option<TensorMemory>,
770 bytes: &[u8],
771 ) -> Result<TensorDyn, crate::Error> {
772 let img = TensorDyn::image(width, height, format, DType::U8, memory)?;
773 let mut map = img.as_u8().unwrap().map()?;
774 map.as_mut_slice()[..bytes.len()].copy_from_slice(bytes);
775 Ok(img)
776 }
777
778 #[test]
780 #[cfg(target_os = "linux")]
781 fn test_g2d_nv12_to_rgba_reference() -> Result<(), crate::Error> {
782 if !is_dma_available() {
783 return Ok(());
784 }
785 let src = load_raw_image(
787 1280,
788 720,
789 PixelFormat::Nv12,
790 Some(TensorMemory::Dma),
791 include_bytes!(concat!(
792 env!("CARGO_MANIFEST_DIR"),
793 "/../../testdata/camera720p.nv12"
794 )),
795 )?;
796
797 let reference = load_raw_image(
799 1280,
800 720,
801 PixelFormat::Rgba,
802 None,
803 include_bytes!(concat!(
804 env!("CARGO_MANIFEST_DIR"),
805 "/../../testdata/camera720p.rgba"
806 )),
807 )?;
808
809 let mut dst = TensorDyn::image(
811 1280,
812 720,
813 PixelFormat::Rgba,
814 DType::U8,
815 Some(TensorMemory::Dma),
816 )?;
817 let mut g2d = G2DProcessor::new()?;
818 let src_dyn = src;
819 let mut dst_dyn = dst;
820 g2d.convert(
821 &src_dyn,
822 &mut dst_dyn,
823 Rotation::None,
824 Flip::None,
825 Crop::no_crop(),
826 )?;
827 dst = {
828 let mut __t = dst_dyn.into_u8().unwrap();
829 __t.set_format(PixelFormat::Rgba)
830 .map_err(|e| crate::Error::Internal(e.to_string()))?;
831 TensorDyn::from(__t)
832 };
833
834 let cpu_dst = TensorDyn::image(1280, 720, PixelFormat::Rgba, DType::U8, None)?;
836 cpu_dst
837 .as_u8()
838 .unwrap()
839 .map()?
840 .as_mut_slice()
841 .copy_from_slice(dst.as_u8().unwrap().map()?.as_slice());
842
843 compare_images(&reference, &cpu_dst, 0.98, "g2d_nv12_to_rgba_reference")
844 }
845
846 #[test]
848 #[cfg(target_os = "linux")]
849 fn test_g2d_nv12_to_rgb_reference() -> Result<(), crate::Error> {
850 if !is_dma_available() {
851 return Ok(());
852 }
853 let src = load_raw_image(
855 1280,
856 720,
857 PixelFormat::Nv12,
858 Some(TensorMemory::Dma),
859 include_bytes!(concat!(
860 env!("CARGO_MANIFEST_DIR"),
861 "/../../testdata/camera720p.nv12"
862 )),
863 )?;
864
865 let reference = load_raw_image(
867 1280,
868 720,
869 PixelFormat::Rgb,
870 None,
871 include_bytes!(concat!(
872 env!("CARGO_MANIFEST_DIR"),
873 "/../../testdata/camera720p.rgb"
874 )),
875 )?;
876
877 let mut dst = TensorDyn::image(
879 1280,
880 720,
881 PixelFormat::Rgb,
882 DType::U8,
883 Some(TensorMemory::Dma),
884 )?;
885 let mut g2d = G2DProcessor::new()?;
886 let src_dyn = src;
887 let mut dst_dyn = dst;
888 g2d.convert(
889 &src_dyn,
890 &mut dst_dyn,
891 Rotation::None,
892 Flip::None,
893 Crop::no_crop(),
894 )?;
895 dst = {
896 let mut __t = dst_dyn.into_u8().unwrap();
897 __t.set_format(PixelFormat::Rgb)
898 .map_err(|e| crate::Error::Internal(e.to_string()))?;
899 TensorDyn::from(__t)
900 };
901
902 let cpu_dst = TensorDyn::image(1280, 720, PixelFormat::Rgb, DType::U8, None)?;
904 cpu_dst
905 .as_u8()
906 .unwrap()
907 .map()?
908 .as_mut_slice()
909 .copy_from_slice(dst.as_u8().unwrap().map()?.as_slice());
910
911 compare_images(&reference, &cpu_dst, 0.98, "g2d_nv12_to_rgb_reference")
912 }
913
914 #[test]
916 #[cfg(target_os = "linux")]
917 fn test_g2d_yuyv_to_rgba_reference() -> Result<(), crate::Error> {
918 if !is_dma_available() {
919 return Ok(());
920 }
921 let src = load_raw_image(
923 1280,
924 720,
925 PixelFormat::Yuyv,
926 Some(TensorMemory::Dma),
927 include_bytes!(concat!(
928 env!("CARGO_MANIFEST_DIR"),
929 "/../../testdata/camera720p.yuyv"
930 )),
931 )?;
932
933 let reference = load_raw_image(
935 1280,
936 720,
937 PixelFormat::Rgba,
938 None,
939 include_bytes!(concat!(
940 env!("CARGO_MANIFEST_DIR"),
941 "/../../testdata/camera720p.rgba"
942 )),
943 )?;
944
945 let mut dst = TensorDyn::image(
947 1280,
948 720,
949 PixelFormat::Rgba,
950 DType::U8,
951 Some(TensorMemory::Dma),
952 )?;
953 let mut g2d = G2DProcessor::new()?;
954 let src_dyn = src;
955 let mut dst_dyn = dst;
956 g2d.convert(
957 &src_dyn,
958 &mut dst_dyn,
959 Rotation::None,
960 Flip::None,
961 Crop::no_crop(),
962 )?;
963 dst = {
964 let mut __t = dst_dyn.into_u8().unwrap();
965 __t.set_format(PixelFormat::Rgba)
966 .map_err(|e| crate::Error::Internal(e.to_string()))?;
967 TensorDyn::from(__t)
968 };
969
970 let cpu_dst = TensorDyn::image(1280, 720, PixelFormat::Rgba, DType::U8, None)?;
972 cpu_dst
973 .as_u8()
974 .unwrap()
975 .map()?
976 .as_mut_slice()
977 .copy_from_slice(dst.as_u8().unwrap().map()?.as_slice());
978
979 compare_images(&reference, &cpu_dst, 0.98, "g2d_yuyv_to_rgba_reference")
980 }
981
982 #[test]
984 #[cfg(target_os = "linux")]
985 fn test_g2d_yuyv_to_rgb_reference() -> Result<(), crate::Error> {
986 if !is_dma_available() {
987 return Ok(());
988 }
989 let src = load_raw_image(
991 1280,
992 720,
993 PixelFormat::Yuyv,
994 Some(TensorMemory::Dma),
995 include_bytes!(concat!(
996 env!("CARGO_MANIFEST_DIR"),
997 "/../../testdata/camera720p.yuyv"
998 )),
999 )?;
1000
1001 let reference = load_raw_image(
1003 1280,
1004 720,
1005 PixelFormat::Rgb,
1006 None,
1007 include_bytes!(concat!(
1008 env!("CARGO_MANIFEST_DIR"),
1009 "/../../testdata/camera720p.rgb"
1010 )),
1011 )?;
1012
1013 let mut dst = TensorDyn::image(
1015 1280,
1016 720,
1017 PixelFormat::Rgb,
1018 DType::U8,
1019 Some(TensorMemory::Dma),
1020 )?;
1021 let mut g2d = G2DProcessor::new()?;
1022 let src_dyn = src;
1023 let mut dst_dyn = dst;
1024 g2d.convert(
1025 &src_dyn,
1026 &mut dst_dyn,
1027 Rotation::None,
1028 Flip::None,
1029 Crop::no_crop(),
1030 )?;
1031 dst = {
1032 let mut __t = dst_dyn.into_u8().unwrap();
1033 __t.set_format(PixelFormat::Rgb)
1034 .map_err(|e| crate::Error::Internal(e.to_string()))?;
1035 TensorDyn::from(__t)
1036 };
1037
1038 let cpu_dst = TensorDyn::image(1280, 720, PixelFormat::Rgb, DType::U8, None)?;
1040 cpu_dst
1041 .as_u8()
1042 .unwrap()
1043 .map()?
1044 .as_mut_slice()
1045 .copy_from_slice(dst.as_u8().unwrap().map()?.as_slice());
1046
1047 compare_images(&reference, &cpu_dst, 0.98, "g2d_yuyv_to_rgb_reference")
1048 }
1049
1050 #[test]
1053 #[cfg(target_os = "linux")]
1054 fn test_g2d_bgra_no_resize() {
1055 for src_fmt in [
1056 PixelFormat::Rgba,
1057 PixelFormat::Yuyv,
1058 PixelFormat::Nv12,
1059 PixelFormat::Bgra,
1060 ] {
1061 test_g2d_bgra_no_resize_(src_fmt).unwrap_or_else(|e| {
1062 panic!("{src_fmt} to PixelFormat::Bgra failed: {e:?}");
1063 });
1064 }
1065 }
1066
1067 fn test_g2d_bgra_no_resize_(g2d_in_fmt: PixelFormat) -> Result<(), crate::Error> {
1068 let file = include_bytes!(concat!(
1069 env!("CARGO_MANIFEST_DIR"),
1070 "/../../testdata/zidane.jpg"
1071 ))
1072 .to_vec();
1073 let src = crate::load_image(&file, Some(PixelFormat::Rgb), None)?;
1074
1075 let mut src2 = TensorDyn::image(1280, 720, g2d_in_fmt, DType::U8, Some(TensorMemory::Dma))?;
1077 let mut cpu_converter = CPUProcessor::new();
1078
1079 if g2d_in_fmt == PixelFormat::Nv12 {
1080 let nv12_bytes = include_bytes!(concat!(
1081 env!("CARGO_MANIFEST_DIR"),
1082 "/../../testdata/zidane.nv12"
1083 ));
1084 src2.as_u8()
1085 .unwrap()
1086 .map()?
1087 .as_mut_slice()
1088 .copy_from_slice(nv12_bytes);
1089 } else {
1090 cpu_converter.convert(&src, &mut src2, Rotation::None, Flip::None, Crop::no_crop())?;
1091 }
1092
1093 let mut g2d = G2DProcessor::new()?;
1094
1095 let mut bgra_dst = TensorDyn::image(
1097 1280,
1098 720,
1099 PixelFormat::Bgra,
1100 DType::U8,
1101 Some(TensorMemory::Dma),
1102 )?;
1103 let src2_dyn = src2;
1104 let mut bgra_dst_dyn = bgra_dst;
1105 g2d.convert(
1106 &src2_dyn,
1107 &mut bgra_dst_dyn,
1108 Rotation::None,
1109 Flip::None,
1110 Crop::no_crop(),
1111 )?;
1112 bgra_dst = {
1113 let mut __t = bgra_dst_dyn.into_u8().unwrap();
1114 __t.set_format(PixelFormat::Bgra)
1115 .map_err(|e| crate::Error::Internal(e.to_string()))?;
1116 TensorDyn::from(__t)
1117 };
1118
1119 let src2 = {
1121 let mut __t = src2_dyn.into_u8().unwrap();
1122 __t.set_format(g2d_in_fmt)
1123 .map_err(|e| crate::Error::Internal(e.to_string()))?;
1124 TensorDyn::from(__t)
1125 };
1126
1127 let mut rgba_dst = TensorDyn::image(
1129 1280,
1130 720,
1131 PixelFormat::Rgba,
1132 DType::U8,
1133 Some(TensorMemory::Dma),
1134 )?;
1135 let src2_dyn2 = src2;
1136 let mut rgba_dst_dyn = rgba_dst;
1137 g2d.convert(
1138 &src2_dyn2,
1139 &mut rgba_dst_dyn,
1140 Rotation::None,
1141 Flip::None,
1142 Crop::no_crop(),
1143 )?;
1144 rgba_dst = {
1145 let mut __t = rgba_dst_dyn.into_u8().unwrap();
1146 __t.set_format(PixelFormat::Rgba)
1147 .map_err(|e| crate::Error::Internal(e.to_string()))?;
1148 TensorDyn::from(__t)
1149 };
1150
1151 let bgra_cpu = TensorDyn::image(1280, 720, PixelFormat::Bgra, DType::U8, None)?;
1153 bgra_cpu
1154 .as_u8()
1155 .unwrap()
1156 .map()?
1157 .as_mut_slice()
1158 .copy_from_slice(bgra_dst.as_u8().unwrap().map()?.as_slice());
1159
1160 let rgba_cpu = TensorDyn::image(1280, 720, PixelFormat::Rgba, DType::U8, None)?;
1161 rgba_cpu
1162 .as_u8()
1163 .unwrap()
1164 .map()?
1165 .as_mut_slice()
1166 .copy_from_slice(rgba_dst.as_u8().unwrap().map()?.as_slice());
1167
1168 let bgra_map = bgra_cpu.as_u8().unwrap().map()?;
1170 let rgba_map = rgba_cpu.as_u8().unwrap().map()?;
1171 let bgra_buf = bgra_map.as_slice();
1172 let rgba_buf = rgba_map.as_slice();
1173
1174 assert_eq!(bgra_buf.len(), rgba_buf.len());
1175 for (i, (bc, rc)) in bgra_buf
1176 .chunks_exact(4)
1177 .zip(rgba_buf.chunks_exact(4))
1178 .enumerate()
1179 {
1180 assert_eq!(
1181 bc[0], rc[2],
1182 "{g2d_in_fmt} to PixelFormat::Bgra: pixel {i} B mismatch",
1183 );
1184 assert_eq!(
1185 bc[1], rc[1],
1186 "{g2d_in_fmt} to PixelFormat::Bgra: pixel {i} G mismatch",
1187 );
1188 assert_eq!(
1189 bc[2], rc[0],
1190 "{g2d_in_fmt} to PixelFormat::Bgra: pixel {i} R mismatch",
1191 );
1192 assert_eq!(
1193 bc[3], rc[3],
1194 "{g2d_in_fmt} to PixelFormat::Bgra: pixel {i} A mismatch",
1195 );
1196 }
1197 Ok(())
1198 }
1199}