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