use super::{
error::{McvError, Result},
MatchResult, Roi,
};
#[cfg(feature = "opencv")]
use opencv::{core::Mat, imgproc};
pub struct RgbaFrame<'a> {
width: u32,
height: u32,
bytes: &'a [u8],
#[cfg(feature = "opencv")]
gray: Option<Mat>,
#[cfg(feature = "opencv")]
bgr: Option<Mat>,
#[cfg(feature = "ocr")]
dynamic_image: Option<::image::DynamicImage>,
}
impl<'a> RgbaFrame<'a> {
pub fn new(width: u32, height: u32, bytes: &'a [u8]) -> Result<Self> {
if width == 0 || height == 0 {
return Err(McvError::InvalidImage(format!(
"RGBA frame dimensions must be positive, got {width}x{height}"
)));
}
let expected = (width as usize)
.checked_mul(height as usize)
.and_then(|value| value.checked_mul(4))
.ok_or_else(|| {
McvError::InvalidImage(format!(
"RGBA frame dimensions are too large: {width}x{height}"
))
})?;
if bytes.len() != expected {
return Err(McvError::InvalidImage(format!(
"RGBA buffer length mismatch: got {}, expected {}",
bytes.len(),
expected
)));
}
Ok(Self {
width,
height,
bytes,
#[cfg(feature = "opencv")]
gray: None,
#[cfg(feature = "opencv")]
bgr: None,
#[cfg(feature = "ocr")]
dynamic_image: None,
})
}
pub fn width(&self) -> u32 {
self.width
}
pub fn height(&self) -> u32 {
self.height
}
pub fn bytes(&self) -> &'a [u8] {
self.bytes
}
#[cfg(feature = "opencv")]
pub fn gray(&mut self) -> Result<&Mat> {
if self.gray.is_none() {
let rgba = self.rgba_mat()?;
let mut gray = Mat::default();
imgproc::cvt_color_def(&rgba, &mut gray, imgproc::COLOR_RGBA2GRAY)?;
self.gray = Some(gray);
}
Ok(self.gray.as_ref().expect("gray image is initialized"))
}
#[cfg(feature = "opencv")]
pub fn bgr(&mut self) -> Result<&Mat> {
if self.bgr.is_none() {
let rgba = self.rgba_mat()?;
let mut bgr = Mat::default();
imgproc::cvt_color_def(&rgba, &mut bgr, imgproc::COLOR_RGBA2BGR)?;
self.bgr = Some(bgr);
}
Ok(self.bgr.as_ref().expect("BGR image is initialized"))
}
#[cfg(feature = "ocr")]
pub fn dynamic_image(&mut self) -> Result<&::image::DynamicImage> {
if self.dynamic_image.is_none() {
let buffer = ::image::RgbaImage::from_raw(self.width, self.height, self.bytes.to_vec())
.ok_or_else(|| {
McvError::InvalidImage(format!(
"failed to create RGBA image {}x{}",
self.width, self.height
))
})?;
self.dynamic_image = Some(::image::DynamicImage::ImageRgba8(buffer));
}
Ok(self
.dynamic_image
.as_ref()
.expect("DynamicImage is initialized"))
}
#[cfg(feature = "opencv")]
fn rgba_mat(&self) -> Result<opencv::boxed_ref::BoxedRef<'_, Mat>> {
Ok(Mat::new_rows_cols_with_bytes::<opencv::core::Vec4b>(
self.height as i32,
self.width as i32,
self.bytes,
)?)
}
}
pub trait VisionTemplate {
fn find(&mut self, image: &mut RgbaFrame<'_>) -> Result<Option<MatchResult>> {
self.find_with_roi(image, None)
}
fn find_with_roi(
&mut self,
image: &mut RgbaFrame<'_>,
roi: Option<Roi>,
) -> Result<Option<MatchResult>>;
}