use opencv::core::{Mat, MatTraitConst, CV_8U};
use opencv::prelude::*;
use opencv_rs_core::{ImageOpsError, MatView, PixelFormat};
pub struct OpenCvMatView<'a> {
mat: &'a Mat,
pixel_format: PixelFormat,
width: u32,
height: u32,
}
impl<'a> OpenCvMatView<'a> {
pub fn try_from_mat(mat: &'a Mat) -> Result<Self, ImageOpsError> {
let depth = mat.depth();
if depth != CV_8U {
return Err(ImageOpsError::InvalidParameter(
"OpenCvMatView: only CV_8U depth is supported",
));
}
if !mat.is_continuous() {
return Err(ImageOpsError::InvalidParameter(
"OpenCvMatView: non-continuous Mat is not supported",
));
}
let pixel_format = match mat.channels() {
1 => PixelFormat::Mono8,
3 => PixelFormat::Bgr8,
_ => {
return Err(ImageOpsError::InvalidParameter(
"OpenCvMatView: only 1- or 3-channel mats are supported",
));
}
};
let width = u32::try_from(mat.cols())
.map_err(|_| ImageOpsError::InvalidParameter("OpenCvMatView: negative cols"))?;
let height = u32::try_from(mat.rows())
.map_err(|_| ImageOpsError::InvalidParameter("OpenCvMatView: negative rows"))?;
Ok(Self {
mat,
pixel_format,
width,
height,
})
}
pub fn as_mat(&self) -> &Mat {
self.mat
}
}
impl<'a> MatView for OpenCvMatView<'a> {
fn width(&self) -> u32 {
self.width
}
fn height(&self) -> u32 {
self.height
}
fn channels(&self) -> u32 {
self.pixel_format.channels()
}
fn pixel_format(&self) -> PixelFormat {
self.pixel_format
}
fn data(&self) -> &[u8] {
self.mat
.data_bytes()
.expect("OpenCvMatView invariant: continuity checked at construction")
}
}
pub unsafe fn slice_to_mat(
slice: &[u8],
rows: i32,
cols: i32,
channels: i32,
) -> opencv::Result<Mat> {
let width_1c = cols * channels;
let view = Mat::new_rows_cols_with_data(rows, width_1c, slice)?;
let reshaped = if channels > 1 {
view.reshape(channels, rows)?
} else {
return view.try_clone();
};
reshaped.try_clone()
}
#[cfg(test)]
mod tests {
use super::*;
use opencv::core::MatTraitConst;
#[test]
fn slice_to_mat_mono_round_trip() {
let data: Vec<u8> = (0..16u8).collect();
let mat = unsafe { slice_to_mat(&data, 4, 4, 1) }.expect("build mono mat");
assert_eq!(mat.rows(), 4);
assert_eq!(mat.cols(), 4);
assert_eq!(mat.channels(), 1);
assert_eq!(mat.data_bytes().unwrap(), data.as_slice());
}
#[test]
fn slice_to_mat_bgr_round_trip() {
let data: Vec<u8> = (0..48u8).collect();
let mat = unsafe { slice_to_mat(&data, 4, 4, 3) }.expect("build bgr mat");
assert_eq!(mat.rows(), 4);
assert_eq!(mat.cols(), 4);
assert_eq!(mat.channels(), 3);
assert_eq!(mat.data_bytes().unwrap(), data.as_slice());
}
#[test]
fn try_from_mat_rejects_non_continuous() {
use opencv::core::CV_8UC1;
let mut buf: Vec<u8> = vec![0u8; 32];
let mat = unsafe {
Mat::new_rows_cols_with_data_unsafe(
4,
4,
CV_8UC1,
buf.as_mut_ptr().cast::<std::ffi::c_void>(),
8,
)
}
.expect("build non-continuous mat");
assert!(!mat.is_continuous());
match OpenCvMatView::try_from_mat(&mat) {
Ok(_) => panic!("expected non-continuous Mat to be rejected"),
Err(ImageOpsError::InvalidParameter(msg)) => {
assert!(msg.contains("non-continuous"), "got: {msg}");
}
Err(other) => panic!("expected InvalidParameter, got {other:?}"),
}
}
}