Skip to main content

zenpixels/
hdr.rs

1//! HDR metadata types.
2//!
3//! Pure data types for HDR content description. These travel with pixel
4//! data alongside [`Cicp`](crate::Cicp) and [`ColorContext`](crate::ColorContext).
5//!
6//! For tone mapping and HDR processing functions, see
7//! [`zenpixels-convert::hdr`](https://docs.rs/zenpixels-convert/latest/zenpixels_convert/hdr/).
8
9/// HDR content light level metadata (CEA-861.3 / CTA-861-H).
10///
11/// Describes the peak brightness characteristics of HDR content.
12/// Used by AVIF, JXL, PNG (cLLi chunk), and video containers.
13#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
14#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
15pub struct ContentLightLevel {
16    /// Maximum Content Light Level (MaxCLL) in cd/m² (nits).
17    /// Peak luminance of any single pixel in the content.
18    pub max_content_light_level: u16,
19    /// Maximum Frame-Average Light Level (MaxFALL) in cd/m².
20    /// Peak average luminance of any single frame.
21    pub max_frame_average_light_level: u16,
22}
23
24impl ContentLightLevel {
25    /// Create content light level metadata.
26    pub const fn new(max_content_light_level: u16, max_frame_average_light_level: u16) -> Self {
27        Self {
28            max_content_light_level,
29            max_frame_average_light_level,
30        }
31    }
32}
33
34/// Mastering display color volume metadata (SMPTE ST 2086).
35///
36/// Describes the display on which the content was mastered, enabling
37/// downstream displays to reproduce the creator's intent.
38#[derive(Clone, Copy, Debug, Default, PartialEq)]
39#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
40pub struct MasteringDisplay {
41    /// RGB primaries of the mastering display in CIE 1931 xy coordinates.
42    /// `[[rx, ry], [gx, gy], [bx, by]]`.
43    pub primaries_xy: [[f32; 2]; 3],
44    /// White point in CIE 1931 xy coordinates `[wx, wy]`.
45    pub white_point_xy: [f32; 2],
46    /// Maximum display luminance in cd/m².
47    pub max_luminance: f32,
48    /// Minimum display luminance in cd/m².
49    pub min_luminance: f32,
50}
51
52impl MasteringDisplay {
53    /// Create mastering display metadata from CIE 1931 xy coordinates and cd/m² luminances.
54    pub const fn new(
55        primaries_xy: [[f32; 2]; 3],
56        white_point_xy: [f32; 2],
57        max_luminance: f32,
58        min_luminance: f32,
59    ) -> Self {
60        Self {
61            primaries_xy,
62            white_point_xy,
63            max_luminance,
64            min_luminance,
65        }
66    }
67
68    /// BT.2020 primaries with D65 white point, 10000 nits peak (HDR10 reference).
69    pub const HDR10_REFERENCE: Self = Self {
70        primaries_xy: [[0.708, 0.292], [0.170, 0.797], [0.131, 0.046]],
71        white_point_xy: [0.3127, 0.3290],
72        max_luminance: 10000.0,
73        min_luminance: 0.0001,
74    };
75
76    /// Display P3 primaries with D65 white point, 1000 nits.
77    pub const DISPLAY_P3_1000: Self = Self {
78        primaries_xy: [[0.680, 0.320], [0.265, 0.690], [0.150, 0.060]],
79        white_point_xy: [0.3127, 0.3290],
80        max_luminance: 1000.0,
81        min_luminance: 0.0001,
82    };
83}
84
85#[cfg(test)]
86mod tests {
87    use super::*;
88
89    #[test]
90    fn content_light_level_new() {
91        let cll = ContentLightLevel::new(1000, 500);
92        assert_eq!(cll.max_content_light_level, 1000);
93        assert_eq!(cll.max_frame_average_light_level, 500);
94    }
95
96    #[test]
97    fn content_light_level_default() {
98        let cll = ContentLightLevel::default();
99        assert_eq!(cll.max_content_light_level, 0);
100        assert_eq!(cll.max_frame_average_light_level, 0);
101    }
102
103    #[test]
104    fn mastering_display_new() {
105        let md = MasteringDisplay::new(
106            [[0.68, 0.32], [0.265, 0.69], [0.15, 0.06]],
107            [0.3127, 0.329],
108            1000.0,
109            0.001,
110        );
111        assert_eq!(md.max_luminance, 1000.0);
112        assert_eq!(md.min_luminance, 0.001);
113    }
114
115    #[test]
116    fn mastering_display_constants() {
117        assert_eq!(MasteringDisplay::HDR10_REFERENCE.max_luminance, 10000.0);
118        assert_eq!(MasteringDisplay::DISPLAY_P3_1000.max_luminance, 1000.0);
119    }
120
121    #[test]
122    fn content_light_level_clone_eq() {
123        let a = ContentLightLevel::new(100, 50);
124        let b = a;
125        assert_eq!(a, b);
126    }
127
128    #[test]
129    #[cfg(feature = "std")]
130    fn content_light_level_hash() {
131        use core::hash::{Hash, Hasher};
132        let a = ContentLightLevel::new(100, 50);
133        let b = a;
134        let mut h1 = std::hash::DefaultHasher::new();
135        a.hash(&mut h1);
136        let mut h2 = std::hash::DefaultHasher::new();
137        b.hash(&mut h2);
138        assert_eq!(h1.finish(), h2.finish());
139    }
140}