Skip to main content

gamut_color/
format.rs

1//! Coded-plane bit depth and chroma subsampling.
2//!
3//! These describe a codec's *coded* planes, distinct from an interleaved buffer's layout — that is
4//! the [`Pixel`](gamut_core::Pixel) vocabulary (`Rgb8`, `Rgba8`, …) in `gamut-core`. [`BitDepth`] is
5//! wired into the AV1 reconstruction; [`ChromaSubsampling`] models only `Cs444` (4:4:4) at M0, with
6//! the subsampled variants reserved for M2 (see `gamut-avif/STATUS.md`).
7
8/// Bits per sample of a coded plane.
9#[repr(u8)]
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
11pub enum BitDepth {
12    /// 8 bits per sample.
13    Eight = 8,
14    /// 10 bits per sample (M2).
15    Ten = 10,
16    /// 12 bits per sample (M2).
17    Twelve = 12,
18}
19
20impl BitDepth {
21    /// Number of bits per sample.
22    #[must_use]
23    pub fn bits(self) -> u8 {
24        self as u8
25    }
26
27    /// The [`BitDepth`] for `bits` (8, 10, or 12), or `None` for any other value. The inverse of
28    /// [`BitDepth::bits`], for turning a codec's raw integer bit depth back into the typed form.
29    #[must_use]
30    pub fn from_bits(bits: u32) -> Option<Self> {
31        match bits {
32            8 => Some(BitDepth::Eight),
33            10 => Some(BitDepth::Ten),
34            12 => Some(BitDepth::Twelve),
35            _ => None,
36        }
37    }
38}
39
40/// Chroma subsampling of the coded planes (AV1 `subsampling_x` / `subsampling_y`, §5.5.2).
41#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
42pub enum ChromaSubsampling {
43    /// 4:4:4 — full-resolution chroma (`subsampling_x = subsampling_y = 0`). Required for identity.
44    Cs444,
45    /// 4:2:2 — horizontally halved chroma (M2).
46    Cs422,
47    /// 4:2:0 — halved in both directions (M2).
48    Cs420,
49    /// 4:0:0 — monochrome, no chroma planes (M2).
50    Cs400,
51}
52
53impl ChromaSubsampling {
54    /// Returns `(subsampling_x, subsampling_y)` as the AV1 sequence-header flags.
55    #[must_use]
56    pub fn subsampling(self) -> (u8, u8) {
57        match self {
58            ChromaSubsampling::Cs444 | ChromaSubsampling::Cs400 => (0, 0),
59            ChromaSubsampling::Cs422 => (1, 0),
60            ChromaSubsampling::Cs420 => (1, 1),
61        }
62    }
63}
64
65#[cfg(test)]
66mod tests {
67    use super::*;
68
69    #[test]
70    fn bit_depth_bits() {
71        assert_eq!(BitDepth::Eight.bits(), 8);
72        assert_eq!(BitDepth::Ten.bits(), 10);
73        assert_eq!(BitDepth::Twelve.bits(), 12);
74        // from_bits is the inverse of bits() for the three valid depths; other values are None.
75        for d in [BitDepth::Eight, BitDepth::Ten, BitDepth::Twelve] {
76            assert_eq!(BitDepth::from_bits(u32::from(d.bits())), Some(d));
77        }
78        assert_eq!(BitDepth::from_bits(16), None);
79        assert_eq!(BitDepth::from_bits(0), None);
80    }
81
82    #[test]
83    fn subsampling_flags() {
84        assert_eq!(ChromaSubsampling::Cs444.subsampling(), (0, 0));
85        assert_eq!(ChromaSubsampling::Cs420.subsampling(), (1, 1));
86        assert_eq!(ChromaSubsampling::Cs422.subsampling(), (1, 0));
87    }
88}