styx_codec/decoder/raw/
mod.rs

1//! Raw format decoders (pixel format conversions).
2
3use styx_core::prelude::ColorSpace;
4
5mod bayer;
6mod bgr;
7mod bgra;
8mod i420;
9mod mono;
10mod nv12;
11mod passthrough;
12mod rgb48;
13mod rgba;
14mod yuv;
15mod yuv420p;
16mod yuyv;
17
18pub use bayer::{BayerToRgbDecoder, bayer_decoder_for, bayer_info};
19pub use bgr::BgrToRgbDecoder;
20pub use bgra::BgraToRgbDecoder;
21pub use i420::I420ToRgbDecoder;
22pub use mono::{Mono8ToRgbDecoder, Mono16ToRgbDecoder};
23pub use nv12::{Nv12ToBgrDecoder, Nv12ToRgbDecoder};
24pub use passthrough::PassthroughDecoder;
25pub use rgb48::Rgb48ToRgbDecoder;
26pub use rgba::RgbaToRgbDecoder;
27pub use yuv::{NvToRgbDecoder, Packed422ToRgbDecoder, PlanarYuvToRgbDecoder};
28pub use yuv420p::Yuv420pToRgbDecoder;
29pub use yuyv::{YuyvToLumaDecoder, YuyvToRgbDecoder};
30
31#[derive(Clone, Copy)]
32struct YuvCoeffs {
33    r_v: i32,
34    g_u: i32,
35    g_v: i32,
36    b_u: i32,
37    full_range: bool,
38}
39
40const BT709: YuvCoeffs = YuvCoeffs {
41    r_v: 459,
42    g_u: 55,
43    g_v: 136,
44    b_u: 541,
45    full_range: false,
46};
47
48// Full-range Rec.601 coefficients (Y range 0..255).
49const BT601_FULL: YuvCoeffs = YuvCoeffs {
50    r_v: 359,
51    g_u: 88,
52    g_v: 183,
53    b_u: 454,
54    full_range: true,
55};
56
57const BT2020: YuvCoeffs = YuvCoeffs {
58    r_v: 430,
59    g_u: 48,
60    g_v: 166,
61    b_u: 549,
62    full_range: false,
63};
64
65/// Integer conversion with clamping using limited-range YUV coefficients.
66#[inline(always)]
67pub(crate) fn yuv_to_rgb(y: i32, u: i32, v: i32, color: ColorSpace) -> (u8, u8, u8) {
68    let coeffs = match color {
69        ColorSpace::Bt709 => BT709,
70        ColorSpace::Bt2020 => BT2020,
71        // `Srgb` in our metadata means "full-range output" (libcamera frequently reports sYCC).
72        // libcamera's sYCC uses a Rec.601 YCbCr matrix with full-range.
73        ColorSpace::Srgb => BT601_FULL,
74        // Default unknown to limited-range Rec.709; BT.601 assumptions tend to skew heavily.
75        ColorSpace::Unknown => BT709,
76    };
77    let d = u - 128;
78    let e = v - 128;
79    let (c, scale) = if coeffs.full_range {
80        (y.max(0), 256)
81    } else {
82        (y.saturating_sub(16).max(0), 298)
83    };
84    let r = (scale * c + coeffs.r_v * e + 128) >> 8;
85    let g = (scale * c - coeffs.g_u * d - coeffs.g_v * e + 128) >> 8;
86    let b = (scale * c + coeffs.b_u * d + 128) >> 8;
87    (
88        r.clamp(0, 255) as u8,
89        g.clamp(0, 255) as u8,
90        b.clamp(0, 255) as u8,
91    )
92}