1use crate::TransferFunction;
11
12pub use zenpixels::hdr::{ContentLightLevel, MasteringDisplay};
14
15#[derive(Clone, Copy, Debug, PartialEq)]
20pub struct HdrMetadata {
21 pub transfer: TransferFunction,
23 pub content_light_level: Option<ContentLightLevel>,
25 pub mastering_display: Option<MasteringDisplay>,
27}
28
29impl HdrMetadata {
30 #[must_use]
32 pub fn is_hdr(&self) -> bool {
33 matches!(self.transfer, TransferFunction::Pq | TransferFunction::Hlg)
34 }
35
36 #[must_use]
38 pub fn is_sdr(&self) -> bool {
39 !self.is_hdr()
40 }
41
42 pub fn hdr10(cll: ContentLightLevel) -> Self {
44 Self {
45 transfer: TransferFunction::Pq,
46 content_light_level: Some(cll),
47 mastering_display: Some(MasteringDisplay::HDR10_REFERENCE),
48 }
49 }
50
51 pub fn hlg() -> Self {
53 Self {
54 transfer: TransferFunction::Hlg,
55 content_light_level: None,
56 mastering_display: None,
57 }
58 }
59}
60
61#[inline]
72#[must_use]
73pub fn reinhard_tonemap(v: f32) -> f32 {
74 v / (1.0 + v)
75}
76
77#[inline]
81#[must_use]
82pub fn reinhard_inverse(v: f32) -> f32 {
83 if v >= 1.0 {
84 return f32::MAX;
85 }
86 v / (1.0 - v)
87}
88
89#[cfg(feature = "std")]
96#[inline]
97#[must_use]
98pub fn exposure_tonemap(v: f32, exposure: f32) -> f32 {
99 (v * 2.0f32.powf(exposure)).clamp(0.0, 1.0)
100}
101
102#[cfg(test)]
103mod tests {
104 use super::*;
105
106 #[test]
107 fn reinhard_boundaries() {
108 assert_eq!(reinhard_tonemap(0.0), 0.0);
109 assert!((reinhard_tonemap(1.0) - 0.5).abs() < 1e-6);
110 assert!(reinhard_tonemap(1000.0) > 0.99);
111 assert!(reinhard_tonemap(1000.0) < 1.0);
112 }
113
114 #[test]
115 fn reinhard_roundtrip() {
116 for &v in &[0.0, 0.1, 0.5, 1.0, 2.0, 10.0, 100.0] {
117 let mapped = reinhard_tonemap(v);
118 let unmapped = reinhard_inverse(mapped);
119 assert!(
120 (unmapped - v).abs() < 1e-4,
121 "Reinhard roundtrip failed for {v}: got {unmapped}"
122 );
123 }
124 }
125
126 #[test]
127 fn hdr_metadata_is_hdr() {
128 assert!(HdrMetadata::hdr10(ContentLightLevel::default()).is_hdr());
129 assert!(HdrMetadata::hlg().is_hdr());
130 assert!(
131 HdrMetadata {
132 transfer: TransferFunction::Srgb,
133 content_light_level: None,
134 mastering_display: None,
135 }
136 .is_sdr()
137 );
138 }
139
140 #[test]
141 fn hdr10_constructor() {
142 let cll = ContentLightLevel::new(4000, 1000);
143 let meta = HdrMetadata::hdr10(cll);
144 assert!(meta.is_hdr());
145 assert_eq!(meta.transfer, TransferFunction::Pq);
146 assert_eq!(meta.content_light_level, Some(cll));
147 assert!(meta.mastering_display.is_some());
148 }
149
150 #[test]
151 fn hlg_constructor() {
152 let meta = HdrMetadata::hlg();
153 assert!(meta.is_hdr());
154 assert_eq!(meta.transfer, TransferFunction::Hlg);
155 assert!(meta.content_light_level.is_none());
156 assert!(meta.mastering_display.is_none());
157 }
158
159 #[test]
160 #[cfg(feature = "std")]
161 fn exposure_tonemap_values() {
162 assert!((exposure_tonemap(0.5, 0.0) - 0.5).abs() < 1e-6);
164 assert!((exposure_tonemap(0.25, 1.0) - 0.5).abs() < 1e-5);
166 assert!((exposure_tonemap(0.5, -1.0) - 0.25).abs() < 1e-5);
168 assert_eq!(exposure_tonemap(0.8, 1.0), 1.0);
170 assert_eq!(exposure_tonemap(0.0, 5.0), 0.0);
171 }
172
173 #[test]
174 fn reinhard_inverse_at_one() {
175 assert_eq!(reinhard_inverse(1.0), f32::MAX);
176 }
177
178 #[test]
179 fn hdr_metadata_clone_partial_eq() {
180 let a = HdrMetadata::hlg();
181 let b = a;
182 assert_eq!(a, b);
183 }
184}