Skip to main content

opencv_rs_core/
mat_view.rs

1//! [`MatView`] trait and a pure-Rust owned implementation.
2
3use crate::{ImageOpsError, PixelFormat};
4
5/// Read-only view over an image buffer with pixel-format metadata.
6pub trait MatView {
7    /// Width in pixels.
8    fn width(&self) -> u32;
9    /// Height in pixels.
10    fn height(&self) -> u32;
11    /// Number of channels per pixel.
12    fn channels(&self) -> u32;
13    /// The pixel format describing the interleaved byte layout.
14    fn pixel_format(&self) -> PixelFormat;
15    /// The raw pixel bytes in row-major interleaved order.
16    fn data(&self) -> &[u8];
17}
18
19/// Pure-Rust owned mat-view for use in tests and pure-Rust pipelines.
20#[derive(Debug, Clone)]
21pub struct OwnedMatView {
22    width: u32,
23    height: u32,
24    pixel_format: PixelFormat,
25    data: Vec<u8>,
26}
27
28impl OwnedMatView {
29    /// Build a new owned mat-view. Fails if `data.len()` does not match
30    /// `width * height * pixel_format.channels()`.
31    pub fn new(
32        width: u32,
33        height: u32,
34        pixel_format: PixelFormat,
35        data: Vec<u8>,
36    ) -> Result<Self, ImageOpsError> {
37        let expected = (width as usize) * (height as usize) * (pixel_format.channels() as usize);
38        if data.len() != expected {
39            return Err(ImageOpsError::InvalidParameter(
40                "OwnedMatView: data length does not match width*height*channels",
41            ));
42        }
43        Ok(Self {
44            width,
45            height,
46            pixel_format,
47            data,
48        })
49    }
50
51    /// Allocate a zero-filled mat-view of the given shape and format.
52    pub fn zeros(width: u32, height: u32, pixel_format: PixelFormat) -> Self {
53        let len = (width as usize) * (height as usize) * (pixel_format.channels() as usize);
54        Self {
55            width,
56            height,
57            pixel_format,
58            data: vec![0u8; len],
59        }
60    }
61
62    /// Consume the view and return the backing buffer.
63    pub fn into_data(self) -> Vec<u8> {
64        self.data
65    }
66
67    /// Mutable access to the backing buffer.
68    pub fn data_mut(&mut self) -> &mut [u8] {
69        &mut self.data
70    }
71}
72
73impl MatView for OwnedMatView {
74    fn width(&self) -> u32 {
75        self.width
76    }
77    fn height(&self) -> u32 {
78        self.height
79    }
80    fn channels(&self) -> u32 {
81        self.pixel_format.channels()
82    }
83    fn pixel_format(&self) -> PixelFormat {
84        self.pixel_format
85    }
86    fn data(&self) -> &[u8] {
87        &self.data
88    }
89}
90
91#[cfg(test)]
92mod tests {
93    use super::*;
94
95    #[test]
96    fn new_accepts_matching_length() {
97        let v = OwnedMatView::new(2, 3, PixelFormat::Mono8, vec![0u8; 6]).unwrap();
98        assert_eq!(v.width(), 2);
99        assert_eq!(v.height(), 3);
100        assert_eq!(v.channels(), 1);
101        assert_eq!(v.pixel_format(), PixelFormat::Mono8);
102        assert_eq!(v.data().len(), 6);
103    }
104
105    #[test]
106    fn new_rejects_mismatched_length() {
107        let err = OwnedMatView::new(2, 3, PixelFormat::Bgr8, vec![0u8; 6]).unwrap_err();
108        assert!(matches!(err, ImageOpsError::InvalidParameter(_)));
109    }
110
111    #[test]
112    fn zeros_allocates_zeroed_buffer() {
113        let v = OwnedMatView::zeros(4, 2, PixelFormat::Bgr8);
114        assert_eq!(v.data().len(), 4 * 2 * 3);
115        assert_eq!(v.channels(), 3);
116        assert_eq!(v.pixel_format(), PixelFormat::Bgr8);
117        assert!(v.data().iter().all(|&b| b == 0));
118    }
119
120    #[test]
121    fn into_data_returns_buffer() {
122        let v = OwnedMatView::new(1, 1, PixelFormat::Mono8, vec![7u8]).unwrap();
123        assert_eq!(v.into_data(), vec![7u8]);
124    }
125
126    #[test]
127    fn data_mut_allows_mutation() {
128        let mut v = OwnedMatView::zeros(1, 1, PixelFormat::Mono8);
129        v.data_mut()[0] = 42;
130        assert_eq!(v.data()[0], 42);
131    }
132}