Skip to main content

nokhwa_core/
buffer.rs

1/*
2 * Copyright 2022 l1npengtul <l1npengtul@protonmail.com> / The Nokhwa Contributors
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *     http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16use crate::{
17    error::NokhwaError,
18    pixel_format::FormatDecoder,
19    types::{FrameFormat, Resolution},
20};
21use bytes::Bytes;
22use image::ImageBuffer;
23use std::time::Duration;
24#[cfg(feature = "opencv-mat")]
25use opencv::{boxed_ref::BoxedRef, core::Mat};
26
27/// A buffer returned by a camera to accommodate custom decoding.
28/// Contains information of Resolution, the buffer's [`FrameFormat`], and the buffer.
29///
30/// Note that decoding on the main thread **will** decrease your performance and lead to dropped frames.
31#[derive(Clone, Debug, Hash, PartialOrd, PartialEq, Eq)]
32pub struct Buffer {
33    resolution: Resolution,
34    buffer: Bytes,
35    source_frame_format: FrameFormat,
36    capture_timestamp: Option<Duration>,
37}
38
39impl Buffer {
40    /// Creates a new buffer with a [`&[u8]`].
41    #[must_use]
42    #[inline]
43    pub fn new(res: Resolution, buf: &[u8], source_frame_format: FrameFormat) -> Self {
44        Self {
45            resolution: res,
46            buffer: Bytes::copy_from_slice(buf),
47            source_frame_format,
48            capture_timestamp: None,
49        }
50    }
51
52    /// Creates a new buffer with a [`&[u8]`] and a backend-provided capture timestamp.
53    #[must_use]
54    #[inline]
55    pub fn with_timestamp(
56        res: Resolution,
57        buf: &[u8],
58        source_frame_format: FrameFormat,
59        capture_timestamp: Option<Duration>,
60    ) -> Self {
61        Self {
62            resolution: res,
63            buffer: Bytes::copy_from_slice(buf),
64            source_frame_format,
65            capture_timestamp,
66        }
67    }
68
69    /// Get the backend-provided capture timestamp, if available.
70    #[must_use]
71    pub fn capture_timestamp(&self) -> Option<Duration> {
72        self.capture_timestamp
73    }
74
75    /// Get the [`Resolution`] of this buffer.
76    #[must_use]
77    pub fn resolution(&self) -> Resolution {
78        self.resolution
79    }
80
81    /// Get the data of this buffer.
82    #[must_use]
83    pub fn buffer(&self) -> &[u8] {
84        &self.buffer
85    }
86
87    /// Get a owned version of this buffer.
88    #[must_use]
89    pub fn buffer_bytes(&self) -> Bytes {
90        self.buffer.clone()
91    }
92
93    /// Get the [`FrameFormat`] of this buffer.
94    #[must_use]
95    pub fn source_frame_format(&self) -> FrameFormat {
96        self.source_frame_format
97    }
98
99    /// Decodes a image with allocation using the provided [`FormatDecoder`].
100    /// # Errors
101    /// Will error when the decoding fails.
102    #[inline]
103    pub fn decode_image<F: FormatDecoder>(
104        &self,
105    ) -> Result<ImageBuffer<F::Output, Vec<u8>>, NokhwaError> {
106        let new_data = F::write_output(self.source_frame_format, self.resolution, &self.buffer)?;
107        let image =
108            ImageBuffer::from_raw(self.resolution.width_x, self.resolution.height_y, new_data)
109                .ok_or(NokhwaError::ProcessFrameError {
110                    src: self.source_frame_format,
111                    destination: stringify!(F).to_string(),
112                    error: "Failed to create buffer".to_string(),
113                })?;
114        Ok(image)
115    }
116
117    /// Decodes a image with allocation using the provided [`FormatDecoder`] into a `buffer`.
118    /// # Errors
119    /// Will error when the decoding fails, or the provided buffer is too small.
120    #[inline]
121    pub fn decode_image_to_buffer<F: FormatDecoder>(
122        &self,
123        buffer: &mut [u8],
124    ) -> Result<(), NokhwaError> {
125        F::write_output_buffer(
126            self.source_frame_format,
127            self.resolution,
128            &self.buffer,
129            buffer,
130        )
131    }
132
133    /// Decodes an image with allocation using the provided [`FormatDecoder`] into a [`Mat`](https://docs.rs/opencv/latest/opencv/core/struct.Mat.html).
134    ///
135    /// Note that this does a clone when creating the buffer, to decouple the lifetime of the internal data to the temporary Buffer. If you want to avoid this, please see [`decode_opencv_mat`](Self::decode_opencv_mat).
136    ///
137    /// This is **NOT** coherent when the input data is not Gray8, GrayAlpha87, RGB8, or RGBA8
138    /// # Errors
139    /// Will error when the decoding fails, or `OpenCV` failed to create/copy the [`Mat`](https://docs.rs/opencv/latest/opencv/core/struct.Mat.html).
140    /// # Safety
141    /// This function uses `unsafe` in order to create the [`Mat`](https://docs.rs/opencv/latest/opencv/core/struct.Mat.html). Please see [`Mat::new_rows_cols_with_data`](https://docs.rs/opencv/latest/opencv/core/struct.Mat.html#method.new_rows_cols_with_data) for more.
142    ///
143    /// Most notably, the `data` **must** stay in scope for the duration of the [`Mat`](https://docs.rs/opencv/latest/opencv/core/struct.Mat.html) or bad, ***bad*** things happen.
144    #[cfg(feature = "opencv-mat")]
145    #[cfg_attr(feature = "docs-features", doc(cfg(feature = "opencv-mat")))]
146    pub fn decode_opencv_mat<F: FormatDecoder>(
147        &mut self,
148    ) -> Result<BoxedRef<'_, Mat>, NokhwaError> {
149        use crate::buffer::channel_defs::make_mat;
150
151        make_mat::<F>(self.resolution, self.buffer())
152    }
153
154    /// Decodes an image with allocation using the provided [`FormatDecoder`] into a [`Mat`](https://docs.rs/opencv/latest/opencv/core/struct.Mat.html).
155    ///
156    /// This is **NOT** coherent when the input data is not Gray8, GrayAlpha87, RGB8, or RGBA8
157    /// # Errors
158    /// Will error when the decoding fails, or `OpenCV` failed to create/copy the [`Mat`](https://docs.rs/opencv/latest/opencv/core/struct.Mat.html).
159    #[cfg(feature = "opencv-mat")]
160    #[cfg_attr(feature = "docs-features", doc(cfg(feature = "opencv-mat")))]
161    #[allow(clippy::cast_possible_wrap)]
162    pub fn decode_into_opencv_mat<F: FormatDecoder>(
163        &mut self,
164        dst: &mut Mat,
165    ) -> Result<(), NokhwaError> {
166        use bytes::Buf;
167        use image::Pixel;
168        use opencv::core::{
169            Mat, MatTraitConst, MatTraitManual, Scalar, CV_8UC1, CV_8UC2, CV_8UC3, CV_8UC4,
170        };
171
172        let array_type = match F::Output::CHANNEL_COUNT {
173            1 => CV_8UC1,
174            2 => CV_8UC2,
175            3 => CV_8UC3,
176            4 => CV_8UC4,
177            _ => {
178                return Err(NokhwaError::ProcessFrameError {
179                    src: FrameFormat::RAWRGB,
180                    destination: "OpenCV Mat".to_string(),
181                    error: "Invalid Decoder FormatDecoder Channel Count".to_string(),
182                })
183            }
184        };
185
186        // If destination does not exist, create a new matrix.
187        if dst.empty() {
188            *dst = Mat::new_rows_cols_with_default(
189                self.resolution.height_y as i32,
190                self.resolution.width_x as i32,
191                array_type,
192                Scalar::default(),
193            )
194            .map_err(|why| NokhwaError::ProcessFrameError {
195                src: FrameFormat::RAWRGB,
196                destination: "OpenCV Mat".to_string(),
197                error: why.to_string(),
198            })?;
199        } else {
200            if dst.typ() != array_type {
201                return Err(NokhwaError::ProcessFrameError {
202                    src: FrameFormat::RAWRGB,
203                    destination: "OpenCV Mat".to_string(),
204                    error: "Invalid Matrix Channel Count".to_string(),
205                });
206            }
207
208            if dst.rows() != self.resolution.height_y as _
209                || dst.cols() != self.resolution.width_x as _
210            {
211                return Err(NokhwaError::ProcessFrameError {
212                    src: FrameFormat::RAWRGB,
213                    destination: "OpenCV Mat".to_string(),
214                    error: "Invalid Matrix Dimensions".to_string(),
215                });
216            }
217        }
218
219        let mut bytes = match dst.data_bytes_mut() {
220            Ok(bytes) => bytes,
221            Err(_e) => {
222                return Err(NokhwaError::ProcessFrameError {
223                    src: FrameFormat::RAWRGB,
224                    destination: "OpenCV Mat".to_string(),
225                    error: "Matrix Must Be Continuous".to_string(),
226                })
227            }
228        };
229
230        let mut buffer = self.buffer.as_ref();
231        if bytes.len() != buffer.len() {
232            return Err(NokhwaError::ProcessFrameError {
233                src: FrameFormat::RAWRGB,
234                destination: "OpenCV Mat".to_string(),
235                error: "Matrix Buffer Size Mismatch".to_string(),
236            });
237        }
238
239        buffer.copy_to_slice(&mut bytes);
240
241        Ok(())
242    }
243}
244
245/// Channel definitions and utilities for making Mat for OpenCV
246///
247/// You (probably) shouldn't use this.
248#[cfg(feature = "opencv-mat")]
249pub mod channel_defs {
250    use crate::error::NokhwaError;
251    use crate::pixel_format::FormatDecoder;
252    use crate::types::{FrameFormat, Resolution};
253    use bytemuck::{cast_slice, Pod, Zeroable};
254    use image::Pixel;
255
256    #[cfg(feature = "opencv-mat")]
257    #[cfg_attr(feature = "docs-features", doc(cfg(feature = "opencv-mat")))]
258    pub(crate) fn make_mat<F>(
259        resolution: Resolution,
260        data: &[u8],
261    ) -> Result<opencv::boxed_ref::BoxedRef<'_, opencv::core::Mat>, NokhwaError>
262    where
263        F: FormatDecoder,
264    {
265        use crate::buffer::channel_defs::*;
266        use opencv::core::Mat;
267
268        let mat = match F::Output::CHANNEL_COUNT {
269            1 => Mat::new_rows_cols_with_data::<G8>(
270                resolution.width() as i32,
271                resolution.height() as i32,
272                cast_slice(data),
273            ),
274            2 => Mat::new_rows_cols_with_data::<GA8>(
275                resolution.width() as i32,
276                resolution.height() as i32,
277                cast_slice(data),
278            ),
279            3 => Mat::new_rows_cols_with_data::<RGB8>(
280                resolution.width() as i32,
281                resolution.height() as i32,
282                cast_slice(data),
283            ),
284            4 => Mat::new_rows_cols_with_data::<RGBA8>(
285                resolution.width() as i32,
286                resolution.height() as i32,
287                cast_slice(data),
288            ),
289            _ => {
290                return Err(NokhwaError::ProcessFrameError {
291                    src: FrameFormat::RAWRGB,
292                    destination: "OpenCV Mat".to_string(),
293                    error: "Invalid Decoder FormatDecoder Channel Count".to_string(),
294                })
295            }
296        };
297
298        match mat {
299            Ok(m) => Ok(m),
300            Err(why) => Err(NokhwaError::ProcessFrameError {
301                src: FrameFormat::RAWRGB,
302                destination: "OpenCV Mat".to_string(),
303                error: why.to_string(),
304            }),
305        }
306    }
307
308    /// Three u8
309    #[repr(transparent)]
310    #[derive(Copy, Clone, Debug)]
311    pub struct RGB8 {
312        pub data: [u8; 3],
313    }
314
315    unsafe impl opencv::core::DataType for RGB8 {
316        fn opencv_depth() -> i32 {
317            1
318        }
319
320        fn opencv_channels() -> i32 {
321            3
322        }
323    }
324
325    unsafe impl Pod for RGB8 {}
326
327    unsafe impl Zeroable for RGB8 {}
328
329    /// Two u8
330    #[repr(transparent)]
331    #[derive(Copy, Clone, Debug)]
332    pub struct GA8 {
333        pub data: [u8; 2],
334    }
335
336    unsafe impl opencv::core::DataType for GA8 {
337        fn opencv_depth() -> i32 {
338            1
339        }
340
341        fn opencv_channels() -> i32 {
342            2
343        }
344    }
345
346    unsafe impl Zeroable for GA8 {}
347
348    unsafe impl Pod for GA8 {}
349
350    /// One u8
351    #[derive(Copy, Clone, Debug)]
352    pub struct G8 {
353        pub data: u8,
354    }
355
356    unsafe impl opencv::core::DataType for G8 {
357        fn opencv_depth() -> i32 {
358            1
359        }
360
361        fn opencv_channels() -> i32 {
362            1
363        }
364    }
365
366    unsafe impl Zeroable for G8 {}
367
368    unsafe impl Pod for G8 {}
369
370    /// Four u8
371    #[repr(transparent)]
372    #[derive(Copy, Clone, Debug)]
373    pub struct RGBA8 {
374        pub data: [u8; 4],
375    }
376
377    unsafe impl opencv::core::DataType for RGBA8 {
378        fn opencv_depth() -> i32 {
379            1
380        }
381
382        fn opencv_channels() -> i32 {
383            4
384        }
385    }
386
387    unsafe impl Zeroable for RGBA8 {}
388
389    unsafe impl Pod for RGBA8 {}
390}