1use 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#[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 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 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 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 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
166pub 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
178fn yuv422_into_rgb(
180 src_yuv422: &dyn HasRowChunksExact<formats::pixel_format::YUV422>,
181 dest_rgb: &mut dyn HasRowChunksExactMut<RGB8>,
182) -> Result<()> {
183 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 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
277fn 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 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
330fn 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 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
355fn 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 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 }
379 }
380 Ok(())
381}
382
383fn 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
408fn 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
431fn 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
447fn 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 let mut dest_buf = vec![0u8; frame.height() as usize * dest_stride];
463 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 Ok(CowImage::Owned(
472 OImage::new(frame.width(), frame.height(), dest_stride, dest_buf).unwrap(),
473 ))
474 }
475}
476
477pub 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(); OImage::new(width, height, stride, image_data).unwrap()
493}
494
495pub 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
516fn 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
540fn force_buffer_pixel_format_ref<FMT1, FMT2>(
542 orig: ImageBufferMutRef<'_, FMT1>,
543) -> ImageBufferMutRef<'_, FMT2> {
544 ImageBufferMutRef::new(orig.data)
545}
546
547pub 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 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 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 convert_into(&source, &mut dest)?;
588
589 Ok(CowImage::Owned(dest))
591}
592
593pub 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 src_fmt == dest_fmt {
618 return Ok(CowImage::Borrowed(force_pixel_format_ref(source)));
619 }
620
621 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 convert_into(source, &mut dest)?;
629
630 Ok(CowImage::Owned(dest))
632}
633
634pub 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 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 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 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 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 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 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 match src_fmt {
721 formats::pixel_format::PixFmt::RGB8 => {
722 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 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 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 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 let exact_stride = remove_padding(source)?;
755 bayer_into_rgb(&exact_stride, &mut tmp_rgb)?;
756 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 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 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
780enum 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
840fn 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
856pub 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 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#[derive(Copy, Clone, Debug, PartialEq, Eq)]
916pub enum EncoderOptions {
917 Jpeg(u8),
919 Png,
921}
922
923pub 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 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 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 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 let half_stride = dest_stride; 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 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 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 const STRIDE: usize = 10;
1154 const W: u32 = 8;
1155 const H: u32 = 6;
1156 let mut image_data = vec![255; H as usize * STRIDE];
1158 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 const STRIDE: usize = 30;
1185 const W: u32 = 8;
1186 const H: u32 = 6;
1187 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 const STRIDE: usize = 6;
1220 const W: u32 = 4;
1221 const H: u32 = 2;
1222 let mut image_data = vec![42; H as usize * STRIDE];
1224 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 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 const STRIDE: usize = 6;
1253 const W: u32 = 4;
1254 const H: u32 = 4;
1255 let mut image_data = vec![42; H as usize * STRIDE];
1257 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 let im2 = convert_ref::<_, formats::pixel_format::Mono8>(&frame).unwrap();
1275 assert_eq!(im2.image_data(), frame.image_data());
1277
1278 let const_ptr = frame.image_data().as_ptr();
1280 let data_ptr = const_ptr as *mut u8;
1282 unsafe {
1284 *data_ptr = 2;
1285 }
1286 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 let im2 = convert_ref::<_, formats::pixel_format::RGB8>(&frame).unwrap();
1296 assert_eq!(im2.image_data(), frame.image_data());
1298
1299 let const_ptr = frame.image_data().as_ptr();
1301 let data_ptr = const_ptr as *mut u8;
1303 unsafe {
1305 *data_ptr = 2;
1306 }
1307 assert_eq!(im2.image_data()[0], 2);
1309 }
1310
1311 #[test]
1312 fn test_rgb_yuv_roundtrip() {
1313 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 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 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}