Skip to main content

mediaframe/source/
gbrapf32.rs

1//! Walker for the `Gbrapf32` source format (`AV_PIX_FMT_GBRAPF32{LE,BE}`) — four
2//! full-resolution `f32` planes in **G, B, R, A** order.
3//!
4//! Alpha is real per-pixel; nominal range `[0.0, 1.0]` (opaque = 1.0).
5//! Integer outputs clamp colour channels to `[0.0, 1.0]` before scaling;
6//! float outputs are lossless pass-through.
7//!
8//! The marker carries `<const BE: bool = false>`: `Gbrapf32`
9//! (= `Gbrapf32<false>`) is the LE source; `Gbrapf32<true>` is the BE
10//! source. The walker [`gbrapf32_to::<BE>`] propagates `BE` from
11//! [`Gbrapf32Frame<'_, BE>`] into the sinker dispatch.
12
13use crate::{
14  PixelSink, SourceFormat,
15  frame::{Gbrapf32Frame, Gbrapf32LeFrame},
16  source::sealed::Sealed,
17};
18
19/// Zero-sized marker for the planar GBRAP float-32 source format
20/// (`AV_PIX_FMT_GBRAPF32{LE,BE}`). `<const BE: bool>` defaults to `false`.
21#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
22pub struct Gbrapf32<const BE: bool = false>;
23
24impl<const BE: bool> Sealed for Gbrapf32<BE> {}
25impl<const BE: bool> SourceFormat for Gbrapf32<BE> {}
26
27/// One output row from a [`Gbrapf32Frame`](crate::frame::Gbrapf32Frame) — four full-width `f32` slices
28/// in G / B / R / A order. Use [`Self::g`] / [`Self::b`] / [`Self::r`] /
29/// [`Self::a`].
30#[derive(Debug, Clone, Copy)]
31pub struct Gbrapf32Row<'a> {
32  g: &'a [f32],
33  b: &'a [f32],
34  r: &'a [f32],
35  a: &'a [f32],
36  row: usize,
37}
38
39impl<'a> Gbrapf32Row<'a> {
40  #[cfg_attr(not(tarpaulin), inline(always))]
41  pub(crate) fn new(g: &'a [f32], b: &'a [f32], r: &'a [f32], a: &'a [f32], row: usize) -> Self {
42    Self { g, b, r, a, row }
43  }
44
45  /// Green plane row — `width` `f32` elements.
46  #[cfg_attr(not(tarpaulin), inline(always))]
47  pub fn g(&self) -> &'a [f32] {
48    self.g
49  }
50  /// Blue plane row — `width` `f32` elements.
51  #[cfg_attr(not(tarpaulin), inline(always))]
52  pub fn b(&self) -> &'a [f32] {
53    self.b
54  }
55  /// Red plane row — `width` `f32` elements.
56  #[cfg_attr(not(tarpaulin), inline(always))]
57  pub fn r(&self) -> &'a [f32] {
58    self.r
59  }
60  /// Alpha plane row — `width` `f32` elements (opaque = 1.0).
61  #[cfg_attr(not(tarpaulin), inline(always))]
62  pub fn a(&self) -> &'a [f32] {
63    self.a
64  }
65  /// Output row index within the frame (0-based).
66  #[cfg_attr(not(tarpaulin), inline(always))]
67  pub const fn row(&self) -> usize {
68    self.row
69  }
70}
71
72/// Sinks that consume rows of a [`Gbrapf32`] source. Defaults to LE
73/// (`BE = false`) for back-compat.
74pub trait Gbrapf32Sink<const BE: bool = false>:
75  for<'a> PixelSink<Input<'a> = Gbrapf32Row<'a>>
76{
77}
78
79/// Walks a [`Gbrapf32Frame<'_, BE>`] row by row, dispatching each row to
80/// the sink. Propagates `<const BE: bool>` from the frame into
81/// [`Gbrapf32Sink<BE>`]. Use the LE-only [`gbrapf32_to`] wrapper for
82/// pre-Phase-4 explicit-turbofish callers.
83pub fn gbrapf32_to_endian<S, const BE: bool>(
84  src: &Gbrapf32Frame<'_, BE>,
85  sink: &mut S,
86) -> Result<(), S::Error>
87where
88  S: Gbrapf32Sink<BE>,
89{
90  sink.begin_frame(src.width(), src.height())?;
91
92  let w = src.width() as usize;
93  let h = src.height() as usize;
94  let g_plane = src.g();
95  let b_plane = src.b();
96  let r_plane = src.r();
97  let a_plane = src.a();
98  let g_stride = src.g_stride() as usize;
99  let b_stride = src.b_stride() as usize;
100  let r_stride = src.r_stride() as usize;
101  let a_stride = src.a_stride() as usize;
102
103  for row in 0..h {
104    let g = &g_plane[row * g_stride..row * g_stride + w];
105    let b = &b_plane[row * b_stride..row * b_stride + w];
106    let r = &r_plane[row * r_stride..row * r_stride + w];
107    let a = &a_plane[row * a_stride..row * a_stride + w];
108    sink.process(Gbrapf32Row::new(g, b, r, a, row))?;
109  }
110  Ok(())
111}
112
113/// LE-only back-compat wrapper preserving the pre-Phase-4 walker
114/// signature. Forwards to [`gbrapf32_to_endian`] with `BE = false`.
115///
116/// Rust forbids defaults on function-position const-generic parameters,
117/// so an explicit-turbofish caller written before the Phase-4 BE
118/// migration (`gbrapf32_to::<MySink>(...)`) would otherwise fail to
119/// compile. Keeping this single-generic wrapper preserves source
120/// compatibility for those call sites. BE-aware callers should use
121/// [`gbrapf32_to_endian`] directly.
122#[cfg_attr(not(tarpaulin), inline(always))]
123pub fn gbrapf32_to<S>(src: &Gbrapf32LeFrame<'_>, sink: &mut S) -> Result<(), S::Error>
124where
125  S: Gbrapf32Sink<false>,
126{
127  gbrapf32_to_endian::<S, false>(src, sink)
128}
129
130#[cfg(all(test, feature = "std"))]
131mod tests {
132  use super::*;
133  use crate::PixelSink;
134  use core::convert::Infallible;
135
136  struct CountingSink {
137    rows_seen: usize,
138    last_a_len: usize,
139    last_row_idx: usize,
140  }
141
142  impl PixelSink for CountingSink {
143    type Input<'r> = Gbrapf32Row<'r>;
144    type Error = Infallible;
145    fn begin_frame(&mut self, _w: u32, _h: u32) -> Result<(), Infallible> {
146      Ok(())
147    }
148    fn process(&mut self, row: Gbrapf32Row<'_>) -> Result<(), Infallible> {
149      self.rows_seen += 1;
150      self.last_a_len = row.a().len();
151      self.last_row_idx = row.row();
152      Ok(())
153    }
154  }
155
156  impl Gbrapf32Sink for CountingSink {}
157
158  // Compile-pass regression for the codex round-1 finding on PR #109
159  // (hand-written `gbrapf32_to`). See `gbrpf32::tests` for full rationale.
160  // BE-aware callers should use `gbrapf32_to_endian::<S, BE>` directly.
161  #[test]
162  fn gbrapf32_to_explicit_turbofish_one_generic_compiles() {
163    #[allow(clippy::type_complexity)]
164    fn _check<S: Gbrapf32Sink>() {
165      let _: fn(&Gbrapf32LeFrame<'_>, &mut S) -> Result<(), S::Error> = gbrapf32_to::<S>;
166    }
167  }
168
169  #[test]
170  fn gbrapf32_walker_visits_every_row_once() {
171    let buf = std::vec![1.0f32; 4 * 4];
172    let frame = Gbrapf32LeFrame::try_new(&buf, &buf, &buf, &buf, 4, 4, 4, 4, 4, 4).unwrap();
173    let mut sink = CountingSink {
174      rows_seen: 0,
175      last_a_len: 0,
176      last_row_idx: 0,
177    };
178    gbrapf32_to(&frame, &mut sink).unwrap();
179    assert_eq!(sink.rows_seen, 4);
180    assert_eq!(sink.last_a_len, 4);
181    assert_eq!(sink.last_row_idx, 3);
182  }
183}