colorutils_rs/
gamma_curves.rs1#[inline]
9pub 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]
23pub 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]
37pub 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]
51pub 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]
65pub 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]
72pub 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]
79pub 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]
93pub 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]
107pub 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]
117pub fn log100_to_linear(gamma: f32) -> f32 {
119 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]
129pub fn log100_sqrt10_to_linear(gamma: f32) -> f32 {
131 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]
141pub 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]
151pub 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]
167pub 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)]
183pub 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]
195pub fn gamma2p2_from_linear(linear: f32) -> f32 {
197 pure_gamma_function(linear, 1f32 / 2.2f32)
198}
199
200#[inline]
201pub fn gamma2p2_to_linear(gamma: f32) -> f32 {
203 pure_gamma_function(gamma, 2.2f32)
204}
205
206#[inline]
207pub fn gamma2p8_from_linear(linear: f32) -> f32 {
209 pure_gamma_function(linear, 1f32 / 2.8f32)
210}
211
212#[inline]
213pub fn gamma2p8_to_linear(gamma: f32) -> f32 {
215 pure_gamma_function(gamma, 2.8f32)
216}
217
218#[inline]
219pub 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 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]
236pub 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 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]
254pub 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 linear * HLG_WHITE_NITS / SDR_WHITE_NITS
271}
272
273#[inline]
274pub fn hlg_from_linear(linear: f32) -> f32 {
276 const SDR_WHITE_NITS: f32 = 203.;
277 const HLG_WHITE_NITS: f32 = 1000.;
278 let mut linear = (linear * (SDR_WHITE_NITS / HLG_WHITE_NITS)).clamp(0., 1.);
280 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]
292pub 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)]
299pub enum TransferFunction {
301 Srgb,
303 Rec709,
305 Gamma2p2,
307 Gamma2p8,
309 Smpte428,
311 Log100,
313 Log100Sqrt10,
315 Bt1361,
317 Smpte240,
319 Pq,
321 Hlg,
323 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}