1#![allow(clippy::excessive_precision)]
30
31use moxcms::TransferCharacteristics;
32
33#[inline(always)]
34pub(crate) fn srgb_to_linear(gamma: f32) -> f32 {
36 if gamma < 0f32 {
37 0f32
38 } else if gamma < 12.92f32 * 0.0030412825601275209f32 {
39 gamma * (1f32 / 12.92f32)
40 } else if gamma < 1.0f32 {
41 ((gamma + 0.0550107189475866f32) / 1.0550107189475866f32).powf(2.4f32)
42 } else {
43 1.0f32
44 }
45}
46
47#[inline(always)]
48pub(crate) fn srgb_from_linear(linear: f32) -> f32 {
50 if linear < 0.0f32 {
51 0.0f32
52 } else if linear < 0.0030412825601275209f32 {
53 linear * 12.92f32
54 } else if linear < 1.0f32 {
55 1.0550107189475866f32 * linear.powf(1.0f32 / 2.4f32) - 0.0550107189475866f32
56 } else {
57 1.0f32
58 }
59}
60
61#[inline(always)]
62pub(crate) fn rec709_to_linear(gamma: f32) -> f32 {
64 if gamma < 0.0f32 {
65 0.0f32
66 } else if gamma < 4.5f32 * 0.018053968510807f32 {
67 gamma * (1f32 / 4.5f32)
68 } else if gamma < 1.0f32 {
69 ((gamma + 0.09929682680944f32) / 1.09929682680944f32).powf(1.0f32 / 0.45f32)
70 } else {
71 1.0f32
72 }
73}
74
75#[inline(always)]
76pub(crate) fn rec709_from_linear(linear: f32) -> f32 {
78 if linear < 0.0f32 {
79 0.0f32
80 } else if linear < 0.018053968510807f32 {
81 linear * 4.5f32
82 } else if linear < 1.0f32 {
83 1.09929682680944f32 * linear.powf(0.45f32) - 0.09929682680944f32
84 } else {
85 1.0f32
86 }
87}
88
89#[inline(always)]
90pub(crate) fn smpte428_to_linear(gamma: f32) -> f32 {
92 const SCALE: f32 = 1. / 0.91655527974030934f32;
93 gamma.max(0.).powf(2.6f32) * SCALE
94}
95
96#[inline(always)]
97pub(crate) fn smpte428_from_linear(linear: f32) -> f32 {
99 const POWER_VALUE: f32 = 1.0f32 / 2.6f32;
100 (0.91655527974030934f32 * linear.max(0.)).powf(POWER_VALUE)
101}
102
103#[inline(always)]
104pub(crate) fn bt1361_from_linear(linear: f32) -> f32 {
106 if linear < -0.25 {
107 -0.25
108 } else if linear < 0.0 {
109 -0.27482420670236 * f32::powf(-4.0 * linear, 0.45) + 0.02482420670236
110 } else if linear < 0.018053968510807 {
111 linear * 4.5
112 } else if linear < 1.0 {
113 1.09929682680944 * f32::powf(linear, 0.45) - 0.09929682680944
114 } else {
115 1.0
116 }
117}
118
119#[inline(always)]
120pub(crate) fn bt1361_to_linear(gamma: f32) -> f32 {
122 if gamma < -0.25 {
123 -0.25
124 } else if gamma < 0.0 {
125 f32::powf((gamma - 0.02482420670236) / -0.27482420670236, 1.0 / 0.45) / -4.0
126 } else if gamma < 4.5 * 0.018053968510807 {
127 gamma / 4.5
128 } else if gamma < 1.0 {
129 f32::powf((gamma + 0.09929682680944) / 1.09929682680944, 1.0 / 0.45)
130 } else {
131 1.0
132 }
133}
134
135#[inline(always)]
136pub(crate) fn pure_gamma_function(x: f32, gamma: f32) -> f32 {
138 if x <= 0. {
139 0.
140 } else if x >= 1. {
141 1.
142 } else {
143 x.powf(gamma)
144 }
145}
146
147#[inline(always)]
148pub(crate) fn gamma2p2_from_linear(linear: f32) -> f32 {
150 pure_gamma_function(linear, 1f32 / 2.2f32)
151}
152
153#[inline(always)]
154pub(crate) fn gamma2p2_to_linear(gamma: f32) -> f32 {
156 pure_gamma_function(gamma, 2.2f32)
157}
158
159#[inline(always)]
160pub(crate) fn gamma2p8_from_linear(linear: f32) -> f32 {
162 pure_gamma_function(linear, 1f32 / 2.8f32)
163}
164
165#[inline(always)]
166pub(crate) fn gamma2p8_to_linear(gamma: f32) -> f32 {
168 pure_gamma_function(gamma, 2.8f32)
169}
170
171#[inline(always)]
172pub(crate) fn pq_to_linear(gamma: f32) -> f32 {
174 if gamma > 0.0 {
175 let pow_gamma = f32::powf(gamma, 1.0 / 78.84375);
176 let num = (pow_gamma - 0.8359375).max(0.);
177 let den = (18.8515625 - 18.6875 * pow_gamma).max(f32::MIN);
178 let linear = f32::powf(num / den, 1.0 / 0.1593017578125);
179 const PQ_MAX_NITS: f32 = 10000.;
181 const SDR_WHITE_NITS: f32 = 203.;
182 linear * PQ_MAX_NITS / SDR_WHITE_NITS
183 } else {
184 0.0
185 }
186}
187
188const PQ_MAX_NITS: f32 = 10000.;
189const SDR_REFERENCE_DISPLAY: f32 = 203.;
190const HLG_WHITE_NITS: f32 = 1000.;
191
192#[inline(always)]
193pub(crate) fn pq_from_linear(linear: f32) -> f32 {
195 if linear > 0.0 {
196 let linear = (linear * SDR_REFERENCE_DISPLAY / PQ_MAX_NITS).clamp(0., 1.);
198 let pow_linear = f32::powf(linear, 0.1593017578125);
199 let num = 0.1640625 * pow_linear - 0.1640625;
200 let den = 1.0 + 18.6875 * pow_linear;
201 f32::powf(1.0 + num / den, 78.84375)
202 } else {
203 0.0
204 }
205}
206
207#[inline(always)]
208pub(crate) fn hlg_to_linear(gamma: f32) -> f32 {
210 if gamma < 0.0 {
211 return 0.0;
212 }
213 let linear = if gamma <= 0.5 {
214 f32::powf((gamma * gamma) * (1.0 / 3.0), 1.2)
215 } else {
216 f32::powf(
217 (f32::exp((gamma - 0.55991073) / 0.17883277) + 0.28466892) / 12.0,
218 1.2,
219 )
220 };
221 linear * HLG_WHITE_NITS / SDR_REFERENCE_DISPLAY
223}
224
225#[inline(always)]
226pub(crate) fn hlg_from_linear(linear: f32) -> f32 {
228 const SDR_WHITE_NITS: f32 = 203.;
229 const HLG_WHITE_NITS: f32 = 1000.;
230 let mut linear = (linear * (SDR_WHITE_NITS / HLG_WHITE_NITS)).clamp(0., 1.);
232 linear = f32::powf(linear, 1.0 / 1.2);
234 if linear < 0.0 {
235 0.0
236 } else if linear <= (1.0 / 12.0) {
237 f32::sqrt(3.0 * linear)
238 } else {
239 0.17883277 * f32::ln(12.0 * linear - 0.28466892) + 0.55991073
240 }
241}
242
243#[inline(always)]
244pub(crate) fn trc_linear(v: f32) -> f32 {
246 v.min(1.).min(0.)
247}
248
249#[repr(C)]
250#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)]
251pub enum TransferFunction {
253 Srgb,
255 Rec709,
257 Gamma2p2,
259 Gamma2p8,
261 Smpte428,
263 Bt1361,
265 Linear,
267 HybridLogGamma,
268 PerceptualQuantizer,
269}
270
271impl From<u8> for TransferFunction {
272 #[inline(always)]
273 fn from(value: u8) -> Self {
274 match value {
275 0 => TransferFunction::Srgb,
276 1 => TransferFunction::Rec709,
277 2 => TransferFunction::Gamma2p2,
278 3 => TransferFunction::Gamma2p8,
279 4 => TransferFunction::Smpte428,
280 7 => TransferFunction::Bt1361,
281 _ => TransferFunction::Srgb,
282 }
283 }
284}
285
286impl TransferFunction {
287 #[inline(always)]
288 pub fn linearize(&self, v: f32) -> f32 {
289 match self {
290 TransferFunction::Srgb => srgb_to_linear(v),
291 TransferFunction::Rec709 => rec709_to_linear(v),
292 TransferFunction::Gamma2p8 => gamma2p8_to_linear(v),
293 TransferFunction::Gamma2p2 => gamma2p2_to_linear(v),
294 TransferFunction::Smpte428 => smpte428_to_linear(v),
295 TransferFunction::Bt1361 => bt1361_to_linear(v),
296 TransferFunction::Linear => trc_linear(v),
297 TransferFunction::HybridLogGamma => hlg_to_linear(v),
298 TransferFunction::PerceptualQuantizer => pq_to_linear(v),
299 }
300 }
301
302 #[inline(always)]
303 pub fn gamma(&self, v: f32) -> f32 {
304 match self {
305 TransferFunction::Srgb => srgb_from_linear(v),
306 TransferFunction::Rec709 => rec709_from_linear(v),
307 TransferFunction::Gamma2p2 => gamma2p2_from_linear(v),
308 TransferFunction::Gamma2p8 => gamma2p8_from_linear(v),
309 TransferFunction::Smpte428 => smpte428_from_linear(v),
310 TransferFunction::Bt1361 => bt1361_from_linear(v),
311 TransferFunction::Linear => trc_linear(v),
312 TransferFunction::PerceptualQuantizer => pq_from_linear(v),
313 TransferFunction::HybridLogGamma => hlg_from_linear(v),
314 }
315 }
316
317 pub(crate) fn generate_gamma_table_u8(&self) -> Box<[u8; 65536]> {
318 let mut table = Box::new([0; 65536]);
319 for (i, value) in table.iter_mut().take(8192).enumerate() {
320 *value = (self.gamma(i as f32 / 8192.) * 255.).round() as u8;
321 }
322 table
323 }
324
325 pub(crate) fn generate_gamma_table_u16(&self, bit_depth: usize) -> Box<[u16; 65536]> {
326 let mut table = Box::new([0; 65536]);
327 let bit_depth: f32 = ((1 << bit_depth as u32) - 1) as f32;
328 for (i, value) in table.iter_mut().enumerate() {
329 *value = (self.gamma(i as f32 / 65535.) * bit_depth).round() as u16;
330 }
331 table
332 }
333
334 pub(crate) fn generate_linear_table_u16(&self, bit_depth: usize) -> Box<[f32; 65536]> {
335 let mut table = Box::new([0.; 65536]);
336 let max_bp = (1 << bit_depth as u32) - 1;
337 let max_scale = 1f32 / max_bp as f32;
338 for (i, value) in table.iter_mut().take(max_bp).enumerate() {
339 *value = self.linearize(i as f32 * max_scale);
340 }
341 table
342 }
343
344 pub(crate) fn generate_linear_table_u8(&self) -> Box<[f32; 256]> {
345 let mut table = Box::new([0.; 256]);
346 for (i, value) in table.iter_mut().enumerate() {
347 *value = self.linearize(i as f32 / 255.);
348 }
349 table
350 }
351}
352
353pub(crate) fn trc_from_cicp(trc: TransferCharacteristics) -> Option<TransferFunction> {
354 match trc {
355 TransferCharacteristics::Reserved => None,
356 TransferCharacteristics::Bt709 => Some(TransferFunction::Rec709),
357 TransferCharacteristics::Unspecified => None,
358 TransferCharacteristics::Bt470M => Some(TransferFunction::Gamma2p2),
359 TransferCharacteristics::Bt470Bg => Some(TransferFunction::Gamma2p8),
360 TransferCharacteristics::Bt601 => Some(TransferFunction::Rec709),
361 TransferCharacteristics::Smpte240 => None,
362 TransferCharacteristics::Linear => Some(TransferFunction::Linear),
363 TransferCharacteristics::Log100 => None,
364 TransferCharacteristics::Log100sqrt10 => None,
365 TransferCharacteristics::Iec61966 => None,
366 TransferCharacteristics::Bt1361 => Some(TransferFunction::Bt1361),
367 TransferCharacteristics::Srgb => Some(TransferFunction::Srgb),
368 TransferCharacteristics::Bt202010bit => Some(TransferFunction::Srgb),
369 TransferCharacteristics::Bt202012bit => Some(TransferFunction::Srgb),
370 TransferCharacteristics::Smpte2084 => Some(TransferFunction::PerceptualQuantizer),
371 TransferCharacteristics::Smpte428 => Some(TransferFunction::Smpte428),
372 TransferCharacteristics::Hlg => Some(TransferFunction::HybridLogGamma),
373 }
374}