convert_image/
lib.rs

1//! Conversion functions to convert between image formats.
2//!
3//! This crate contains a number of functions and helper types allow converting
4//! to and from types specified in the [machine_vision_formats] crate, such as
5//! the trait [machine_vision_formats::ImageData].
6
7// TODO: Add support for Reversible Color Transform (RCT) YUV types
8
9use bayer as wang_debayer;
10use machine_vision_formats as formats;
11
12use formats::{
13    cow::CowImage,
14    image_ref::{ImageRef, ImageRefMut},
15    iter::{HasRowChunksExact, HasRowChunksExactMut},
16    owned::OImage,
17    pixel_format::{Mono8, NV12, RGB8},
18    ImageBufferMutRef, OwnedImageStride, PixFmt, PixelFormat,
19};
20
21type Result<T> = std::result::Result<T, Error>;
22
23/// Possible errors
24#[derive(thiserror::Error, Debug)]
25pub enum Error {
26    #[error("unimplemented pixel_format: {0:?}")]
27    UnimplementedPixelFormat(PixFmt),
28    #[error("unimplemented ROI width conversion")]
29    UnimplementedRoiWidthConversion,
30    #[error("ROI size exceeds original image")]
31    RoiExceedsOriginal,
32    #[error("invalid allocated buffer size")]
33    InvalidAllocatedBufferSize,
34    #[error("invalid allocated buffer stride")]
35    InvalidAllocatedBufferStride,
36    #[error("{0}")]
37    Bayer(#[from] wang_debayer::BayerError),
38    #[error("io error: {0}")]
39    Io(#[from] std::io::Error),
40    #[error("{0}")]
41    Image(#[from] image::ImageError),
42    #[error("unimplemented conversion {0} -> {1}")]
43    UnimplementedConversion(PixFmt, PixFmt),
44}
45
46#[inline]
47const fn calc_min_stride(w: u32, pixfmt: PixFmt) -> usize {
48    w as usize * pixfmt.bits_per_pixel() as usize / 8
49}
50
51#[inline]
52const fn calc_min_buf_size(w: u32, h: u32, stride: usize, pixfmt: PixFmt) -> usize {
53    if h == 0 {
54        return 0;
55    }
56    let all_but_last = (h - 1) as usize * stride;
57    let last = calc_min_stride(w, pixfmt);
58    debug_assert!(stride >= last);
59    all_but_last + last
60}
61
62#[allow(non_camel_case_types)]
63#[allow(non_snake_case)]
64#[derive(PartialEq, Eq, Debug)]
65struct RGB888 {
66    R: u8,
67    G: u8,
68    B: u8,
69}
70
71#[cfg(test)]
72impl RGB888 {
73    fn max_channel_distance(&self, other: &RGB888) -> i32 {
74        let dr = (self.R as i32 - other.R as i32).abs();
75        let dg = (self.G as i32 - other.G as i32).abs();
76        let db = (self.B as i32 - other.B as i32).abs();
77
78        let m1 = if dr > dg { dr } else { dg };
79
80        if m1 > db {
81            m1
82        } else {
83            db
84        }
85    }
86
87    fn distance(&self, other: &RGB888) -> i32 {
88        let dr = (self.R as i32 - other.R as i32).abs();
89        let dg = (self.G as i32 - other.G as i32).abs();
90        let db = (self.B as i32 - other.B as i32).abs();
91        dr + dg + db
92    }
93}
94
95#[allow(non_camel_case_types)]
96#[allow(non_snake_case)]
97#[derive(PartialEq, Eq, Debug)]
98struct YUV444 {
99    Y: u8,
100    U: u8,
101    V: u8,
102}
103
104#[test]
105fn test_f32_to_u8() {
106    // Validate some conversion assumptions.
107    assert_eq!(-1.0f32 as u8, 0u8);
108    assert_eq!(-2.0f32 as u8, 0u8);
109    assert_eq!(-100.0f32 as u8, 0u8);
110    assert_eq!(255.0f32 as u8, 255u8);
111    assert_eq!(255.1f32 as u8, 255u8);
112    assert_eq!(255.8f32 as u8, 255u8);
113    assert_eq!(5000.0f32 as u8, 255u8);
114}
115
116#[allow(non_snake_case)]
117fn YUV444_bt601_toRGB(Y: u8, U: u8, V: u8) -> RGB888 {
118    // See https://en.wikipedia.org/wiki/YCbCr
119    let Y = Y as f32;
120    let U = U as f32 - 128.0;
121    let V = V as f32 - 128.0;
122
123    let R = 1.0 * Y + 1.402 * V;
124    let G = 1.0 * Y + -0.344136 * U + -0.714136 * V;
125    let B = 1.0 * Y + 1.772 * U;
126
127    RGB888 {
128        R: R as u8,
129        G: G as u8,
130        B: B as u8,
131    }
132}
133
134#[allow(non_snake_case)]
135#[inline]
136fn RGB888toYUV444_bt601_full_swing(R: u8, G: u8, B: u8) -> YUV444 {
137    // See http://en.wikipedia.org/wiki/YUV and
138    // https://en.wikipedia.org/wiki/YCbCr. Should we consider converting to f32
139    // representation as in YUV444_bt601_toRGB above?
140    let Y = RGB888toY4_bt601_full_swing(R, G, B);
141    let R = R as i32;
142    let G = G as i32;
143    let B = B as i32;
144    let U = ((-43 * R - 84 * G + 127 * B + 128) >> 8) + 128;
145    let V = ((127 * R - 106 * G - 21 * B + 128) >> 8) + 128;
146    YUV444 {
147        Y,
148        U: U as u8,
149        V: V as u8,
150    }
151}
152
153#[allow(non_snake_case)]
154#[inline]
155fn RGB888toY4_bt601_full_swing(R: u8, G: u8, B: u8) -> u8 {
156    // See http://en.wikipedia.org/wiki/YUV and
157    // https://en.wikipedia.org/wiki/YCbCr. Should we consider converting to f32
158    // representation as in YUV444_bt601_toRGB above?
159    let R = R as i32;
160    let G = G as i32;
161    let B = B as i32;
162    let Y = (77 * R + 150 * G + 29 * B + 128) >> 8;
163    Y as u8
164}
165
166/// Convert an input [image::DynamicImage] to an RGB8 image.
167pub fn image_to_rgb8(
168    input: image::DynamicImage,
169) -> Result<impl OwnedImageStride<formats::pixel_format::RGB8>> {
170    let rgb = input.to_rgb8();
171    let (width, height) = rgb.dimensions();
172    let stride = width as usize * 3;
173    let data = rgb.into_vec();
174
175    Ok(OImage::new(width, height, stride, data).unwrap())
176}
177
178/// Copy an YUV422 input image to a pre-allocated RGB8 buffer.
179fn yuv422_into_rgb(
180    src_yuv422: &dyn HasRowChunksExact<formats::pixel_format::YUV422>,
181    dest_rgb: &mut dyn HasRowChunksExactMut<RGB8>,
182) -> Result<()> {
183    // The destination must be at least this large per row.
184    let min_stride = src_yuv422.width() as usize * PixFmt::RGB8.bits_per_pixel() as usize / 8;
185    if dest_rgb.stride() < min_stride {
186        return Err(Error::InvalidAllocatedBufferStride);
187    }
188
189    let expected_size = dest_rgb.stride() * src_yuv422.height() as usize;
190    if dest_rgb.buffer_mut_ref().data.len() != expected_size {
191        return Err(Error::InvalidAllocatedBufferSize);
192    }
193
194    let w = src_yuv422.width() as usize;
195    for (src_row, dest_row) in src_yuv422
196        .rowchunks_exact()
197        .zip(dest_rgb.rowchunks_exact_mut())
198    {
199        for (result_chunk, yuv422_pixpair) in dest_row[..(w * 3)]
200            .chunks_exact_mut(6)
201            .zip(src_row[..w * 2].chunks_exact(4))
202        {
203            let u = yuv422_pixpair[0];
204            let y1 = yuv422_pixpair[1];
205            let v = yuv422_pixpair[2];
206            let y2 = yuv422_pixpair[3];
207
208            let tmp_rgb1 = YUV444_bt601_toRGB(y1, u, v);
209            let tmp_rgb2 = YUV444_bt601_toRGB(y2, u, v);
210
211            result_chunk[0] = tmp_rgb1.R;
212            result_chunk[1] = tmp_rgb1.G;
213            result_chunk[2] = tmp_rgb1.B;
214
215            result_chunk[3] = tmp_rgb2.R;
216            result_chunk[4] = tmp_rgb2.G;
217            result_chunk[5] = tmp_rgb2.B;
218        }
219    }
220    Ok(())
221}
222
223fn into_yuv444<FMT>(
224    frame: &dyn HasRowChunksExact<FMT>,
225    dest: &mut dyn HasRowChunksExactMut<machine_vision_formats::pixel_format::YUV444>,
226) -> Result<()>
227where
228    FMT: PixelFormat,
229{
230    if frame.width() != dest.width() || frame.height() != dest.height() {
231        return Err(Error::InvalidAllocatedBufferSize);
232    }
233    let w = frame.width() as usize;
234
235    // Convert to Mono8 or RGB8 TODO: if input encoding is YUV already, do
236    // something better than this. We can assume that it won't be YUV444 because
237    // we check for such no-op calls in `convert_into()`. But other YUV
238    // encodings won't be caught there and we should avoid a round-trip through
239    // RGB.
240    let frame = to_rgb8_or_mono8(frame)?;
241
242    match &frame {
243        SupportedEncoding::Mono(mono) => {
244            for (dest_row, src_row) in dest.rowchunks_exact_mut().zip(mono.rowchunks_exact()) {
245                for (dest_pixel, src_pixel) in
246                    dest_row[..(w * 3)].chunks_exact_mut(3).zip(&src_row[..w])
247                {
248                    let yuv = YUV444 {
249                        Y: *src_pixel,
250                        U: 128,
251                        V: 128,
252                    };
253                    dest_pixel[0] = yuv.Y;
254                    dest_pixel[1] = yuv.U;
255                    dest_pixel[2] = yuv.V;
256                }
257            }
258        }
259        SupportedEncoding::Rgb(rgb) => {
260            for (dest_row, src_row) in dest.rowchunks_exact_mut().zip(rgb.rowchunks_exact()) {
261                for (dest_pixel, src_pixel) in dest_row[..(w * 3)]
262                    .chunks_exact_mut(3)
263                    .zip(src_row[..(w * 3)].chunks_exact(3))
264                {
265                    let yuv =
266                        RGB888toYUV444_bt601_full_swing(src_pixel[0], src_pixel[1], src_pixel[2]);
267                    dest_pixel[0] = yuv.Y;
268                    dest_pixel[1] = yuv.U;
269                    dest_pixel[2] = yuv.V;
270                }
271            }
272        }
273    };
274    Ok(())
275}
276
277/// Copy an input bayer image to a pre-allocated RGB8 buffer.
278fn bayer_into_rgb<FMT>(
279    frame: &dyn HasRowChunksExact<FMT>,
280    dest_rgb: &mut dyn HasRowChunksExactMut<RGB8>,
281) -> Result<()>
282where
283    FMT: formats::PixelFormat,
284{
285    let dest_stride = dest_rgb.stride();
286
287    if frame.stride() != frame.width() as usize {
288        return Err(Error::UnimplementedRoiWidthConversion);
289    }
290
291    // The debayer code expects exactly this stride.
292    let expected_stride = frame.width() as usize * PixFmt::RGB8.bits_per_pixel() as usize / 8;
293    if dest_stride != expected_stride {
294        return Err(Error::InvalidAllocatedBufferStride);
295    }
296
297    let src_fmt = machine_vision_formats::pixel_format::pixfmt::<FMT>().unwrap();
298
299    let cfa = match src_fmt {
300        formats::pixel_format::PixFmt::BayerRG8 => wang_debayer::CFA::RGGB,
301        formats::pixel_format::PixFmt::BayerGB8 => wang_debayer::CFA::GBRG,
302        formats::pixel_format::PixFmt::BayerGR8 => wang_debayer::CFA::GRBG,
303        formats::pixel_format::PixFmt::BayerBG8 => wang_debayer::CFA::BGGR,
304        _ => {
305            return Err(Error::UnimplementedPixelFormat(src_fmt));
306        }
307    };
308
309    use std::io::Cursor;
310
311    {
312        let mut dst = wang_debayer::RasterMut::new(
313            frame.width() as usize,
314            frame.height() as usize,
315            wang_debayer::RasterDepth::Depth8,
316            dest_rgb.buffer_mut_ref().data,
317        );
318
319        wang_debayer::run_demosaic(
320            &mut Cursor::new(&frame.image_data()),
321            wang_debayer::BayerDepth::Depth8,
322            cfa,
323            wang_debayer::Demosaic::Cubic,
324            &mut dst,
325        )?;
326    }
327    Ok(())
328}
329
330/// Copy an input mono8 image to a pre-allocated RGB8 buffer.
331///
332/// This copies the mono channel to each of the R, G and B channels.
333fn mono8_into_rgb8(
334    src: &dyn HasRowChunksExact<formats::pixel_format::Mono8>,
335    dest_rgb: &mut dyn HasRowChunksExactMut<RGB8>,
336) -> Result<()> {
337    let dest_stride = dest_rgb.stride();
338    // The destination must be at least this large per row.
339    let min_stride = src.width() as usize * PixFmt::RGB8.bits_per_pixel() as usize / 8;
340    if dest_stride < min_stride {
341        return Err(Error::InvalidAllocatedBufferStride);
342    }
343
344    let w = src.width() as usize;
345    for (src_row, dest_row) in src.rowchunks_exact().zip(dest_rgb.rowchunks_exact_mut()) {
346        for (dest_pix, src_pix) in dest_row[..(w * 3)].chunks_exact_mut(3).zip(&src_row[..w]) {
347            dest_pix[0] = *src_pix;
348            dest_pix[1] = *src_pix;
349            dest_pix[2] = *src_pix;
350        }
351    }
352    Ok(())
353}
354
355/// Copy an input rgba8 image to a pre-allocated RGB8 buffer.
356fn rgba_into_rgb(
357    frame: &dyn HasRowChunksExact<formats::pixel_format::RGBA8>,
358    dest: &mut dyn HasRowChunksExactMut<RGB8>,
359) -> Result<()> {
360    let dest_stride = dest.stride();
361
362    // The destination must be at least this large per row.
363    let min_stride = frame.width() as usize * PixFmt::RGB8.bits_per_pixel() as usize / 8;
364    if dest_stride < min_stride {
365        return Err(Error::InvalidAllocatedBufferStride);
366    }
367
368    let w = frame.width() as usize;
369    for (src_row, dest_row) in frame.rowchunks_exact().zip(dest.rowchunks_exact_mut()) {
370        for (dest_pix, src_pix) in dest_row[..(w * 3)]
371            .chunks_exact_mut(3)
372            .zip(src_row[..(w * 4)].chunks_exact(4))
373        {
374            dest_pix[0] = src_pix[0];
375            dest_pix[1] = src_pix[1];
376            dest_pix[2] = src_pix[2];
377            // src_pix[3] is not used.
378        }
379    }
380    Ok(())
381}
382
383/// Convert RGB8 image data into pre-allocated Mono8 buffer.
384fn rgb8_into_mono8(
385    frame: &dyn HasRowChunksExact<formats::pixel_format::RGB8>,
386    dest: &mut dyn HasRowChunksExactMut<Mono8>,
387) -> Result<()> {
388    if !(dest.height() == frame.height() && dest.width() == frame.width()) {
389        return Err(Error::InvalidAllocatedBufferSize);
390    }
391
392    let w = frame.width() as usize;
393    for (src_row, dest_row) in frame.rowchunks_exact().zip(dest.rowchunks_exact_mut()) {
394        let y_iter = src_row[..w * 3]
395            .chunks_exact(3)
396            .map(|rgb| RGB888toY4_bt601_full_swing(rgb[0], rgb[1], rgb[2]));
397
398        let dest_iter = dest_row[0..w].iter_mut();
399
400        for (ydest, y) in dest_iter.zip(y_iter) {
401            *ydest = y;
402        }
403    }
404
405    Ok(())
406}
407
408/// Convert YUV444 image data into pre-allocated Mono8 buffer.
409fn yuv444_into_mono8(
410    frame: &dyn HasRowChunksExact<formats::pixel_format::YUV444>,
411    dest: &mut dyn HasRowChunksExactMut<Mono8>,
412) -> Result<()> {
413    if !(dest.height() == frame.height() && dest.width() == frame.width()) {
414        return Err(Error::InvalidAllocatedBufferSize);
415    }
416
417    let w = frame.width() as usize;
418    for (src_row, dest_row) in frame.rowchunks_exact().zip(dest.rowchunks_exact_mut()) {
419        let y_iter = src_row[..w * 3].chunks_exact(3).map(|yuv444| yuv444[0]);
420
421        let dest_iter = dest_row[0..w].iter_mut();
422
423        for (ydest, y) in dest_iter.zip(y_iter) {
424            *ydest = y;
425        }
426    }
427
428    Ok(())
429}
430
431/// Convert NV12 image data into pre-allocated Mono8 buffer.
432fn nv12_into_mono8(
433    frame: &dyn HasRowChunksExact<formats::pixel_format::NV12>,
434    dest: &mut dyn HasRowChunksExactMut<Mono8>,
435) -> Result<()> {
436    if !(dest.height() == frame.height() && dest.width() == frame.width()) {
437        return Err(Error::InvalidAllocatedBufferSize);
438    }
439
440    for (src_row, dest_row) in frame.rowchunks_exact().zip(dest.rowchunks_exact_mut()) {
441        dest_row[..frame.width() as usize].copy_from_slice(&src_row[..frame.width() as usize]);
442    }
443
444    Ok(())
445}
446
447/// If needed, copy original image data to remove stride.
448fn remove_padding<FMT>(frame: &dyn HasRowChunksExact<FMT>) -> Result<CowImage<'_, FMT>>
449where
450    FMT: PixelFormat,
451{
452    let fmt = machine_vision_formats::pixel_format::pixfmt::<FMT>().unwrap();
453    let bytes_per_pixel = fmt.bits_per_pixel() as usize / 8;
454    let dest_stride = frame.width() as usize * bytes_per_pixel;
455    if dest_stride == frame.stride() {
456        Ok(CowImage::Borrowed(force_pixel_format_ref(frame)))
457    } else {
458        if frame.stride() < dest_stride {
459            return Err(Error::InvalidAllocatedBufferStride);
460        }
461        // allocate output
462        let mut dest_buf = vec![0u8; frame.height() as usize * dest_stride];
463        // trim input slice to height
464        frame
465            .rowchunks_exact()
466            .zip(dest_buf.chunks_exact_mut(dest_stride))
467            .for_each(|(src_row_full, dest_row)| {
468                dest_row[..dest_stride].copy_from_slice(&src_row_full[..dest_stride]);
469            });
470        // Return the new buffer as a new image.
471        Ok(CowImage::Owned(
472            OImage::new(frame.width(), frame.height(), dest_stride, dest_buf).unwrap(),
473        ))
474    }
475}
476
477/// Force interpretation of data from frame into another pixel_format.
478///
479/// This moves the data and does not perform conversion of the underlying data,
480/// but rather changes only the rust type. See [force_pixel_format_ref] for a
481/// function which makes a view of the original data.
482pub fn force_pixel_format<FRAME, FMT1, FMT2>(frame: FRAME) -> impl OwnedImageStride<FMT2>
483where
484    FRAME: OwnedImageStride<FMT1>,
485    FMT2: PixelFormat,
486{
487    let width = frame.width();
488    let height = frame.height();
489    let stride = frame.stride();
490    let image_data = frame.into(); // Move the original data.
491
492    OImage::new(width, height, stride, image_data).unwrap()
493}
494
495/// Force interpretation of data from frame into another pixel_format.
496///
497/// This makes a view of the original data and does not perform conversion of
498/// the underlying data, but rather changes only the rust type. See
499/// [force_pixel_format] for a function which consumes the original data and
500/// moves it into the output.
501///
502/// For a mutable version of this function, see [force_pixel_format_ref_mut].
503pub fn force_pixel_format_ref<FMT1, FMT2>(frame: &dyn HasRowChunksExact<FMT1>) -> ImageRef<'_, FMT2>
504where
505    FMT2: PixelFormat,
506{
507    ImageRef::new(
508        frame.width(),
509        frame.height(),
510        frame.stride(),
511        frame.image_data(),
512    )
513    .unwrap()
514}
515
516/// Force interpretation of data from frame into another pixel_format.
517///
518/// This makes a view of the original data and does not perform conversion of
519/// the underlying data, but rather changes only the rust type. See
520/// [force_pixel_format] for a function which consumes the original data and
521/// moves it into the output.
522///
523/// This is a mutable version of [force_pixel_format_ref].
524fn force_pixel_format_ref_mut<FMT1, FMT2>(
525    frame: &mut dyn HasRowChunksExactMut<FMT1>,
526) -> ImageRefMut<'_, FMT2>
527where
528    FMT1: PixelFormat,
529    FMT2: PixelFormat,
530{
531    ImageRefMut::new(
532        frame.width(),
533        frame.height(),
534        frame.stride(),
535        frame.buffer_mut_ref().data,
536    )
537    .unwrap()
538}
539
540/// Force interpretation of data from frame into another pixel_format.
541fn force_buffer_pixel_format_ref<FMT1, FMT2>(
542    orig: ImageBufferMutRef<'_, FMT1>,
543) -> ImageBufferMutRef<'_, FMT2> {
544    ImageBufferMutRef::new(orig.data)
545}
546
547/// Convert input, a frame implementing [`OwnedImageStride<SRC>`], into
548/// an output implementing [`HasRowChunksExact<DEST>`].
549///
550/// The source data will be moved, not copied, into the destination if no format
551/// change is required, otherwise an image with a newly allocated image buffer
552/// will be returned.
553///
554/// This is a general purpose function which should be able to convert between
555/// many types as efficiently as possible. In case no data needs to be copied,
556/// no data is copied.
557///
558/// For a version which converts into a pre-allocated buffer, use `convert_into`
559/// (which will copy the image even if the format remains unchanged).
560pub fn convert_owned<OWNED, SRC, DEST>(source: OWNED) -> Result<impl HasRowChunksExact<DEST>>
561where
562    OWNED: OwnedImageStride<SRC>,
563    SRC: PixelFormat,
564    DEST: PixelFormat,
565{
566    let src_fmt = machine_vision_formats::pixel_format::pixfmt::<SRC>().unwrap();
567    let dest_fmt = machine_vision_formats::pixel_format::pixfmt::<DEST>().unwrap();
568
569    // If format does not change, move original data without copy.
570    if src_fmt == dest_fmt {
571        let width = source.width();
572        let height = source.height();
573        let stride = source.stride();
574        let buf: Vec<u8> = source.into();
575        let dest = OImage::new(width, height, stride, buf).unwrap();
576        return Ok(CowImage::Owned(dest));
577    }
578
579    // Allocate minimal size buffer for new image.
580    let dest_min_stride = dest_fmt.bits_per_pixel() as usize * source.width() as usize / 8;
581    let dest_size = source.height() as usize * dest_min_stride;
582    let image_data = vec![0u8; dest_size];
583    let mut dest =
584        OImage::new(source.width(), source.height(), dest_min_stride, image_data).unwrap();
585
586    // Fill the new buffer.
587    convert_into(&source, &mut dest)?;
588
589    // Return the new buffer as a new image.
590    Ok(CowImage::Owned(dest))
591}
592
593/// Convert input image, a reference to a trait object implementing
594/// [`HasRowChunksExact<SRC>`], into an output implementing
595/// [`HasRowChunksExact<DEST>`].
596///
597/// The output will borrow from the source if no format change is required,
598/// otherwise a newly allocated image will be returned.
599///
600/// This is a general purpose function which should be able to convert between
601/// many types as efficiently as possible. In case no data needs to be copied,
602/// no data will be copied.
603///
604/// For a version which converts into a pre-allocated buffer, use [convert_into]
605/// (which will copy the image even if the format remains unchanged).
606pub fn convert_ref<SRC, DEST>(
607    source: &dyn HasRowChunksExact<SRC>,
608) -> Result<impl HasRowChunksExact<DEST> + '_>
609where
610    SRC: PixelFormat,
611    DEST: PixelFormat,
612{
613    let src_fmt = machine_vision_formats::pixel_format::pixfmt::<SRC>().unwrap();
614    let dest_fmt = machine_vision_formats::pixel_format::pixfmt::<DEST>().unwrap();
615
616    // If format does not change, return reference to original image without copy.
617    if src_fmt == dest_fmt {
618        return Ok(CowImage::Borrowed(force_pixel_format_ref(source)));
619    }
620
621    // Allocate minimal size buffer for new image.
622    let dest_stride = calc_min_stride(source.width(), dest_fmt);
623    let dest_size = calc_min_buf_size(source.width(), source.height(), dest_stride, dest_fmt);
624    let image_data = vec![0u8; dest_size];
625    let mut dest = OImage::new(source.width(), source.height(), dest_stride, image_data).unwrap();
626
627    // Fill the new buffer.
628    convert_into(source, &mut dest)?;
629
630    // Return the new buffer as a new image.
631    Ok(CowImage::Owned(dest))
632}
633
634/// Convert input image, a reference to a trait object implementing
635/// [`HasRowChunksExact<SRC>`], into a mutable reference to an already allocated
636///  destination frame implementing [`HasRowChunksExactMut<DEST>`].
637///
638/// This is a general purpose function which should be able to convert between
639/// many types as efficiently as possible.
640pub fn convert_into<SRC, DEST>(
641    source: &dyn HasRowChunksExact<SRC>,
642    dest: &mut dyn HasRowChunksExactMut<DEST>,
643) -> Result<()>
644where
645    SRC: PixelFormat,
646    DEST: PixelFormat,
647{
648    let src_fmt = machine_vision_formats::pixel_format::pixfmt::<SRC>().unwrap();
649    let dest_fmt = machine_vision_formats::pixel_format::pixfmt::<DEST>().unwrap();
650
651    if dest.width() != source.width() || dest.height() != source.height() {
652        return Err(Error::InvalidAllocatedBufferSize);
653    }
654
655    let dest_stride = dest.stride();
656
657    // If format does not change, copy the data row-by-row to respect strides.
658    if src_fmt == dest_fmt {
659        use itertools::izip;
660        let dest_stride = calc_min_stride(source.width(), dest_fmt);
661        for (src_row, dest_row) in izip![source.rowchunks_exact(), dest.rowchunks_exact_mut(),] {
662            debug_assert_eq!(src_row.len(), dest_stride);
663            dest_row[..dest_stride].copy_from_slice(src_row);
664        }
665        return Ok(());
666    }
667
668    match dest_fmt {
669        formats::pixel_format::PixFmt::RGB8 => {
670            let mut dest_rgb = ImageRefMut::new(
671                dest.width(),
672                dest.height(),
673                dest.stride(),
674                dest.buffer_mut_ref().data,
675            )
676            .unwrap();
677            // Convert to RGB8..
678            match src_fmt {
679                formats::pixel_format::PixFmt::BayerRG8
680                | formats::pixel_format::PixFmt::BayerGB8
681                | formats::pixel_format::PixFmt::BayerGR8
682                | formats::pixel_format::PixFmt::BayerBG8 => {
683                    // .. from bayer.
684                    // The bayer code requires no padding in the input image.
685                    let exact_stride = remove_padding(source)?;
686                    bayer_into_rgb(&exact_stride, &mut dest_rgb)?;
687                    Ok(())
688                }
689                formats::pixel_format::PixFmt::Mono8 => {
690                    // .. from mono8.
691                    let mono8 = force_pixel_format_ref(source);
692                    mono8_into_rgb8(&mono8, &mut dest_rgb)?;
693                    Ok(())
694                }
695                formats::pixel_format::PixFmt::RGBA8 => {
696                    // .. from rgba8.
697                    let rgba8 = force_pixel_format_ref(source);
698                    rgba_into_rgb(&rgba8, &mut dest_rgb)?;
699                    Ok(())
700                }
701                formats::pixel_format::PixFmt::YUV422 => {
702                    // .. from YUV422.
703                    let yuv422 = force_pixel_format_ref(source);
704                    yuv422_into_rgb(&yuv422, &mut dest_rgb)?;
705                    Ok(())
706                }
707                _ => Err(Error::UnimplementedConversion(src_fmt, dest_fmt)),
708            }
709        }
710        formats::pixel_format::PixFmt::Mono8 => {
711            let mut dest_mono8 = ImageRefMut::new(
712                dest.width(),
713                dest.height(),
714                dest.stride(),
715                dest.buffer_mut_ref().data,
716            )
717            .unwrap();
718
719            // Convert to Mono8..
720            match src_fmt {
721                formats::pixel_format::PixFmt::RGB8 => {
722                    // .. from RGB8.
723                    let tmp = force_pixel_format_ref(source);
724                    {
725                        rgb8_into_mono8(&tmp, &mut dest_mono8)?;
726                    }
727                    Ok(())
728                }
729                formats::pixel_format::PixFmt::YUV444 => {
730                    // .. from YUV444.
731                    let yuv444 = force_pixel_format_ref(source);
732                    yuv444_into_mono8(&yuv444, &mut dest_mono8)?;
733                    Ok(())
734                }
735                formats::pixel_format::PixFmt::NV12 => {
736                    // .. from NV12.
737                    let nv12 = force_pixel_format_ref(source);
738                    nv12_into_mono8(&nv12, &mut dest_mono8)?;
739                    Ok(())
740                }
741                formats::pixel_format::PixFmt::BayerRG8
742                | formats::pixel_format::PixFmt::BayerGB8
743                | formats::pixel_format::PixFmt::BayerGR8
744                | formats::pixel_format::PixFmt::BayerBG8 => {
745                    // .. from bayer.
746                    // Convert Bayer first to RGB
747                    let width: usize = source.width().try_into().unwrap();
748                    let height: usize = source.height().try_into().unwrap();
749                    let stride = width * 3;
750                    let rgb_buf = &mut vec![0u8; stride * height];
751                    let mut tmp_rgb =
752                        ImageRefMut::new(source.width(), source.height(), stride, rgb_buf).unwrap();
753                    // The bayer code requires no padding in the input image.
754                    let exact_stride = remove_padding(source)?;
755                    bayer_into_rgb(&exact_stride, &mut tmp_rgb)?;
756                    // Then to mono8
757                    rgb8_into_mono8(&tmp_rgb, &mut dest_mono8)?;
758                    Ok(())
759                }
760                _ => Err(Error::UnimplementedConversion(src_fmt, dest_fmt)),
761            }
762        }
763        formats::pixel_format::PixFmt::YUV444 => {
764            // Convert to YUV444.
765            let mut dest2: ImageRefMut<'_, machine_vision_formats::pixel_format::YUV444> =
766                force_pixel_format_ref_mut(dest);
767            into_yuv444(source, &mut dest2)?;
768            Ok(())
769        }
770        formats::pixel_format::PixFmt::NV12 => {
771            // Convert to NV12.
772            let mut dest2 = force_buffer_pixel_format_ref(dest.buffer_mut_ref());
773            encode_into_nv12_inner(source, &mut dest2, dest_stride)?;
774            Ok(())
775        }
776        _ => Err(Error::UnimplementedConversion(src_fmt, dest_fmt)),
777    }
778}
779
780/// An image which can be directly encoded as RGB8 or Mono8
781///
782/// This nearly supports the HasRowChunksExact trait, but we avoid it because it has a
783/// type parameter specifying the pixel format, whereas we don't use that here
784/// and instead explicitly represent an image with one of two possible pixel
785/// formats.
786enum SupportedEncoding<'a> {
787    Rgb(Box<dyn HasRowChunksExact<formats::pixel_format::RGB8> + 'a>),
788    Mono(Box<dyn HasRowChunksExact<formats::pixel_format::Mono8> + 'a>),
789}
790
791impl SupportedEncoding<'_> {
792    #[inline]
793    fn width(&self) -> u32 {
794        match self {
795            SupportedEncoding::Rgb(m) => m.width(),
796            SupportedEncoding::Mono(m) => m.width(),
797        }
798    }
799    #[inline]
800    fn height(&self) -> u32 {
801        match self {
802            SupportedEncoding::Rgb(m) => m.height(),
803            SupportedEncoding::Mono(m) => m.height(),
804        }
805    }
806    #[inline]
807    fn stride(&self) -> usize {
808        match self {
809            SupportedEncoding::Rgb(m) => m.stride(),
810            SupportedEncoding::Mono(m) => m.stride(),
811        }
812    }
813    #[inline]
814    fn image_data(&self) -> &[u8] {
815        match self {
816            SupportedEncoding::Rgb(m) => m.image_data(),
817            SupportedEncoding::Mono(m) => m.image_data(),
818        }
819    }
820    #[inline]
821    fn rowchunks_exact(&self) -> machine_vision_formats::iter::RowChunksExact {
822        match self {
823            SupportedEncoding::Rgb(m) => m.rowchunks_exact(),
824            SupportedEncoding::Mono(m) => m.rowchunks_exact(),
825        }
826    }
827    #[inline]
828    fn pixfmt(&self) -> formats::pixel_format::PixFmt {
829        match self {
830            SupportedEncoding::Rgb(_) => {
831                machine_vision_formats::pixel_format::pixfmt::<RGB8>().unwrap()
832            }
833            SupportedEncoding::Mono(_) => {
834                machine_vision_formats::pixel_format::pixfmt::<Mono8>().unwrap()
835            }
836        }
837    }
838}
839
840/// If the input is Mono8, return as Mono8, otherwise, return as RGB8.
841fn to_rgb8_or_mono8<FMT>(frame: &dyn HasRowChunksExact<FMT>) -> Result<SupportedEncoding<'_>>
842where
843    FMT: PixelFormat,
844{
845    if machine_vision_formats::pixel_format::pixfmt::<FMT>().unwrap()
846        == formats::pixel_format::PixFmt::Mono8
847    {
848        let im = convert_ref::<_, formats::pixel_format::Mono8>(frame)?;
849        Ok(SupportedEncoding::Mono(Box::new(im)))
850    } else {
851        let im = convert_ref::<_, formats::pixel_format::RGB8>(frame)?;
852        Ok(SupportedEncoding::Rgb(Box::new(im)))
853    }
854}
855
856/// Convert any type implementing [HasRowChunksExact] to an [image::DynamicImage].
857pub fn frame_to_image<FMT>(frame: &dyn HasRowChunksExact<FMT>) -> Result<image::DynamicImage>
858where
859    FMT: PixelFormat,
860{
861    let frame = to_rgb8_or_mono8(frame)?;
862
863    let (coding, pixfmt) = match &frame {
864        SupportedEncoding::Mono(_) => {
865            let pixfmt = machine_vision_formats::pixel_format::pixfmt::<Mono8>().unwrap();
866            debug_assert_eq!(frame.pixfmt(), pixfmt);
867            (image::ColorType::L8, pixfmt)
868        }
869        SupportedEncoding::Rgb(_) => {
870            let pixfmt = machine_vision_formats::pixel_format::pixfmt::<RGB8>().unwrap();
871            debug_assert_eq!(frame.pixfmt(), pixfmt);
872            (image::ColorType::Rgb8, pixfmt)
873        }
874    };
875
876    let packed_stride = calc_min_stride(frame.width(), pixfmt);
877
878    // The encoders in the `image` crate only handle packed inputs. We check if
879    // our data is packed and if not, make a packed copy.
880
881    let mut packed = None;
882    if frame.stride() != packed_stride {
883        let dest_sz = calc_min_buf_size(frame.width(), frame.height(), packed_stride, pixfmt);
884        let mut dest = Vec::with_capacity(dest_sz);
885        for src_row in frame.rowchunks_exact() {
886            debug_assert_eq!(src_row.len(), packed_stride);
887            dest.extend_from_slice(&src_row[..packed_stride]);
888        }
889        packed = Some(dest);
890    }
891
892    let packed = match packed {
893        None => frame.image_data().to_vec(),
894        Some(p) => p,
895    };
896
897    match coding {
898        image::ColorType::L8 => {
899            let imbuf: image::ImageBuffer<image::Luma<_>, _> =
900                image::ImageBuffer::from_raw(frame.width(), frame.height(), packed).unwrap();
901            Ok(imbuf.into())
902        }
903        image::ColorType::Rgb8 => {
904            let imbuf: image::ImageBuffer<image::Rgb<_>, _> =
905                image::ImageBuffer::from_raw(frame.width(), frame.height(), packed).unwrap();
906            Ok(imbuf.into())
907        }
908        _ => {
909            unreachable!()
910        }
911    }
912}
913
914/// How to encode to an image buffer
915#[derive(Copy, Clone, Debug, PartialEq, Eq)]
916pub enum EncoderOptions {
917    /// Encode to a JPEG buffer with a quality specified from 0 to 100.
918    Jpeg(u8),
919    /// Encode to a PNG buffer.
920    Png,
921}
922
923/// Convert any type implementing [HasRowChunksExact] to a Jpeg or Png buffer
924/// using the [EncoderOptions] specified.
925pub fn frame_to_encoded_buffer<FMT>(
926    frame: &dyn HasRowChunksExact<FMT>,
927    opts: EncoderOptions,
928) -> Result<Vec<u8>>
929where
930    FMT: PixelFormat,
931{
932    let mut result = Vec::new();
933
934    let frame = to_rgb8_or_mono8(frame)?;
935
936    let (coding, bytes_per_pixel) = match &frame {
937        SupportedEncoding::Mono(_) => (image::ColorType::L8, 1),
938        SupportedEncoding::Rgb(_) => (image::ColorType::Rgb8, 3),
939    };
940
941    // The encoders in the `image` crate only handle packed inputs. We check if
942    // our data is packed and if not, make a packed copy.
943
944    let mut packed = None;
945    let packed_stride = frame.width() as usize * bytes_per_pixel as usize;
946    if frame.stride() != packed_stride {
947        let mut dest = Vec::with_capacity(packed_stride * frame.height() as usize);
948        let src = frame.image_data();
949        let chunk_iter = src.chunks_exact(frame.stride());
950        if !chunk_iter.remainder().is_empty() {
951            return Err(Error::InvalidAllocatedBufferSize);
952        }
953        for src_row in chunk_iter {
954            dest.extend_from_slice(&src_row[..packed_stride]);
955        }
956        packed = Some(dest);
957    }
958
959    let use_frame = match &packed {
960        None => frame.image_data(),
961        Some(p) => p.as_slice(),
962    };
963
964    match opts {
965        EncoderOptions::Jpeg(quality) => {
966            let mut encoder =
967                image::codecs::jpeg::JpegEncoder::new_with_quality(&mut result, quality);
968            encoder.encode(use_frame, frame.width(), frame.height(), coding.into())?;
969        }
970        EncoderOptions::Png => {
971            use image::ImageEncoder;
972            let encoder = image::codecs::png::PngEncoder::new(&mut result);
973            encoder.write_image(use_frame, frame.width(), frame.height(), coding.into())?;
974        }
975    }
976    Ok(result)
977}
978
979fn encode_into_nv12_inner<FMT>(
980    frame: &dyn HasRowChunksExact<FMT>,
981    dest: &mut ImageBufferMutRef<NV12>,
982    dest_stride: usize,
983) -> Result<()>
984where
985    FMT: PixelFormat,
986{
987    use itertools::izip;
988
989    let frame = to_rgb8_or_mono8(frame)?;
990
991    let luma_size = frame.height() as usize * dest_stride;
992
993    let (nv12_luma, nv12_chroma) = dest.data.split_at_mut(luma_size);
994
995    match &frame {
996        SupportedEncoding::Mono(frame) => {
997            // ported from convertYUVpitchtoNV12 in NvEncoder.cpp
998            let w = frame.width() as usize;
999            for y in 0..frame.height() as usize {
1000                let start = dest_stride * y;
1001                let src = frame.stride() * y;
1002                nv12_luma[start..(start + w)].copy_from_slice(&frame.image_data()[src..(src + w)]);
1003            }
1004
1005            for y in 0..(frame.height() as usize / 2) {
1006                let start = dest_stride * y;
1007                for x in (0..frame.width() as usize).step_by(2) {
1008                    nv12_chroma[start + x] = 128u8;
1009                    nv12_chroma[start + (x + 1)] = 128u8;
1010                }
1011            }
1012        }
1013        SupportedEncoding::Rgb(frame) => {
1014            let w = frame.width() as usize;
1015
1016            // Allocate temporary storage for full-res chroma planes.
1017            // TODO: eliminate this or make it much smaller (e.g. two rows).
1018            let mut u_plane_full: Vec<u8> = vec![0; nv12_luma.len()];
1019            let mut v_plane_full: Vec<u8> = vec![0; nv12_luma.len()];
1020
1021            for (src_row, dest_row, udest_row, vdest_row) in izip![
1022                frame.image_data().chunks_exact(frame.stride()),
1023                nv12_luma.chunks_exact_mut(dest_stride),
1024                u_plane_full.chunks_exact_mut(dest_stride),
1025                v_plane_full.chunks_exact_mut(dest_stride),
1026            ] {
1027                let yuv_iter = src_row[..w * 3]
1028                    .chunks_exact(3)
1029                    .map(|rgb| RGB888toYUV444_bt601_full_swing(rgb[0], rgb[1], rgb[2]));
1030
1031                let dest_iter = dest_row[0..w].iter_mut();
1032
1033                for (ydest, udest, vdest, yuv) in izip![dest_iter, udest_row, vdest_row, yuv_iter] {
1034                    *ydest = yuv.Y;
1035                    *udest = yuv.U;
1036                    *vdest = yuv.V;
1037                }
1038            }
1039
1040            // Now downsample the full-res chroma planes.
1041            let half_stride = dest_stride; // This is not half because the two channels are interleaved.
1042            for y in 0..(frame.height() as usize / 2) {
1043                for x in 0..(frame.width() as usize / 2) {
1044                    let u_sum: u16 = u_plane_full[dest_stride * 2 * y + 2 * x] as u16
1045                        + u_plane_full[dest_stride * 2 * y + 2 * x + 1] as u16
1046                        + u_plane_full[dest_stride * (2 * y + 1) + 2 * x] as u16
1047                        + u_plane_full[dest_stride * (2 * y + 1) + 2 * x + 1] as u16;
1048                    let v_sum: u16 = v_plane_full[dest_stride * 2 * y + 2 * x] as u16
1049                        + v_plane_full[dest_stride * 2 * y + 2 * x + 1] as u16
1050                        + v_plane_full[dest_stride * (2 * y + 1) + 2 * x] as u16
1051                        + v_plane_full[dest_stride * (2 * y + 1) + 2 * x + 1] as u16;
1052
1053                    nv12_chroma[(half_stride * y) + 2 * x] = (u_sum / 4) as u8;
1054                    nv12_chroma[(half_stride * y) + 2 * x + 1] = (v_sum / 4) as u8;
1055                }
1056            }
1057        }
1058    }
1059    Ok(())
1060}
1061
1062#[cfg(test)]
1063mod tests {
1064    use crate::*;
1065    use formats::{ImageBuffer, ImageBufferRef, ImageData, Stride};
1066
1067    /// An RoiImage maintains a reference to the original image but views a
1068    /// subregion of the original data.
1069    struct RoiImage<'a, F> {
1070        data: &'a [u8],
1071        w: u32,
1072        h: u32,
1073        stride: usize,
1074        fmt: std::marker::PhantomData<F>,
1075    }
1076
1077    impl<'a, F> RoiImage<'a, F>
1078    where
1079        F: PixelFormat,
1080    {
1081        /// Create a new `RoiImage` referencing the original `frame`.
1082        fn new(
1083            frame: &'a dyn HasRowChunksExact<F>,
1084            w: u32,
1085            h: u32,
1086            x: u32,
1087            y: u32,
1088        ) -> Result<RoiImage<'a, F>> {
1089            let stride = frame.stride();
1090            let fmt = machine_vision_formats::pixel_format::pixfmt::<F>().unwrap();
1091            let col_offset = x as usize * fmt.bits_per_pixel() as usize / 8;
1092            if col_offset >= stride {
1093                return Err(Error::RoiExceedsOriginal);
1094            }
1095            let offset = y as usize * stride + col_offset;
1096            Ok(RoiImage {
1097                data: &frame.image_data()[offset..],
1098                w,
1099                h,
1100                stride,
1101                fmt: std::marker::PhantomData,
1102            })
1103        }
1104    }
1105
1106    impl<F> Stride for RoiImage<'_, F> {
1107        fn stride(&self) -> usize {
1108            self.stride
1109        }
1110    }
1111
1112    impl<F> ImageData<F> for RoiImage<'_, F> {
1113        fn width(&self) -> u32 {
1114            self.w
1115        }
1116        fn height(&self) -> u32 {
1117            self.h
1118        }
1119        fn buffer_ref(&self) -> ImageBufferRef<'_, F> {
1120            let image_data = self.data;
1121            ImageBufferRef::new(image_data)
1122        }
1123        fn buffer(self) -> ImageBuffer<F> {
1124            let copied = self.data.to_vec();
1125            ImageBuffer::new(copied)
1126        }
1127    }
1128
1129    fn imstr<F>(frame: &dyn HasRowChunksExact<F>) -> String
1130    where
1131        F: PixelFormat,
1132    {
1133        let fmt = machine_vision_formats::pixel_format::pixfmt::<F>().unwrap();
1134        let bytes_per_pixel = fmt.bits_per_pixel() as usize / 8;
1135
1136        frame
1137            .rowchunks_exact()
1138            .map(|row| {
1139                let image_row = &row[..frame.width() as usize * bytes_per_pixel];
1140                image_row
1141                    .chunks_exact(fmt.bits_per_pixel() as usize / 8)
1142                    .map(|x| format!("{:?}", x))
1143                    .collect::<Vec<_>>()
1144                    .join(", ")
1145            })
1146            .collect::<Vec<_>>()
1147            .join("\n")
1148    }
1149
1150    #[test]
1151    fn check_roi_mono8() {
1152        // Create an image where stride is larger than width.
1153        const STRIDE: usize = 10;
1154        const W: u32 = 8;
1155        const H: u32 = 6;
1156        // Create buffer with value of `255` everywhere.
1157        let mut image_data = vec![255; H as usize * STRIDE];
1158        // Set the image part of the buffer to `col*H + row`.
1159        for row in 0..H as usize {
1160            let start_idx = row * STRIDE;
1161            for col in 0..W as usize {
1162                image_data[start_idx + col] = (row * W as usize + col) as u8;
1163            }
1164        }
1165        let frame: OImage<formats::pixel_format::Mono8> =
1166            OImage::new(W, H, STRIDE, image_data).unwrap();
1167        println!("frame: {:?}", frame.image_data());
1168        println!("frame: \n{}", imstr(&frame));
1169        let roi = RoiImage::new(&frame, 6, 2, 1, 1).unwrap();
1170        println!("roi: {:?}", roi.image_data());
1171        println!("roi: \n{}", imstr(&roi));
1172        let small = super::remove_padding(&roi).unwrap();
1173        println!("small: {:?}", small.image_data());
1174        println!("small: \n{}", imstr(&small));
1175        assert_eq!(
1176            small.image_data(),
1177            &[9, 10, 11, 12, 13, 14, 17, 18, 19, 20, 21, 22]
1178        );
1179    }
1180
1181    #[test]
1182    fn check_roi_rgb8() {
1183        // Create an image where stride is larger than width.
1184        const STRIDE: usize = 30;
1185        const W: u32 = 8;
1186        const H: u32 = 6;
1187        // Create buffer with value of `255` everywhere.
1188        let mut image_data = vec![255; H as usize * STRIDE];
1189        for row in 0..H as usize {
1190            let start_idx = row * STRIDE;
1191            for col in 0..W as usize {
1192                let col_offset = col * 3;
1193                image_data[start_idx + col_offset] = ((row * W as usize + col) * 3) as u8;
1194                image_data[start_idx + col_offset + 1] = ((row * W as usize + col) * 3) as u8 + 1;
1195                image_data[start_idx + col_offset + 2] = ((row * W as usize + col) * 3) as u8 + 2;
1196            }
1197        }
1198        let frame: OImage<formats::pixel_format::RGB8> =
1199            OImage::new(W, H, STRIDE, image_data).unwrap();
1200        println!("frame: {:?}", frame.image_data());
1201        println!("frame: \n{}", imstr(&frame));
1202        let roi = RoiImage::new(&frame, 6, 2, 1, 1).unwrap();
1203        println!("roi: {:?}", roi.image_data());
1204        println!("roi: \n{}", imstr(&roi));
1205        let small = super::remove_padding(&roi).unwrap();
1206        println!("small: {:?}", small.image_data());
1207        println!("small: \n{}", imstr(&small));
1208        assert_eq!(
1209            small.image_data(),
1210            &[
1211                27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 51, 52, 53,
1212                54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68
1213            ]
1214        );
1215    }
1216    #[test]
1217    fn check_stride_conversion_to_image() {
1218        // Create an image where stride is larger than width.
1219        const STRIDE: usize = 6;
1220        const W: u32 = 4;
1221        const H: u32 = 2;
1222        // Create buffer with value of `42` everywhere.
1223        let mut image_data = vec![42; H as usize * STRIDE];
1224        // Set the image part of the buffer to `0`.
1225        for row in 0..H as usize {
1226            let start_idx = row * STRIDE;
1227            for col in 0..W as usize {
1228                image_data[start_idx + col] = 0;
1229            }
1230        }
1231        let frame: OImage<formats::pixel_format::Mono8> =
1232            OImage::new(W, H, STRIDE, image_data).unwrap();
1233        let buf = frame_to_encoded_buffer(&frame, EncoderOptions::Png).unwrap();
1234
1235        // Decode the BMP data into an image.
1236        let im2 = image::load_from_memory_with_format(&buf, image::ImageFormat::Png).unwrap();
1237
1238        let im2gray = im2.into_luma8();
1239        assert_eq!(im2gray.width(), W);
1240        assert_eq!(im2gray.height(), H);
1241        for row in 0..H {
1242            for col in 0..W {
1243                let pixel = im2gray.get_pixel(col, row);
1244                assert_eq!(pixel.0[0], 0, "at pixel {},{}", col, row);
1245            }
1246        }
1247    }
1248
1249    #[test]
1250    fn check_bayer_conversion_to_jpg() {
1251        // Create an image where stride is larger than width.
1252        const STRIDE: usize = 6;
1253        const W: u32 = 4;
1254        const H: u32 = 4;
1255        // Create buffer with value of `42` everywhere.
1256        let mut image_data = vec![42; H as usize * STRIDE];
1257        // Set the image part of the buffer to `0`.
1258        for row in 0..H as usize {
1259            let start_idx = row * STRIDE;
1260            for col in 0..W as usize {
1261                image_data[start_idx + col] = 0;
1262            }
1263        }
1264        let frame: OImage<formats::pixel_format::BayerRG8> =
1265            OImage::new(W, H, STRIDE, image_data).unwrap();
1266        frame_to_encoded_buffer(&frame, EncoderOptions::Jpeg(100)).unwrap();
1267    }
1268
1269    #[test]
1270    fn prevent_unnecessary_copy_mono8() {
1271        let frame: OImage<formats::pixel_format::Mono8> =
1272            OImage::new(10, 10, 10, vec![42; 100]).unwrap();
1273        // `im2` has only a reference to original data.
1274        let im2 = convert_ref::<_, formats::pixel_format::Mono8>(&frame).unwrap();
1275        // Confirm the data are correct.
1276        assert_eq!(im2.image_data(), frame.image_data());
1277
1278        // Now get a pointer to the original data.
1279        let const_ptr = frame.image_data().as_ptr();
1280        // Make it mutable.
1281        let data_ptr = const_ptr as *mut u8;
1282        // Now edit the original data.
1283        unsafe {
1284            *data_ptr = 2;
1285        }
1286        // And confirm that `im2` now will also have the new value;
1287        assert_eq!(im2.image_data()[0], 2);
1288    }
1289
1290    #[test]
1291    fn prevent_unnecessary_copy_rgb8() {
1292        let frame: OImage<formats::pixel_format::RGB8> =
1293            OImage::new(10, 10, 30, vec![42; 300]).unwrap();
1294        // `im2` has only a reference to original data.
1295        let im2 = convert_ref::<_, formats::pixel_format::RGB8>(&frame).unwrap();
1296        // Confirm the data are correct.
1297        assert_eq!(im2.image_data(), frame.image_data());
1298
1299        // Now get a pointer to the original data.
1300        let const_ptr = frame.image_data().as_ptr();
1301        // Make it mutable.
1302        let data_ptr = const_ptr as *mut u8;
1303        // Now edit the original data.
1304        unsafe {
1305            *data_ptr = 2;
1306        }
1307        // And confirm that `im2` now will also have the new value;
1308        assert_eq!(im2.image_data()[0], 2);
1309    }
1310
1311    #[test]
1312    fn test_rgb_yuv_roundtrip() {
1313        // Related: reversible color transforms (e.g. YCoCg):
1314        // https://stackoverflow.com/questions/10566668/lossless-rgb-to-ycbcr-transformation
1315        let black_rgb = RGB888 { R: 0, G: 0, B: 0 };
1316        let black_yuv = RGB888toYUV444_bt601_full_swing(black_rgb.R, black_rgb.G, black_rgb.B);
1317        let black_rgb2 = YUV444_bt601_toRGB(black_yuv.Y, black_yuv.U, black_yuv.V);
1318        assert_eq!(black_rgb, black_rgb2);
1319
1320        let white_rgb = RGB888 {
1321            R: 255,
1322            G: 255,
1323            B: 255,
1324        };
1325        let white_yuv = RGB888toYUV444_bt601_full_swing(white_rgb.R, white_rgb.G, white_rgb.B);
1326        let white_rgb2 = YUV444_bt601_toRGB(white_yuv.Y, white_yuv.U, white_yuv.V);
1327        assert_eq!(white_rgb, white_rgb2);
1328
1329        for r in 0..255 {
1330            for g in 0..255 {
1331                for b in 0..255 {
1332                    let expected = RGB888 { R: r, G: g, B: b };
1333                    let yuv = RGB888toYUV444_bt601_full_swing(expected.R, expected.G, expected.B);
1334                    let actual = YUV444_bt601_toRGB(yuv.Y, yuv.U, yuv.V);
1335                    assert!(
1336                        actual.distance(&expected) <= 7,
1337                        "expected: {:?}, actual: {:?}",
1338                        expected,
1339                        actual
1340                    );
1341                    assert!(
1342                        actual.max_channel_distance(&expected) <= 4,
1343                        "expected: {:?}, actual: {:?}",
1344                        expected,
1345                        actual
1346                    );
1347                }
1348            }
1349        }
1350    }
1351
1352    #[test]
1353    // Test MONO8->RGB8
1354    fn test_mono8_rgb8() -> Result<()> {
1355        let orig: OImage<formats::pixel_format::Mono8> =
1356            OImage::new(256, 1, 256, (0u8..=255u8).collect()).unwrap();
1357        let rgb = convert_ref::<_, formats::pixel_format::RGB8>(&orig)?;
1358        for (i, rgb_pix) in rgb.image_data().chunks_exact(3).enumerate() {
1359            assert_eq!(i, rgb_pix[0] as usize);
1360            assert_eq!(i, rgb_pix[1] as usize);
1361            assert_eq!(i, rgb_pix[2] as usize);
1362        }
1363        Ok(())
1364    }
1365
1366    #[test]
1367    fn test_mono8_rgb_roundtrip() -> Result<()> {
1368        let orig: OImage<formats::pixel_format::Mono8> =
1369            OImage::new(256, 1, 256, (0u8..=255u8).collect()).unwrap();
1370        let rgb = convert_ref::<_, formats::pixel_format::RGB8>(&orig)?;
1371        let actual = convert_ref::<_, formats::pixel_format::Mono8>(&rgb)?;
1372        assert_eq!(orig.image_data(), actual.image_data());
1373        Ok(())
1374    }
1375
1376    #[test]
1377    fn test_mono8_nv12_roundtrip() -> Result<()> {
1378        let orig: OImage<formats::pixel_format::Mono8> =
1379            OImage::new(256, 1, 256, (0u8..=255u8).collect()).unwrap();
1380        let nv12 = convert_ref::<_, formats::pixel_format::NV12>(&orig)?;
1381        let actual = convert_ref::<_, formats::pixel_format::Mono8>(&nv12)?;
1382        for i in 0..256 {
1383            assert_eq!(orig.image_data()[i], actual.image_data()[i]);
1384        }
1385        assert_eq!(orig.image_data(), actual.image_data());
1386        Ok(())
1387    }
1388
1389    #[test]
1390    // Test MONO8->YUV444->MONO8.
1391    fn test_mono8_yuv_roundtrip() -> Result<()> {
1392        let orig: OImage<formats::pixel_format::Mono8> =
1393            OImage::new(256, 1, 256, (0u8..=255u8).collect()).unwrap();
1394        let yuv = convert_ref::<_, formats::pixel_format::YUV444>(&orig)?;
1395        let actual = convert_ref::<_, formats::pixel_format::Mono8>(&yuv)?;
1396        assert_eq!(orig.image_data(), actual.image_data());
1397        Ok(())
1398    }
1399}