Skip to main content

mediaframe/source/
gbrapf16.rs

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