Skip to main content

mediaframe/source/
rgbf16.rs

1//! Packed **RGBF16** source (FFmpeg `AV_PIX_FMT_RGBF16`) — 16-bit
2//! half-precision float per channel, byte order `R, G, B` per pixel
3//! (6 bytes / 3 × `half::f16` per pixel).
4//!
5//! Like the Tier 6 8-bit packed-RGB family ([`super::Rgb24`] etc.),
6//! the input is already RGB — there is no chroma matrix work. Outputs
7//! map to the sink's standard channels (with a saturating cast back
8//! to integer for u8 / u16 / luma / HSV outputs):
9//! - `with_rgb` — clamp `[0, 1]` × 255 → packed `R, G, B` u8.
10//! - `with_rgba` — same RGB conversion + constant `0xFF` alpha.
11//! - `with_rgb_u16` — clamp `[0, 1]` × 65535 → packed `R, G, B` u16.
12//! - `with_rgba_u16` — same RGB conversion + constant `0xFFFF` alpha.
13//! - `with_luma` / `with_luma_u16` — derives Y' from R/G/B (after the
14//!   clamp + cast to u8) using the existing `rgb_to_luma_row` /
15//!   `rgb_to_luma_u16_row` kernels.
16//! - `with_hsv` — clamp + cast to u8 staging followed by the existing
17//!   `rgb_to_hsv_row` kernel.
18//! - `with_rgb_f16` — **lossless** half-float pass-through: the source
19//!   row is copied verbatim into the output buffer (HDR values > 1.0
20//!   are preserved).
21//! - `with_rgb_f32` — lossless widening: each `f16` element is widened
22//!   to `f32` (HDR values > 1.0 are preserved).
23//!
24//! HDR values > 1.0 in the source saturate to the output range for
25//! every integer output. No tone mapping is applied.
26//!
27//! Downstream conversion widens `f16` → `f32` at row entry, then
28//! reuses the existing `rgbf32_to_*_row` kernels (Tier 9 completion).
29
30use crate::frame::Rgbf16Frame;
31
32walker! {
33  packed_be {
34    /// Zero-sized marker for the packed **RGBF16** source format.
35    /// `<const BE: bool = false>` mirrors the parent
36    /// [`Rgbf16Frame`](crate::frame::Rgbf16Frame)'s endian flag — `false` (default) selects
37    /// `AV_PIX_FMT_RGBF16LE`, `true` selects `AV_PIX_FMT_RGBF16BE`.
38    #[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
39    marker: Rgbf16,
40    frame: Rgbf16Frame,
41    row: Rgbf16Row,
42    sink: Rgbf16Sink,
43    walker: rgbf16_to,
44    walker_endian: rgbf16_to_endian,
45    buf_field: rgb,
46    elem_type: half::f16,
47    row_elems: |w| w * 3,
48    row_doc: "One row of an [`Rgbf16`] source — `width * 3` packed\n\
49              `half::f16` samples (`R, G, B` per pixel). The Row type\n\
50              is **not** parameterized on `BE` — it just borrows the\n\
51              underlying byte slice; the kernel's BE-aware byte-swap\n\
52              is monomorphized via the parent `Rgbf16<BE>` marker.",
53    walker_doc: "Walks an [`Rgbf16Frame`](crate::frame::Rgbf16Frame) row by row into the sink.\n\
54                 The `<const BE>` parameter is propagated from the\n\
55                 frame to the sink-trait bound (`S: Rgbf16Sink<BE>`)\n\
56                 so the row-kernel call inside `process` monomorphizes\n\
57                 against the same byte order.",
58  }
59}
60
61#[cfg(all(test, feature = "std"))]
62mod tests {
63  use super::*;
64  use crate::{PixelSink, color::Matrix, frame::Rgbf16LeFrame};
65  use core::convert::Infallible;
66
67  struct CountingSink {
68    rows_seen: usize,
69  }
70  impl PixelSink for CountingSink {
71    type Input<'r> = Rgbf16Row<'r>;
72    type Error = Infallible;
73    fn begin_frame(&mut self, _w: u32, _h: u32) -> Result<(), Infallible> {
74      Ok(())
75    }
76    fn process(&mut self, _row: Rgbf16Row<'_>) -> Result<(), Infallible> {
77      self.rows_seen += 1;
78      Ok(())
79    }
80  }
81  impl Rgbf16Sink for CountingSink {}
82
83  // Compile-pass regression for the LE-only custom sink spelling. The
84  // generated `$sink<const BE: bool = false>` carries an LE default so
85  // downstream callers can keep writing `impl Rgbf16Sink for MySink`
86  // (no `<false>`) and `S: Rgbf16Sink` bounds.
87  #[test]
88  fn rgbf16_sink_le_default_compiles_without_const_arg() {
89    fn walks_le<S: Rgbf16Sink>(frame: &Rgbf16LeFrame<'_>, sink: &mut S) -> Result<(), S::Error> {
90      rgbf16_to(frame, true, Matrix::Bt709, sink)
91    }
92
93    let buf = std::vec![half::f16::ZERO; 12 * 4];
94    let frame = Rgbf16LeFrame::new(&buf, 4, 4, 12);
95    let mut sink = CountingSink { rows_seen: 0 };
96    walks_le(&frame, &mut sink).unwrap();
97    assert_eq!(sink.rows_seen, 4);
98  }
99
100  // Compile-pass regression for the codex finding (PR #105 review). Switching
101  // from `walker!(packed)` to `walker!(packed_be)` would otherwise change the
102  // public `rgbf16_to` signature from one generic param (`S`) to two
103  // (`S, const BE: bool`), which breaks downstream callers using the previous
104  // explicit sink spelling `rgbf16_to::<MySink>(...)`. Function-position
105  // const-generic defaults aren't allowed, so the macro emits an LE-only
106  // wrapper preserving the original signature.
107  #[test]
108  fn rgbf16_to_explicit_turbofish_one_generic_compiles() {
109    #[allow(clippy::type_complexity)]
110    fn _check<S: Rgbf16Sink>() {
111      let _: fn(&crate::frame::Rgbf16LeFrame<'_>, bool, Matrix, &mut S) -> Result<(), S::Error> =
112        rgbf16_to::<S>;
113    }
114  }
115}