1use super::{
2 error::{McvError, Result},
3 MatchResult, Roi,
4};
5
6#[cfg(feature = "opencv")]
7use opencv::{core::Mat, imgproc};
8
9pub struct RgbaFrame<'a> {
15 width: u32,
16 height: u32,
17 bytes: &'a [u8],
18 #[cfg(feature = "opencv")]
19 gray: Option<Mat>,
20 #[cfg(feature = "opencv")]
21 bgr: Option<Mat>,
22 #[cfg(feature = "ocr")]
23 dynamic_image: Option<::image::DynamicImage>,
24}
25
26impl<'a> RgbaFrame<'a> {
27 pub fn new(width: u32, height: u32, bytes: &'a [u8]) -> Result<Self> {
28 if width == 0 || height == 0 {
29 return Err(McvError::InvalidImage(format!(
30 "RGBA frame dimensions must be positive, got {width}x{height}"
31 )));
32 }
33 let expected = (width as usize)
34 .checked_mul(height as usize)
35 .and_then(|value| value.checked_mul(4))
36 .ok_or_else(|| {
37 McvError::InvalidImage(format!(
38 "RGBA frame dimensions are too large: {width}x{height}"
39 ))
40 })?;
41 if bytes.len() != expected {
42 return Err(McvError::InvalidImage(format!(
43 "RGBA buffer length mismatch: got {}, expected {}",
44 bytes.len(),
45 expected
46 )));
47 }
48 Ok(Self {
49 width,
50 height,
51 bytes,
52 #[cfg(feature = "opencv")]
53 gray: None,
54 #[cfg(feature = "opencv")]
55 bgr: None,
56 #[cfg(feature = "ocr")]
57 dynamic_image: None,
58 })
59 }
60
61 pub fn width(&self) -> u32 {
62 self.width
63 }
64
65 pub fn height(&self) -> u32 {
66 self.height
67 }
68
69 pub fn bytes(&self) -> &'a [u8] {
70 self.bytes
71 }
72
73 #[cfg(feature = "opencv")]
75 pub fn gray(&mut self) -> Result<&Mat> {
76 if self.gray.is_none() {
77 let rgba = self.rgba_mat()?;
78 let mut gray = Mat::default();
79 imgproc::cvt_color_def(&rgba, &mut gray, imgproc::COLOR_RGBA2GRAY)?;
80 self.gray = Some(gray);
81 }
82 Ok(self.gray.as_ref().expect("gray image is initialized"))
83 }
84
85 #[cfg(feature = "opencv")]
87 pub fn bgr(&mut self) -> Result<&Mat> {
88 if self.bgr.is_none() {
89 let rgba = self.rgba_mat()?;
90 let mut bgr = Mat::default();
91 imgproc::cvt_color_def(&rgba, &mut bgr, imgproc::COLOR_RGBA2BGR)?;
92 self.bgr = Some(bgr);
93 }
94 Ok(self.bgr.as_ref().expect("BGR image is initialized"))
95 }
96
97 #[cfg(feature = "ocr")]
100 pub fn dynamic_image(&mut self) -> Result<&::image::DynamicImage> {
101 if self.dynamic_image.is_none() {
102 let buffer = ::image::RgbaImage::from_raw(self.width, self.height, self.bytes.to_vec())
103 .ok_or_else(|| {
104 McvError::InvalidImage(format!(
105 "failed to create RGBA image {}x{}",
106 self.width, self.height
107 ))
108 })?;
109 self.dynamic_image = Some(::image::DynamicImage::ImageRgba8(buffer));
110 }
111 Ok(self
112 .dynamic_image
113 .as_ref()
114 .expect("DynamicImage is initialized"))
115 }
116
117 #[cfg(feature = "opencv")]
118 fn rgba_mat(&self) -> Result<opencv::boxed_ref::BoxedRef<'_, Mat>> {
119 Ok(Mat::new_rows_cols_with_bytes::<opencv::core::Vec4b>(
120 self.height as i32,
121 self.width as i32,
122 self.bytes,
123 )?)
124 }
125}
126
127pub trait VisionTemplate {
133 fn find(&mut self, image: &mut RgbaFrame<'_>) -> Result<Option<MatchResult>> {
135 self.find_with_roi(image, None)
136 }
137
138 fn find_with_roi(
143 &mut self,
144 image: &mut RgbaFrame<'_>,
145 roi: Option<Roi>,
146 ) -> Result<Option<MatchResult>>;
147}