moxcms/
jzczhz.rs

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 */
29use crate::Xyz;
30use crate::jzazbz::Jzazbz;
31use num_traits::Pow;
32use pxfm::{f_atan2f, f_cbrtf, f_hypot3f, f_hypotf, f_powf, f_sincosf, f_sinf};
33use std::ops::{
34    Add, AddAssign, Div, DivAssign, Index, IndexMut, Mul, MulAssign, Neg, Sub, SubAssign,
35};
36
37/// Represents Jzazbz in polar coordinates as Jzczhz
38#[repr(C)]
39#[derive(Debug, Copy, Clone, PartialOrd, PartialEq)]
40pub struct Jzczhz {
41    /// Jz(lightness) generally expects to be between `0.0..1.0`.
42    pub jz: f32,
43    /// Cz generally expects to be between `-1.0..1.0`.
44    pub cz: f32,
45    /// Hz generally expects to be between `-1.0..1.0`.
46    pub hz: f32,
47}
48
49impl Jzczhz {
50    /// Creates new instance of Jzczhz
51    #[inline]
52    pub fn new(jz: f32, cz: f32, hz: f32) -> Jzczhz {
53        Jzczhz { jz, cz, hz }
54    }
55
56    /// Converts Jzazbz to polar coordinates Jzczhz
57    #[inline]
58    pub fn from_jzazbz(jzazbz: Jzazbz) -> Jzczhz {
59        let cz = f_hypotf(jzazbz.az, jzazbz.bz);
60        let hz = f_atan2f(jzazbz.bz, jzazbz.az);
61        Jzczhz::new(jzazbz.jz, cz, hz)
62    }
63
64    /// Converts Jzczhz into Jzazbz
65    #[inline]
66    pub fn to_jzazbz(&self) -> Jzazbz {
67        let sincos = f_sincosf(self.hz);
68        let az = self.cz * sincos.1;
69        let bz = self.cz * sincos.0;
70        Jzazbz::new(self.jz, az, bz)
71    }
72
73    /// Converts Jzczhz into Jzazbz
74    #[inline]
75    pub fn to_jzazbz_with_luminance(&self) -> Jzazbz {
76        let sincos = f_sincosf(self.hz);
77        let az = self.cz * sincos.1;
78        let bz = self.cz * sincos.0;
79        Jzazbz::new(self.jz, az, bz)
80    }
81
82    /// Converts Jzczhz to *Xyz*
83    #[inline]
84    pub fn to_xyz(&self, display_luminance: f32) -> Xyz {
85        let jzazbz = self.to_jzazbz();
86        jzazbz.to_xyz(display_luminance)
87    }
88
89    /// Converts [Xyz] to [Jzczhz]
90    #[inline]
91    pub fn from_xyz(xyz: Xyz) -> Jzczhz {
92        let jzazbz = Jzazbz::from_xyz(xyz);
93        Jzczhz::from_jzazbz(jzazbz)
94    }
95
96    /// Converts [Xyz] to [Jzczhz]
97    #[inline]
98    pub fn from_xyz_with_display_luminance(xyz: Xyz, luminance: f32) -> Jzczhz {
99        let jzazbz = Jzazbz::from_xyz_with_display_luminance(xyz, luminance);
100        Jzczhz::from_jzazbz(jzazbz)
101    }
102
103    /// Computes distance for *Jzczhz*
104    #[inline]
105    pub fn distance(&self, other: Jzczhz) -> f32 {
106        let djz = self.jz - other.jz;
107        let dcz = self.cz - other.cz;
108        let dhz = self.hz - other.hz;
109        let dh = 2. * (self.cz * other.cz).sqrt() * f_sinf(dhz * 0.5);
110        f_hypot3f(djz, dcz, dh)
111    }
112
113    #[inline]
114    pub fn euclidean_distance(&self, other: Self) -> f32 {
115        let djz = self.jz - other.jz;
116        let dhz = self.hz - other.hz;
117        let dcz = self.cz - other.cz;
118        (djz * djz + dhz * dhz + dcz * dcz).sqrt()
119    }
120
121    #[inline]
122    pub fn taxicab_distance(&self, other: Self) -> f32 {
123        let djz = self.jz - other.jz;
124        let dhz = self.hz - other.hz;
125        let dcz = self.cz - other.cz;
126        djz.abs() + dhz.abs() + dcz.abs()
127    }
128}
129
130impl Index<usize> for Jzczhz {
131    type Output = f32;
132
133    #[inline]
134    fn index(&self, index: usize) -> &f32 {
135        match index {
136            0 => &self.jz,
137            1 => &self.cz,
138            2 => &self.hz,
139            _ => panic!("Index out of bounds for Jzczhz"),
140        }
141    }
142}
143
144impl IndexMut<usize> for Jzczhz {
145    #[inline]
146    fn index_mut(&mut self, index: usize) -> &mut f32 {
147        match index {
148            0 => &mut self.jz,
149            1 => &mut self.cz,
150            2 => &mut self.hz,
151            _ => panic!("Index out of bounds for Jzczhz"),
152        }
153    }
154}
155
156impl Add<f32> for Jzczhz {
157    type Output = Jzczhz;
158
159    #[inline]
160    fn add(self, rhs: f32) -> Self::Output {
161        Jzczhz::new(self.jz + rhs, self.cz + rhs, self.hz + rhs)
162    }
163}
164
165impl Sub<f32> for Jzczhz {
166    type Output = Jzczhz;
167
168    #[inline]
169    fn sub(self, rhs: f32) -> Self::Output {
170        Jzczhz::new(self.jz - rhs, self.cz - rhs, self.hz - rhs)
171    }
172}
173
174impl Mul<f32> for Jzczhz {
175    type Output = Jzczhz;
176
177    #[inline]
178    fn mul(self, rhs: f32) -> Self::Output {
179        Jzczhz::new(self.jz * rhs, self.cz * rhs, self.hz * rhs)
180    }
181}
182
183impl Div<f32> for Jzczhz {
184    type Output = Jzczhz;
185
186    #[inline]
187    fn div(self, rhs: f32) -> Self::Output {
188        Jzczhz::new(self.jz / rhs, self.cz / rhs, self.hz / rhs)
189    }
190}
191
192impl Add<Jzczhz> for Jzczhz {
193    type Output = Jzczhz;
194
195    #[inline]
196    fn add(self, rhs: Jzczhz) -> Self::Output {
197        Jzczhz::new(self.jz + rhs.jz, self.cz + rhs.cz, self.hz + rhs.hz)
198    }
199}
200
201impl Sub<Jzczhz> for Jzczhz {
202    type Output = Jzczhz;
203
204    #[inline]
205    fn sub(self, rhs: Jzczhz) -> Self::Output {
206        Jzczhz::new(self.jz - rhs.jz, self.cz - rhs.cz, self.hz - rhs.hz)
207    }
208}
209
210impl Mul<Jzczhz> for Jzczhz {
211    type Output = Jzczhz;
212
213    #[inline]
214    fn mul(self, rhs: Jzczhz) -> Self::Output {
215        Jzczhz::new(self.jz * rhs.jz, self.cz * rhs.cz, self.hz * rhs.hz)
216    }
217}
218
219impl Div<Jzczhz> for Jzczhz {
220    type Output = Jzczhz;
221
222    #[inline]
223    fn div(self, rhs: Jzczhz) -> Self::Output {
224        Jzczhz::new(self.jz / rhs.jz, self.cz / rhs.cz, self.hz / rhs.hz)
225    }
226}
227
228impl AddAssign<Jzczhz> for Jzczhz {
229    #[inline]
230    fn add_assign(&mut self, rhs: Jzczhz) {
231        self.jz += rhs.jz;
232        self.cz += rhs.cz;
233        self.hz += rhs.hz;
234    }
235}
236
237impl SubAssign<Jzczhz> for Jzczhz {
238    #[inline]
239    fn sub_assign(&mut self, rhs: Jzczhz) {
240        self.jz -= rhs.jz;
241        self.cz -= rhs.cz;
242        self.hz -= rhs.hz;
243    }
244}
245
246impl MulAssign<Jzczhz> for Jzczhz {
247    #[inline]
248    fn mul_assign(&mut self, rhs: Jzczhz) {
249        self.jz *= rhs.jz;
250        self.cz *= rhs.cz;
251        self.hz *= rhs.hz;
252    }
253}
254
255impl DivAssign<Jzczhz> for Jzczhz {
256    #[inline]
257    fn div_assign(&mut self, rhs: Jzczhz) {
258        self.jz /= rhs.jz;
259        self.cz /= rhs.cz;
260        self.hz /= rhs.hz;
261    }
262}
263
264impl AddAssign<f32> for Jzczhz {
265    #[inline]
266    fn add_assign(&mut self, rhs: f32) {
267        self.jz += rhs;
268        self.cz += rhs;
269        self.hz += rhs;
270    }
271}
272
273impl SubAssign<f32> for Jzczhz {
274    #[inline]
275    fn sub_assign(&mut self, rhs: f32) {
276        self.jz -= rhs;
277        self.cz -= rhs;
278        self.hz -= rhs;
279    }
280}
281
282impl MulAssign<f32> for Jzczhz {
283    #[inline]
284    fn mul_assign(&mut self, rhs: f32) {
285        self.jz *= rhs;
286        self.cz *= rhs;
287        self.hz *= rhs;
288    }
289}
290
291impl DivAssign<f32> for Jzczhz {
292    #[inline]
293    fn div_assign(&mut self, rhs: f32) {
294        self.jz /= rhs;
295        self.cz /= rhs;
296        self.hz /= rhs;
297    }
298}
299
300impl Jzczhz {
301    #[inline]
302    pub fn sqrt(&self) -> Jzczhz {
303        Jzczhz::new(self.jz.sqrt(), self.cz.sqrt(), self.hz.sqrt())
304    }
305
306    #[inline]
307    pub fn cbrt(&self) -> Jzczhz {
308        Jzczhz::new(f_cbrtf(self.jz), f_cbrtf(self.cz), f_cbrtf(self.hz))
309    }
310}
311
312impl Pow<f32> for Jzczhz {
313    type Output = Jzczhz;
314
315    #[inline]
316    fn pow(self, rhs: f32) -> Self::Output {
317        Jzczhz::new(
318            f_powf(self.jz, rhs),
319            f_powf(self.cz, rhs),
320            f_powf(self.hz, rhs),
321        )
322    }
323}
324
325impl Pow<Jzczhz> for Jzczhz {
326    type Output = Jzczhz;
327
328    #[inline]
329    fn pow(self, rhs: Jzczhz) -> Self::Output {
330        Jzczhz::new(
331            f_powf(self.jz, rhs.jz),
332            f_powf(self.cz, self.cz),
333            f_powf(self.hz, self.hz),
334        )
335    }
336}
337
338impl Neg for Jzczhz {
339    type Output = Jzczhz;
340
341    #[inline]
342    fn neg(self) -> Self::Output {
343        Jzczhz::new(-self.jz, -self.cz, -self.hz)
344    }
345}
346
347#[cfg(test)]
348mod tests {
349    use super::*;
350
351    #[test]
352    fn jzczhz_round() {
353        let xyz = Xyz::new(0.5, 0.4, 0.3);
354        let jzczhz = Jzczhz::from_xyz_with_display_luminance(xyz, 253.);
355        let old_xyz = jzczhz.to_xyz(253f32);
356        assert!(
357            (xyz.x - old_xyz.x).abs() <= 1e-3,
358            "{:?} != {:?}",
359            xyz,
360            old_xyz
361        );
362        assert!(
363            (xyz.y - old_xyz.y).abs() <= 1e-3,
364            "{:?} != {:?}",
365            xyz,
366            old_xyz
367        );
368        assert!(
369            (xyz.z - old_xyz.z).abs() <= 1e-3,
370            "{:?} != {:?}",
371            xyz,
372            old_xyz
373        );
374    }
375}