/rust/registry/src/index.crates.io-1949cf8c6b5b557f/moxcms-0.7.7/src/trc.rs
Line | Count | Source |
1 | | /* |
2 | | * // Copyright (c) Radzivon Bartoshyk 2/2025. All rights reserved. |
3 | | * // |
4 | | * // Redistribution and use in source and binary forms, with or without modification, |
5 | | * // are permitted provided that the following conditions are met: |
6 | | * // |
7 | | * // 1. Redistributions of source code must retain the above copyright notice, this |
8 | | * // list of conditions and the following disclaimer. |
9 | | * // |
10 | | * // 2. Redistributions in binary form must reproduce the above copyright notice, |
11 | | * // this list of conditions and the following disclaimer in the documentation |
12 | | * // and/or other materials provided with the distribution. |
13 | | * // |
14 | | * // 3. Neither the name of the copyright holder nor the names of its |
15 | | * // contributors may be used to endorse or promote products derived from |
16 | | * // this software without specific prior written permission. |
17 | | * // |
18 | | * // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
19 | | * // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
20 | | * // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
21 | | * // DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE |
22 | | * // FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL |
23 | | * // DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR |
24 | | * // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
25 | | * // CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, |
26 | | * // OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
27 | | * // OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
28 | | */ |
29 | | use crate::cicp::create_rec709_parametric; |
30 | | use crate::matan::is_curve_linear16; |
31 | | use crate::math::m_clamp; |
32 | | use crate::mlaf::{mlaf, neg_mlaf}; |
33 | | use crate::transform::PointeeSizeExpressible; |
34 | | use crate::writer::FloatToFixedU8Fixed8; |
35 | | use crate::{CmsError, ColorProfile, DataColorSpace, Rgb, TransferCharacteristics}; |
36 | | use num_traits::AsPrimitive; |
37 | | use pxfm::{dirty_powf, f_pow, f_powf}; |
38 | | |
39 | | #[derive(Clone, Debug)] |
40 | | pub enum ToneReprCurve { |
41 | | Lut(Vec<u16>), |
42 | | Parametric(Vec<f32>), |
43 | | } |
44 | | |
45 | | impl ToneReprCurve { |
46 | 0 | pub fn inverse(&self) -> Result<ToneReprCurve, CmsError> { |
47 | 0 | match self { |
48 | 0 | ToneReprCurve::Lut(lut) => { |
49 | 0 | let inverse_length = lut.len().max(256); |
50 | 0 | Ok(ToneReprCurve::Lut(invert_lut(lut, inverse_length))) |
51 | | } |
52 | 0 | ToneReprCurve::Parametric(parametric) => ParametricCurve::new(parametric) |
53 | 0 | .and_then(|x| x.invert()) |
54 | 0 | .map(|x| ToneReprCurve::Parametric([x.g, x.a, x.b, x.c, x.d, x.e, x.f].to_vec())) |
55 | 0 | .ok_or(CmsError::BuildTransferFunction), |
56 | | } |
57 | 0 | } |
58 | | |
59 | | /// Creates tone curve evaluator |
60 | 0 | pub fn make_linear_evaluator( |
61 | 0 | &self, |
62 | 0 | ) -> Result<Box<dyn ToneCurveEvaluator + Send + Sync>, CmsError> { |
63 | 0 | match self { |
64 | 0 | ToneReprCurve::Lut(lut) => { |
65 | 0 | if lut.is_empty() { |
66 | 0 | return Ok(Box::new(ToneCurveEvaluatorLinear {})); |
67 | 0 | } |
68 | 0 | if lut.len() == 1 { |
69 | 0 | let gamma = u8_fixed_8number_to_float(lut[0]); |
70 | 0 | return Ok(Box::new(ToneCurveEvaluatorPureGamma { gamma })); |
71 | 0 | } |
72 | 0 | let converted_curve = lut.iter().map(|&x| x as f32 / 65535.0).collect::<Vec<_>>(); |
73 | 0 | Ok(Box::new(ToneCurveLutEvaluator { |
74 | 0 | lut: converted_curve, |
75 | 0 | })) |
76 | | } |
77 | 0 | ToneReprCurve::Parametric(parametric) => { |
78 | 0 | let parametric_curve = |
79 | 0 | ParametricCurve::new(parametric).ok_or(CmsError::BuildTransferFunction)?; |
80 | 0 | Ok(Box::new(ToneCurveParametricEvaluator { |
81 | 0 | parametric: parametric_curve, |
82 | 0 | })) |
83 | | } |
84 | | } |
85 | 0 | } |
86 | | |
87 | | /// Creates tone curve evaluator from transfer characteristics |
88 | 0 | pub fn make_cicp_linear_evaluator( |
89 | 0 | transfer_characteristics: TransferCharacteristics, |
90 | 0 | ) -> Result<Box<dyn ToneCurveEvaluator + Send + Sync>, CmsError> { |
91 | 0 | if !transfer_characteristics.has_transfer_curve() { |
92 | 0 | return Err(CmsError::BuildTransferFunction); |
93 | 0 | } |
94 | 0 | Ok(Box::new(ToneCurveCicpLinearEvaluator { |
95 | 0 | trc: transfer_characteristics, |
96 | 0 | })) |
97 | 0 | } |
98 | | |
99 | | /// Creates tone curve inverse evaluator |
100 | 0 | pub fn make_gamma_evaluator( |
101 | 0 | &self, |
102 | 0 | ) -> Result<Box<dyn ToneCurveEvaluator + Send + Sync>, CmsError> { |
103 | 0 | match self { |
104 | 0 | ToneReprCurve::Lut(lut) => { |
105 | 0 | if lut.is_empty() { |
106 | 0 | return Ok(Box::new(ToneCurveEvaluatorLinear {})); |
107 | 0 | } |
108 | 0 | if lut.len() == 1 { |
109 | 0 | let gamma = 1. / u8_fixed_8number_to_float(lut[0]); |
110 | 0 | return Ok(Box::new(ToneCurveEvaluatorPureGamma { gamma })); |
111 | 0 | } |
112 | 0 | let inverted_lut = invert_lut(lut, 16384); |
113 | 0 | let converted_curve = inverted_lut |
114 | 0 | .iter() |
115 | 0 | .map(|&x| x as f32 / 65535.0) |
116 | 0 | .collect::<Vec<_>>(); |
117 | 0 | Ok(Box::new(ToneCurveLutEvaluator { |
118 | 0 | lut: converted_curve, |
119 | 0 | })) |
120 | | } |
121 | 0 | ToneReprCurve::Parametric(parametric) => { |
122 | 0 | let parametric_curve = ParametricCurve::new(parametric) |
123 | 0 | .and_then(|x| x.invert()) |
124 | 0 | .ok_or(CmsError::BuildTransferFunction)?; |
125 | 0 | Ok(Box::new(ToneCurveParametricEvaluator { |
126 | 0 | parametric: parametric_curve, |
127 | 0 | })) |
128 | | } |
129 | | } |
130 | 0 | } |
131 | | |
132 | | /// Creates tone curve inverse evaluator from transfer characteristics |
133 | 0 | pub fn make_cicp_gamma_evaluator( |
134 | 0 | transfer_characteristics: TransferCharacteristics, |
135 | 0 | ) -> Result<Box<dyn ToneCurveEvaluator + Send + Sync>, CmsError> { |
136 | 0 | if !transfer_characteristics.has_transfer_curve() { |
137 | 0 | return Err(CmsError::BuildTransferFunction); |
138 | 0 | } |
139 | 0 | Ok(Box::new(ToneCurveCicpGammaEvaluator { |
140 | 0 | trc: transfer_characteristics, |
141 | 0 | })) |
142 | 0 | } |
143 | | } |
144 | | |
145 | | struct ToneCurveCicpLinearEvaluator { |
146 | | trc: TransferCharacteristics, |
147 | | } |
148 | | |
149 | | struct ToneCurveCicpGammaEvaluator { |
150 | | trc: TransferCharacteristics, |
151 | | } |
152 | | |
153 | | impl ToneCurveEvaluator for ToneCurveCicpLinearEvaluator { |
154 | 0 | fn evaluate_tristimulus(&self, rgb: Rgb<f32>) -> Rgb<f32> { |
155 | 0 | Rgb::new( |
156 | 0 | self.trc.linearize(rgb.r as f64) as f32, |
157 | 0 | self.trc.linearize(rgb.g as f64) as f32, |
158 | 0 | self.trc.linearize(rgb.b as f64) as f32, |
159 | | ) |
160 | 0 | } |
161 | | |
162 | 0 | fn evaluate_value(&self, value: f32) -> f32 { |
163 | 0 | self.trc.linearize(value as f64) as f32 |
164 | 0 | } |
165 | | } |
166 | | |
167 | | impl ToneCurveEvaluator for ToneCurveCicpGammaEvaluator { |
168 | 0 | fn evaluate_tristimulus(&self, rgb: Rgb<f32>) -> Rgb<f32> { |
169 | 0 | Rgb::new( |
170 | 0 | self.trc.gamma(rgb.r as f64) as f32, |
171 | 0 | self.trc.gamma(rgb.g as f64) as f32, |
172 | 0 | self.trc.gamma(rgb.b as f64) as f32, |
173 | | ) |
174 | 0 | } |
175 | | |
176 | 0 | fn evaluate_value(&self, value: f32) -> f32 { |
177 | 0 | self.trc.gamma(value as f64) as f32 |
178 | 0 | } |
179 | | } |
180 | | |
181 | | struct ToneCurveLutEvaluator { |
182 | | lut: Vec<f32>, |
183 | | } |
184 | | |
185 | | impl ToneCurveEvaluator for ToneCurveLutEvaluator { |
186 | 0 | fn evaluate_value(&self, value: f32) -> f32 { |
187 | 0 | lut_interp_linear_float(value, &self.lut) |
188 | 0 | } |
189 | | |
190 | 0 | fn evaluate_tristimulus(&self, rgb: Rgb<f32>) -> Rgb<f32> { |
191 | 0 | Rgb::new( |
192 | 0 | lut_interp_linear_float(rgb.r, &self.lut), |
193 | 0 | lut_interp_linear_float(rgb.g, &self.lut), |
194 | 0 | lut_interp_linear_float(rgb.b, &self.lut), |
195 | | ) |
196 | 0 | } |
197 | | } |
198 | | |
199 | 0 | pub(crate) fn build_trc_table(num_entries: i32, eotf: impl Fn(f64) -> f64) -> Vec<u16> { |
200 | 0 | let mut table = vec![0u16; num_entries as usize]; |
201 | | |
202 | 0 | for (i, table_value) in table.iter_mut().enumerate() { |
203 | 0 | let x: f64 = i as f64 / (num_entries - 1) as f64; |
204 | 0 | let y: f64 = eotf(x); |
205 | | let mut output: f64; |
206 | 0 | output = y * 65535.0 + 0.5; |
207 | 0 | if output > 65535.0 { |
208 | 0 | output = 65535.0 |
209 | 0 | } |
210 | 0 | if output < 0.0 { |
211 | 0 | output = 0.0 |
212 | 0 | } |
213 | 0 | *table_value = output.floor() as u16; |
214 | | } |
215 | 0 | table |
216 | 0 | } Unexecuted instantiation: moxcms::trc::build_trc_table::<moxcms::gamma::pq_to_linear> Unexecuted instantiation: moxcms::trc::build_trc_table::<moxcms::gamma::hlg_to_linear> Unexecuted instantiation: moxcms::trc::build_trc_table::<moxcms::gamma::bt1361_to_linear> Unexecuted instantiation: moxcms::trc::build_trc_table::<moxcms::gamma::log100_to_linear> Unexecuted instantiation: moxcms::trc::build_trc_table::<moxcms::gamma::iec61966_to_linear> Unexecuted instantiation: moxcms::trc::build_trc_table::<moxcms::gamma::smpte240_to_linear> Unexecuted instantiation: moxcms::trc::build_trc_table::<moxcms::gamma::smpte428_to_linear> Unexecuted instantiation: moxcms::trc::build_trc_table::<moxcms::gamma::log100_sqrt10_to_linear> |
217 | | |
218 | | /// Creates Tone Reproduction curve from gamma |
219 | 0 | pub fn curve_from_gamma(gamma: f32) -> ToneReprCurve { |
220 | 0 | ToneReprCurve::Lut(vec![gamma.to_u8_fixed8()]) |
221 | 0 | } |
222 | | |
223 | | #[derive(Debug)] |
224 | | struct ParametricCurve { |
225 | | g: f32, |
226 | | a: f32, |
227 | | b: f32, |
228 | | c: f32, |
229 | | d: f32, |
230 | | e: f32, |
231 | | f: f32, |
232 | | } |
233 | | |
234 | | impl ParametricCurve { |
235 | | #[allow(clippy::many_single_char_names)] |
236 | 0 | fn new(params: &[f32]) -> Option<ParametricCurve> { |
237 | | // convert from the variable number of parameters |
238 | | // contained in profiles to a unified representation. |
239 | 0 | let g: f32 = params[0]; |
240 | 0 | match params[1..] { |
241 | 0 | [] => Some(ParametricCurve { |
242 | 0 | g, |
243 | 0 | a: 1., |
244 | 0 | b: 0., |
245 | 0 | c: 1., |
246 | 0 | d: 0., |
247 | 0 | e: 0., |
248 | 0 | f: 0., |
249 | 0 | }), |
250 | 0 | [a, b] => Some(ParametricCurve { |
251 | 0 | g, |
252 | 0 | a, |
253 | 0 | b, |
254 | 0 | c: 0., |
255 | 0 | d: -b / a, |
256 | 0 | e: 0., |
257 | 0 | f: 0., |
258 | 0 | }), |
259 | 0 | [a, b, c] => Some(ParametricCurve { |
260 | 0 | g, |
261 | 0 | a, |
262 | 0 | b, |
263 | 0 | c: 0., |
264 | 0 | d: -b / a, |
265 | 0 | e: c, |
266 | 0 | f: c, |
267 | 0 | }), |
268 | 0 | [a, b, c, d] => Some(ParametricCurve { |
269 | 0 | g, |
270 | 0 | a, |
271 | 0 | b, |
272 | 0 | c, |
273 | 0 | d, |
274 | 0 | e: 0., |
275 | 0 | f: 0., |
276 | 0 | }), |
277 | 0 | [a, b, c, d, e, f] => Some(ParametricCurve { |
278 | 0 | g, |
279 | 0 | a, |
280 | 0 | b, |
281 | 0 | c, |
282 | 0 | d, |
283 | 0 | e, |
284 | 0 | f, |
285 | 0 | }), |
286 | 0 | _ => None, |
287 | | } |
288 | 0 | } |
289 | | |
290 | 0 | fn is_linear(&self) -> bool { |
291 | 0 | (self.g - 1.0).abs() < 1e-5 |
292 | 0 | && (self.a - 1.0).abs() < 1e-5 |
293 | 0 | && self.b.abs() < 1e-5 |
294 | 0 | && self.c.abs() < 1e-5 |
295 | 0 | } |
296 | | |
297 | 0 | fn eval(&self, x: f32) -> f32 { |
298 | 0 | if x < self.d { |
299 | 0 | self.c * x + self.f |
300 | | } else { |
301 | 0 | f_powf(self.a * x + self.b, self.g) + self.e |
302 | | } |
303 | 0 | } |
304 | | |
305 | | #[allow(dead_code)] |
306 | | #[allow(clippy::many_single_char_names)] |
307 | 0 | fn invert(&self) -> Option<ParametricCurve> { |
308 | | // First check if the function is continuous at the cross-over point d. |
309 | 0 | let d1 = f_powf(self.a * self.d + self.b, self.g) + self.e; |
310 | 0 | let d2 = self.c * self.d + self.f; |
311 | | |
312 | 0 | if (d1 - d2).abs() > 0.1 { |
313 | 0 | return None; |
314 | 0 | } |
315 | 0 | let d = d1; |
316 | | |
317 | | // y = (a * x + b)^g + e |
318 | | // y - e = (a * x + b)^g |
319 | | // (y - e)^(1/g) = a*x + b |
320 | | // (y - e)^(1/g) - b = a*x |
321 | | // (y - e)^(1/g)/a - b/a = x |
322 | | // ((y - e)/a^g)^(1/g) - b/a = x |
323 | | // ((1/(a^g)) * y - e/(a^g))^(1/g) - b/a = x |
324 | 0 | let a = 1. / f_powf(self.a, self.g); |
325 | 0 | let b = -self.e / f_powf(self.a, self.g); |
326 | 0 | let g = 1. / self.g; |
327 | 0 | let e = -self.b / self.a; |
328 | | |
329 | | // y = c * x + f |
330 | | // y - f = c * x |
331 | | // y/c - f/c = x |
332 | | let (c, f); |
333 | 0 | if d <= 0. { |
334 | 0 | c = 1.; |
335 | 0 | f = 0.; |
336 | 0 | } else { |
337 | 0 | c = 1. / self.c; |
338 | 0 | f = -self.f / self.c; |
339 | 0 | } |
340 | | |
341 | | // if self.d > 0. and self.c == 0 as is likely with type 1 and 2 parametric function |
342 | | // then c and f will not be finite. |
343 | 0 | if !(g.is_finite() |
344 | 0 | && a.is_finite() |
345 | 0 | && b.is_finite() |
346 | 0 | && c.is_finite() |
347 | 0 | && d.is_finite() |
348 | 0 | && e.is_finite() |
349 | 0 | && f.is_finite()) |
350 | | { |
351 | 0 | return None; |
352 | 0 | } |
353 | | |
354 | 0 | Some(ParametricCurve { |
355 | 0 | g, |
356 | 0 | a, |
357 | 0 | b, |
358 | 0 | c, |
359 | 0 | d, |
360 | 0 | e, |
361 | 0 | f, |
362 | 0 | }) |
363 | 0 | } |
364 | | } |
365 | | |
366 | | #[inline] |
367 | 0 | pub(crate) fn u8_fixed_8number_to_float(x: u16) -> f32 { |
368 | | // 0x0000 = 0. |
369 | | // 0x0100 = 1. |
370 | | // 0xffff = 255 + 255/256 |
371 | 0 | (x as i32 as f64 / 256.0) as f32 |
372 | 0 | } |
373 | | |
374 | 0 | fn passthrough_table<T: PointeeSizeExpressible, const N: usize, const BIT_DEPTH: usize>() |
375 | 0 | -> Box<[f32; N]> { |
376 | 0 | let mut gamma_table = Box::new([0f32; N]); |
377 | 0 | let max_value = if T::FINITE { |
378 | 0 | (1 << BIT_DEPTH) - 1 |
379 | | } else { |
380 | 0 | T::NOT_FINITE_LINEAR_TABLE_SIZE - 1 |
381 | | }; |
382 | 0 | let cap_values = if T::FINITE { |
383 | 0 | (1u32 << BIT_DEPTH) as usize |
384 | | } else { |
385 | 0 | T::NOT_FINITE_LINEAR_TABLE_SIZE |
386 | | }; |
387 | 0 | assert!(cap_values <= N, "Invalid lut table construction"); |
388 | 0 | let scale_value = 1f64 / max_value as f64; |
389 | 0 | for (i, g) in gamma_table.iter_mut().enumerate().take(cap_values) { |
390 | 0 | *g = (i as f64 * scale_value) as f32; |
391 | 0 | } |
392 | | |
393 | 0 | gamma_table |
394 | 0 | } Unexecuted instantiation: moxcms::trc::passthrough_table::<f64, 65536, 1> Unexecuted instantiation: moxcms::trc::passthrough_table::<f32, 65536, 1> Unexecuted instantiation: moxcms::trc::passthrough_table::<f32, 16384, 1> Unexecuted instantiation: moxcms::trc::passthrough_table::<f32, 65535, 1> Unexecuted instantiation: moxcms::trc::passthrough_table::<u8, 256, 8> Unexecuted instantiation: moxcms::trc::passthrough_table::<u16, 65536, 16> Unexecuted instantiation: moxcms::trc::passthrough_table::<u16, 65536, 10> Unexecuted instantiation: moxcms::trc::passthrough_table::<u16, 65536, 12> Unexecuted instantiation: moxcms::trc::passthrough_table::<u16, 65536, 13> |
395 | | |
396 | 0 | fn linear_forward_table<T: PointeeSizeExpressible, const N: usize, const BIT_DEPTH: usize>( |
397 | 0 | gamma: u16, |
398 | 0 | ) -> Box<[f32; N]> { |
399 | 0 | let mut gamma_table = Box::new([0f32; N]); |
400 | 0 | let gamma_float: f32 = u8_fixed_8number_to_float(gamma); |
401 | 0 | let max_value = if T::FINITE { |
402 | 0 | (1 << BIT_DEPTH) - 1 |
403 | | } else { |
404 | 0 | T::NOT_FINITE_LINEAR_TABLE_SIZE - 1 |
405 | | }; |
406 | 0 | let cap_values = if T::FINITE { |
407 | 0 | (1u32 << BIT_DEPTH) as usize |
408 | | } else { |
409 | 0 | T::NOT_FINITE_LINEAR_TABLE_SIZE |
410 | | }; |
411 | 0 | assert!(cap_values <= N, "Invalid lut table construction"); |
412 | 0 | let scale_value = 1f64 / max_value as f64; |
413 | 0 | for (i, g) in gamma_table.iter_mut().enumerate().take(cap_values) { |
414 | 0 | *g = f_pow(i as f64 * scale_value, gamma_float as f64) as f32; |
415 | 0 | } |
416 | | |
417 | 0 | gamma_table |
418 | 0 | } Unexecuted instantiation: moxcms::trc::linear_forward_table::<f64, 65536, 1> Unexecuted instantiation: moxcms::trc::linear_forward_table::<f32, 65536, 1> Unexecuted instantiation: moxcms::trc::linear_forward_table::<f32, 65535, 1> Unexecuted instantiation: moxcms::trc::linear_forward_table::<u8, 256, 8> Unexecuted instantiation: moxcms::trc::linear_forward_table::<u16, 65536, 16> Unexecuted instantiation: moxcms::trc::linear_forward_table::<u16, 65536, 10> Unexecuted instantiation: moxcms::trc::linear_forward_table::<u16, 65536, 12> Unexecuted instantiation: moxcms::trc::linear_forward_table::<u16, 65536, 13> |
419 | | |
420 | | #[inline(always)] |
421 | 0 | pub(crate) fn lut_interp_linear_float(x: f32, table: &[f32]) -> f32 { |
422 | 0 | let value = x.min(1.).max(0.) * (table.len() - 1) as f32; |
423 | | |
424 | 0 | let upper: i32 = value.ceil() as i32; |
425 | 0 | let lower: i32 = value.floor() as i32; |
426 | | |
427 | 0 | let diff = upper as f32 - value; |
428 | 0 | let tu = table[upper as usize]; |
429 | 0 | mlaf(neg_mlaf(tu, tu, diff), table[lower as usize], diff) |
430 | 0 | } |
431 | | |
432 | | /// Lut interpolation float where values is already clamped |
433 | | #[inline(always)] |
434 | | #[allow(dead_code)] |
435 | 0 | pub(crate) fn lut_interp_linear_float_clamped(x: f32, table: &[f32]) -> f32 { |
436 | 0 | let value = x * (table.len() - 1) as f32; |
437 | | |
438 | 0 | let upper: i32 = value.ceil() as i32; |
439 | 0 | let lower: i32 = value.floor() as i32; |
440 | | |
441 | 0 | let diff = upper as f32 - value; |
442 | 0 | let tu = table[upper as usize]; |
443 | 0 | mlaf(neg_mlaf(tu, tu, diff), table[lower as usize], diff) |
444 | 0 | } |
445 | | |
446 | | #[inline] |
447 | 0 | pub(crate) fn lut_interp_linear(input_value: f64, table: &[u16]) -> f32 { |
448 | 0 | let mut input_value = input_value; |
449 | 0 | if table.is_empty() { |
450 | 0 | return input_value as f32; |
451 | 0 | } |
452 | | |
453 | 0 | input_value *= (table.len() - 1) as f64; |
454 | | |
455 | 0 | let upper: i32 = input_value.ceil() as i32; |
456 | 0 | let lower: i32 = input_value.floor() as i32; |
457 | 0 | let w0 = table[(upper as usize).min(table.len() - 1)] as f64; |
458 | 0 | let w1 = 1. - (upper as f64 - input_value); |
459 | 0 | let w2 = table[(lower as usize).min(table.len() - 1)] as f64; |
460 | 0 | let w3 = upper as f64 - input_value; |
461 | 0 | let value: f32 = mlaf(w2 * w3, w0, w1) as f32; |
462 | 0 | value * (1.0 / 65535.0) |
463 | 0 | } |
464 | | |
465 | 0 | fn linear_lut_interpolate<T: PointeeSizeExpressible, const N: usize, const BIT_DEPTH: usize>( |
466 | 0 | table: &[u16], |
467 | 0 | ) -> Box<[f32; N]> { |
468 | 0 | let mut gamma_table = Box::new([0f32; N]); |
469 | 0 | let max_value = if T::FINITE { |
470 | 0 | (1 << BIT_DEPTH) - 1 |
471 | | } else { |
472 | 0 | T::NOT_FINITE_LINEAR_TABLE_SIZE - 1 |
473 | | }; |
474 | 0 | let cap_values = if T::FINITE { |
475 | 0 | (1u32 << BIT_DEPTH) as usize |
476 | | } else { |
477 | 0 | T::NOT_FINITE_LINEAR_TABLE_SIZE |
478 | | }; |
479 | 0 | assert!(cap_values <= N, "Invalid lut table construction"); |
480 | 0 | let scale_value = 1f64 / max_value as f64; |
481 | 0 | for (i, g) in gamma_table.iter_mut().enumerate().take(cap_values) { |
482 | 0 | *g = lut_interp_linear(i as f64 * scale_value, table); |
483 | 0 | } |
484 | 0 | gamma_table |
485 | 0 | } Unexecuted instantiation: moxcms::trc::linear_lut_interpolate::<f64, 65536, 1> Unexecuted instantiation: moxcms::trc::linear_lut_interpolate::<f32, 65536, 1> Unexecuted instantiation: moxcms::trc::linear_lut_interpolate::<f32, 65535, 1> Unexecuted instantiation: moxcms::trc::linear_lut_interpolate::<u8, 256, 8> Unexecuted instantiation: moxcms::trc::linear_lut_interpolate::<u16, 65536, 16> Unexecuted instantiation: moxcms::trc::linear_lut_interpolate::<u16, 65536, 10> Unexecuted instantiation: moxcms::trc::linear_lut_interpolate::<u16, 65536, 12> Unexecuted instantiation: moxcms::trc::linear_lut_interpolate::<u16, 65536, 13> |
486 | | |
487 | 0 | fn linear_curve_parametric<T: PointeeSizeExpressible, const N: usize, const BIT_DEPTH: usize>( |
488 | 0 | params: &[f32], |
489 | 0 | ) -> Option<Box<[f32; N]>> { |
490 | 0 | let params = ParametricCurve::new(params)?; |
491 | 0 | let mut gamma_table = Box::new([0f32; N]); |
492 | 0 | let max_value = if T::FINITE { |
493 | 0 | (1 << BIT_DEPTH) - 1 |
494 | | } else { |
495 | 0 | T::NOT_FINITE_LINEAR_TABLE_SIZE - 1 |
496 | | }; |
497 | 0 | let cap_value = if T::FINITE { |
498 | 0 | 1 << BIT_DEPTH |
499 | | } else { |
500 | 0 | T::NOT_FINITE_LINEAR_TABLE_SIZE |
501 | | }; |
502 | 0 | let scale_value = 1f32 / max_value as f32; |
503 | 0 | for (i, g) in gamma_table.iter_mut().enumerate().take(cap_value) { |
504 | 0 | let x = i as f32 * scale_value; |
505 | 0 | *g = m_clamp(params.eval(x), 0.0, 1.0); |
506 | 0 | } |
507 | 0 | Some(gamma_table) |
508 | 0 | } Unexecuted instantiation: moxcms::trc::linear_curve_parametric::<f64, 65536, 1> Unexecuted instantiation: moxcms::trc::linear_curve_parametric::<f32, 65536, 1> Unexecuted instantiation: moxcms::trc::linear_curve_parametric::<f32, 65535, 1> Unexecuted instantiation: moxcms::trc::linear_curve_parametric::<u8, 256, 8> Unexecuted instantiation: moxcms::trc::linear_curve_parametric::<u16, 65536, 16> Unexecuted instantiation: moxcms::trc::linear_curve_parametric::<u16, 65536, 10> Unexecuted instantiation: moxcms::trc::linear_curve_parametric::<u16, 65536, 12> Unexecuted instantiation: moxcms::trc::linear_curve_parametric::<u16, 65536, 13> |
509 | | |
510 | 0 | fn linear_curve_parametric_s<const N: usize>(params: &[f32]) -> Option<Box<[f32; N]>> { |
511 | 0 | let params = ParametricCurve::new(params)?; |
512 | 0 | let mut gamma_table = Box::new([0f32; N]); |
513 | 0 | let scale_value = 1f32 / (N - 1) as f32; |
514 | 0 | for (i, g) in gamma_table.iter_mut().enumerate().take(N) { |
515 | 0 | let x = i as f32 * scale_value; |
516 | 0 | *g = m_clamp(params.eval(x), 0.0, 1.0); |
517 | 0 | } |
518 | 0 | Some(gamma_table) |
519 | 0 | } Unexecuted instantiation: moxcms::trc::linear_curve_parametric_s::<65536> Unexecuted instantiation: moxcms::trc::linear_curve_parametric_s::<4096> Unexecuted instantiation: moxcms::trc::linear_curve_parametric_s::<8192> Unexecuted instantiation: moxcms::trc::linear_curve_parametric_s::<16384> Unexecuted instantiation: moxcms::trc::linear_curve_parametric_s::<32768> Unexecuted instantiation: moxcms::trc::linear_curve_parametric_s::<4092> |
520 | | |
521 | 0 | pub(crate) fn make_gamma_linear_table< |
522 | 0 | T: Default + Copy + 'static + PointeeSizeExpressible, |
523 | 0 | const BUCKET: usize, |
524 | 0 | const N: usize, |
525 | 0 | >( |
526 | 0 | bit_depth: usize, |
527 | 0 | ) -> Box<[T; BUCKET]> |
528 | 0 | where |
529 | 0 | f32: AsPrimitive<T>, |
530 | | { |
531 | 0 | let mut table = Box::new([T::default(); BUCKET]); |
532 | 0 | let max_range = if T::FINITE { |
533 | 0 | (1f64 / ((N - 1) as f64 / (1 << bit_depth) as f64)) as f32 |
534 | | } else { |
535 | 0 | (1f64 / ((N - 1) as f64)) as f32 |
536 | | }; |
537 | 0 | for (v, output) in table.iter_mut().take(N).enumerate() { |
538 | 0 | if T::FINITE { |
539 | 0 | *output = (v as f32 * max_range).round().as_(); |
540 | 0 | } else { |
541 | 0 | *output = (v as f32 * max_range).as_(); |
542 | 0 | } |
543 | | } |
544 | 0 | table |
545 | 0 | } Unexecuted instantiation: moxcms::trc::make_gamma_linear_table::<f64, 65536, 65536> Unexecuted instantiation: moxcms::trc::make_gamma_linear_table::<f32, 65536, 32768> Unexecuted instantiation: moxcms::trc::make_gamma_linear_table::<u8, 65536, 4096> Unexecuted instantiation: moxcms::trc::make_gamma_linear_table::<u16, 65536, 65536> Unexecuted instantiation: moxcms::trc::make_gamma_linear_table::<u16, 65536, 8192> Unexecuted instantiation: moxcms::trc::make_gamma_linear_table::<u16, 65536, 16384> Unexecuted instantiation: moxcms::trc::make_gamma_linear_table::<u16, 65536, 4092> |
546 | | |
547 | | #[inline] |
548 | 0 | fn lut_interp_linear_gamma_impl< |
549 | 0 | T: Default + Copy + 'static + PointeeSizeExpressible, |
550 | 0 | const N: usize, |
551 | 0 | const BIT_DEPTH: usize, |
552 | 0 | >( |
553 | 0 | input_value: u32, |
554 | 0 | table: &[u16], |
555 | 0 | ) -> T |
556 | 0 | where |
557 | 0 | u32: AsPrimitive<T>, |
558 | | { |
559 | | // Start scaling input_value to the length of the array: GAMMA_CAP*(length-1). |
560 | | // We'll divide out the GAMMA_CAP next |
561 | 0 | let mut value: u32 = input_value * (table.len() - 1) as u32; |
562 | 0 | let cap_value = N - 1; |
563 | | // equivalent to ceil(value/GAMMA_CAP) |
564 | 0 | let upper: u32 = value.div_ceil(cap_value as u32); |
565 | | // equivalent to floor(value/GAMMA_CAP) |
566 | 0 | let lower: u32 = value / cap_value as u32; |
567 | | // interp is the distance from upper to value scaled to 0..GAMMA_CAP |
568 | 0 | let interp: u32 = value % cap_value as u32; |
569 | 0 | let lw_value = table[lower as usize]; |
570 | 0 | let hw_value = table[upper as usize]; |
571 | | // the table values range from 0..65535 |
572 | 0 | value = mlaf( |
573 | 0 | hw_value as u32 * interp, |
574 | 0 | lw_value as u32, |
575 | 0 | (N - 1) as u32 - interp, |
576 | 0 | ); // 0..(65535*GAMMA_CAP) |
577 | | |
578 | | // round and scale |
579 | 0 | let max_colors = if T::FINITE { (1 << BIT_DEPTH) - 1 } else { 1 }; |
580 | 0 | value += (cap_value * 65535 / max_colors / 2) as u32; // scale to 0...max_colors |
581 | 0 | value /= (cap_value * 65535 / max_colors) as u32; |
582 | 0 | value.as_() |
583 | 0 | } Unexecuted instantiation: moxcms::trc::lut_interp_linear_gamma_impl::<u8, 4096, 8> Unexecuted instantiation: moxcms::trc::lut_interp_linear_gamma_impl::<u16, 65536, 16> Unexecuted instantiation: moxcms::trc::lut_interp_linear_gamma_impl::<u16, 8192, 10> Unexecuted instantiation: moxcms::trc::lut_interp_linear_gamma_impl::<u16, 16384, 12> Unexecuted instantiation: moxcms::trc::lut_interp_linear_gamma_impl::<u16, 4092, 8> |
584 | | |
585 | | #[inline] |
586 | 0 | fn lut_interp_linear_gamma_impl_f32< |
587 | 0 | T: Default + Copy + 'static + PointeeSizeExpressible, |
588 | 0 | const N: usize, |
589 | 0 | const BIT_DEPTH: usize, |
590 | 0 | >( |
591 | 0 | input_value: u32, |
592 | 0 | table: &[u16], |
593 | 0 | ) -> T |
594 | 0 | where |
595 | 0 | f32: AsPrimitive<T>, |
596 | | { |
597 | | // Start scaling input_value to the length of the array: GAMMA_CAP*(length-1). |
598 | | // We'll divide out the GAMMA_CAP next |
599 | 0 | let guess: u32 = input_value * (table.len() - 1) as u32; |
600 | 0 | let cap_value = N - 1; |
601 | | // equivalent to ceil(value/GAMMA_CAP) |
602 | 0 | let upper: u32 = guess.div_ceil(cap_value as u32); |
603 | | // equivalent to floor(value/GAMMA_CAP) |
604 | 0 | let lower: u32 = guess / cap_value as u32; |
605 | | // interp is the distance from upper to value scaled to 0..GAMMA_CAP |
606 | 0 | let interp: u32 = guess % cap_value as u32; |
607 | 0 | let lw_value = table[lower as usize]; |
608 | 0 | let hw_value = table[upper as usize]; |
609 | | // the table values range from 0..65535 |
610 | 0 | let mut value = mlaf( |
611 | 0 | hw_value as f32 * interp as f32, |
612 | 0 | lw_value as f32, |
613 | 0 | (N - 1) as f32 - interp as f32, |
614 | | ); // 0..(65535*GAMMA_CAP) |
615 | | |
616 | | // round and scale |
617 | 0 | let max_colors = if T::FINITE { (1 << BIT_DEPTH) - 1 } else { 1 }; |
618 | 0 | value /= (cap_value * 65535 / max_colors) as f32; |
619 | 0 | value.as_() |
620 | 0 | } Unexecuted instantiation: moxcms::trc::lut_interp_linear_gamma_impl_f32::<f64, 65536, 1> Unexecuted instantiation: moxcms::trc::lut_interp_linear_gamma_impl_f32::<f32, 32768, 1> |
621 | | |
622 | | #[doc(hidden)] |
623 | | pub trait GammaLutInterpolate { |
624 | | fn gamma_lut_interp< |
625 | | T: Default + Copy + 'static + PointeeSizeExpressible, |
626 | | const N: usize, |
627 | | const BIT_DEPTH: usize, |
628 | | >( |
629 | | input_value: u32, |
630 | | table: &[u16], |
631 | | ) -> T |
632 | | where |
633 | | u32: AsPrimitive<T>, |
634 | | f32: AsPrimitive<T>; |
635 | | } |
636 | | |
637 | | macro_rules! gamma_lut_interp_fixed { |
638 | | ($i_type: ident) => { |
639 | | impl GammaLutInterpolate for $i_type { |
640 | | #[inline] |
641 | 0 | fn gamma_lut_interp< |
642 | 0 | T: Default + Copy + 'static + PointeeSizeExpressible, |
643 | 0 | const N: usize, |
644 | 0 | const BIT_DEPTH: usize, |
645 | 0 | >( |
646 | 0 | input_value: u32, |
647 | 0 | table: &[u16], |
648 | 0 | ) -> T |
649 | 0 | where |
650 | 0 | u32: AsPrimitive<T>, |
651 | | { |
652 | 0 | lut_interp_linear_gamma_impl::<T, N, BIT_DEPTH>(input_value, table) |
653 | 0 | } Unexecuted instantiation: <u8 as moxcms::trc::GammaLutInterpolate>::gamma_lut_interp::<u8, 4096, 8> Unexecuted instantiation: <u16 as moxcms::trc::GammaLutInterpolate>::gamma_lut_interp::<u16, 65536, 16> Unexecuted instantiation: <u16 as moxcms::trc::GammaLutInterpolate>::gamma_lut_interp::<u16, 8192, 10> Unexecuted instantiation: <u16 as moxcms::trc::GammaLutInterpolate>::gamma_lut_interp::<u16, 16384, 12> Unexecuted instantiation: <u16 as moxcms::trc::GammaLutInterpolate>::gamma_lut_interp::<u16, 4092, 8> |
654 | | } |
655 | | }; |
656 | | } |
657 | | |
658 | | gamma_lut_interp_fixed!(u8); |
659 | | gamma_lut_interp_fixed!(u16); |
660 | | |
661 | | macro_rules! gammu_lut_interp_float { |
662 | | ($f_type: ident) => { |
663 | | impl GammaLutInterpolate for $f_type { |
664 | | #[inline] |
665 | 0 | fn gamma_lut_interp< |
666 | 0 | T: Default + Copy + 'static + PointeeSizeExpressible, |
667 | 0 | const N: usize, |
668 | 0 | const BIT_DEPTH: usize, |
669 | 0 | >( |
670 | 0 | input_value: u32, |
671 | 0 | table: &[u16], |
672 | 0 | ) -> T |
673 | 0 | where |
674 | 0 | f32: AsPrimitive<T>, |
675 | 0 | u32: AsPrimitive<T>, |
676 | | { |
677 | 0 | lut_interp_linear_gamma_impl_f32::<T, N, BIT_DEPTH>(input_value, table) |
678 | 0 | } Unexecuted instantiation: <f32 as moxcms::trc::GammaLutInterpolate>::gamma_lut_interp::<f32, 32768, 1> Unexecuted instantiation: <f64 as moxcms::trc::GammaLutInterpolate>::gamma_lut_interp::<f64, 65536, 1> |
679 | | } |
680 | | }; |
681 | | } |
682 | | |
683 | | gammu_lut_interp_float!(f32); |
684 | | gammu_lut_interp_float!(f64); |
685 | | |
686 | 0 | pub(crate) fn make_gamma_lut< |
687 | 0 | T: Default + Copy + 'static + PointeeSizeExpressible + GammaLutInterpolate, |
688 | 0 | const BUCKET: usize, |
689 | 0 | const N: usize, |
690 | 0 | const BIT_DEPTH: usize, |
691 | 0 | >( |
692 | 0 | table: &[u16], |
693 | 0 | ) -> Box<[T; BUCKET]> |
694 | 0 | where |
695 | 0 | u32: AsPrimitive<T>, |
696 | 0 | f32: AsPrimitive<T>, |
697 | | { |
698 | 0 | let mut new_table = Box::new([T::default(); BUCKET]); |
699 | 0 | for (v, output) in new_table.iter_mut().take(N).enumerate() { |
700 | 0 | *output = T::gamma_lut_interp::<T, N, BIT_DEPTH>(v as u32, table); |
701 | 0 | } |
702 | 0 | new_table |
703 | 0 | } Unexecuted instantiation: moxcms::trc::make_gamma_lut::<f64, 65536, 65536, 1> Unexecuted instantiation: moxcms::trc::make_gamma_lut::<f32, 65536, 32768, 1> Unexecuted instantiation: moxcms::trc::make_gamma_lut::<u8, 65536, 4096, 8> Unexecuted instantiation: moxcms::trc::make_gamma_lut::<u16, 65536, 65536, 16> Unexecuted instantiation: moxcms::trc::make_gamma_lut::<u16, 65536, 8192, 10> Unexecuted instantiation: moxcms::trc::make_gamma_lut::<u16, 65536, 16384, 12> Unexecuted instantiation: moxcms::trc::make_gamma_lut::<u16, 65536, 4092, 8> |
704 | | |
705 | | #[inline] |
706 | 0 | pub(crate) fn lut_interp_linear16(input_value: u16, table: &[u16]) -> u16 { |
707 | | // Start scaling input_value to the length of the array: 65535*(length-1). |
708 | | // We'll divide out the 65535 next |
709 | 0 | let mut value: u32 = input_value as u32 * (table.len() as u32 - 1); |
710 | 0 | let upper: u16 = value.div_ceil(65535) as u16; // equivalent to ceil(value/65535) |
711 | 0 | let lower: u16 = (value / 65535) as u16; // equivalent to floor(value/65535) |
712 | | // interp is the distance from upper to value scaled to 0..65535 |
713 | 0 | let interp: u32 = value % 65535; // 0..65535*65535 |
714 | 0 | value = (table[upper as usize] as u32 * interp |
715 | 0 | + table[lower as usize] as u32 * (65535 - interp)) |
716 | 0 | / 65535; |
717 | 0 | value as u16 |
718 | 0 | } |
719 | | |
720 | | #[inline] |
721 | 0 | pub(crate) fn lut_interp_linear16_boxed<const N: usize>(input_value: u16, table: &[u16; N]) -> u16 { |
722 | | // Start scaling input_value to the length of the array: 65535*(length-1). |
723 | | // We'll divide out the 65535 next |
724 | 0 | let mut value: u32 = input_value as u32 * (table.len() as u32 - 1); |
725 | 0 | let upper: u16 = value.div_ceil(65535) as u16; // equivalent to ceil(value/65535) |
726 | 0 | let lower: u16 = (value / 65535) as u16; // equivalent to floor(value/65535) |
727 | | // interp is the distance from upper to value scaled to 0..65535 |
728 | 0 | let interp: u32 = value % 65535; // 0..65535*65535 |
729 | 0 | value = (table[upper as usize] as u32 * interp |
730 | 0 | + table[lower as usize] as u32 * (65535 - interp)) |
731 | 0 | / 65535; |
732 | 0 | value as u16 |
733 | 0 | } Unexecuted instantiation: moxcms::trc::lut_interp_linear16_boxed::<65536> Unexecuted instantiation: moxcms::trc::lut_interp_linear16_boxed::<4096> Unexecuted instantiation: moxcms::trc::lut_interp_linear16_boxed::<8192> Unexecuted instantiation: moxcms::trc::lut_interp_linear16_boxed::<16384> Unexecuted instantiation: moxcms::trc::lut_interp_linear16_boxed::<32768> Unexecuted instantiation: moxcms::trc::lut_interp_linear16_boxed::<4092> |
734 | | |
735 | 0 | fn make_gamma_pow_table< |
736 | 0 | T: Default + Copy + 'static + PointeeSizeExpressible, |
737 | 0 | const BUCKET: usize, |
738 | 0 | const N: usize, |
739 | 0 | >( |
740 | 0 | gamma: f32, |
741 | 0 | bit_depth: usize, |
742 | 0 | ) -> Box<[T; BUCKET]> |
743 | 0 | where |
744 | 0 | f32: AsPrimitive<T>, |
745 | | { |
746 | 0 | let mut table = Box::new([T::default(); BUCKET]); |
747 | 0 | let scale = 1f32 / (N - 1) as f32; |
748 | 0 | let cap = ((1 << bit_depth) - 1) as f32; |
749 | 0 | if T::FINITE { |
750 | 0 | for (v, output) in table.iter_mut().take(N).enumerate() { |
751 | 0 | *output = (cap * f_powf(v as f32 * scale, gamma)).round().as_(); |
752 | 0 | } |
753 | | } else { |
754 | 0 | for (v, output) in table.iter_mut().take(N).enumerate() { |
755 | 0 | *output = (cap * f_powf(v as f32 * scale, gamma)).as_(); |
756 | 0 | } |
757 | | } |
758 | 0 | table |
759 | 0 | } Unexecuted instantiation: moxcms::trc::make_gamma_pow_table::<f64, 65536, 65536> Unexecuted instantiation: moxcms::trc::make_gamma_pow_table::<f32, 65536, 32768> Unexecuted instantiation: moxcms::trc::make_gamma_pow_table::<u8, 65536, 4096> Unexecuted instantiation: moxcms::trc::make_gamma_pow_table::<u16, 65536, 65536> Unexecuted instantiation: moxcms::trc::make_gamma_pow_table::<u16, 65536, 8192> Unexecuted instantiation: moxcms::trc::make_gamma_pow_table::<u16, 65536, 16384> Unexecuted instantiation: moxcms::trc::make_gamma_pow_table::<u16, 65536, 4092> |
760 | | |
761 | 0 | fn make_gamma_parametric_table< |
762 | 0 | T: Default + Copy + 'static + PointeeSizeExpressible, |
763 | 0 | const BUCKET: usize, |
764 | 0 | const N: usize, |
765 | 0 | const BIT_DEPTH: usize, |
766 | 0 | >( |
767 | 0 | parametric_curve: ParametricCurve, |
768 | 0 | ) -> Box<[T; BUCKET]> |
769 | 0 | where |
770 | 0 | f32: AsPrimitive<T>, |
771 | | { |
772 | 0 | let mut table = Box::new([T::default(); BUCKET]); |
773 | 0 | let scale = 1f32 / (N - 1) as f32; |
774 | 0 | let cap = ((1 << BIT_DEPTH) - 1) as f32; |
775 | 0 | if T::FINITE { |
776 | 0 | for (v, output) in table.iter_mut().take(N).enumerate() { |
777 | 0 | *output = (cap * parametric_curve.eval(v as f32 * scale)) |
778 | 0 | .round() |
779 | 0 | .as_(); |
780 | 0 | } |
781 | | } else { |
782 | 0 | for (v, output) in table.iter_mut().take(N).enumerate() { |
783 | 0 | *output = (cap * parametric_curve.eval(v as f32 * scale)).as_(); |
784 | 0 | } |
785 | | } |
786 | 0 | table |
787 | 0 | } Unexecuted instantiation: moxcms::trc::make_gamma_parametric_table::<f64, 65536, 65536, 1> Unexecuted instantiation: moxcms::trc::make_gamma_parametric_table::<f32, 65536, 32768, 1> Unexecuted instantiation: moxcms::trc::make_gamma_parametric_table::<u8, 65536, 4096, 8> Unexecuted instantiation: moxcms::trc::make_gamma_parametric_table::<u16, 65536, 65536, 16> Unexecuted instantiation: moxcms::trc::make_gamma_parametric_table::<u16, 65536, 8192, 10> Unexecuted instantiation: moxcms::trc::make_gamma_parametric_table::<u16, 65536, 16384, 12> Unexecuted instantiation: moxcms::trc::make_gamma_parametric_table::<u16, 65536, 4092, 8> |
788 | | |
789 | | #[inline] |
790 | 0 | fn compare_parametric(src: &[f32], dst: &[f32]) -> bool { |
791 | 0 | for (src, dst) in src.iter().zip(dst.iter()) { |
792 | 0 | if (src - dst).abs() > 1e-4 { |
793 | 0 | return false; |
794 | 0 | } |
795 | | } |
796 | 0 | true |
797 | 0 | } |
798 | | |
799 | 0 | fn lut_inverse_interp16(value: u16, lut_table: &[u16]) -> u16 { |
800 | 0 | let mut l: i32 = 1; // 'int' Give spacing for negative values |
801 | 0 | let mut r: i32 = 0x10000; |
802 | 0 | let mut x: i32 = 0; |
803 | | let mut res: i32; |
804 | 0 | let length = lut_table.len() as i32; |
805 | | |
806 | 0 | let mut num_zeroes: i32 = 0; |
807 | 0 | for &item in lut_table.iter() { |
808 | 0 | if item == 0 { |
809 | 0 | num_zeroes += 1 |
810 | | } else { |
811 | 0 | break; |
812 | | } |
813 | | } |
814 | | |
815 | 0 | if num_zeroes == 0 && value as i32 == 0 { |
816 | 0 | return 0u16; |
817 | 0 | } |
818 | 0 | let mut num_of_polys: i32 = 0; |
819 | 0 | for &item in lut_table.iter().rev() { |
820 | 0 | if item == 0xffff { |
821 | 0 | num_of_polys += 1 |
822 | | } else { |
823 | 0 | break; |
824 | | } |
825 | | } |
826 | | // Does the curve belong to this case? |
827 | 0 | if num_zeroes > 1 || num_of_polys > 1 { |
828 | | let a_0: i32; |
829 | | let b_0: i32; |
830 | | // Identify if value fall downto 0 or FFFF zone |
831 | 0 | if value as i32 == 0 { |
832 | 0 | return 0u16; |
833 | 0 | } |
834 | | // if (Value == 0xFFFF) return 0xFFFF; |
835 | | // else restrict to valid zone |
836 | 0 | if num_zeroes > 1 { |
837 | 0 | a_0 = (num_zeroes - 1) * 0xffff / (length - 1); |
838 | 0 | l = a_0 - 1 |
839 | 0 | } |
840 | 0 | if num_of_polys > 1 { |
841 | 0 | b_0 = (length - 1 - num_of_polys) * 0xffff / (length - 1); |
842 | 0 | r = b_0 + 1 |
843 | 0 | } |
844 | 0 | } |
845 | 0 | if r <= l { |
846 | | // If this happens LutTable is not invertible |
847 | 0 | return 0u16; |
848 | 0 | } |
849 | | |
850 | 0 | while r > l { |
851 | 0 | x = (l + r) / 2; |
852 | 0 | res = lut_interp_linear16((x - 1) as u16, lut_table) as i32; |
853 | 0 | if res == value as i32 { |
854 | | // Found exact match. |
855 | 0 | return (x - 1) as u16; |
856 | 0 | } |
857 | 0 | if res > value as i32 { |
858 | 0 | r = x - 1 |
859 | | } else { |
860 | 0 | l = x + 1 |
861 | | } |
862 | | } |
863 | | |
864 | | // Not found, should we interpolate? |
865 | | |
866 | | // Get surrounding nodes |
867 | 0 | debug_assert!(x >= 1); |
868 | | |
869 | 0 | let val2: f64 = (length - 1) as f64 * ((x - 1) as f64 / 65535.0); |
870 | 0 | let cell0: i32 = val2.floor() as i32; |
871 | 0 | let cell1: i32 = val2.ceil() as i32; |
872 | 0 | if cell0 == cell1 { |
873 | 0 | return x as u16; |
874 | 0 | } |
875 | | |
876 | 0 | let y0: f64 = lut_table[cell0 as usize] as f64; |
877 | 0 | let x0: f64 = 65535.0 * cell0 as f64 / (length - 1) as f64; |
878 | 0 | let y1: f64 = lut_table[cell1 as usize] as f64; |
879 | 0 | let x1: f64 = 65535.0 * cell1 as f64 / (length - 1) as f64; |
880 | 0 | let a: f64 = (y1 - y0) / (x1 - x0); |
881 | 0 | let b: f64 = mlaf(y0, -a, x0); |
882 | 0 | if a.abs() < 0.01f64 { |
883 | 0 | return x as u16; |
884 | 0 | } |
885 | 0 | let f: f64 = (value as i32 as f64 - b) / a; |
886 | 0 | if f < 0.0 { |
887 | 0 | return 0u16; |
888 | 0 | } |
889 | 0 | if f >= 65535.0 { |
890 | 0 | return 0xffffu16; |
891 | 0 | } |
892 | 0 | (f + 0.5f64).floor() as u16 |
893 | 0 | } |
894 | | |
895 | 0 | fn lut_inverse_interp16_boxed<const N: usize>(value: u16, lut_table: &[u16; N]) -> u16 { |
896 | 0 | let mut l: i32 = 1; // 'int' Give spacing for negative values |
897 | 0 | let mut r: i32 = 0x10000; |
898 | 0 | let mut x: i32 = 0; |
899 | | let mut res: i32; |
900 | 0 | let length = lut_table.len() as i32; |
901 | | |
902 | 0 | let mut num_zeroes: i32 = 0; |
903 | 0 | for &item in lut_table.iter() { |
904 | 0 | if item == 0 { |
905 | 0 | num_zeroes += 1 |
906 | | } else { |
907 | 0 | break; |
908 | | } |
909 | | } |
910 | | |
911 | 0 | if num_zeroes == 0 && value as i32 == 0 { |
912 | 0 | return 0u16; |
913 | 0 | } |
914 | 0 | let mut num_of_polys: i32 = 0; |
915 | 0 | for &item in lut_table.iter().rev() { |
916 | 0 | if item == 0xffff { |
917 | 0 | num_of_polys += 1 |
918 | | } else { |
919 | 0 | break; |
920 | | } |
921 | | } |
922 | | // Does the curve belong to this case? |
923 | 0 | if num_zeroes > 1 || num_of_polys > 1 { |
924 | | let a_0: i32; |
925 | | let b_0: i32; |
926 | | // Identify if value fall downto 0 or FFFF zone |
927 | 0 | if value as i32 == 0 { |
928 | 0 | return 0u16; |
929 | 0 | } |
930 | | // if (Value == 0xFFFF) return 0xFFFF; |
931 | | // else restrict to valid zone |
932 | 0 | if num_zeroes > 1 { |
933 | 0 | a_0 = (num_zeroes - 1) * 0xffff / (length - 1); |
934 | 0 | l = a_0 - 1 |
935 | 0 | } |
936 | 0 | if num_of_polys > 1 { |
937 | 0 | b_0 = (length - 1 - num_of_polys) * 0xffff / (length - 1); |
938 | 0 | r = b_0 + 1 |
939 | 0 | } |
940 | 0 | } |
941 | 0 | if r <= l { |
942 | | // If this happens LutTable is not invertible |
943 | 0 | return 0u16; |
944 | 0 | } |
945 | | |
946 | 0 | while r > l { |
947 | 0 | x = (l + r) / 2; |
948 | 0 | res = lut_interp_linear16_boxed((x - 1) as u16, lut_table) as i32; |
949 | 0 | if res == value as i32 { |
950 | | // Found exact match. |
951 | 0 | return (x - 1) as u16; |
952 | 0 | } |
953 | 0 | if res > value as i32 { |
954 | 0 | r = x - 1 |
955 | | } else { |
956 | 0 | l = x + 1 |
957 | | } |
958 | | } |
959 | | |
960 | | // Not found, should we interpolate? |
961 | | |
962 | | // Get surrounding nodes |
963 | 0 | debug_assert!(x >= 1); |
964 | | |
965 | 0 | let val2: f64 = (length - 1) as f64 * ((x - 1) as f64 / 65535.0); |
966 | 0 | let cell0: i32 = val2.floor() as i32; |
967 | 0 | let cell1: i32 = val2.ceil() as i32; |
968 | 0 | if cell0 == cell1 { |
969 | 0 | return x as u16; |
970 | 0 | } |
971 | | |
972 | 0 | let y0: f64 = lut_table[cell0 as usize] as f64; |
973 | 0 | let x0: f64 = 65535.0 * cell0 as f64 / (length - 1) as f64; |
974 | 0 | let y1: f64 = lut_table[cell1 as usize] as f64; |
975 | 0 | let x1: f64 = 65535.0 * cell1 as f64 / (length - 1) as f64; |
976 | 0 | let a: f64 = (y1 - y0) / (x1 - x0); |
977 | 0 | let b: f64 = mlaf(y0, -a, x0); |
978 | 0 | if a.abs() < 0.01f64 { |
979 | 0 | return x as u16; |
980 | 0 | } |
981 | 0 | let f: f64 = (value as i32 as f64 - b) / a; |
982 | 0 | if f < 0.0 { |
983 | 0 | return 0u16; |
984 | 0 | } |
985 | 0 | if f >= 65535.0 { |
986 | 0 | return 0xffffu16; |
987 | 0 | } |
988 | 0 | (f + 0.5f64).floor() as u16 |
989 | 0 | } Unexecuted instantiation: moxcms::trc::lut_inverse_interp16_boxed::<65536> Unexecuted instantiation: moxcms::trc::lut_inverse_interp16_boxed::<4096> Unexecuted instantiation: moxcms::trc::lut_inverse_interp16_boxed::<8192> Unexecuted instantiation: moxcms::trc::lut_inverse_interp16_boxed::<16384> Unexecuted instantiation: moxcms::trc::lut_inverse_interp16_boxed::<32768> Unexecuted instantiation: moxcms::trc::lut_inverse_interp16_boxed::<4092> |
990 | | |
991 | 0 | fn invert_lut(table: &[u16], out_length: usize) -> Vec<u16> { |
992 | | // For now, we invert the lut by creating a lut of size out_length |
993 | | // and attempting to look up a value for each entry using lut_inverse_interp16 |
994 | 0 | let mut output = vec![0u16; out_length]; |
995 | 0 | let scale_value = 65535f64 / (out_length - 1) as f64; |
996 | 0 | for (i, out) in output.iter_mut().enumerate() { |
997 | 0 | let x: f64 = i as f64 * scale_value; |
998 | 0 | let input: u16 = (x + 0.5f64).floor() as u16; |
999 | 0 | *out = lut_inverse_interp16(input, table); |
1000 | 0 | } |
1001 | 0 | output |
1002 | 0 | } |
1003 | | |
1004 | 0 | fn invert_lut_boxed<const N: usize>(table: &[u16; N], out_length: usize) -> Vec<u16> { |
1005 | | // For now, we invert the lut by creating a lut of size out_length |
1006 | | // and attempting to look up a value for each entry using lut_inverse_interp16 |
1007 | 0 | let mut output = vec![0u16; out_length]; |
1008 | 0 | let scale_value = 65535f64 / (out_length - 1) as f64; |
1009 | 0 | for (i, out) in output.iter_mut().enumerate() { |
1010 | 0 | let x: f64 = i as f64 * scale_value; |
1011 | 0 | let input: u16 = (x + 0.5f64).floor() as u16; |
1012 | 0 | *out = lut_inverse_interp16_boxed(input, table); |
1013 | 0 | } |
1014 | 0 | output |
1015 | 0 | } Unexecuted instantiation: moxcms::trc::invert_lut_boxed::<65536> Unexecuted instantiation: moxcms::trc::invert_lut_boxed::<4096> Unexecuted instantiation: moxcms::trc::invert_lut_boxed::<8192> Unexecuted instantiation: moxcms::trc::invert_lut_boxed::<16384> Unexecuted instantiation: moxcms::trc::invert_lut_boxed::<32768> Unexecuted instantiation: moxcms::trc::invert_lut_boxed::<4092> |
1016 | | |
1017 | | impl ToneReprCurve { |
1018 | 0 | pub(crate) fn to_clut(&self) -> Result<Vec<f32>, CmsError> { |
1019 | 0 | match self { |
1020 | 0 | ToneReprCurve::Lut(lut) => { |
1021 | 0 | if lut.is_empty() { |
1022 | 0 | let passthrough_table = passthrough_table::<f32, 16384, 1>(); |
1023 | 0 | Ok(passthrough_table.to_vec()) |
1024 | | } else { |
1025 | 0 | Ok(lut |
1026 | 0 | .iter() |
1027 | 0 | .map(|&x| x as f32 * (1. / 65535.)) |
1028 | 0 | .collect::<Vec<_>>()) |
1029 | | } |
1030 | | } |
1031 | | ToneReprCurve::Parametric(_) => { |
1032 | 0 | let curve = self |
1033 | 0 | .build_linearize_table::<f32, 65535, 1>() |
1034 | 0 | .ok_or(CmsError::InvalidTrcCurve)?; |
1035 | 0 | let max_value = f32::NOT_FINITE_LINEAR_TABLE_SIZE - 1; |
1036 | 0 | let sliced = &curve[..max_value]; |
1037 | 0 | Ok(sliced.to_vec()) |
1038 | | } |
1039 | | } |
1040 | 0 | } |
1041 | | |
1042 | 0 | pub(crate) fn build_linearize_table< |
1043 | 0 | T: PointeeSizeExpressible, |
1044 | 0 | const N: usize, |
1045 | 0 | const BIT_DEPTH: usize, |
1046 | 0 | >( |
1047 | 0 | &self, |
1048 | 0 | ) -> Option<Box<[f32; N]>> { |
1049 | 0 | match self { |
1050 | 0 | ToneReprCurve::Parametric(params) => linear_curve_parametric::<T, N, BIT_DEPTH>(params), |
1051 | 0 | ToneReprCurve::Lut(data) => match data.len() { |
1052 | 0 | 0 => Some(passthrough_table::<T, N, BIT_DEPTH>()), |
1053 | 0 | 1 => Some(linear_forward_table::<T, N, BIT_DEPTH>(data[0])), |
1054 | 0 | _ => Some(linear_lut_interpolate::<T, N, BIT_DEPTH>(data)), |
1055 | | }, |
1056 | | } |
1057 | 0 | } Unexecuted instantiation: <moxcms::trc::ToneReprCurve>::build_linearize_table::<f64, 65536, 1> Unexecuted instantiation: <moxcms::trc::ToneReprCurve>::build_linearize_table::<f32, 65536, 1> Unexecuted instantiation: <moxcms::trc::ToneReprCurve>::build_linearize_table::<f32, 65535, 1> Unexecuted instantiation: <moxcms::trc::ToneReprCurve>::build_linearize_table::<u8, 256, 8> Unexecuted instantiation: <moxcms::trc::ToneReprCurve>::build_linearize_table::<u16, 65536, 16> Unexecuted instantiation: <moxcms::trc::ToneReprCurve>::build_linearize_table::<u16, 65536, 10> Unexecuted instantiation: <moxcms::trc::ToneReprCurve>::build_linearize_table::<u16, 65536, 12> Unexecuted instantiation: <moxcms::trc::ToneReprCurve>::build_linearize_table::<u16, 65536, 13> |
1058 | | |
1059 | 0 | pub(crate) fn build_gamma_table< |
1060 | 0 | T: Default + Copy + 'static + PointeeSizeExpressible + GammaLutInterpolate, |
1061 | 0 | const BUCKET: usize, |
1062 | 0 | const N: usize, |
1063 | 0 | const BIT_DEPTH: usize, |
1064 | 0 | >( |
1065 | 0 | &self, |
1066 | 0 | ) -> Option<Box<[T; BUCKET]>> |
1067 | 0 | where |
1068 | 0 | f32: AsPrimitive<T>, |
1069 | 0 | u32: AsPrimitive<T>, |
1070 | | { |
1071 | 0 | match self { |
1072 | 0 | ToneReprCurve::Parametric(params) => { |
1073 | 0 | if params.len() == 5 { |
1074 | 0 | let srgb_params = vec![2.4, 1. / 1.055, 0.055 / 1.055, 1. / 12.92, 0.04045]; |
1075 | 0 | let rec709_params = create_rec709_parametric(); |
1076 | | |
1077 | 0 | let mut lc_params: [f32; 5] = [0.; 5]; |
1078 | 0 | for (dst, src) in lc_params.iter_mut().zip(params.iter()) { |
1079 | 0 | *dst = *src; |
1080 | 0 | } |
1081 | | |
1082 | 0 | if compare_parametric(lc_params.as_slice(), srgb_params.as_slice()) { |
1083 | 0 | return Some( |
1084 | 0 | TransferCharacteristics::Srgb |
1085 | 0 | .make_gamma_table::<T, BUCKET, N>(BIT_DEPTH), |
1086 | 0 | ); |
1087 | 0 | } |
1088 | | |
1089 | 0 | if compare_parametric(lc_params.as_slice(), rec709_params.as_slice()) { |
1090 | 0 | return Some( |
1091 | 0 | TransferCharacteristics::Bt709 |
1092 | 0 | .make_gamma_table::<T, BUCKET, N>(BIT_DEPTH), |
1093 | 0 | ); |
1094 | 0 | } |
1095 | 0 | } |
1096 | | |
1097 | 0 | let parametric_curve = ParametricCurve::new(params); |
1098 | 0 | if let Some(v) = parametric_curve? |
1099 | 0 | .invert() |
1100 | 0 | .map(|x| make_gamma_parametric_table::<T, BUCKET, N, BIT_DEPTH>(x)) Unexecuted instantiation: <moxcms::trc::ToneReprCurve>::build_gamma_table::<f64, 65536, 65536, 1>::{closure#0} Unexecuted instantiation: <moxcms::trc::ToneReprCurve>::build_gamma_table::<f32, 65536, 32768, 1>::{closure#0} Unexecuted instantiation: <moxcms::trc::ToneReprCurve>::build_gamma_table::<u8, 65536, 4096, 8>::{closure#0} Unexecuted instantiation: <moxcms::trc::ToneReprCurve>::build_gamma_table::<u16, 65536, 65536, 16>::{closure#0} Unexecuted instantiation: <moxcms::trc::ToneReprCurve>::build_gamma_table::<u16, 65536, 8192, 10>::{closure#0} Unexecuted instantiation: <moxcms::trc::ToneReprCurve>::build_gamma_table::<u16, 65536, 16384, 12>::{closure#0} Unexecuted instantiation: <moxcms::trc::ToneReprCurve>::build_gamma_table::<u16, 65536, 4092, 8>::{closure#0} |
1101 | | { |
1102 | 0 | return Some(v); |
1103 | 0 | } |
1104 | | |
1105 | 0 | let mut gamma_table_uint = Box::new([0; N]); |
1106 | | |
1107 | 0 | let inverted_size: usize = N; |
1108 | 0 | let gamma_table = linear_curve_parametric_s::<N>(params)?; |
1109 | 0 | for (&src, dst) in gamma_table.iter().zip(gamma_table_uint.iter_mut()) { |
1110 | 0 | *dst = (src * 65535f32) as u16; |
1111 | 0 | } |
1112 | 0 | let inverted = invert_lut_boxed(&gamma_table_uint, inverted_size); |
1113 | 0 | Some(make_gamma_lut::<T, BUCKET, N, BIT_DEPTH>(&inverted)) |
1114 | | } |
1115 | 0 | ToneReprCurve::Lut(data) => match data.len() { |
1116 | 0 | 0 => Some(make_gamma_linear_table::<T, BUCKET, N>(BIT_DEPTH)), |
1117 | 0 | 1 => Some(make_gamma_pow_table::<T, BUCKET, N>( |
1118 | 0 | 1. / u8_fixed_8number_to_float(data[0]), |
1119 | 0 | BIT_DEPTH, |
1120 | 0 | )), |
1121 | | _ => { |
1122 | 0 | let mut inverted_size = data.len(); |
1123 | 0 | if inverted_size < 256 { |
1124 | 0 | inverted_size = 256 |
1125 | 0 | } |
1126 | 0 | let inverted = invert_lut(data, inverted_size); |
1127 | 0 | Some(make_gamma_lut::<T, BUCKET, N, BIT_DEPTH>(&inverted)) |
1128 | | } |
1129 | | }, |
1130 | | } |
1131 | 0 | } Unexecuted instantiation: <moxcms::trc::ToneReprCurve>::build_gamma_table::<f64, 65536, 65536, 1> Unexecuted instantiation: <moxcms::trc::ToneReprCurve>::build_gamma_table::<f32, 65536, 32768, 1> Unexecuted instantiation: <moxcms::trc::ToneReprCurve>::build_gamma_table::<u8, 65536, 4096, 8> Unexecuted instantiation: <moxcms::trc::ToneReprCurve>::build_gamma_table::<u16, 65536, 65536, 16> Unexecuted instantiation: <moxcms::trc::ToneReprCurve>::build_gamma_table::<u16, 65536, 8192, 10> Unexecuted instantiation: <moxcms::trc::ToneReprCurve>::build_gamma_table::<u16, 65536, 16384, 12> Unexecuted instantiation: <moxcms::trc::ToneReprCurve>::build_gamma_table::<u16, 65536, 4092, 8> |
1132 | | } |
1133 | | |
1134 | | impl ColorProfile { |
1135 | | /// Produces LUT for 8 bit tone linearization |
1136 | 0 | pub fn build_8bit_lin_table( |
1137 | 0 | &self, |
1138 | 0 | trc: &Option<ToneReprCurve>, |
1139 | 0 | ) -> Result<Box<[f32; 256]>, CmsError> { |
1140 | 0 | trc.as_ref() |
1141 | 0 | .and_then(|trc| trc.build_linearize_table::<u8, 256, 8>()) |
1142 | 0 | .ok_or(CmsError::BuildTransferFunction) |
1143 | 0 | } |
1144 | | |
1145 | | /// Produces LUT for Gray transfer curve with N depth |
1146 | 0 | pub fn build_gray_linearize_table< |
1147 | 0 | T: PointeeSizeExpressible, |
1148 | 0 | const N: usize, |
1149 | 0 | const BIT_DEPTH: usize, |
1150 | 0 | >( |
1151 | 0 | &self, |
1152 | 0 | ) -> Result<Box<[f32; N]>, CmsError> { |
1153 | 0 | self.gray_trc |
1154 | 0 | .as_ref() |
1155 | 0 | .and_then(|trc| trc.build_linearize_table::<T, N, BIT_DEPTH>()) Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_gray_linearize_table::<f64, 65536, 1>::{closure#0} Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_gray_linearize_table::<f32, 65536, 1>::{closure#0} Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_gray_linearize_table::<u8, 256, 8>::{closure#0} Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_gray_linearize_table::<u16, 65536, 16>::{closure#0} Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_gray_linearize_table::<u16, 65536, 10>::{closure#0} Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_gray_linearize_table::<u16, 65536, 12>::{closure#0} |
1156 | 0 | .ok_or(CmsError::BuildTransferFunction) |
1157 | 0 | } Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_gray_linearize_table::<f64, 65536, 1> Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_gray_linearize_table::<f32, 65536, 1> Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_gray_linearize_table::<u8, 256, 8> Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_gray_linearize_table::<u16, 65536, 16> Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_gray_linearize_table::<u16, 65536, 10> Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_gray_linearize_table::<u16, 65536, 12> |
1158 | | |
1159 | | /// Produces LUT for Red transfer curve with N depth |
1160 | 0 | pub fn build_r_linearize_table< |
1161 | 0 | T: PointeeSizeExpressible, |
1162 | 0 | const N: usize, |
1163 | 0 | const BIT_DEPTH: usize, |
1164 | 0 | >( |
1165 | 0 | &self, |
1166 | 0 | use_cicp: bool, |
1167 | 0 | ) -> Result<Box<[f32; N]>, CmsError> { |
1168 | 0 | if use_cicp { |
1169 | 0 | if let Some(tc) = self.cicp.as_ref().map(|c| c.transfer_characteristics) { |
1170 | 0 | if tc.has_transfer_curve() { |
1171 | 0 | return Ok(tc.make_linear_table::<T, N, BIT_DEPTH>()); |
1172 | 0 | } |
1173 | 0 | } |
1174 | 0 | } |
1175 | 0 | self.red_trc |
1176 | 0 | .as_ref() |
1177 | 0 | .and_then(|trc| trc.build_linearize_table::<T, N, BIT_DEPTH>()) Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_r_linearize_table::<f64, 65536, 1>::{closure#1} Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_r_linearize_table::<f32, 65536, 1>::{closure#1} Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_r_linearize_table::<u8, 256, 8>::{closure#1} Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_r_linearize_table::<u16, 65536, 16>::{closure#1} Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_r_linearize_table::<u16, 65536, 10>::{closure#1} Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_r_linearize_table::<u16, 65536, 12>::{closure#1} |
1178 | 0 | .ok_or(CmsError::BuildTransferFunction) |
1179 | 0 | } Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_r_linearize_table::<f64, 65536, 1> Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_r_linearize_table::<f32, 65536, 1> Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_r_linearize_table::<u8, 256, 8> Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_r_linearize_table::<u16, 65536, 16> Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_r_linearize_table::<u16, 65536, 10> Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_r_linearize_table::<u16, 65536, 12> |
1180 | | |
1181 | | /// Produces LUT for Green transfer curve with N depth |
1182 | 0 | pub fn build_g_linearize_table< |
1183 | 0 | T: PointeeSizeExpressible, |
1184 | 0 | const N: usize, |
1185 | 0 | const BIT_DEPTH: usize, |
1186 | 0 | >( |
1187 | 0 | &self, |
1188 | 0 | use_cicp: bool, |
1189 | 0 | ) -> Result<Box<[f32; N]>, CmsError> { |
1190 | 0 | if use_cicp { |
1191 | 0 | if let Some(tc) = self.cicp.as_ref().map(|c| c.transfer_characteristics) { |
1192 | 0 | if tc.has_transfer_curve() { |
1193 | 0 | return Ok(tc.make_linear_table::<T, N, BIT_DEPTH>()); |
1194 | 0 | } |
1195 | 0 | } |
1196 | 0 | } |
1197 | 0 | self.green_trc |
1198 | 0 | .as_ref() |
1199 | 0 | .and_then(|trc| trc.build_linearize_table::<T, N, BIT_DEPTH>()) Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_g_linearize_table::<f64, 65536, 1>::{closure#1} Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_g_linearize_table::<f32, 65536, 1>::{closure#1} Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_g_linearize_table::<u8, 256, 8>::{closure#1} Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_g_linearize_table::<u16, 65536, 16>::{closure#1} Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_g_linearize_table::<u16, 65536, 10>::{closure#1} Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_g_linearize_table::<u16, 65536, 12>::{closure#1} |
1200 | 0 | .ok_or(CmsError::BuildTransferFunction) |
1201 | 0 | } Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_g_linearize_table::<f64, 65536, 1> Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_g_linearize_table::<f32, 65536, 1> Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_g_linearize_table::<u8, 256, 8> Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_g_linearize_table::<u16, 65536, 16> Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_g_linearize_table::<u16, 65536, 10> Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_g_linearize_table::<u16, 65536, 12> |
1202 | | |
1203 | | /// Produces LUT for Blue transfer curve with N depth |
1204 | 0 | pub fn build_b_linearize_table< |
1205 | 0 | T: PointeeSizeExpressible, |
1206 | 0 | const N: usize, |
1207 | 0 | const BIT_DEPTH: usize, |
1208 | 0 | >( |
1209 | 0 | &self, |
1210 | 0 | use_cicp: bool, |
1211 | 0 | ) -> Result<Box<[f32; N]>, CmsError> { |
1212 | 0 | if use_cicp { |
1213 | 0 | if let Some(tc) = self.cicp.as_ref().map(|c| c.transfer_characteristics) { |
1214 | 0 | if tc.has_transfer_curve() { |
1215 | 0 | return Ok(tc.make_linear_table::<T, N, BIT_DEPTH>()); |
1216 | 0 | } |
1217 | 0 | } |
1218 | 0 | } |
1219 | 0 | self.blue_trc |
1220 | 0 | .as_ref() |
1221 | 0 | .and_then(|trc| trc.build_linearize_table::<T, N, BIT_DEPTH>()) Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_b_linearize_table::<f64, 65536, 1>::{closure#1} Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_b_linearize_table::<f32, 65536, 1>::{closure#1} Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_b_linearize_table::<u8, 256, 8>::{closure#1} Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_b_linearize_table::<u16, 65536, 16>::{closure#1} Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_b_linearize_table::<u16, 65536, 10>::{closure#1} Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_b_linearize_table::<u16, 65536, 12>::{closure#1} |
1222 | 0 | .ok_or(CmsError::BuildTransferFunction) |
1223 | 0 | } Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_b_linearize_table::<f64, 65536, 1> Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_b_linearize_table::<f32, 65536, 1> Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_b_linearize_table::<u8, 256, 8> Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_b_linearize_table::<u16, 65536, 16> Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_b_linearize_table::<u16, 65536, 10> Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_b_linearize_table::<u16, 65536, 12> |
1224 | | |
1225 | | /// Build gamma table for 8 bit depth |
1226 | | /// Only 4092 first bins are used and values scaled in 0..255 |
1227 | 0 | pub fn build_8bit_gamma_table( |
1228 | 0 | &self, |
1229 | 0 | trc: &Option<ToneReprCurve>, |
1230 | 0 | use_cicp: bool, |
1231 | 0 | ) -> Result<Box<[u16; 65536]>, CmsError> { |
1232 | 0 | self.build_gamma_table::<u16, 65536, 4092, 8>(trc, use_cicp) |
1233 | 0 | } |
1234 | | |
1235 | | /// Build gamma table for 10 bit depth |
1236 | | /// Only 8192 first bins are used and values scaled in 0..1023 |
1237 | 0 | pub fn build_10bit_gamma_table( |
1238 | 0 | &self, |
1239 | 0 | trc: &Option<ToneReprCurve>, |
1240 | 0 | use_cicp: bool, |
1241 | 0 | ) -> Result<Box<[u16; 65536]>, CmsError> { |
1242 | 0 | self.build_gamma_table::<u16, 65536, 8192, 10>(trc, use_cicp) |
1243 | 0 | } |
1244 | | |
1245 | | /// Build gamma table for 12 bit depth |
1246 | | /// Only 16384 first bins are used and values scaled in 0..4095 |
1247 | 0 | pub fn build_12bit_gamma_table( |
1248 | 0 | &self, |
1249 | 0 | trc: &Option<ToneReprCurve>, |
1250 | 0 | use_cicp: bool, |
1251 | 0 | ) -> Result<Box<[u16; 65536]>, CmsError> { |
1252 | 0 | self.build_gamma_table::<u16, 65536, 16384, 12>(trc, use_cicp) |
1253 | 0 | } |
1254 | | |
1255 | | /// Build gamma table for 16 bit depth |
1256 | | /// Only 16384 first bins are used and values scaled in 0..65535 |
1257 | 0 | pub fn build_16bit_gamma_table( |
1258 | 0 | &self, |
1259 | 0 | trc: &Option<ToneReprCurve>, |
1260 | 0 | use_cicp: bool, |
1261 | 0 | ) -> Result<Box<[u16; 65536]>, CmsError> { |
1262 | 0 | self.build_gamma_table::<u16, 65536, 65536, 16>(trc, use_cicp) |
1263 | 0 | } |
1264 | | |
1265 | | /// Builds gamma table checking CICP for Transfer characteristics first. |
1266 | 0 | pub fn build_gamma_table< |
1267 | 0 | T: Default + Copy + 'static + PointeeSizeExpressible + GammaLutInterpolate, |
1268 | 0 | const BUCKET: usize, |
1269 | 0 | const N: usize, |
1270 | 0 | const BIT_DEPTH: usize, |
1271 | 0 | >( |
1272 | 0 | &self, |
1273 | 0 | trc: &Option<ToneReprCurve>, |
1274 | 0 | use_cicp: bool, |
1275 | 0 | ) -> Result<Box<[T; BUCKET]>, CmsError> |
1276 | 0 | where |
1277 | 0 | f32: AsPrimitive<T>, |
1278 | 0 | u32: AsPrimitive<T>, |
1279 | | { |
1280 | 0 | if use_cicp { |
1281 | 0 | if let Some(tc) = self.cicp.as_ref().map(|c| c.transfer_characteristics) { |
1282 | 0 | if tc.has_transfer_curve() { |
1283 | 0 | return Ok(tc.make_gamma_table::<T, BUCKET, N>(BIT_DEPTH)); |
1284 | 0 | } |
1285 | 0 | } |
1286 | 0 | } |
1287 | 0 | trc.as_ref() |
1288 | 0 | .and_then(|trc| trc.build_gamma_table::<T, BUCKET, N, BIT_DEPTH>()) Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_gamma_table::<f64, 65536, 65536, 1>::{closure#1} Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_gamma_table::<f32, 65536, 32768, 1>::{closure#1} Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_gamma_table::<u8, 65536, 4096, 8>::{closure#1} Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_gamma_table::<u16, 65536, 65536, 16>::{closure#1} Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_gamma_table::<u16, 65536, 8192, 10>::{closure#1} Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_gamma_table::<u16, 65536, 16384, 12>::{closure#1} Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_gamma_table::<u16, 65536, 4092, 8>::{closure#1} |
1289 | 0 | .ok_or(CmsError::BuildTransferFunction) |
1290 | 0 | } Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_gamma_table::<f64, 65536, 65536, 1> Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_gamma_table::<f32, 65536, 32768, 1> Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_gamma_table::<u8, 65536, 4096, 8> Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_gamma_table::<u16, 65536, 65536, 16> Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_gamma_table::<u16, 65536, 8192, 10> Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_gamma_table::<u16, 65536, 16384, 12> Unexecuted instantiation: <moxcms::profile::ColorProfile>::build_gamma_table::<u16, 65536, 4092, 8> |
1291 | | |
1292 | | /// Checks if profile gamma can work in extended precision and we have implementation for this |
1293 | 0 | pub(crate) fn try_extended_gamma_evaluator( |
1294 | 0 | &self, |
1295 | 0 | ) -> Option<Box<dyn ToneCurveEvaluator + Send + Sync>> { |
1296 | 0 | if let Some(tc) = self.cicp.as_ref().map(|c| c.transfer_characteristics) { |
1297 | 0 | if tc.has_transfer_curve() { |
1298 | 0 | return Some(Box::new(ToneCurveCicpEvaluator { |
1299 | 0 | rgb_trc: tc.extended_gamma_tristimulus(), |
1300 | 0 | trc: tc.extended_gamma_single(), |
1301 | 0 | })); |
1302 | 0 | } |
1303 | 0 | } |
1304 | 0 | if !self.are_all_trc_the_same() { |
1305 | 0 | return None; |
1306 | 0 | } |
1307 | 0 | let reference_trc = if self.color_space == DataColorSpace::Gray { |
1308 | 0 | self.gray_trc.as_ref() |
1309 | | } else { |
1310 | 0 | self.red_trc.as_ref() |
1311 | | }; |
1312 | 0 | if let Some(red_trc) = reference_trc { |
1313 | 0 | return Self::make_gamma_evaluator_all_the_same(red_trc); |
1314 | 0 | } |
1315 | 0 | None |
1316 | 0 | } |
1317 | | |
1318 | 0 | fn make_gamma_evaluator_all_the_same( |
1319 | 0 | red_trc: &ToneReprCurve, |
1320 | 0 | ) -> Option<Box<dyn ToneCurveEvaluator + Send + Sync>> { |
1321 | 0 | match red_trc { |
1322 | 0 | ToneReprCurve::Lut(lut) => { |
1323 | 0 | if lut.is_empty() { |
1324 | 0 | return Some(Box::new(ToneCurveEvaluatorLinear {})); |
1325 | 0 | } |
1326 | 0 | if lut.len() == 1 { |
1327 | 0 | let gamma = 1. / u8_fixed_8number_to_float(lut[0]); |
1328 | 0 | return Some(Box::new(ToneCurveEvaluatorPureGamma { gamma })); |
1329 | 0 | } |
1330 | 0 | None |
1331 | | } |
1332 | 0 | ToneReprCurve::Parametric(params) => { |
1333 | 0 | if params.len() == 5 { |
1334 | 0 | let srgb_params = vec![2.4, 1. / 1.055, 0.055 / 1.055, 1. / 12.92, 0.04045]; |
1335 | 0 | let rec709_params = create_rec709_parametric(); |
1336 | | |
1337 | 0 | let mut lc_params: [f32; 5] = [0.; 5]; |
1338 | 0 | for (dst, src) in lc_params.iter_mut().zip(params.iter()) { |
1339 | 0 | *dst = *src; |
1340 | 0 | } |
1341 | | |
1342 | 0 | if compare_parametric(lc_params.as_slice(), srgb_params.as_slice()) { |
1343 | 0 | return Some(Box::new(ToneCurveCicpEvaluator { |
1344 | 0 | rgb_trc: TransferCharacteristics::Srgb.extended_gamma_tristimulus(), |
1345 | 0 | trc: TransferCharacteristics::Srgb.extended_gamma_single(), |
1346 | 0 | })); |
1347 | 0 | } |
1348 | | |
1349 | 0 | if compare_parametric(lc_params.as_slice(), rec709_params.as_slice()) { |
1350 | 0 | return Some(Box::new(ToneCurveCicpEvaluator { |
1351 | 0 | rgb_trc: TransferCharacteristics::Bt709.extended_gamma_tristimulus(), |
1352 | 0 | trc: TransferCharacteristics::Bt709.extended_gamma_single(), |
1353 | 0 | })); |
1354 | 0 | } |
1355 | 0 | } |
1356 | | |
1357 | 0 | let parametric_curve = ParametricCurve::new(params); |
1358 | 0 | if let Some(v) = parametric_curve?.invert() { |
1359 | 0 | return Some(Box::new(ToneCurveParametricEvaluator { parametric: v })); |
1360 | 0 | } |
1361 | 0 | None |
1362 | | } |
1363 | | } |
1364 | 0 | } |
1365 | | |
1366 | | /// Check if all TRC are the same |
1367 | 0 | pub(crate) fn are_all_trc_the_same(&self) -> bool { |
1368 | 0 | if self.color_space == DataColorSpace::Gray { |
1369 | 0 | return true; |
1370 | 0 | } |
1371 | 0 | if let (Some(red_trc), Some(green_trc), Some(blue_trc)) = |
1372 | 0 | (&self.red_trc, &self.green_trc, &self.blue_trc) |
1373 | | { |
1374 | 0 | if !matches!( |
1375 | 0 | (red_trc, green_trc, blue_trc), |
1376 | | ( |
1377 | | ToneReprCurve::Lut(_), |
1378 | | ToneReprCurve::Lut(_), |
1379 | | ToneReprCurve::Lut(_), |
1380 | | ) | ( |
1381 | | ToneReprCurve::Parametric(_), |
1382 | | ToneReprCurve::Parametric(_), |
1383 | | ToneReprCurve::Parametric(_) |
1384 | | ) |
1385 | | ) { |
1386 | 0 | return false; |
1387 | 0 | } |
1388 | 0 | if let (ToneReprCurve::Lut(lut0), ToneReprCurve::Lut(lut1), ToneReprCurve::Lut(lut2)) = |
1389 | 0 | (red_trc, green_trc, blue_trc) |
1390 | | { |
1391 | 0 | if lut0 == lut1 || lut1 == lut2 { |
1392 | 0 | return true; |
1393 | 0 | } |
1394 | 0 | } |
1395 | | if let ( |
1396 | 0 | ToneReprCurve::Parametric(lut0), |
1397 | 0 | ToneReprCurve::Parametric(lut1), |
1398 | 0 | ToneReprCurve::Parametric(lut2), |
1399 | 0 | ) = (red_trc, green_trc, blue_trc) |
1400 | | { |
1401 | 0 | if lut0 == lut1 || lut1 == lut2 { |
1402 | 0 | return true; |
1403 | 0 | } |
1404 | 0 | } |
1405 | 0 | } |
1406 | 0 | false |
1407 | 0 | } |
1408 | | |
1409 | | /// Checks if profile is matrix shaper, have same TRC and TRC is linear. |
1410 | 0 | pub(crate) fn is_linear_matrix_shaper(&self) -> bool { |
1411 | 0 | if !self.is_matrix_shaper() { |
1412 | 0 | return false; |
1413 | 0 | } |
1414 | 0 | if !self.are_all_trc_the_same() { |
1415 | 0 | return false; |
1416 | 0 | } |
1417 | 0 | if let Some(red_trc) = &self.red_trc { |
1418 | 0 | return match red_trc { |
1419 | 0 | ToneReprCurve::Lut(lut) => { |
1420 | 0 | if lut.is_empty() { |
1421 | 0 | return true; |
1422 | 0 | } |
1423 | 0 | if is_curve_linear16(lut) { |
1424 | 0 | return true; |
1425 | 0 | } |
1426 | 0 | false |
1427 | | } |
1428 | 0 | ToneReprCurve::Parametric(params) => { |
1429 | 0 | if let Some(curve) = ParametricCurve::new(params) { |
1430 | 0 | return curve.is_linear(); |
1431 | 0 | } |
1432 | 0 | false |
1433 | | } |
1434 | | }; |
1435 | 0 | } |
1436 | 0 | false |
1437 | 0 | } |
1438 | | |
1439 | | /// Checks if profile linearization can work in extended precision and we have implementation for this |
1440 | 0 | pub(crate) fn try_extended_linearizing_evaluator( |
1441 | 0 | &self, |
1442 | 0 | ) -> Option<Box<dyn ToneCurveEvaluator + Send + Sync>> { |
1443 | 0 | if let Some(tc) = self.cicp.as_ref().map(|c| c.transfer_characteristics) { |
1444 | 0 | if tc.has_transfer_curve() { |
1445 | 0 | return Some(Box::new(ToneCurveCicpEvaluator { |
1446 | 0 | rgb_trc: tc.extended_linear_tristimulus(), |
1447 | 0 | trc: tc.extended_linear_single(), |
1448 | 0 | })); |
1449 | 0 | } |
1450 | 0 | } |
1451 | 0 | if !self.are_all_trc_the_same() { |
1452 | 0 | return None; |
1453 | 0 | } |
1454 | 0 | let reference_trc = if self.color_space == DataColorSpace::Gray { |
1455 | 0 | self.gray_trc.as_ref() |
1456 | | } else { |
1457 | 0 | self.red_trc.as_ref() |
1458 | | }; |
1459 | 0 | if let Some(red_trc) = reference_trc { |
1460 | 0 | if let Some(value) = Self::make_linear_curve_evaluator_all_the_same(red_trc) { |
1461 | 0 | return value; |
1462 | 0 | } |
1463 | 0 | } |
1464 | 0 | None |
1465 | 0 | } |
1466 | | |
1467 | 0 | fn make_linear_curve_evaluator_all_the_same( |
1468 | 0 | evaluator_curve: &ToneReprCurve, |
1469 | 0 | ) -> Option<Option<Box<dyn ToneCurveEvaluator + Send + Sync>>> { |
1470 | 0 | match evaluator_curve { |
1471 | 0 | ToneReprCurve::Lut(lut) => { |
1472 | 0 | if lut.is_empty() { |
1473 | 0 | return Some(Some(Box::new(ToneCurveEvaluatorLinear {}))); |
1474 | 0 | } |
1475 | 0 | if lut.len() == 1 { |
1476 | 0 | let gamma = u8_fixed_8number_to_float(lut[0]); |
1477 | 0 | return Some(Some(Box::new(ToneCurveEvaluatorPureGamma { gamma }))); |
1478 | 0 | } |
1479 | | } |
1480 | 0 | ToneReprCurve::Parametric(params) => { |
1481 | 0 | if params.len() == 5 { |
1482 | 0 | let srgb_params = vec![2.4, 1. / 1.055, 0.055 / 1.055, 1. / 12.92, 0.04045]; |
1483 | 0 | let rec709_params = create_rec709_parametric(); |
1484 | | |
1485 | 0 | let mut lc_params: [f32; 5] = [0.; 5]; |
1486 | 0 | for (dst, src) in lc_params.iter_mut().zip(params.iter()) { |
1487 | 0 | *dst = *src; |
1488 | 0 | } |
1489 | | |
1490 | 0 | if compare_parametric(lc_params.as_slice(), srgb_params.as_slice()) { |
1491 | 0 | return Some(Some(Box::new(ToneCurveCicpEvaluator { |
1492 | 0 | rgb_trc: TransferCharacteristics::Srgb.extended_linear_tristimulus(), |
1493 | 0 | trc: TransferCharacteristics::Srgb.extended_linear_single(), |
1494 | 0 | }))); |
1495 | 0 | } |
1496 | | |
1497 | 0 | if compare_parametric(lc_params.as_slice(), rec709_params.as_slice()) { |
1498 | 0 | return Some(Some(Box::new(ToneCurveCicpEvaluator { |
1499 | 0 | rgb_trc: TransferCharacteristics::Bt709.extended_linear_tristimulus(), |
1500 | 0 | trc: TransferCharacteristics::Bt709.extended_linear_single(), |
1501 | 0 | }))); |
1502 | 0 | } |
1503 | 0 | } |
1504 | | |
1505 | 0 | let parametric_curve = ParametricCurve::new(params); |
1506 | 0 | if let Some(v) = parametric_curve { |
1507 | 0 | return Some(Some(Box::new(ToneCurveParametricEvaluator { |
1508 | 0 | parametric: v, |
1509 | 0 | }))); |
1510 | 0 | } |
1511 | | } |
1512 | | } |
1513 | 0 | None |
1514 | 0 | } |
1515 | | } |
1516 | | |
1517 | | pub(crate) struct ToneCurveCicpEvaluator { |
1518 | | rgb_trc: fn(Rgb<f32>) -> Rgb<f32>, |
1519 | | trc: fn(f32) -> f32, |
1520 | | } |
1521 | | |
1522 | | pub(crate) struct ToneCurveParametricEvaluator { |
1523 | | parametric: ParametricCurve, |
1524 | | } |
1525 | | |
1526 | | pub(crate) struct ToneCurveEvaluatorPureGamma { |
1527 | | gamma: f32, |
1528 | | } |
1529 | | |
1530 | | pub(crate) struct ToneCurveEvaluatorLinear {} |
1531 | | |
1532 | | impl ToneCurveEvaluator for ToneCurveCicpEvaluator { |
1533 | 0 | fn evaluate_tristimulus(&self, rgb: Rgb<f32>) -> Rgb<f32> { |
1534 | 0 | (self.rgb_trc)(rgb) |
1535 | 0 | } |
1536 | | |
1537 | 0 | fn evaluate_value(&self, value: f32) -> f32 { |
1538 | 0 | (self.trc)(value) |
1539 | 0 | } |
1540 | | } |
1541 | | |
1542 | | impl ToneCurveEvaluator for ToneCurveParametricEvaluator { |
1543 | 0 | fn evaluate_tristimulus(&self, rgb: Rgb<f32>) -> Rgb<f32> { |
1544 | 0 | Rgb::new( |
1545 | 0 | self.parametric.eval(rgb.r), |
1546 | 0 | self.parametric.eval(rgb.g), |
1547 | 0 | self.parametric.eval(rgb.b), |
1548 | | ) |
1549 | 0 | } |
1550 | | |
1551 | 0 | fn evaluate_value(&self, value: f32) -> f32 { |
1552 | 0 | self.parametric.eval(value) |
1553 | 0 | } |
1554 | | } |
1555 | | |
1556 | | impl ToneCurveEvaluator for ToneCurveEvaluatorPureGamma { |
1557 | 0 | fn evaluate_tristimulus(&self, rgb: Rgb<f32>) -> Rgb<f32> { |
1558 | 0 | Rgb::new( |
1559 | 0 | dirty_powf(rgb.r, self.gamma), |
1560 | 0 | dirty_powf(rgb.g, self.gamma), |
1561 | 0 | dirty_powf(rgb.b, self.gamma), |
1562 | | ) |
1563 | 0 | } |
1564 | | |
1565 | 0 | fn evaluate_value(&self, value: f32) -> f32 { |
1566 | 0 | dirty_powf(value, self.gamma) |
1567 | 0 | } |
1568 | | } |
1569 | | |
1570 | | impl ToneCurveEvaluator for ToneCurveEvaluatorLinear { |
1571 | 0 | fn evaluate_tristimulus(&self, rgb: Rgb<f32>) -> Rgb<f32> { |
1572 | 0 | rgb |
1573 | 0 | } |
1574 | | |
1575 | 0 | fn evaluate_value(&self, value: f32) -> f32 { |
1576 | 0 | value |
1577 | 0 | } |
1578 | | } |
1579 | | |
1580 | | pub trait ToneCurveEvaluator { |
1581 | | fn evaluate_tristimulus(&self, rgb: Rgb<f32>) -> Rgb<f32>; |
1582 | | fn evaluate_value(&self, value: f32) -> f32; |
1583 | | } |