1#![forbid(unsafe_code)]
7#![allow(clippy::unreadable_literal)]
8#![allow(clippy::items_after_statements)]
9#![allow(clippy::unnecessary_wraps)]
10#![allow(clippy::struct_excessive_bools)]
11#![allow(clippy::identity_op)]
12#![allow(clippy::range_plus_one)]
13#![allow(clippy::needless_range_loop)]
14#![allow(clippy::useless_conversion)]
15#![allow(clippy::redundant_closure_for_method_calls)]
16#![allow(clippy::single_match_else)]
17#![allow(dead_code)]
18#![allow(clippy::doc_markdown)]
19#![allow(clippy::unused_self)]
20#![allow(clippy::trivially_copy_pass_by_ref)]
21#![allow(clippy::cast_possible_truncation)]
22#![allow(clippy::cast_sign_loss)]
23#![allow(clippy::cast_possible_wrap)]
24#![allow(clippy::missing_errors_doc)]
25#![allow(clippy::similar_names)]
26#![allow(clippy::match_same_arms)]
27#![allow(clippy::cast_precision_loss)]
28#![allow(clippy::cast_lossless)]
29#![allow(clippy::too_many_arguments)]
30#![allow(clippy::many_single_char_names)]
31#![allow(clippy::comparison_chain)]
32#![allow(clippy::if_then_some_else_none)]
33
34use super::{FrameBuffer, PlaneBuffer, ReconstructResult};
35
36#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
42pub enum OutputFormat {
43 #[default]
45 YuvPlanar,
46 YuvSemiPlanar,
48 Rgb,
50 Rgba,
52 Bgr,
54 Bgra,
56}
57
58impl OutputFormat {
59 #[must_use]
61 pub const fn num_planes(self) -> usize {
62 match self {
63 Self::YuvPlanar => 3,
64 Self::YuvSemiPlanar => 2,
65 Self::Rgb | Self::Bgr => 1,
66 Self::Rgba | Self::Bgra => 1,
67 }
68 }
69
70 #[must_use]
72 pub const fn bytes_per_pixel(self) -> usize {
73 match self {
74 Self::YuvPlanar | Self::YuvSemiPlanar => 1,
75 Self::Rgb | Self::Bgr => 3,
76 Self::Rgba | Self::Bgra => 4,
77 }
78 }
79
80 #[must_use]
82 pub const fn is_planar(self) -> bool {
83 matches!(self, Self::YuvPlanar | Self::YuvSemiPlanar)
84 }
85
86 #[must_use]
88 pub const fn is_rgb(self) -> bool {
89 matches!(self, Self::Rgb | Self::Rgba | Self::Bgr | Self::Bgra)
90 }
91}
92
93#[derive(Clone, Debug)]
99pub struct OutputConfig {
100 pub format: OutputFormat,
102 pub bit_depth: u8,
104 pub dither: bool,
106 pub full_range: bool,
108 pub width: Option<u32>,
110 pub height: Option<u32>,
112}
113
114impl Default for OutputConfig {
115 fn default() -> Self {
116 Self {
117 format: OutputFormat::YuvPlanar,
118 bit_depth: 8,
119 dither: false,
120 full_range: false,
121 width: None,
122 height: None,
123 }
124 }
125}
126
127impl OutputConfig {
128 #[must_use]
130 pub fn new(format: OutputFormat) -> Self {
131 Self {
132 format,
133 ..Default::default()
134 }
135 }
136
137 #[must_use]
139 pub const fn with_bit_depth(mut self, bit_depth: u8) -> Self {
140 self.bit_depth = bit_depth;
141 self
142 }
143
144 #[must_use]
146 pub const fn with_dither(mut self) -> Self {
147 self.dither = true;
148 self
149 }
150
151 #[must_use]
153 pub const fn with_full_range(mut self) -> Self {
154 self.full_range = true;
155 self
156 }
157
158 #[must_use]
160 pub const fn with_dimensions(mut self, width: u32, height: u32) -> Self {
161 self.width = Some(width);
162 self.height = Some(height);
163 self
164 }
165}
166
167#[derive(Clone, Debug)]
173pub struct OutputBuffer {
174 planes: Vec<Vec<u8>>,
176 width: u32,
178 height: u32,
180 format: OutputFormat,
182 bit_depth: u8,
184 timestamp: i64,
186}
187
188impl OutputBuffer {
189 #[must_use]
191 pub fn new(width: u32, height: u32, format: OutputFormat, bit_depth: u8) -> Self {
192 let bytes_per_sample = if bit_depth > 8 { 2 } else { 1 };
193
194 let planes = match format {
195 OutputFormat::YuvPlanar => {
196 vec![
197 vec![0u8; width as usize * height as usize * bytes_per_sample],
198 vec![0u8; (width as usize / 2) * (height as usize / 2) * bytes_per_sample],
199 vec![0u8; (width as usize / 2) * (height as usize / 2) * bytes_per_sample],
200 ]
201 }
202 OutputFormat::YuvSemiPlanar => {
203 vec![
204 vec![0u8; width as usize * height as usize * bytes_per_sample],
205 vec![0u8; (width as usize / 2) * (height as usize / 2) * 2 * bytes_per_sample],
206 ]
207 }
208 OutputFormat::Rgb | OutputFormat::Bgr => {
209 vec![vec![
210 0u8;
211 width as usize * height as usize * 3 * bytes_per_sample
212 ]]
213 }
214 OutputFormat::Rgba | OutputFormat::Bgra => {
215 vec![vec![
216 0u8;
217 width as usize * height as usize * 4 * bytes_per_sample
218 ]]
219 }
220 };
221
222 Self {
223 planes,
224 width,
225 height,
226 format,
227 bit_depth,
228 timestamp: 0,
229 }
230 }
231
232 #[must_use]
234 pub const fn width(&self) -> u32 {
235 self.width
236 }
237
238 #[must_use]
240 pub const fn height(&self) -> u32 {
241 self.height
242 }
243
244 #[must_use]
246 pub const fn format(&self) -> OutputFormat {
247 self.format
248 }
249
250 #[must_use]
252 pub const fn bit_depth(&self) -> u8 {
253 self.bit_depth
254 }
255
256 #[must_use]
258 pub const fn timestamp(&self) -> i64 {
259 self.timestamp
260 }
261
262 pub fn set_timestamp(&mut self, timestamp: i64) {
264 self.timestamp = timestamp;
265 }
266
267 #[must_use]
269 pub fn plane(&self, index: usize) -> &[u8] {
270 self.planes.get(index).map_or(&[], Vec::as_slice)
271 }
272
273 pub fn plane_mut(&mut self, index: usize) -> &mut [u8] {
275 self.planes
276 .get_mut(index)
277 .map_or(&mut [], Vec::as_mut_slice)
278 }
279
280 #[must_use]
282 pub fn planes(&self) -> &[Vec<u8>] {
283 &self.planes
284 }
285
286 #[must_use]
288 pub fn size_bytes(&self) -> usize {
289 self.planes.iter().map(Vec::len).sum()
290 }
291}
292
293#[derive(Debug)]
299pub struct OutputFormatter {
300 config: OutputConfig,
302 dither_state: u32,
304}
305
306impl Default for OutputFormatter {
307 fn default() -> Self {
308 Self::new()
309 }
310}
311
312impl OutputFormatter {
313 #[must_use]
315 pub fn new() -> Self {
316 Self {
317 config: OutputConfig::default(),
318 dither_state: 0,
319 }
320 }
321
322 #[must_use]
324 pub fn with_config(config: OutputConfig) -> Self {
325 Self {
326 config,
327 dither_state: 0,
328 }
329 }
330
331 pub fn set_config(&mut self, config: OutputConfig) {
333 self.config = config;
334 }
335
336 #[must_use]
338 pub fn config(&self) -> &OutputConfig {
339 &self.config
340 }
341
342 pub fn format(&mut self, frame: &FrameBuffer) -> ReconstructResult<OutputBuffer> {
348 let width = self.config.width.unwrap_or(frame.width());
349 let height = self.config.height.unwrap_or(frame.height());
350
351 let mut output =
352 OutputBuffer::new(width, height, self.config.format, self.config.bit_depth);
353 output.set_timestamp(frame.timestamp());
354
355 match self.config.format {
356 OutputFormat::YuvPlanar => self.format_yuv_planar(frame, &mut output)?,
357 OutputFormat::YuvSemiPlanar => self.format_yuv_semi_planar(frame, &mut output)?,
358 OutputFormat::Rgb => self.format_rgb(frame, &mut output, false, false)?,
359 OutputFormat::Rgba => self.format_rgb(frame, &mut output, true, false)?,
360 OutputFormat::Bgr => self.format_rgb(frame, &mut output, false, true)?,
361 OutputFormat::Bgra => self.format_rgb(frame, &mut output, true, true)?,
362 }
363
364 Ok(output)
365 }
366
367 fn format_yuv_planar(
369 &mut self,
370 frame: &FrameBuffer,
371 output: &mut OutputBuffer,
372 ) -> ReconstructResult<()> {
373 self.copy_plane(frame.y_plane(), output.plane_mut(0));
375
376 if let Some(u) = frame.u_plane() {
378 self.copy_plane(u, output.plane_mut(1));
379 }
380
381 if let Some(v) = frame.v_plane() {
383 self.copy_plane(v, output.plane_mut(2));
384 }
385
386 Ok(())
387 }
388
389 fn format_yuv_semi_planar(
391 &mut self,
392 frame: &FrameBuffer,
393 output: &mut OutputBuffer,
394 ) -> ReconstructResult<()> {
395 self.copy_plane(frame.y_plane(), output.plane_mut(0));
397
398 if let (Some(u), Some(v)) = (frame.u_plane(), frame.v_plane()) {
400 let uv_plane = output.plane_mut(1);
401 let u_data = u.data();
402 let v_data = v.data();
403
404 let mut dst_idx = 0;
405 for (u_val, v_val) in u_data.iter().zip(v_data.iter()) {
406 let u_byte = self.convert_sample(*u_val, frame.bit_depth(), self.config.bit_depth);
407 let v_byte = self.convert_sample(*v_val, frame.bit_depth(), self.config.bit_depth);
408
409 if dst_idx + 1 < uv_plane.len() {
410 uv_plane[dst_idx] = u_byte;
411 uv_plane[dst_idx + 1] = v_byte;
412 dst_idx += 2;
413 }
414 }
415 }
416
417 Ok(())
418 }
419
420 fn format_rgb(
422 &mut self,
423 frame: &FrameBuffer,
424 output: &mut OutputBuffer,
425 with_alpha: bool,
426 bgr_order: bool,
427 ) -> ReconstructResult<()> {
428 let width = output.width() as usize;
429 let height = output.height() as usize;
430 let bpp = if with_alpha { 4 } else { 3 };
431 let rgb_data = output.plane_mut(0);
432
433 let y_plane = frame.y_plane();
434 let u_plane = frame.u_plane();
435 let v_plane = frame.v_plane();
436
437 for y in 0..height {
438 for x in 0..width {
439 let y_val = y_plane.get(x as u32, y as u32);
441 let (u_val, v_val) = if let (Some(u), Some(v)) = (u_plane, v_plane) {
442 let cx = (x / 2) as u32;
443 let cy = (y / 2) as u32;
444 (u.get(cx, cy), v.get(cx, cy))
445 } else {
446 (128, 128)
447 };
448
449 let (r, g, b) = yuv_to_rgb(y_val, u_val, v_val, frame.bit_depth());
451
452 let idx = (y * width + x) * bpp;
454 if idx + bpp <= rgb_data.len() {
455 if bgr_order {
456 rgb_data[idx] = b;
457 rgb_data[idx + 1] = g;
458 rgb_data[idx + 2] = r;
459 } else {
460 rgb_data[idx] = r;
461 rgb_data[idx + 1] = g;
462 rgb_data[idx + 2] = b;
463 }
464 if with_alpha {
465 rgb_data[idx + 3] = 255;
466 }
467 }
468 }
469 }
470
471 Ok(())
472 }
473
474 fn copy_plane(&mut self, src: &PlaneBuffer, dst: &mut [u8]) {
476 let src_bd = src.bit_depth();
477 let dst_bd = self.config.bit_depth;
478 let src_data = src.data();
479
480 if self.config.dither && dst_bd < src_bd {
481 for (i, &sample) in src_data.iter().enumerate() {
483 if i < dst.len() {
484 dst[i] = self.convert_sample_dithered(sample, src_bd, dst_bd);
485 }
486 }
487 } else {
488 for (i, &sample) in src_data.iter().enumerate() {
490 if i < dst.len() {
491 dst[i] = self.convert_sample(sample, src_bd, dst_bd);
492 }
493 }
494 }
495 }
496
497 fn convert_sample(&self, sample: i16, src_bd: u8, dst_bd: u8) -> u8 {
499 let src_max = (1i32 << src_bd) - 1;
500 let dst_max = (1i32 << dst_bd) - 1;
501
502 let clamped = (i32::from(sample)).clamp(0, src_max);
503
504 if src_bd == dst_bd {
505 clamped as u8
506 } else if dst_bd < src_bd {
507 let shift = src_bd - dst_bd;
509 (clamped >> shift) as u8
510 } else {
511 let shift = dst_bd - src_bd;
513 ((clamped << shift) | (clamped >> (src_bd - shift))).min(dst_max) as u8
514 }
515 }
516
517 fn convert_sample_dithered(&mut self, sample: i16, src_bd: u8, dst_bd: u8) -> u8 {
519 let src_max = (1i32 << src_bd) - 1;
520
521 let clamped = (i32::from(sample)).clamp(0, src_max);
522 let shift = src_bd.saturating_sub(dst_bd);
523
524 if shift == 0 {
525 return clamped as u8;
526 }
527
528 let dither = (self.dither_state & ((1 << shift) - 1)) as i32;
530 self.dither_state = self
531 .dither_state
532 .wrapping_mul(1664525)
533 .wrapping_add(1013904223);
534
535 let result = (clamped + dither) >> shift;
536 result.min(255) as u8
537 }
538}
539
540fn yuv_to_rgb(y: i16, u: i16, v: i16, bd: u8) -> (u8, u8, u8) {
542 let half = 1i32 << (bd - 1);
543
544 let y_val = i32::from(y);
545 let u_val = i32::from(u) - half;
546 let v_val = i32::from(v) - half;
547
548 let r = y_val + ((359 * v_val) >> 8);
550 let g = y_val - ((88 * u_val + 183 * v_val) >> 8);
551 let b = y_val + ((454 * u_val) >> 8);
552
553 let shift = bd.saturating_sub(8);
555
556 (
557 ((r >> shift).clamp(0, 255)) as u8,
558 ((g >> shift).clamp(0, 255)) as u8,
559 ((b >> shift).clamp(0, 255)) as u8,
560 )
561}
562
563#[cfg(test)]
568mod tests {
569 use super::*;
570 use crate::reconstruct::ChromaSubsampling;
571
572 #[test]
573 fn test_output_format() {
574 assert_eq!(OutputFormat::YuvPlanar.num_planes(), 3);
575 assert_eq!(OutputFormat::YuvSemiPlanar.num_planes(), 2);
576 assert_eq!(OutputFormat::Rgb.num_planes(), 1);
577 assert_eq!(OutputFormat::Rgba.num_planes(), 1);
578
579 assert_eq!(OutputFormat::Rgb.bytes_per_pixel(), 3);
580 assert_eq!(OutputFormat::Rgba.bytes_per_pixel(), 4);
581
582 assert!(OutputFormat::YuvPlanar.is_planar());
583 assert!(!OutputFormat::Rgb.is_planar());
584 assert!(OutputFormat::Rgb.is_rgb());
585 }
586
587 #[test]
588 fn test_output_config() {
589 let config = OutputConfig::new(OutputFormat::Rgb)
590 .with_bit_depth(8)
591 .with_full_range()
592 .with_dither()
593 .with_dimensions(1920, 1080);
594
595 assert_eq!(config.format, OutputFormat::Rgb);
596 assert_eq!(config.bit_depth, 8);
597 assert!(config.full_range);
598 assert!(config.dither);
599 assert_eq!(config.width, Some(1920));
600 assert_eq!(config.height, Some(1080));
601 }
602
603 #[test]
604 fn test_output_buffer() {
605 let buffer = OutputBuffer::new(1920, 1080, OutputFormat::YuvPlanar, 8);
606
607 assert_eq!(buffer.width(), 1920);
608 assert_eq!(buffer.height(), 1080);
609 assert_eq!(buffer.format(), OutputFormat::YuvPlanar);
610 assert_eq!(buffer.bit_depth(), 8);
611
612 assert_eq!(buffer.plane(0).len(), 1920 * 1080);
614 assert_eq!(buffer.plane(1).len(), 960 * 540);
615 assert_eq!(buffer.plane(2).len(), 960 * 540);
616 }
617
618 #[test]
619 fn test_output_buffer_rgb() {
620 let buffer = OutputBuffer::new(64, 64, OutputFormat::Rgb, 8);
621 assert_eq!(buffer.plane(0).len(), 64 * 64 * 3);
622 }
623
624 #[test]
625 fn test_output_buffer_rgba() {
626 let buffer = OutputBuffer::new(64, 64, OutputFormat::Rgba, 8);
627 assert_eq!(buffer.plane(0).len(), 64 * 64 * 4);
628 }
629
630 #[test]
631 fn test_output_formatter_creation() {
632 let formatter = OutputFormatter::new();
633 assert_eq!(formatter.config().format, OutputFormat::YuvPlanar);
634 }
635
636 #[test]
637 fn test_output_formatter_with_config() {
638 let config = OutputConfig::new(OutputFormat::Rgb);
639 let formatter = OutputFormatter::with_config(config);
640 assert_eq!(formatter.config().format, OutputFormat::Rgb);
641 }
642
643 #[test]
644 fn test_output_formatter_format_yuv() {
645 let frame = FrameBuffer::new(64, 64, 8, ChromaSubsampling::Cs420);
646 let mut formatter = OutputFormatter::new();
647
648 let output = formatter.format(&frame).expect("should succeed");
649 assert_eq!(output.width(), 64);
650 assert_eq!(output.height(), 64);
651 assert_eq!(output.format(), OutputFormat::YuvPlanar);
652 }
653
654 #[test]
655 fn test_output_formatter_format_rgb() {
656 let frame = FrameBuffer::new(64, 64, 8, ChromaSubsampling::Cs420);
657 let config = OutputConfig::new(OutputFormat::Rgb);
658 let mut formatter = OutputFormatter::with_config(config);
659
660 let output = formatter.format(&frame).expect("should succeed");
661 assert_eq!(output.format(), OutputFormat::Rgb);
662 assert_eq!(output.plane(0).len(), 64 * 64 * 3);
663 }
664
665 #[test]
666 fn test_yuv_to_rgb() {
667 let (r, g, b) = yuv_to_rgb(0, 128, 128, 8);
669 assert!(r < 10 && g < 10 && b < 10);
670
671 let (r, g, b) = yuv_to_rgb(255, 128, 128, 8);
673 assert!(r > 245 && g > 245 && b > 245);
674 }
675
676 #[test]
677 fn test_convert_sample() {
678 let formatter = OutputFormatter::new();
679
680 assert_eq!(formatter.convert_sample(128, 8, 8), 128);
682
683 assert_eq!(formatter.convert_sample(512, 10, 8), 128);
685
686 assert_eq!(formatter.convert_sample(2048, 12, 8), 128);
688 }
689
690 #[test]
691 fn test_convert_sample_clamping() {
692 let formatter = OutputFormatter::new();
693
694 assert_eq!(formatter.convert_sample(-10, 8, 8), 0);
696
697 assert_eq!(formatter.convert_sample(300, 8, 8), 255);
699 }
700}