/rust/registry/src/index.crates.io-1949cf8c6b5b557f/moxcms-0.7.11/src/jzazbz.rs
Line | Count | Source |
1 | | /* |
2 | | * // Copyright (c) Radzivon Bartoshyk 3/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::Xyz; |
30 | | use crate::jzczhz::Jzczhz; |
31 | | use crate::mlaf::mlaf; |
32 | | use num_traits::Pow; |
33 | | use pxfm::{dirty_powf, f_cbrtf, f_powf}; |
34 | | use std::ops::{ |
35 | | Add, AddAssign, Div, DivAssign, Index, IndexMut, Mul, MulAssign, Neg, Sub, SubAssign, |
36 | | }; |
37 | | |
38 | | #[inline] |
39 | 0 | fn perceptual_quantizer(x: f32) -> f32 { |
40 | 0 | if x <= 0. { |
41 | 0 | return 0.; |
42 | 0 | } |
43 | 0 | let xx = dirty_powf(x * 1e-4, 0.1593017578125); |
44 | 0 | let rs = dirty_powf( |
45 | 0 | mlaf(0.8359375, 18.8515625, xx) / mlaf(1., 18.6875, xx), |
46 | | 134.034375, |
47 | | ); |
48 | 0 | if rs.is_nan() { |
49 | 0 | return 0.; |
50 | 0 | } |
51 | 0 | rs |
52 | 0 | } |
53 | | |
54 | | #[inline] |
55 | 0 | fn perceptual_quantizer_inverse(x: f32) -> f32 { |
56 | 0 | if x <= 0. { |
57 | 0 | return 0.; |
58 | 0 | } |
59 | 0 | let xx = dirty_powf(x, 7.460772656268214e-03); |
60 | 0 | let rs = 1e4 |
61 | 0 | * dirty_powf( |
62 | 0 | (0.8359375 - xx) / mlaf(-18.8515625, 18.6875, xx), |
63 | 0 | 6.277394636015326, |
64 | 0 | ); |
65 | 0 | if rs.is_nan() { |
66 | 0 | return 0.; |
67 | 0 | } |
68 | 0 | rs |
69 | 0 | } |
70 | | |
71 | | #[repr(C)] |
72 | | #[derive(Debug, Copy, Clone, PartialOrd, PartialEq, Default)] |
73 | | /// Represents Jzazbz |
74 | | pub struct Jzazbz { |
75 | | /// Jz(lightness) generally expects to be between `0.0..1.0`. |
76 | | pub jz: f32, |
77 | | /// Az generally expects to be between `-0.5..0.5`. |
78 | | pub az: f32, |
79 | | /// Bz generally expects to be between `-0.5..0.5`. |
80 | | pub bz: f32, |
81 | | } |
82 | | |
83 | | impl Jzazbz { |
84 | | /// Constructs new instance |
85 | | #[inline] |
86 | 0 | pub fn new(jz: f32, az: f32, bz: f32) -> Jzazbz { |
87 | 0 | Jzazbz { jz, az, bz } |
88 | 0 | } |
89 | | |
90 | | /// Creates new [Jzazbz] from CIE [Xyz]. |
91 | | /// |
92 | | /// JzAzBz is defined in D65 white point, adapt XYZ if needed first. |
93 | | #[inline] |
94 | 0 | pub fn from_xyz(xyz: Xyz) -> Jzazbz { |
95 | 0 | Self::from_xyz_with_display_luminance(xyz, 200.) |
96 | 0 | } |
97 | | |
98 | | /// Creates new [Jzazbz] from CIE [Xyz]. |
99 | | /// |
100 | | /// JzAzBz is defined in D65 white point, adapt XYZ if needed first. |
101 | | #[inline] |
102 | 0 | pub fn from_xyz_with_display_luminance(xyz: Xyz, display_luminance: f32) -> Jzazbz { |
103 | 0 | let abs_xyz = xyz * display_luminance; |
104 | 0 | let lp = perceptual_quantizer(mlaf( |
105 | 0 | mlaf(0.674207838 * abs_xyz.x, 0.382799340, abs_xyz.y), |
106 | | -0.047570458, |
107 | 0 | abs_xyz.z, |
108 | | )); |
109 | 0 | let mp = perceptual_quantizer(mlaf( |
110 | 0 | mlaf(0.149284160 * abs_xyz.x, 0.739628340, abs_xyz.y), |
111 | | 0.083327300, |
112 | 0 | abs_xyz.z, |
113 | | )); |
114 | 0 | let sp = perceptual_quantizer(mlaf( |
115 | 0 | mlaf(0.070941080 * abs_xyz.x, 0.174768000, abs_xyz.y), |
116 | | 0.670970020, |
117 | 0 | abs_xyz.z, |
118 | | )); |
119 | 0 | let iz = 0.5 * (lp + mp); |
120 | 0 | let az = mlaf(mlaf(3.524000 * lp, -4.066708, mp), 0.542708, sp); |
121 | 0 | let bz = mlaf(mlaf(0.199076 * lp, 1.096799, mp), -1.295875, sp); |
122 | 0 | let jz = (0.44 * iz) / mlaf(1., -0.56, iz) - 1.6295499532821566e-11; |
123 | 0 | Jzazbz::new(jz, az, bz) |
124 | 0 | } |
125 | | |
126 | | /// Converts [Jzazbz] to [Xyz] D65 |
127 | | #[inline] |
128 | 0 | pub fn to_xyz(&self, display_luminance: f32) -> Xyz { |
129 | 0 | let jz = self.jz + 1.6295499532821566e-11; |
130 | | |
131 | 0 | let iz = jz / mlaf(0.44f32, 0.56, jz); |
132 | 0 | let l = perceptual_quantizer_inverse(mlaf( |
133 | 0 | mlaf(iz, 1.386050432715393e-1, self.az), |
134 | | 5.804731615611869e-2, |
135 | 0 | self.bz, |
136 | | )); |
137 | 0 | let m = perceptual_quantizer_inverse(mlaf( |
138 | 0 | mlaf(iz, -1.386050432715393e-1, self.az), |
139 | | -5.804731615611891e-2, |
140 | 0 | self.bz, |
141 | | )); |
142 | 0 | let s = perceptual_quantizer_inverse(mlaf( |
143 | 0 | mlaf(iz, -9.601924202631895e-2, self.az), |
144 | | -8.118918960560390e-1, |
145 | 0 | self.bz, |
146 | | )); |
147 | 0 | let x = mlaf( |
148 | 0 | mlaf(1.661373055774069e+00 * l, -9.145230923250668e-01, m), |
149 | | 2.313620767186147e-01, |
150 | 0 | s, |
151 | | ); |
152 | 0 | let y = mlaf( |
153 | 0 | mlaf(-3.250758740427037e-01 * l, 1.571847038366936e+00, m), |
154 | | -2.182538318672940e-01, |
155 | 0 | s, |
156 | | ); |
157 | 0 | let z = mlaf( |
158 | 0 | mlaf(-9.098281098284756e-02 * l, -3.127282905230740e-01, m), |
159 | | 1.522766561305260e+00, |
160 | 0 | s, |
161 | | ); |
162 | 0 | let rel_luminance = 1f32 / display_luminance; |
163 | 0 | Xyz::new(x, y, z) * rel_luminance |
164 | 0 | } |
165 | | |
166 | | /// Converts into *Jzczhz* |
167 | | #[inline] |
168 | 0 | pub fn to_jzczhz(&self) -> Jzczhz { |
169 | 0 | Jzczhz::from_jzazbz(*self) |
170 | 0 | } |
171 | | |
172 | | #[inline] |
173 | 0 | pub fn euclidean_distance(&self, other: Self) -> f32 { |
174 | 0 | let djz = self.jz - other.jz; |
175 | 0 | let daz = self.az - other.az; |
176 | 0 | let dbz = self.bz - other.bz; |
177 | 0 | (djz * djz + daz * daz + dbz * dbz).sqrt() |
178 | 0 | } |
179 | | |
180 | | #[inline] |
181 | 0 | pub fn taxicab_distance(&self, other: Self) -> f32 { |
182 | 0 | let djz = self.jz - other.jz; |
183 | 0 | let daz = self.az - other.az; |
184 | 0 | let dbz = self.bz - other.bz; |
185 | 0 | djz.abs() + daz.abs() + dbz.abs() |
186 | 0 | } |
187 | | } |
188 | | |
189 | | impl Index<usize> for Jzazbz { |
190 | | type Output = f32; |
191 | | |
192 | | #[inline] |
193 | 0 | fn index(&self, index: usize) -> &f32 { |
194 | 0 | match index { |
195 | 0 | 0 => &self.jz, |
196 | 0 | 1 => &self.az, |
197 | 0 | 2 => &self.bz, |
198 | 0 | _ => panic!("Index out of bounds for Jzazbz"), |
199 | | } |
200 | 0 | } |
201 | | } |
202 | | |
203 | | impl IndexMut<usize> for Jzazbz { |
204 | | #[inline] |
205 | 0 | fn index_mut(&mut self, index: usize) -> &mut f32 { |
206 | 0 | match index { |
207 | 0 | 0 => &mut self.jz, |
208 | 0 | 1 => &mut self.az, |
209 | 0 | 2 => &mut self.bz, |
210 | 0 | _ => panic!("Index out of bounds for Jzazbz"), |
211 | | } |
212 | 0 | } |
213 | | } |
214 | | |
215 | | impl Add<f32> for Jzazbz { |
216 | | type Output = Jzazbz; |
217 | | |
218 | | #[inline] |
219 | 0 | fn add(self, rhs: f32) -> Self::Output { |
220 | 0 | Jzazbz::new(self.jz + rhs, self.az + rhs, self.bz + rhs) |
221 | 0 | } |
222 | | } |
223 | | |
224 | | impl Sub<f32> for Jzazbz { |
225 | | type Output = Jzazbz; |
226 | | |
227 | | #[inline] |
228 | 0 | fn sub(self, rhs: f32) -> Self::Output { |
229 | 0 | Jzazbz::new(self.jz - rhs, self.az - rhs, self.bz - rhs) |
230 | 0 | } |
231 | | } |
232 | | |
233 | | impl Mul<f32> for Jzazbz { |
234 | | type Output = Jzazbz; |
235 | | |
236 | | #[inline] |
237 | 0 | fn mul(self, rhs: f32) -> Self::Output { |
238 | 0 | Jzazbz::new(self.jz * rhs, self.az * rhs, self.bz * rhs) |
239 | 0 | } |
240 | | } |
241 | | |
242 | | impl Div<f32> for Jzazbz { |
243 | | type Output = Jzazbz; |
244 | | |
245 | | #[inline] |
246 | 0 | fn div(self, rhs: f32) -> Self::Output { |
247 | 0 | Jzazbz::new(self.jz / rhs, self.az / rhs, self.bz / rhs) |
248 | 0 | } |
249 | | } |
250 | | |
251 | | impl Add<Jzazbz> for Jzazbz { |
252 | | type Output = Jzazbz; |
253 | | |
254 | | #[inline] |
255 | 0 | fn add(self, rhs: Jzazbz) -> Self::Output { |
256 | 0 | Jzazbz::new(self.jz + rhs.jz, self.az + rhs.az, self.bz + rhs.bz) |
257 | 0 | } |
258 | | } |
259 | | |
260 | | impl Sub<Jzazbz> for Jzazbz { |
261 | | type Output = Jzazbz; |
262 | | |
263 | | #[inline] |
264 | 0 | fn sub(self, rhs: Jzazbz) -> Self::Output { |
265 | 0 | Jzazbz::new(self.jz - rhs.jz, self.az - rhs.az, self.bz - rhs.bz) |
266 | 0 | } |
267 | | } |
268 | | |
269 | | impl Mul<Jzazbz> for Jzazbz { |
270 | | type Output = Jzazbz; |
271 | | |
272 | | #[inline] |
273 | 0 | fn mul(self, rhs: Jzazbz) -> Self::Output { |
274 | 0 | Jzazbz::new(self.jz * rhs.jz, self.az * rhs.az, self.bz * rhs.bz) |
275 | 0 | } |
276 | | } |
277 | | |
278 | | impl Div<Jzazbz> for Jzazbz { |
279 | | type Output = Jzazbz; |
280 | | |
281 | | #[inline] |
282 | 0 | fn div(self, rhs: Jzazbz) -> Self::Output { |
283 | 0 | Jzazbz::new(self.jz / rhs.jz, self.az / rhs.az, self.bz / rhs.bz) |
284 | 0 | } |
285 | | } |
286 | | |
287 | | impl AddAssign<Jzazbz> for Jzazbz { |
288 | | #[inline] |
289 | 0 | fn add_assign(&mut self, rhs: Jzazbz) { |
290 | 0 | self.jz += rhs.jz; |
291 | 0 | self.az += rhs.az; |
292 | 0 | self.bz += rhs.bz; |
293 | 0 | } |
294 | | } |
295 | | |
296 | | impl SubAssign<Jzazbz> for Jzazbz { |
297 | | #[inline] |
298 | 0 | fn sub_assign(&mut self, rhs: Jzazbz) { |
299 | 0 | self.jz -= rhs.jz; |
300 | 0 | self.az -= rhs.az; |
301 | 0 | self.bz -= rhs.bz; |
302 | 0 | } |
303 | | } |
304 | | |
305 | | impl MulAssign<Jzazbz> for Jzazbz { |
306 | | #[inline] |
307 | 0 | fn mul_assign(&mut self, rhs: Jzazbz) { |
308 | 0 | self.jz *= rhs.jz; |
309 | 0 | self.az *= rhs.az; |
310 | 0 | self.bz *= rhs.bz; |
311 | 0 | } |
312 | | } |
313 | | |
314 | | impl DivAssign<Jzazbz> for Jzazbz { |
315 | | #[inline] |
316 | 0 | fn div_assign(&mut self, rhs: Jzazbz) { |
317 | 0 | self.jz /= rhs.jz; |
318 | 0 | self.az /= rhs.az; |
319 | 0 | self.bz /= rhs.bz; |
320 | 0 | } |
321 | | } |
322 | | |
323 | | impl AddAssign<f32> for Jzazbz { |
324 | | #[inline] |
325 | 0 | fn add_assign(&mut self, rhs: f32) { |
326 | 0 | self.jz += rhs; |
327 | 0 | self.az += rhs; |
328 | 0 | self.bz += rhs; |
329 | 0 | } |
330 | | } |
331 | | |
332 | | impl SubAssign<f32> for Jzazbz { |
333 | | #[inline] |
334 | 0 | fn sub_assign(&mut self, rhs: f32) { |
335 | 0 | self.jz -= rhs; |
336 | 0 | self.az -= rhs; |
337 | 0 | self.bz -= rhs; |
338 | 0 | } |
339 | | } |
340 | | |
341 | | impl MulAssign<f32> for Jzazbz { |
342 | | #[inline] |
343 | 0 | fn mul_assign(&mut self, rhs: f32) { |
344 | 0 | self.jz *= rhs; |
345 | 0 | self.az *= rhs; |
346 | 0 | self.bz *= rhs; |
347 | 0 | } |
348 | | } |
349 | | |
350 | | impl DivAssign<f32> for Jzazbz { |
351 | | #[inline] |
352 | 0 | fn div_assign(&mut self, rhs: f32) { |
353 | 0 | self.jz /= rhs; |
354 | 0 | self.az /= rhs; |
355 | 0 | self.bz /= rhs; |
356 | 0 | } |
357 | | } |
358 | | |
359 | | impl Neg for Jzazbz { |
360 | | type Output = Jzazbz; |
361 | | |
362 | | #[inline] |
363 | 0 | fn neg(self) -> Self::Output { |
364 | 0 | Jzazbz::new(-self.jz, -self.az, -self.bz) |
365 | 0 | } |
366 | | } |
367 | | |
368 | | impl Jzazbz { |
369 | | #[inline] |
370 | 0 | pub fn sqrt(&self) -> Jzazbz { |
371 | 0 | Jzazbz::new(self.jz.sqrt(), self.az.sqrt(), self.bz.sqrt()) |
372 | 0 | } |
373 | | |
374 | | #[inline] |
375 | 0 | pub fn cbrt(&self) -> Jzazbz { |
376 | 0 | Jzazbz::new(f_cbrtf(self.jz), f_cbrtf(self.az), f_cbrtf(self.bz)) |
377 | 0 | } |
378 | | } |
379 | | |
380 | | impl Pow<f32> for Jzazbz { |
381 | | type Output = Jzazbz; |
382 | | |
383 | | #[inline] |
384 | 0 | fn pow(self, rhs: f32) -> Self::Output { |
385 | 0 | Jzazbz::new( |
386 | 0 | f_powf(self.jz, rhs), |
387 | 0 | f_powf(self.az, rhs), |
388 | 0 | f_powf(self.bz, rhs), |
389 | | ) |
390 | 0 | } |
391 | | } |
392 | | |
393 | | impl Pow<Jzazbz> for Jzazbz { |
394 | | type Output = Jzazbz; |
395 | | |
396 | | #[inline] |
397 | 0 | fn pow(self, rhs: Jzazbz) -> Self::Output { |
398 | 0 | Jzazbz::new( |
399 | 0 | f_powf(self.jz, rhs.jz), |
400 | 0 | f_powf(self.az, self.az), |
401 | 0 | f_powf(self.bz, self.bz), |
402 | | ) |
403 | 0 | } |
404 | | } |
405 | | |
406 | | #[cfg(test)] |
407 | | mod tests { |
408 | | use super::*; |
409 | | |
410 | | #[test] |
411 | | fn jzazbz_round() { |
412 | | let xyz = Xyz::new(0.5, 0.4, 0.3); |
413 | | let jzazbz = Jzazbz::from_xyz_with_display_luminance(xyz, 253f32); |
414 | | let old_xyz = jzazbz.to_xyz(253f32); |
415 | | assert!( |
416 | | (xyz.x - old_xyz.x).abs() <= 1e-3, |
417 | | "{:?} != {:?}", |
418 | | xyz, |
419 | | old_xyz |
420 | | ); |
421 | | assert!( |
422 | | (xyz.y - old_xyz.y).abs() <= 1e-3, |
423 | | "{:?} != {:?}", |
424 | | xyz, |
425 | | old_xyz |
426 | | ); |
427 | | assert!( |
428 | | (xyz.z - old_xyz.z).abs() <= 1e-3, |
429 | | "{:?} != {:?}", |
430 | | xyz, |
431 | | old_xyz |
432 | | ); |
433 | | } |
434 | | } |