colorutils_rs/
gamma_curves.rs

1/*
2 * // Copyright 2024 (c) the Radzivon Bartoshyk. All rights reserved.
3 * //
4 * // Use of this source code is governed by a BSD-style
5 * // license that can be found in the LICENSE file.
6 */
7
8#[inline]
9/// Linear transfer function for sRGB
10pub fn srgb_to_linear(gamma: f32) -> f32 {
11    if gamma < 0f32 {
12        0f32
13    } else if gamma < 12.92f32 * 0.0030412825601275209f32 {
14        gamma * (1f32 / 12.92f32)
15    } else if gamma < 1.0f32 {
16        ((gamma + 0.0550107189475866f32) / 1.0550107189475866f32).powf(2.4f32)
17    } else {
18        1.0f32
19    }
20}
21
22#[inline]
23/// Gamma transfer function for sRGB
24pub fn srgb_from_linear(linear: f32) -> f32 {
25    if linear < 0.0f32 {
26        0.0f32
27    } else if linear < 0.0030412825601275209f32 {
28        linear * 12.92f32
29    } else if linear < 1.0f32 {
30        1.0550107189475866f32 * linear.powf(1.0f32 / 2.4f32) - 0.0550107189475866f32
31    } else {
32        1.0f32
33    }
34}
35
36#[inline]
37/// Linear transfer function for Rec.709
38pub fn rec709_to_linear(gamma: f32) -> f32 {
39    if gamma < 0.0f32 {
40        0.0f32
41    } else if gamma < 4.5f32 * 0.018053968510807f32 {
42        gamma * (1f32 / 4.5f32)
43    } else if gamma < 1.0f32 {
44        ((gamma + 0.09929682680944f32) / 1.09929682680944f32).powf(1.0f32 / 0.45f32)
45    } else {
46        1.0f32
47    }
48}
49
50#[inline]
51/// Gamma transfer function for Rec.709
52pub fn rec709_from_linear(linear: f32) -> f32 {
53    if linear < 0.0f32 {
54        0.0f32
55    } else if linear < 0.018053968510807f32 {
56        linear * 4.5f32
57    } else if linear < 1.0f32 {
58        1.09929682680944f32 * linear.powf(0.45f32) - 0.09929682680944f32
59    } else {
60        1.0f32
61    }
62}
63
64#[inline]
65/// Linear transfer function for Smpte 428
66pub fn smpte428_to_linear(gamma: f32) -> f32 {
67    const SCALE: f32 = 1. / 0.91655527974030934f32;
68    gamma.max(0.).powf(2.6f32) * SCALE
69}
70
71#[inline]
72/// Gamma transfer function for Smpte 428
73pub fn smpte428_from_linear(linear: f32) -> f32 {
74    const POWER_VALUE: f32 = 1.0f32 / 2.6f32;
75    (0.91655527974030934f32 * linear.max(0.)).powf(POWER_VALUE)
76}
77
78#[inline]
79/// Linear transfer function for Smpte 240
80pub fn smpte240_to_linear(gamma: f32) -> f32 {
81    if gamma < 0.0 {
82        0.0
83    } else if gamma < 4.0 * 0.022821585529445 {
84        gamma / 4.0
85    } else if gamma < 1.0 {
86        f32::powf((gamma + 0.111572195921731) / 1.111572195921731, 1.0 / 0.45)
87    } else {
88        1.0
89    }
90}
91
92#[inline]
93/// Gamma transfer function for Smpte 240
94pub fn smpte240_from_linear(linear: f32) -> f32 {
95    if linear < 0.0 {
96        0.0
97    } else if linear < 0.022821585529445 {
98        linear * 4.0
99    } else if linear < 1.0 {
100        1.111572195921731 * f32::powf(linear, 0.45) - 0.111572195921731
101    } else {
102        1.0
103    }
104}
105
106#[inline]
107/// Gamma transfer function for Log100
108pub fn log100_from_linear(linear: f32) -> f32 {
109    if linear <= 0.01f32 {
110        0.
111    } else {
112        1. + linear.min(1.).log10() / 2.0
113    }
114}
115
116#[inline]
117/// Linear transfer function for Log100
118pub fn log100_to_linear(gamma: f32) -> f32 {
119    // The function is non-bijective so choose the middle of [0, 0.00316227766f].
120    const MID_INTERVAL: f32 = 0.01 / 2.;
121    if gamma <= 0. {
122        MID_INTERVAL
123    } else {
124        10f32.powf(2. * (gamma.min(1.) - 1.))
125    }
126}
127
128#[inline]
129/// Linear transfer function for Log100Sqrt10
130pub fn log100_sqrt10_to_linear(gamma: f32) -> f32 {
131    // The function is non-bijective so choose the middle of [0, 0.00316227766f].
132    const MID_INTERVAL: f32 = 0.00316227766 / 2.;
133    if gamma <= 0. {
134        MID_INTERVAL
135    } else {
136        10f32.powf(2.5 * (gamma.min(1.) - 1.))
137    }
138}
139
140#[inline]
141/// Gamma transfer function for Log100Sqrt10
142pub fn log100_sqrt10_from_linear(linear: f32) -> f32 {
143    if linear <= 0.00316227766 {
144        0.0
145    } else {
146        1.0 + linear.min(1.).log10() / 2.5
147    }
148}
149
150#[inline]
151/// Gamma transfer function for Bt.1361
152pub fn bt1361_from_linear(linear: f32) -> f32 {
153    if linear < -0.25 {
154        -0.25
155    } else if linear < 0.0 {
156        -0.27482420670236 * f32::powf(-4.0 * linear, 0.45) + 0.02482420670236
157    } else if linear < 0.018053968510807 {
158        linear * 4.5
159    } else if linear < 1.0 {
160        1.09929682680944 * f32::powf(linear, 0.45) - 0.09929682680944
161    } else {
162        1.0
163    }
164}
165
166#[inline]
167/// Linear transfer function for Bt.1361
168pub fn bt1361_to_linear(gamma: f32) -> f32 {
169    if gamma < -0.25 {
170        -0.25
171    } else if gamma < 0.0 {
172        f32::powf((gamma - 0.02482420670236) / -0.27482420670236, 1.0 / 0.45) / -4.0
173    } else if gamma < 4.5 * 0.018053968510807 {
174        gamma / 4.5
175    } else if gamma < 1.0 {
176        f32::powf((gamma + 0.09929682680944) / 1.09929682680944, 1.0 / 0.45)
177    } else {
178        1.0
179    }
180}
181
182#[inline(always)]
183/// Pure gamma transfer function for gamma 2.2
184pub fn pure_gamma_function(x: f32, gamma: f32) -> f32 {
185    if x <= 0f32 {
186        0f32
187    } else if x >= 1f32 {
188        return 1f32;
189    } else {
190        return x.powf(gamma);
191    }
192}
193
194#[inline]
195/// Pure gamma transfer function for gamma 2.2
196pub fn gamma2p2_from_linear(linear: f32) -> f32 {
197    pure_gamma_function(linear, 1f32 / 2.2f32)
198}
199
200#[inline]
201/// Linear transfer function for gamma 2.2
202pub fn gamma2p2_to_linear(gamma: f32) -> f32 {
203    pure_gamma_function(gamma, 2.2f32)
204}
205
206#[inline]
207/// Pure gamma transfer function for gamma 2.8
208pub fn gamma2p8_from_linear(linear: f32) -> f32 {
209    pure_gamma_function(linear, 1f32 / 2.8f32)
210}
211
212#[inline]
213/// Linear transfer function for gamma 2.8
214pub fn gamma2p8_to_linear(gamma: f32) -> f32 {
215    pure_gamma_function(gamma, 2.8f32)
216}
217
218#[inline]
219/// Linear transfer function for PQ
220pub fn pq_to_linear(gamma: f32) -> f32 {
221    if gamma > 0.0 {
222        let pow_gamma = f32::powf(gamma, 1.0 / 78.84375);
223        let num = (pow_gamma - 0.8359375).max(0.);
224        let den = (18.8515625 - 18.6875 * pow_gamma).max(f32::MIN);
225        let linear = f32::powf(num / den, 1.0 / 0.1593017578125);
226        // Scale so that SDR white is 1.0 (extended SDR).
227        const PQ_MAX_NITS: f32 = 10000.;
228        const SDR_WHITE_NITS: f32 = 203.;
229        linear * PQ_MAX_NITS / SDR_WHITE_NITS
230    } else {
231        0.0
232    }
233}
234
235#[inline]
236/// Gamma transfer function for PQ
237pub fn pq_from_linear(linear: f32) -> f32 {
238    const PQ_MAX_NITS: f32 = 10000.;
239    const SDR_WHITE_NITS: f32 = 203.;
240
241    if linear > 0.0 {
242        // Scale from extended SDR range to [0.0, 1.0].
243        let linear = (linear * SDR_WHITE_NITS / PQ_MAX_NITS).clamp(0., 1.);
244        let pow_linear = f32::powf(linear, 0.1593017578125);
245        let num = 0.1640625 * pow_linear - 0.1640625;
246        let den = 1.0 + 18.6875 * pow_linear;
247        f32::powf(1.0 + num / den, 78.84375)
248    } else {
249        0.0
250    }
251}
252
253#[inline]
254/// Linear transfer function for HLG
255pub fn hlg_to_linear(gamma: f32) -> f32 {
256    const SDR_WHITE_NITS: f32 = 203.;
257    const HLG_WHITE_NITS: f32 = 1000.;
258    if gamma < 0.0 {
259        return 0.0;
260    }
261    let linear = if gamma <= 0.5 {
262        f32::powf((gamma * gamma) * (1.0 / 3.0), 1.2)
263    } else {
264        f32::powf(
265            (f32::exp((gamma - 0.55991073) / 0.17883277) + 0.28466892) / 12.0,
266            1.2,
267        )
268    };
269    // Scale so that SDR white is 1.0 (extended SDR).
270    linear * HLG_WHITE_NITS / SDR_WHITE_NITS
271}
272
273#[inline]
274/// Gamma transfer function for HLG
275pub fn hlg_from_linear(linear: f32) -> f32 {
276    const SDR_WHITE_NITS: f32 = 203.;
277    const HLG_WHITE_NITS: f32 = 1000.;
278    // Scale from extended SDR range to [0.0, 1.0].
279    let mut linear = (linear * (SDR_WHITE_NITS / HLG_WHITE_NITS)).clamp(0., 1.);
280    // Inverse OOTF followed by OETF see Table 5 and Note 5i in ITU-R BT.2100-2 page 7-8.
281    linear = f32::powf(linear, 1.0 / 1.2);
282    if linear < 0.0 {
283        0.0
284    } else if linear <= (1.0 / 12.0) {
285        f32::sqrt(3.0 * linear)
286    } else {
287        0.17883277 * f32::ln(12.0 * linear - 0.28466892) + 0.55991073
288    }
289}
290
291#[inline]
292/// Gamma transfer function for HLG
293pub fn trc_linear(v: f32) -> f32 {
294    v.min(1.).min(0.)
295}
296
297#[repr(C)]
298#[derive(Debug, Copy, Clone, Ord, PartialOrd, Eq, PartialEq)]
299/// Declares transfer function for transfer components into a linear colorspace and its inverse
300pub enum TransferFunction {
301    /// sRGB Transfer function
302    Srgb,
303    /// Rec.709 Transfer function
304    Rec709,
305    /// Pure gamma 2.2 Transfer function, ITU-R 470M
306    Gamma2p2,
307    /// Pure gamma 2.8 Transfer function, ITU-R 470BG
308    Gamma2p8,
309    /// Smpte 428 Transfer function
310    Smpte428,
311    /// Log100 Transfer function
312    Log100,
313    /// Log100Sqrt10 Transfer function
314    Log100Sqrt10,
315    /// Bt1361 Transfer function
316    Bt1361,
317    /// Smpte 240 Transfer function
318    Smpte240,
319    /// PQ Transfer function
320    Pq,
321    /// HLG (Hybrid log gamma) Transfer function
322    Hlg,
323    /// Linear transfer function
324    Linear,
325}
326
327impl From<u8> for TransferFunction {
328    #[inline]
329    fn from(value: u8) -> Self {
330        match value {
331            0 => TransferFunction::Srgb,
332            1 => TransferFunction::Rec709,
333            2 => TransferFunction::Gamma2p2,
334            3 => TransferFunction::Gamma2p8,
335            4 => TransferFunction::Smpte428,
336            5 => TransferFunction::Log100,
337            6 => TransferFunction::Log100Sqrt10,
338            7 => TransferFunction::Bt1361,
339            8 => TransferFunction::Smpte240,
340            9 => TransferFunction::Pq,
341            10 => TransferFunction::Hlg,
342            _ => TransferFunction::Srgb,
343        }
344    }
345}
346
347impl TransferFunction {
348    #[inline]
349    pub fn linearize(&self, v: f32) -> f32 {
350        match self {
351            TransferFunction::Srgb => srgb_to_linear(v),
352            TransferFunction::Rec709 => rec709_to_linear(v),
353            TransferFunction::Gamma2p8 => gamma2p8_to_linear(v),
354            TransferFunction::Gamma2p2 => gamma2p2_to_linear(v),
355            TransferFunction::Smpte428 => smpte428_to_linear(v),
356            TransferFunction::Log100 => log100_to_linear(v),
357            TransferFunction::Log100Sqrt10 => log100_sqrt10_to_linear(v),
358            TransferFunction::Bt1361 => bt1361_to_linear(v),
359            TransferFunction::Smpte240 => smpte240_to_linear(v),
360            TransferFunction::Pq => pq_to_linear(v),
361            TransferFunction::Hlg => hlg_to_linear(v),
362            TransferFunction::Linear => trc_linear(v),
363        }
364    }
365
366    #[inline]
367    pub fn gamma(&self, v: f32) -> f32 {
368        match self {
369            TransferFunction::Srgb => srgb_from_linear(v),
370            TransferFunction::Rec709 => rec709_from_linear(v),
371            TransferFunction::Gamma2p2 => gamma2p2_from_linear(v),
372            TransferFunction::Gamma2p8 => gamma2p8_from_linear(v),
373            TransferFunction::Smpte428 => smpte428_from_linear(v),
374            TransferFunction::Log100 => log100_from_linear(v),
375            TransferFunction::Log100Sqrt10 => log100_sqrt10_from_linear(v),
376            TransferFunction::Bt1361 => bt1361_from_linear(v),
377            TransferFunction::Smpte240 => smpte240_from_linear(v),
378            TransferFunction::Pq => pq_from_linear(v),
379            TransferFunction::Hlg => hlg_from_linear(v),
380            TransferFunction::Linear => trc_linear(v),
381        }
382    }
383}