Skip to main content

mediaframe/source/
monowhite.rs

1//! [`MonowhiteFrame`](crate::frame::MonowhiteFrame) walker — 1-bit-per-pixel, MSB-first encoding,
2//! bit=0 → white (Y=255), bit=1 → black (Y=0). Inverted polarity from
3//! Monoblack.
4//!
5//! Note: `Monoblack` / `Monowhite` walkers are hand-written rather than
6//! generated via `walker! { packed { ... } }`. The packed macro arm assumes
7//! ≥ 1 byte per pixel; 1-bit-per-pixel formats need byte→pixel index expansion
8//! (one byte covers 8 pixels) which doesn't fit the macro's per-element shape.
9
10use crate::{PixelSink, color::KernelMatrix, frame::MonowhiteFrame};
11
12/// Marker type for the `Monowhite` source format (FFmpeg
13/// `AV_PIX_FMT_MONOWHITE`).
14#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
15pub struct Monowhite;
16
17impl crate::source::sealed::Sealed for Monowhite {}
18impl crate::SourceFormat for Monowhite {}
19
20/// A single row from a [`MonowhiteFrame`](crate::frame::MonowhiteFrame) — byte buffer (8 pixels per
21/// byte, MSB first, inverted polarity).
22#[derive(Debug, Clone, Copy)]
23pub struct MonowhiteRow<'a> {
24  data: &'a [u8],
25  width: u32,
26  row: usize,
27  matrix: KernelMatrix,
28  full_range: bool,
29}
30
31impl<'a> MonowhiteRow<'a> {
32  /// Constructs a new row slice.
33  #[cfg_attr(not(tarpaulin), inline(always))]
34  pub(crate) const fn new(
35    data: &'a [u8],
36    width: u32,
37    row: usize,
38    matrix: KernelMatrix,
39    full_range: bool,
40  ) -> Self {
41    Self {
42      data,
43      width,
44      row,
45      matrix,
46      full_range,
47    }
48  }
49
50  /// Byte data for this row.
51  #[cfg_attr(not(tarpaulin), inline(always))]
52  pub const fn data(&self) -> &'a [u8] {
53    self.data
54  }
55
56  /// Output row index within the frame.
57  #[cfg_attr(not(tarpaulin), inline(always))]
58  pub const fn row(&self) -> usize {
59    self.row
60  }
61
62  /// Color matrix, read once from the sink.
63  #[cfg_attr(not(tarpaulin), inline(always))]
64  pub const fn matrix(&self) -> KernelMatrix {
65    self.matrix
66  }
67
68  /// Full-range flag carried through from the kernel call.
69  #[cfg_attr(not(tarpaulin), inline(always))]
70  pub const fn full_range(&self) -> bool {
71    self.full_range
72  }
73
74  /// Frame width in pixels.
75  // `is_empty` is not provided: `MonoFrame::try_new` rejects width=0, so
76  // a zero-width row can never be constructed and `is_empty` would always
77  // return false. The clippy lint is suppressed for the same reason.
78  #[allow(clippy::len_without_is_empty)]
79  #[cfg_attr(not(tarpaulin), inline(always))]
80  pub fn len(&self) -> usize {
81    self.width as usize
82  }
83}
84
85/// Sinks that consume rows of the Monowhite source format.
86pub trait MonowhiteSink: for<'a> PixelSink<Input<'a> = MonowhiteRow<'a>> {}
87
88/// Walks a [`MonowhiteFrame`](crate::frame::MonowhiteFrame) row by row, dispatching each row to the
89/// sink.
90pub fn monowhite_to<S: MonowhiteSink>(
91  src: &MonowhiteFrame<'_>,
92  full_range: bool,
93  sink: &mut S,
94) -> Result<(), S::Error> {
95  sink.begin_frame(src.width(), src.height())?;
96  let matrix = sink.kernel_matrix();
97
98  let w = src.width();
99  let h = src.height() as usize;
100  let stride = src.stride() as usize;
101  let packed_bytes = w.div_ceil(8) as usize;
102  let data = src.data();
103
104  for row in 0..h {
105    let start = row * stride;
106    let avail = data.len().saturating_sub(start);
107    let row_data = &data[start..start + packed_bytes.min(avail)];
108    sink.process(MonowhiteRow::new(row_data, w, row, matrix, full_range))?;
109  }
110  Ok(())
111}