1use crate::utils::mlaf;
8use crate::{
9 EuclideanDistance, Jzczhz, Rgb, TaxicabDistance, TransferFunction, Xyz, SRGB_TO_XYZ_D65,
10 XYZ_TO_SRGB_D65,
11};
12use num_traits::Pow;
13use std::ops::{
14 Add, AddAssign, Div, DivAssign, Index, IndexMut, Mul, MulAssign, Neg, Sub, SubAssign,
15};
16
17#[inline]
18fn perceptual_quantizer(x: f32) -> f32 {
19 if x <= 0. {
20 return 0.;
21 }
22 let xx = f32::powf(x * 1e-4, 0.1593017578125);
23 let rs = f32::powf(
24 (0.8359375 + 18.8515625 * xx) / (1. + 18.6875 * xx),
25 134.034375,
26 );
27 if rs.is_nan() {
28 return 0.;
29 }
30 rs
31}
32
33#[inline]
34fn perceptual_quantizer_inverse(x: f32) -> f32 {
35 if x <= 0. {
36 return 0.;
37 }
38 let xx = f32::powf(x, 7.460772656268214e-03);
39 let rs = 1e4
40 * f32::powf(
41 (0.8359375 - xx) / (18.6875 * xx - 18.8515625),
42 6.277394636015326,
43 );
44 if rs.is_nan() {
45 return 0.;
46 }
47 rs
48}
49
50#[repr(C)]
51#[derive(Debug, Copy, Clone, PartialOrd, PartialEq)]
52pub struct Jzazbz {
54 pub jz: f32,
56 pub az: f32,
58 pub bz: f32,
60 pub display_luminance: f32,
62}
63
64impl Jzazbz {
65 #[inline]
67 pub fn new(jz: f32, az: f32, bz: f32) -> Jzazbz {
68 Jzazbz {
69 jz,
70 az,
71 bz,
72 display_luminance: 200f32,
73 }
74 }
75
76 #[inline]
78 pub fn new_with_luminance(jz: f32, az: f32, bz: f32, display_luminance: f32) -> Jzazbz {
79 Jzazbz {
80 jz,
81 az,
82 bz,
83 display_luminance,
84 }
85 }
86
87 #[inline]
88 pub fn from_xyz(xyz: Xyz) -> Jzazbz {
89 Self::from_xyz_with_display_luminance(xyz, 200f32)
90 }
91
92 #[inline]
93 pub fn from_xyz_with_display_luminance(xyz: Xyz, display_luminance: f32) -> Jzazbz {
94 let abs_xyz = xyz.to_absolute_luminance(display_luminance);
95 let lp = perceptual_quantizer(mlaf(
96 mlaf(0.674207838 * abs_xyz.x, 0.382799340, abs_xyz.y),
97 -0.047570458,
98 abs_xyz.z,
99 ));
100 let mp = perceptual_quantizer(mlaf(
101 mlaf(0.149284160 * abs_xyz.x, 0.739628340, abs_xyz.y),
102 0.083327300,
103 abs_xyz.z,
104 ));
105 let sp = perceptual_quantizer(mlaf(
106 mlaf(0.070941080 * abs_xyz.x, 0.174768000, abs_xyz.y),
107 0.670970020,
108 abs_xyz.z,
109 ));
110 let iz = 0.5 * (lp + mp);
111 let az = mlaf(mlaf(3.524000 * lp, -4.066708, mp), 0.542708, sp);
112 let bz = mlaf(mlaf(0.199076 * lp, 1.096799, mp), -1.295875, sp);
113 let jz = (0.44 * iz) / (1. - 0.56 * iz) - 1.6295499532821566e-11;
114 Jzazbz::new_with_luminance(jz, az, bz, display_luminance)
115 }
116
117 #[inline]
123 pub fn from_rgb(rgb: Rgb<u8>, transfer_function: TransferFunction) -> Jzazbz {
124 let xyz = rgb.to_xyz(&SRGB_TO_XYZ_D65, transfer_function);
125 Self::from_xyz_with_display_luminance(xyz, 200.)
126 }
127
128 #[inline]
134 pub fn from_rgb_with_luminance(
135 rgb: Rgb<u8>,
136 display_luminance: f32,
137 transfer_function: TransferFunction,
138 ) -> Jzazbz {
139 let xyz = rgb.to_xyz(&SRGB_TO_XYZ_D65, transfer_function);
140 Self::from_xyz_with_display_luminance(xyz, display_luminance)
141 }
142
143 #[inline]
145 pub fn to_xyz(&self) -> Xyz {
146 let jz = self.jz + 1.6295499532821566e-11;
147
148 let iz = jz / (0.44f32 + 0.56f32 * jz);
149 let l = perceptual_quantizer_inverse(mlaf(
150 mlaf(iz, 1.386050432715393e-1, self.az),
151 5.804731615611869e-2,
152 self.bz,
153 ));
154 let m = perceptual_quantizer_inverse(mlaf(
155 mlaf(iz, -1.386050432715393e-1, self.az),
156 -5.804731615611891e-2,
157 self.bz,
158 ));
159 let s = perceptual_quantizer_inverse(mlaf(
160 mlaf(iz, -9.601924202631895e-2, self.az),
161 -8.118918960560390e-1,
162 self.bz,
163 ));
164 let x = mlaf(
165 mlaf(1.661373055774069e+00 * l, -9.145230923250668e-01, m),
166 2.313620767186147e-01,
167 s,
168 );
169 let y = mlaf(
170 mlaf(-3.250758740427037e-01 * l, 1.571847038366936e+00, m),
171 -2.182538318672940e-01,
172 s,
173 );
174 let z = mlaf(
175 mlaf(-9.098281098284756e-02 * l, -3.127282905230740e-01, m),
176 1.522766561305260e+00,
177 s,
178 );
179 Xyz::new(x, y, z).to_relative_luminance(self.display_luminance)
180 }
181
182 #[inline]
184 pub fn to_linear_rgb(&self) -> Rgb<f32> {
185 let xyz = self.to_xyz();
186 xyz.to_linear_rgb(&XYZ_TO_SRGB_D65)
187 }
188
189 #[inline]
191 pub fn to_rgb(&self, transfer_function: TransferFunction) -> Rgb<u8> {
192 let linear_rgb = self.to_linear_rgb().gamma(transfer_function);
193 linear_rgb.to_u8()
194 }
195
196 #[inline]
198 pub fn to_jzczhz(&self) -> Jzczhz {
199 Jzczhz::from_jzazbz(*self)
200 }
201}
202
203impl EuclideanDistance for Jzazbz {
204 #[inline]
205 fn euclidean_distance(&self, other: Self) -> f32 {
206 let djz = self.jz - other.jz;
207 let daz = self.az - other.az;
208 let dbz = self.bz - other.bz;
209 (djz * djz + daz * daz + dbz * dbz).sqrt()
210 }
211}
212
213impl TaxicabDistance for Jzazbz {
214 #[inline]
215 fn taxicab_distance(&self, other: Self) -> f32 {
216 let djz = self.jz - other.jz;
217 let daz = self.az - other.az;
218 let dbz = self.bz - other.bz;
219 djz.abs() + daz.abs() + dbz.abs()
220 }
221}
222
223impl Index<usize> for Jzazbz {
224 type Output = f32;
225
226 #[inline]
227 fn index(&self, index: usize) -> &f32 {
228 match index {
229 0 => &self.jz,
230 1 => &self.az,
231 2 => &self.bz,
232 _ => panic!("Index out of bounds for Jzazbz"),
233 }
234 }
235}
236
237impl IndexMut<usize> for Jzazbz {
238 #[inline]
239 fn index_mut(&mut self, index: usize) -> &mut f32 {
240 match index {
241 0 => &mut self.jz,
242 1 => &mut self.az,
243 2 => &mut self.bz,
244 _ => panic!("Index out of bounds for Jzazbz"),
245 }
246 }
247}
248
249impl Add<f32> for Jzazbz {
250 type Output = Jzazbz;
251
252 #[inline]
253 fn add(self, rhs: f32) -> Self::Output {
254 Jzazbz::new(self.jz + rhs, self.az + rhs, self.bz + rhs)
255 }
256}
257
258impl Sub<f32> for Jzazbz {
259 type Output = Jzazbz;
260
261 #[inline]
262 fn sub(self, rhs: f32) -> Self::Output {
263 Jzazbz::new(self.jz - rhs, self.az - rhs, self.bz - rhs)
264 }
265}
266
267impl Mul<f32> for Jzazbz {
268 type Output = Jzazbz;
269
270 #[inline]
271 fn mul(self, rhs: f32) -> Self::Output {
272 Jzazbz::new(self.jz * rhs, self.az * rhs, self.bz * rhs)
273 }
274}
275
276impl Div<f32> for Jzazbz {
277 type Output = Jzazbz;
278
279 #[inline]
280 fn div(self, rhs: f32) -> Self::Output {
281 Jzazbz::new(self.jz / rhs, self.az / rhs, self.bz / rhs)
282 }
283}
284
285impl Add<Jzazbz> for Jzazbz {
286 type Output = Jzazbz;
287
288 #[inline]
289 fn add(self, rhs: Jzazbz) -> Self::Output {
290 Jzazbz::new(self.jz + rhs.jz, self.az + rhs.az, self.bz + rhs.bz)
291 }
292}
293
294impl Sub<Jzazbz> for Jzazbz {
295 type Output = Jzazbz;
296
297 #[inline]
298 fn sub(self, rhs: Jzazbz) -> Self::Output {
299 Jzazbz::new(self.jz - rhs.jz, self.az - rhs.az, self.bz - rhs.bz)
300 }
301}
302
303impl Mul<Jzazbz> for Jzazbz {
304 type Output = Jzazbz;
305
306 #[inline]
307 fn mul(self, rhs: Jzazbz) -> Self::Output {
308 Jzazbz::new(self.jz * rhs.jz, self.az * rhs.az, self.bz * rhs.bz)
309 }
310}
311
312impl Div<Jzazbz> for Jzazbz {
313 type Output = Jzazbz;
314
315 #[inline]
316 fn div(self, rhs: Jzazbz) -> Self::Output {
317 Jzazbz::new(self.jz / rhs.jz, self.az / rhs.az, self.bz / rhs.bz)
318 }
319}
320
321impl AddAssign<Jzazbz> for Jzazbz {
322 #[inline]
323 fn add_assign(&mut self, rhs: Jzazbz) {
324 self.jz += rhs.jz;
325 self.az += rhs.az;
326 self.bz += rhs.bz;
327 }
328}
329
330impl SubAssign<Jzazbz> for Jzazbz {
331 #[inline]
332 fn sub_assign(&mut self, rhs: Jzazbz) {
333 self.jz -= rhs.jz;
334 self.az -= rhs.az;
335 self.bz -= rhs.bz;
336 }
337}
338
339impl MulAssign<Jzazbz> for Jzazbz {
340 #[inline]
341 fn mul_assign(&mut self, rhs: Jzazbz) {
342 self.jz *= rhs.jz;
343 self.az *= rhs.az;
344 self.bz *= rhs.bz;
345 }
346}
347
348impl DivAssign<Jzazbz> for Jzazbz {
349 #[inline]
350 fn div_assign(&mut self, rhs: Jzazbz) {
351 self.jz /= rhs.jz;
352 self.az /= rhs.az;
353 self.bz /= rhs.bz;
354 }
355}
356
357impl AddAssign<f32> for Jzazbz {
358 #[inline]
359 fn add_assign(&mut self, rhs: f32) {
360 self.jz += rhs;
361 self.az += rhs;
362 self.bz += rhs;
363 }
364}
365
366impl SubAssign<f32> for Jzazbz {
367 #[inline]
368 fn sub_assign(&mut self, rhs: f32) {
369 self.jz -= rhs;
370 self.az -= rhs;
371 self.bz -= rhs;
372 }
373}
374
375impl MulAssign<f32> for Jzazbz {
376 #[inline]
377 fn mul_assign(&mut self, rhs: f32) {
378 self.jz *= rhs;
379 self.az *= rhs;
380 self.bz *= rhs;
381 }
382}
383
384impl DivAssign<f32> for Jzazbz {
385 #[inline]
386 fn div_assign(&mut self, rhs: f32) {
387 self.jz /= rhs;
388 self.az /= rhs;
389 self.bz /= rhs;
390 }
391}
392
393impl Neg for Jzazbz {
394 type Output = Jzazbz;
395
396 #[inline]
397 fn neg(self) -> Self::Output {
398 Jzazbz::new(-self.jz, -self.az, -self.bz)
399 }
400}
401
402impl Jzazbz {
403 #[inline]
404 pub fn sqrt(&self) -> Jzazbz {
405 Jzazbz::new(
406 if self.jz < 0. { 0. } else { self.jz.sqrt() },
407 if self.az < 0. { 0. } else { self.az.sqrt() },
408 if self.bz < 0. { 0. } else { self.bz.sqrt() },
409 )
410 }
411
412 #[inline]
413 pub fn cbrt(&self) -> Jzazbz {
414 Jzazbz::new(self.jz.cbrt(), self.az.cbrt(), self.bz.cbrt())
415 }
416}
417
418impl Pow<f32> for Jzazbz {
419 type Output = Jzazbz;
420
421 #[inline]
422 fn pow(self, rhs: f32) -> Self::Output {
423 Jzazbz::new(self.jz.powf(rhs), self.az.powf(rhs), self.bz.powf(rhs))
424 }
425}
426
427impl Pow<Jzazbz> for Jzazbz {
428 type Output = Jzazbz;
429
430 #[inline]
431 fn pow(self, rhs: Jzazbz) -> Self::Output {
432 Jzazbz::new(
433 self.jz.powf(rhs.jz),
434 self.az.powf(self.az),
435 self.bz.powf(self.bz),
436 )
437 }
438}