strand_dynamic_frame/lib.rs
1// Copyright (C) The Strand-Braid Authors
2// SPDX-License-Identifier: MIT OR Apache-2.0
3
4//! Images from machine vision cameras used in [Strand
5//! Camera](https://strawlab.org/strand-cam).
6//!
7//! Building on the [`machine_vision_formats`] crate which provides compile-time
8//! pixel formats, this crate provides types for images whose pixel format is
9//! determined at runtime. This allows for flexibility in handling images data
10//! whose pixel format is known only dynamically, such as when reading an image
11//! from disk.
12//!
13//! There are two types here:
14//! - [DynamicFrame]: A borrowed view of an image with a dynamic pixel format.
15//! - [DynamicFrameOwned]: An owned version of `DynamicFrame` that contains its
16//! own buffer.
17//!
18//! When compiled with the `convert-image` feature, this crate also provides
19//! conversion methods to convert the dynamic frame into a static pixel format
20//! using the [`convert_image`](https://docs.rs/convert-image) crate.
21
22#![warn(missing_docs)]
23#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
24
25use std::borrow::Cow;
26
27use machine_vision_formats as formats;
28
29use formats::{ImageStride, PixFmt, PixelFormat, Stride, image_ref::ImageRef};
30
31#[cfg(feature = "convert-image")]
32use formats::{cow::CowImage, owned::OImage};
33
34// TODO: investigate if we can implement std::borrow::Borrow<DynamicFrame> for
35// DynamicFrameOwned. I think not due to the issues
36// [here](https://users.rust-lang.org/t/how-to-implement-borrow-for-my-own-struct/73023).
37
38#[macro_export]
39/// Macro to match all dynamic pixel formats and execute a block of code with a typed image reference.
40macro_rules! match_all_dynamic_fmts {
41 ($self:expr_2021, $x:ident, $block:expr_2021, $err:expr_2021) => {{
42 use machine_vision_formats::{
43 PixFmt,
44 pixel_format::{
45 BayerBG8, BayerBG32f, BayerGB8, BayerGB32f, BayerGR8, BayerGR32f, BayerRG8,
46 BayerRG32f, Mono8, Mono32f, NV12, RGB8, RGBA8, YUV422, YUV444,
47 },
48 };
49 match $self.pixel_format() {
50 PixFmt::Mono8 => {
51 let $x = $self.as_static::<Mono8>().unwrap();
52 $block
53 }
54 PixFmt::Mono32f => {
55 let $x = $self.as_static::<Mono32f>().unwrap();
56 $block
57 }
58 PixFmt::RGB8 => {
59 let $x = $self.as_static::<RGB8>().unwrap();
60 $block
61 }
62 PixFmt::RGBA8 => {
63 let $x = $self.as_static::<RGBA8>().unwrap();
64 $block
65 }
66 PixFmt::BayerRG8 => {
67 let $x = $self.as_static::<BayerRG8>().unwrap();
68 $block
69 }
70 PixFmt::BayerRG32f => {
71 let $x = $self.as_static::<BayerRG32f>().unwrap();
72 $block
73 }
74 PixFmt::BayerBG8 => {
75 let $x = $self.as_static::<BayerBG8>().unwrap();
76 $block
77 }
78 PixFmt::BayerBG32f => {
79 let $x = $self.as_static::<BayerBG32f>().unwrap();
80 $block
81 }
82 PixFmt::BayerGB8 => {
83 let $x = $self.as_static::<BayerGB8>().unwrap();
84 $block
85 }
86 PixFmt::BayerGB32f => {
87 let $x = $self.as_static::<BayerGB32f>().unwrap();
88 $block
89 }
90 PixFmt::BayerGR8 => {
91 let $x = $self.as_static::<BayerGR8>().unwrap();
92 $block
93 }
94 PixFmt::BayerGR32f => {
95 let $x = $self.as_static::<BayerGR32f>().unwrap();
96 $block
97 }
98 PixFmt::YUV444 => {
99 let $x = $self.as_static::<YUV444>().unwrap();
100 $block
101 }
102 PixFmt::YUV422 => {
103 let $x = $self.as_static::<YUV422>().unwrap();
104 $block
105 }
106 PixFmt::NV12 => {
107 let $x = $self.as_static::<NV12>().unwrap();
108 $block
109 }
110 _ => {
111 return Err($err);
112 }
113 }
114 }};
115}
116
117#[inline]
118const fn calc_min_stride(w: u32, pixfmt: PixFmt) -> usize {
119 w as usize * pixfmt.bits_per_pixel() as usize / 8
120}
121
122#[inline]
123const fn calc_min_buf_size(w: u32, h: u32, stride: usize, pixfmt: PixFmt) -> usize {
124 if h == 0 {
125 return 0;
126 }
127 let all_but_last = (h - 1) as usize * stride;
128 let last = calc_min_stride(w, pixfmt);
129 debug_assert!(stride >= last);
130 all_but_last + last
131}
132
133/// An image whose pixel format is determined at runtime.
134///
135/// This type is used to represent images where the pixel format is not known at
136/// compile time, allowing for flexibility in handling various image formats.
137///
138/// It can be created from raw image data and provides methods to access the
139/// image dimensions, pixel format, and raw data. It also supports conversion to
140/// static pixel formats and encoding to various formats.
141///
142/// # Type Parameters
143/// * `'a` - Lifetime of the borrowed data. If you want to own the data, use
144/// [`DynamicFrameOwned`].
145/// # Notes
146/// * This type is not `Sync` or `Send` because it contains a borrowed buffer.
147/// If you need to share it across threads, use [`DynamicFrameOwned`] instead.
148/// * The pixel format is represented by the [`PixFmt`] enum, which allows for
149/// various pixel formats like `Mono8`, `RGB8`, etc.
150/// * The buffer must be large enough to hold the image data for the specified
151/// dimensions and pixel format.
152///
153/// # Examples
154/// ```rust
155/// # use strand_dynamic_frame::DynamicFrame;
156/// # use machine_vision_formats::PixFmt;
157/// // Create a frame from raw data
158/// let data = vec![0u8; 1920 * 1080];
159/// let frame = DynamicFrame::from_buf(1920, 1080, 1920, data, PixFmt::Mono8).unwrap();
160///
161/// // Check the pixel format
162/// assert_eq!(frame.pixel_format(), PixFmt::Mono8);
163///
164/// // Get dimensions
165/// println!("Size: {}x{}", frame.width(), frame.height());
166/// ```
167#[derive(Clone)]
168pub struct DynamicFrame<'a> {
169 width: u32,
170 height: u32,
171 stride: usize,
172 buf: Cow<'a, [u8]>,
173 pixfmt: PixFmt,
174}
175
176/// An owned version of [`DynamicFrame`] that contains its own buffer.
177#[derive(Clone)]
178pub struct DynamicFrameOwned {
179 width: u32,
180 height: u32,
181 stride: usize,
182 pixfmt: PixFmt,
183 buf: Vec<u8>,
184}
185
186/// A copy-on-write dynamic frame that can be either borrowed or owned.
187pub enum CowDynamicFrame<'a> {
188 /// A borrowed dynamic frame.
189 Borrowed(DynamicFrame<'a>),
190 /// An owned dynamic frame.
191 Owned(DynamicFrameOwned),
192}
193
194impl CowDynamicFrame<'_> {
195 /// Return a borrowed view of this frame as a [`DynamicFrame`].
196 #[must_use]
197 pub fn borrow(&self) -> DynamicFrame<'_> {
198 match self {
199 CowDynamicFrame::Borrowed(f) => f.clone(),
200 CowDynamicFrame::Owned(f) => f.borrow(),
201 }
202 }
203}
204
205impl std::fmt::Debug for DynamicFrameOwned {
206 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
207 f.debug_struct("DynamicFrameOwned")
208 .field("width", &self.width)
209 .field("height", &self.height)
210 .field("stride", &self.stride)
211 .field("pixfmt", &self.pixfmt)
212 .finish_non_exhaustive()
213 }
214}
215
216impl Stride for DynamicFrameOwned {
217 fn stride(&self) -> usize {
218 self.stride
219 }
220}
221
222impl DynamicFrameOwned {
223 /// Return a new [`DynamicFrameOwned`] from a statically typed frame. This
224 /// moves the input data.
225 pub fn from_static<FRAME, FMT>(frame: FRAME) -> Self
226 where
227 FRAME: ImageStride<FMT> + Into<Vec<u8>>,
228 FMT: PixelFormat,
229 {
230 let pixfmt = formats::pixel_format::pixfmt::<FMT>().unwrap();
231 let width = frame.width();
232 let height = frame.height();
233 let stride = frame.stride();
234 let min_size = calc_min_buf_size(width, height, stride, pixfmt);
235 let mut buf: Vec<u8> = frame.into();
236 buf.truncate(min_size);
237 Self {
238 width,
239 height,
240 stride,
241 pixfmt,
242 buf,
243 }
244 }
245
246 /// Return a new [`DynamicFrameOwned`] from a reference to a statically
247 /// typed frame. This copies the input data.
248 pub fn from_static_ref<FMT: PixelFormat>(frame: &dyn ImageStride<FMT>) -> Self {
249 let pixfmt = formats::pixel_format::pixfmt::<FMT>().unwrap();
250 let image_data = frame.image_data();
251 let min_size = calc_min_buf_size(frame.width(), frame.height(), frame.stride(), pixfmt);
252 Self {
253 width: frame.width(),
254 height: frame.height(),
255 stride: frame.stride(),
256 buf: image_data[..min_size].to_vec(),
257 pixfmt,
258 }
259 }
260
261 /// Creates a new [`DynamicFrameOwned`] from raw image data.
262 ///
263 /// This function moves the provided buffer into the new frame without
264 /// copying. The buffer size must be appropriate for the given dimensions
265 /// and pixel format.
266 ///
267 /// # Parameters
268 /// * `w` - Image width in pixels
269 /// * `h` - Image height in pixels
270 /// * `s` - Row stride in bytes (must be >= width * `bytes_per_pixel`)
271 /// * `buf` - Raw image data buffer
272 /// * `pixfmt` - Pixel format of the image data
273 ///
274 /// # Returns
275 /// * `Some(DynamicFrameOwned)` if the buffer is valid for the given parameters
276 /// * `None` if the buffer is too small.
277 #[must_use]
278 pub fn from_buf(w: u32, h: u32, stride: usize, buf: Vec<u8>, pixfmt: PixFmt) -> Option<Self> {
279 let min_size = calc_min_buf_size(w, h, stride, pixfmt);
280 if buf.len() < min_size {
281 return None; // Buffer too small
282 }
283 Some(Self {
284 width: w,
285 height: h,
286 stride,
287 buf,
288 pixfmt,
289 })
290 }
291
292 /// Return a borrowed view of this frame as a [`DynamicFrame`].
293 #[must_use]
294 pub fn borrow(&self) -> DynamicFrame<'_> {
295 DynamicFrame {
296 width: self.width,
297 height: self.height,
298 stride: self.stride,
299 buf: Cow::Borrowed(&self.buf),
300 pixfmt: self.pixfmt,
301 }
302 }
303
304 // /// Return a mutable borrowed view of this frame as a [`DynamicFrame`].
305 // pub fn borrow_mut(&mut self) -> DynamicFrame<'_> {
306 // DynamicFrame {
307 // width: self.width,
308 // height: self.height,
309 // stride: self.stride,
310 // buf: Cow::Borrowed(&self.buf),
311 // pixfmt: self.pixfmt,
312 // }
313 // }
314
315 /// Moves data into a new [`DynamicFrameOwned`] containing a region of
316 /// interest (ROI) within the image without copying.
317 ///
318 /// The ROI is defined by the specified left, top, width, and height
319 /// parameters. If the specified ROI is out of bounds or the buffer is too
320 /// small, this method returns `None`.
321 ///
322 /// # Parameters
323 /// * `left` - The left coordinate of the ROI in pixels
324 /// * `top` - The top coordinate of the ROI in pixels
325 /// * `width` - The width of the ROI in pixels
326 /// * `height` - The height of the ROI in pixels
327 ///
328 /// # Returns
329 /// * `Some(DynamicFrameOwned)` if the ROI is valid and the buffer is large
330 /// enough
331 /// * `None` if the ROI is out of bounds or the buffer is too small
332 ///
333 /// To create a view with a ROI, use [`Self::borrow().roi()`].
334 #[must_use]
335 pub fn roi(self, left: u32, top: u32, width: u32, height: u32) -> Option<DynamicFrameOwned> {
336 if left != 0 || top != 0 {
337 todo!();
338 }
339 if left + width > self.width || top + height > self.height {
340 return None; // ROI out of bounds
341 }
342 let stride = self.stride;
343 let new_min_size = calc_min_buf_size(width, height, stride, self.pixfmt);
344 if self.buf.len() < new_min_size {
345 return None; // Buffer too small for ROI
346 }
347 Some(DynamicFrameOwned {
348 width,
349 height,
350 stride,
351 buf: self.buf,
352 pixfmt: self.pixfmt,
353 })
354 }
355
356 /// Moves the `DynamicFrameOwned` into a static pixel format.
357 ///
358 /// If the requested pixel format matches the current format, this method
359 /// returns an [`formats::owned::OImage`] that owns the data without
360 /// copying. Otherwise, it returns `None` since the data cannot be moved to
361 /// a static format.
362 ///
363 /// To convert to a static format when the target format may not match the
364 /// current format, use [`Self::into_pixel_format<FMT>()`] (requires the
365 /// `convert-image` feature).
366 ///
367 /// # Type Parameters
368 /// * `FMT` - The target pixel format type
369 ///
370 /// # Returns
371 /// * `Some(OImage<FMT>)` - If the target format matches the current format,
372 /// returns an owned image in the specified format
373 /// * `None` - If the target format does not match the current format
374 ///
375 #[must_use]
376 pub fn as_static<FMT: PixelFormat>(self) -> Option<formats::owned::OImage<FMT>> {
377 let pixfmt = formats::pixel_format::pixfmt::<FMT>().unwrap();
378 if pixfmt == self.pixfmt {
379 // Simply return the image data as a borrowed view
380 Some(
381 formats::owned::OImage::new(self.width, self.height, self.stride, self.buf)
382 .unwrap(),
383 )
384 } else {
385 // Cannot convert to static format
386 None
387 }
388 }
389
390 #[cfg(feature = "convert-image")]
391 /// Converts the image to the specified pixel format, returning an
392 /// [`OImage`] that owns the data.
393 ///
394 /// If the requested pixel format matches the current format, this method
395 /// moves the data without copying. Otherwise, the data is converted and a
396 /// new owned image is returned. In both cases, the original image data is
397 /// consumed.
398 ///
399 /// To move the data to a specified pixel format while excluding the
400 /// possibility of converting the format in case the requested format does
401 /// not match the current format, use [`Self::as_static<FMT>()`].
402 ///
403 /// # Type Parameters
404 /// * `FMT` - The target pixel format type
405 ///
406 /// # Returns
407 /// * `Ok(OImage<FMT>)` - If conversion is successful, returns an owned
408 /// image in the specified format
409 /// * `Err(convert_image::Error)` - If conversion fails
410 ///
411 /// # Examples
412 /// ```rust
413 /// # use strand_dynamic_frame::DynamicFrameOwned;
414 /// # use machine_vision_formats::{PixFmt, pixel_format::Mono8};
415 /// let data = vec![64u8; 2000];
416 /// let frame = DynamicFrameOwned::from_buf(40, 50, 40, data, PixFmt::Mono8).unwrap();
417 ///
418 /// // No conversion or copying needed - returns original data
419 /// let owned_image = frame.into_pixel_format::<Mono8>().unwrap();
420 /// ```
421 pub fn into_pixel_format<FMT>(self) -> Result<OImage<FMT>, convert_image::Error>
422 where
423 FMT: PixelFormat,
424 {
425 let dest_fmt = formats::pixel_format::pixfmt::<FMT>().unwrap();
426 let self_ = self.borrow();
427 if dest_fmt == self_.pixel_format() {
428 // Fast path. Simply return the data.
429 Ok(OImage::new(self_.width(), self_.height(), self_.stride(), self.buf).unwrap())
430 } else {
431 // Conversion path. Allocate a new buffer and convert the data.
432 let width = self_.width();
433 let dest_stride = calc_min_stride(width, dest_fmt);
434 let mut dest = OImage::zeros(width, self_.height(), dest_stride).unwrap();
435 self_.into_pixel_format_dest(&mut dest)?;
436 Ok(dest)
437 }
438 }
439
440 /// Returns the width of the image in pixels.
441 ///
442 /// # Examples
443 /// ```rust
444 /// # use strand_dynamic_frame::DynamicFrame;
445 /// # use machine_vision_formats::PixFmt;
446 /// let data = vec![0u8; 1500];
447 /// let frame = DynamicFrame::from_buf(50, 10, 150, data, PixFmt::RGB8).unwrap();
448 /// assert_eq!(frame.width(), 50);
449 /// ```
450 #[must_use]
451 pub fn width(&self) -> u32 {
452 self.width
453 }
454
455 /// Returns the height of the image in pixels.
456 ///
457 /// # Examples
458 /// ```rust
459 /// # use strand_dynamic_frame::DynamicFrame;
460 /// # use machine_vision_formats::PixFmt;
461 /// let data = vec![0u8; 2000];
462 /// let frame = DynamicFrame::from_buf(40, 50, 40, data, PixFmt::Mono8).unwrap();
463 /// assert_eq!(frame.height(), 50);
464 /// ```
465 #[must_use]
466 pub fn height(&self) -> u32 {
467 self.height
468 }
469}
470
471impl<'a> DynamicFrame<'a> {
472 /// Return a new [`DynamicFrameOwned`] by copying data.
473 #[must_use]
474 pub fn copy_to_owned(&self) -> DynamicFrameOwned {
475 let pixfmt = self.pixfmt;
476 let width = self.width;
477 let height = self.height;
478 let stride = self.stride;
479 let buf = self.buf.to_vec();
480 DynamicFrameOwned {
481 width,
482 height,
483 stride,
484 pixfmt,
485 buf,
486 }
487 }
488
489 /// Return a new [`DynamicFrame`] from a reference to a statically
490 /// typed frame. This does not copy the input data.
491 pub fn from_static_ref<FMT: PixelFormat>(frame: &'a dyn ImageStride<FMT>) -> Self {
492 let pixfmt = formats::pixel_format::pixfmt::<FMT>().unwrap();
493 let image_data = frame.image_data();
494 let min_size = calc_min_buf_size(frame.width(), frame.height(), frame.stride(), pixfmt);
495 let image_data = &image_data[..min_size];
496 Self {
497 width: frame.width(),
498 height: frame.height(),
499 stride: frame.stride(),
500 buf: std::borrow::Cow::Borrowed(image_data),
501 pixfmt,
502 }
503 }
504
505 /// Creates a new [`DynamicFrame`] from raw image data.
506 ///
507 /// This function moves the provided buffer into the new frame without
508 /// copying. The buffer size must be appropriate for the given dimensions
509 /// and pixel format.
510 ///
511 /// # Parameters
512 /// * `w` - Image width in pixels
513 /// * `h` - Image height in pixels
514 /// * `s` - Row stride in bytes (must be >= width * `bytes_per_pixel`)
515 /// * `buf` - Raw image data buffer
516 /// * `pixfmt` - Pixel format of the image data
517 ///
518 /// # Returns
519 /// * `Some(DynamicFrame)` if the buffer is valid for the given parameters
520 /// * `None` if the buffer is too small.
521 ///
522 /// # Examples
523 /// ```rust
524 /// # use strand_dynamic_frame::DynamicFrame;
525 /// # use machine_vision_formats::PixFmt;
526 /// let data = vec![128u8; 640 * 480]; // Gray image data
527 /// let frame = DynamicFrame::from_buf(640, 480, 640, data, PixFmt::Mono8);
528 /// assert!(frame.is_some());
529 /// ```
530 #[must_use]
531 pub fn from_buf(w: u32, h: u32, stride: usize, buf: Vec<u8>, pixfmt: PixFmt) -> Option<Self> {
532 let min_size = calc_min_buf_size(w, h, stride, pixfmt);
533 if buf.len() < min_size {
534 return None; // Buffer too small
535 }
536 Some(Self {
537 width: w,
538 height: h,
539 stride,
540 buf: Cow::Owned(buf),
541 pixfmt,
542 })
543 }
544
545 /// Returns the width of the image in pixels.
546 ///
547 /// # Examples
548 /// ```rust
549 /// # use strand_dynamic_frame::DynamicFrame;
550 /// # use machine_vision_formats::PixFmt;
551 /// let data = vec![0u8; 1500];
552 /// let frame = DynamicFrame::from_buf(50, 10, 150, data, PixFmt::RGB8).unwrap();
553 /// assert_eq!(frame.width(), 50);
554 /// ```
555 #[must_use]
556 pub fn width(&self) -> u32 {
557 self.width
558 }
559
560 /// Returns the height of the image in pixels.
561 ///
562 /// # Examples
563 /// ```rust
564 /// # use strand_dynamic_frame::DynamicFrame;
565 /// # use machine_vision_formats::PixFmt;
566 /// let data = vec![0u8; 2000];
567 /// let frame = DynamicFrame::from_buf(40, 50, 40, data, PixFmt::Mono8).unwrap();
568 /// assert_eq!(frame.height(), 50);
569 /// ```
570 #[must_use]
571 pub fn height(&self) -> u32 {
572 self.height
573 }
574
575 /// Returns a view of the raw image data as bytes.
576 ///
577 /// This method provides access to the underlying pixel data without any
578 /// type information about the pixel format. The returned slice contains
579 /// the raw bytes that make up the image.
580 ///
581 /// The data layout depends on the pixel format and stride. Use [`pixel_format()`](Self::pixel_format)
582 /// to determine how to interpret the bytes.
583 fn minimum_image_data_without_format(&self) -> &[u8] {
584 let min_size = calc_min_buf_size(self.width, self.height, self.stride, self.pixfmt);
585 &self.buf[..min_size]
586 }
587
588 /// Creates a new [`DynamicFrame`] from an existing frame using borrowed data.
589 ///
590 /// This function copies the image data from the source frame and creates a
591 /// new [`DynamicFrame`]. The original frame remains unchanged.
592 ///
593 /// # Type Parameters
594 /// * `FMT` - The pixel format type of the source frame
595 ///
596 /// # Parameters
597 /// * `frame` - Reference to the source frame implementing [`ImageStride`]
598 ///
599 /// # Examples
600 /// ```rust
601 /// # use strand_dynamic_frame::DynamicFrame;
602 /// # use machine_vision_formats::owned::OImage;
603 /// # use machine_vision_formats::pixel_format::Mono8;
604 /// let source = OImage::<Mono8>::new(100, 100, 100, vec![0u8; 10000]).unwrap();
605 /// let dynamic_frame = DynamicFrame::copy_from(&source);
606 /// assert_eq!(dynamic_frame.width(), 100);
607 /// ```
608 pub fn copy_from<FMT: PixelFormat>(frame: &'a dyn ImageStride<FMT>) -> Self {
609 let width = frame.width();
610 let height = frame.height();
611 let stride = frame.stride();
612 let pixfmt = formats::pixel_format::pixfmt::<FMT>().unwrap();
613 let min_size = calc_min_buf_size(width, height, stride, pixfmt);
614 let data = frame.image_data();
615 debug_assert!(
616 data.len() >= min_size,
617 "Buffer too small for image dimensions and pixel format"
618 );
619 let min_data = &data[..min_size];
620 Self {
621 width,
622 height,
623 stride,
624 buf: Cow::Borrowed(min_data),
625 pixfmt,
626 }
627 }
628
629 #[cfg(feature = "convert-image")]
630 /// Converts the image to the specified pixel format, returning a [`CowImage`] that may borrow or own the data.
631 ///
632 /// If the requested pixel format matches the current format, this method returns
633 /// a borrowed view of the data without copying. Otherwise, the data is converted
634 /// and a new owned image is returned.
635 ///
636 /// # Type Parameters
637 /// * `FMT` - The target pixel format type
638 ///
639 /// # Returns
640 /// * `Ok(CowImage<FMT>)` - Either a borrowed view or owned converted image
641 /// * `Err(convert_image::Error)` - If conversion fails
642 ///
643 /// # Examples
644 /// ```rust
645 /// # use strand_dynamic_frame::DynamicFrame;
646 /// # use machine_vision_formats::{PixFmt, pixel_format::Mono8};
647 /// let data = vec![64u8; 2000];
648 /// let frame = DynamicFrame::from_buf(20, 10, 200, data, PixFmt::Mono8).unwrap();
649 ///
650 /// // No conversion needed - returns borrowed view
651 /// let cow_image = frame.into_pixel_format::<Mono8>().unwrap();
652 /// ```
653 pub fn into_pixel_format<FMT>(&self) -> Result<CowImage<'_, FMT>, convert_image::Error>
654 where
655 FMT: PixelFormat,
656 {
657 let dest_fmt = formats::pixel_format::pixfmt::<FMT>().unwrap();
658 if dest_fmt == self.pixel_format() {
659 // Fast path. Simply return the data.
660 Ok(CowImage::Borrowed(
661 ImageRef::new(
662 self.width(),
663 self.height(),
664 self.stride(),
665 self.minimum_image_data_without_format(),
666 )
667 .unwrap(),
668 ))
669 } else {
670 // Conversion path. Allocate a new buffer and convert the data.
671 let width = self.width();
672 let dest_stride = calc_min_stride(width, dest_fmt);
673 let mut dest = OImage::zeros(width, self.height(), dest_stride).unwrap();
674 self.into_pixel_format_dest(&mut dest)?;
675 Ok(CowImage::Owned(dest))
676 }
677 }
678
679 /// Return a borrowed view of the image data as a static pixel format.
680 ///
681 /// This method allows you to treat the dynamic frame as a specific pixel format
682 /// without copying the data, as long as the pixel format matches.
683 ///
684 /// # Type Parameters
685 /// * `FMT` - The target pixel format type
686 ///
687 /// # Returns
688 /// * `Some(ImageRef<FMT>)` if the pixel format matches
689 /// * `None` if the pixel format does not match
690 ///
691 /// # Examples
692 /// ```rust
693 /// # use strand_dynamic_frame::DynamicFrame;
694 /// # use machine_vision_formats::{PixFmt, pixel_format::Mono8, image_ref::ImageRef, ImageData};
695 /// // Create a dynamic frame with Mono8 pixel format.
696 /// let data = vec![128u8; 1000];
697 /// let frame = DynamicFrame::from_buf(100, 10, 100, data, PixFmt::Mono8).unwrap();
698 ///
699 /// // Convert to a static Mono8 view
700 /// let static_view: Option<ImageRef<Mono8>> = frame.as_static();
701 /// assert!(static_view.is_some());
702 /// assert_eq!(static_view.unwrap().width(), 100);
703 /// ```
704 #[must_use]
705 pub fn as_static<FMT: PixelFormat>(&'a self) -> Option<ImageRef<'a, FMT>> {
706 let pixfmt = formats::pixel_format::pixfmt::<FMT>().unwrap();
707 if pixfmt == self.pixel_format() {
708 // Simply return the image data as a borrowed view
709 Some(
710 ImageRef::new(
711 self.width(),
712 self.height(),
713 self.stride(),
714 self.minimum_image_data_without_format(),
715 )
716 .unwrap(),
717 )
718 } else {
719 // Cannot convert to static format
720 None
721 }
722 }
723
724 /// Converts the image data into a mutable destination buffer of the
725 /// specified pixel format.
726 ///
727 /// This method will convert the data in-place, modifying the destination
728 /// buffer to match the pixel format of the source image.
729 ///
730 /// # Parameters
731 /// * `dest` - A mutable reference to the destination buffer implementing
732 /// [`machine_vision_formats::iter::HasRowChunksExactMut`] for the target
733 /// pixel format.
734 ///
735 /// # Returns
736 /// * `Ok(())` if the conversion was successful
737 /// * `Err(convert_image::Error)` if the conversion fails
738 ///
739 /// # Examples
740 /// ```rust
741 /// # use strand_dynamic_frame::DynamicFrame;
742 /// # use machine_vision_formats::{PixFmt, pixel_format::Mono8, iter::HasRowChunksExactMut,owned::OImage, ImageData, Stride};
743 /// // Create a dynamic frame with RGB8 pixel format.
744 /// let data = vec![255u8; 3000]; // RGB8 data for 100x10 image
745 /// let frame = DynamicFrame::from_buf(100, 10, 300, data, PixFmt::RGB8).unwrap();
746 ///
747 /// // Create a destination buffer for Mono8 format
748 /// let mut dest = OImage::<Mono8>::zeros(100, 10, 100).unwrap();
749 ///
750 /// // Convert the frame into the destination buffer
751 /// frame.into_pixel_format_dest(&mut dest).unwrap();
752 /// assert_eq!(dest.width(), 100);
753 /// assert_eq!(dest.height(), 10);
754 /// assert_eq!(dest.stride(), 100);
755 /// ```
756 #[cfg(feature = "convert-image")]
757 pub fn into_pixel_format_dest<FMT>(
758 &self,
759 dest: &mut dyn machine_vision_formats::iter::HasRowChunksExactMut<FMT>,
760 ) -> Result<(), convert_image::Error>
761 where
762 FMT: PixelFormat,
763 {
764 let pixfmt = self.pixel_format();
765 match_all_dynamic_fmts!(
766 self,
767 x,
768 convert_image::convert_into(&x, dest),
769 convert_image::Error::UnimplementedPixelFormat(pixfmt)
770 )
771 }
772
773 /// Converts the image to a byte buffer encoded in the specified format.
774 ///
775 /// This method encodes the image data into a format suitable for storage or transmission.
776 /// The encoding options can be specified using [`convert_image::EncoderOptions`].
777 ///
778 /// # Parameters
779 /// * `opts` - Encoding options for the output format
780 ///
781 /// # Returns
782 /// * `Ok(Vec<u8>)` - The encoded image data as a byte vector
783 /// * `Err(convert_image::Error)` - If the encoding fails
784 ///
785 /// # Examples
786 /// ```rust
787 /// # use strand_dynamic_frame::DynamicFrame;
788 /// # use machine_vision_formats::PixFmt;
789 /// let data = vec![255u8; 3000]; // RGB8 data for 100x10 image
790 /// let frame = DynamicFrame::from_buf(100, 10, 300, data, PixFmt::RGB8).unwrap();
791 ///
792 /// // Encode the frame to PNG bytes
793 /// let encoded_buffer = frame.to_encoded_buffer(convert_image::EncoderOptions::Png).unwrap();
794 /// assert!(!encoded_buffer.is_empty());
795 /// ```
796 #[cfg(feature = "convert-image")]
797 pub fn to_encoded_buffer(
798 &self,
799 opts: convert_image::EncoderOptions,
800 ) -> Result<Vec<u8>, convert_image::Error> {
801 let pixfmt = self.pixel_format();
802 match_all_dynamic_fmts!(
803 self,
804 x,
805 convert_image::frame_to_encoded_buffer(&x, opts),
806 convert_image::Error::UnimplementedPixelFormat(pixfmt)
807 )
808 }
809
810 /// Returns the pixel format of this image.
811 ///
812 /// # Examples
813 /// ```rust
814 /// # use strand_dynamic_frame::DynamicFrame;
815 /// # use machine_vision_formats::PixFmt;
816 /// let data = vec![0u8; 300];
817 /// let frame = DynamicFrame::from_buf(10, 10, 30, data, PixFmt::RGB8).unwrap();
818 /// assert_eq!(frame.pixel_format(), PixFmt::RGB8);
819 /// ```
820 #[must_use]
821 pub fn pixel_format(&self) -> PixFmt {
822 self.pixfmt
823 }
824
825 /// Forces the image data to be interpreted as a different pixel format without converting the data.
826 ///
827 /// Use this method with caution - the resulting image may not be valid if the buffer
828 /// size is incompatible with the new pixel format requirements.
829 ///
830 /// # Parameters
831 /// * `pixfmt` - The new pixel format to interpret the data as
832 ///
833 /// # Returns
834 /// * `Some(DynamicFrame)` if the buffer size is compatible with the new format
835 /// * `None` if the buffer is too small for the new format
836 ///
837 /// # Examples
838 /// ```rust
839 /// # use strand_dynamic_frame::DynamicFrame;
840 /// # use machine_vision_formats::PixFmt;
841 /// // Create a Mono8 image
842 /// let data = vec![128u8; 1000];
843 /// let frame = DynamicFrame::from_buf(100, 10, 100, data, PixFmt::Mono8).unwrap();
844 ///
845 /// // Force it to be interpreted as a different format (if buffer size allows)
846 /// let forced_frame = frame.force_pixel_format(PixFmt::Mono8);
847 /// assert!(forced_frame.is_some());
848 /// ```
849 #[must_use]
850 pub fn force_pixel_format(self, pixfmt: PixFmt) -> Option<DynamicFrame<'a>> {
851 let new_min_size = calc_min_buf_size(self.width, self.height, self.stride, pixfmt);
852 if self.buf.len() < new_min_size {
853 None // Buffer too small for new pixel format
854 } else {
855 Some(DynamicFrame {
856 width: self.width,
857 height: self.height,
858 stride: self.stride,
859 buf: self.buf,
860 pixfmt,
861 })
862 }
863 }
864
865 /// Returns a new [`DynamicFrame`] representing a region of interest (ROI) within the image.
866 ///
867 /// The ROI is defined by the specified left, top, width, and height parameters.
868 /// If the specified ROI is out of bounds or the buffer is too small, this method returns `None`.
869 ///
870 /// # Parameters
871 /// * `left` - The left coordinate of the ROI in pixels
872 /// * `top` - The top coordinate of the ROI in pixels
873 /// * `width` - The width of the ROI in pixels
874 /// * `height` - The height of the ROI in pixels
875 ///
876 /// # Returns
877 /// * `Some(DynamicFrame)` if the ROI is valid and the buffer is large enough
878 /// * `None` if the ROI is out of bounds or the buffer is too small
879 #[must_use]
880 pub fn roi(&'a self, left: u32, top: u32, width: u32, height: u32) -> Option<DynamicFrame<'a>> {
881 if left + width > self.width || top + height > self.height {
882 return None; // ROI out of bounds
883 }
884 if left != 0 || top != 0 {
885 todo!();
886 }
887 let stride = self.stride;
888 let new_min_size = calc_min_buf_size(width, height, stride, self.pixfmt);
889 if self.buf.len() < new_min_size {
890 return None; // Buffer too small for ROI
891 }
892 Some(DynamicFrame {
893 width,
894 height,
895 stride,
896 buf: Cow::Borrowed(&self.buf[..new_min_size]),
897 pixfmt: self.pixfmt,
898 })
899 }
900}
901
902/// Compile-time test to ensure [`DynamicFrame`] implements the [`Send`] trait.
903fn _test_dynamic_frame_is_send() {
904 fn implements<T: Send>() {}
905 implements::<DynamicFrame>();
906}
907
908impl std::fmt::Debug for DynamicFrame<'_> {
909 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
910 f.debug_struct("DynamicFrame")
911 .field("width", &self.width)
912 .field("height", &self.height)
913 .field("stride", &self.stride)
914 .field("pixfmt", &self.pixfmt)
915 .finish_non_exhaustive()
916 }
917}
918
919impl Stride for DynamicFrame<'_> {
920 /// Returns the stride (bytes per row) of the image.
921 ///
922 /// The stride represents the number of bytes from the start of one row
923 /// to the start of the next row. This may be larger than the minimum
924 /// required by the pixel format due to alignment requirements.
925 ///
926 /// # Examples
927 /// ```rust
928 /// # use strand_dynamic_frame::DynamicFrame;
929 /// # use machine_vision_formats::{PixFmt, Stride};
930 /// let data = vec![0u8; 1000];
931 /// let frame = DynamicFrame::from_buf(10, 10, 100, data, PixFmt::Mono8).unwrap();
932 /// assert_eq!(frame.stride(), 100);
933 /// ```
934 fn stride(&self) -> usize {
935 self.stride
936 }
937}