Coverage Report

Created: 2026-01-09 07:43

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/rust/registry/src/index.crates.io-1949cf8c6b5b557f/moxcms-0.7.11/src/oklch.rs
Line
Count
Source
1
/*
2
 * // Copyright 2024 (c) the Radzivon Bartoshyk. All rights reserved.
3
 * //
4
 * // Use of this source code is governed by a BSD-style
5
 * // license that can be found in the LICENSE file.
6
 */
7
use crate::{Oklab, Rgb};
8
use num_traits::Pow;
9
use pxfm::{f_atan2f, f_cbrtf, f_hypotf, f_powf, f_sincosf};
10
use std::ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign};
11
12
/// Represents *Oklch* colorspace
13
#[repr(C)]
14
#[derive(Copy, Clone, PartialOrd, PartialEq)]
15
pub struct Oklch {
16
    /// Lightness
17
    pub l: f32,
18
    /// Chroma
19
    pub c: f32,
20
    /// Hue
21
    pub h: f32,
22
}
23
24
impl Oklch {
25
    /// Creates new instance
26
    #[inline]
27
0
    pub const fn new(l: f32, c: f32, h: f32) -> Oklch {
28
0
        Oklch { l, c, h }
29
0
    }
30
31
    /// Converts Linear [Rgb] into [Oklch]
32
    ///
33
    /// # Arguments
34
    /// `transfer_function` - Transfer function into linear colorspace and its inverse
35
    #[inline]
36
0
    pub fn from_linear_rgb(rgb: Rgb<f32>) -> Oklch {
37
0
        let oklab = Oklab::from_linear_rgb(rgb);
38
0
        Oklch::from_oklab(oklab)
39
0
    }
40
41
    /// Converts [Oklch] into linear [Rgb]
42
    #[inline]
43
0
    pub fn to_linear_rgb(&self) -> Rgb<f32> {
44
0
        let oklab = self.to_oklab();
45
0
        oklab.to_linear_rgb()
46
0
    }
47
48
    /// Converts *Oklab* to *Oklch*
49
    #[inline]
50
0
    pub fn from_oklab(oklab: Oklab) -> Oklch {
51
0
        let chroma = f_hypotf(oklab.b, oklab.a);
52
0
        let hue = f_atan2f(oklab.b, oklab.a);
53
0
        Oklch::new(oklab.l, chroma, hue)
54
0
    }
55
56
    /// Converts *Oklch* to *Oklab*
57
    #[inline]
58
0
    pub fn to_oklab(&self) -> Oklab {
59
0
        let l = self.l;
60
0
        let sincos = f_sincosf(self.h);
61
0
        let a = self.c * sincos.1;
62
0
        let b = self.c * sincos.0;
63
0
        Oklab::new(l, a, b)
64
0
    }
65
}
66
67
impl Oklch {
68
    #[inline]
69
0
    pub fn euclidean_distance(&self, other: Self) -> f32 {
70
0
        let dl = self.l - other.l;
71
0
        let dc = self.c - other.c;
72
0
        let dh = self.h - other.h;
73
0
        (dl * dl + dc * dc + dh * dh).sqrt()
74
0
    }
75
}
76
77
impl Oklch {
78
    #[inline]
79
0
    pub fn taxicab_distance(&self, other: Self) -> f32 {
80
0
        let dl = self.l - other.l;
81
0
        let dc = self.c - other.c;
82
0
        let dh = self.h - other.h;
83
0
        dl.abs() + dc.abs() + dh.abs()
84
0
    }
85
}
86
87
impl Add<Oklch> for Oklch {
88
    type Output = Oklch;
89
90
    #[inline]
91
0
    fn add(self, rhs: Self) -> Oklch {
92
0
        Oklch::new(self.l + rhs.l, self.c + rhs.c, self.h + rhs.h)
93
0
    }
94
}
95
96
impl Add<f32> for Oklch {
97
    type Output = Oklch;
98
99
    #[inline]
100
0
    fn add(self, rhs: f32) -> Oklch {
101
0
        Oklch::new(self.l + rhs, self.c + rhs, self.h + rhs)
102
0
    }
103
}
104
105
impl AddAssign<Oklch> for Oklch {
106
    #[inline]
107
0
    fn add_assign(&mut self, rhs: Oklch) {
108
0
        self.l += rhs.l;
109
0
        self.c += rhs.c;
110
0
        self.h += rhs.h;
111
0
    }
112
}
113
114
impl AddAssign<f32> for Oklch {
115
    #[inline]
116
0
    fn add_assign(&mut self, rhs: f32) {
117
0
        self.l += rhs;
118
0
        self.c += rhs;
119
0
        self.h += rhs;
120
0
    }
121
}
122
123
impl Mul<f32> for Oklch {
124
    type Output = Oklch;
125
126
    #[inline]
127
0
    fn mul(self, rhs: f32) -> Self::Output {
128
0
        Oklch::new(self.l * rhs, self.c * rhs, self.h * rhs)
129
0
    }
130
}
131
132
impl Mul<Oklch> for Oklch {
133
    type Output = Oklch;
134
135
    #[inline]
136
0
    fn mul(self, rhs: Oklch) -> Self::Output {
137
0
        Oklch::new(self.l * rhs.l, self.c * rhs.c, self.h * rhs.h)
138
0
    }
139
}
140
141
impl MulAssign<f32> for Oklch {
142
    #[inline]
143
0
    fn mul_assign(&mut self, rhs: f32) {
144
0
        self.l *= rhs;
145
0
        self.c *= rhs;
146
0
        self.h *= rhs;
147
0
    }
148
}
149
150
impl MulAssign<Oklch> for Oklch {
151
    #[inline]
152
0
    fn mul_assign(&mut self, rhs: Oklch) {
153
0
        self.l *= rhs.l;
154
0
        self.c *= rhs.c;
155
0
        self.h *= rhs.h;
156
0
    }
157
}
158
159
impl Sub<f32> for Oklch {
160
    type Output = Oklch;
161
162
    #[inline]
163
0
    fn sub(self, rhs: f32) -> Self::Output {
164
0
        Oklch::new(self.l - rhs, self.c - rhs, self.h - rhs)
165
0
    }
166
}
167
168
impl Sub<Oklch> for Oklch {
169
    type Output = Oklch;
170
171
    #[inline]
172
0
    fn sub(self, rhs: Oklch) -> Self::Output {
173
0
        Oklch::new(self.l - rhs.l, self.c - rhs.c, self.h - rhs.h)
174
0
    }
175
}
176
177
impl SubAssign<f32> for Oklch {
178
    #[inline]
179
0
    fn sub_assign(&mut self, rhs: f32) {
180
0
        self.l -= rhs;
181
0
        self.c -= rhs;
182
0
        self.h -= rhs;
183
0
    }
184
}
185
186
impl SubAssign<Oklch> for Oklch {
187
    #[inline]
188
0
    fn sub_assign(&mut self, rhs: Oklch) {
189
0
        self.l -= rhs.l;
190
0
        self.c -= rhs.c;
191
0
        self.h -= rhs.h;
192
0
    }
193
}
194
195
impl Div<f32> for Oklch {
196
    type Output = Oklch;
197
198
    #[inline]
199
0
    fn div(self, rhs: f32) -> Self::Output {
200
0
        Oklch::new(self.l / rhs, self.c / rhs, self.h / rhs)
201
0
    }
202
}
203
204
impl Div<Oklch> for Oklch {
205
    type Output = Oklch;
206
207
    #[inline]
208
0
    fn div(self, rhs: Oklch) -> Self::Output {
209
0
        Oklch::new(self.l / rhs.l, self.c / rhs.c, self.h / rhs.h)
210
0
    }
211
}
212
213
impl DivAssign<f32> for Oklch {
214
    #[inline]
215
0
    fn div_assign(&mut self, rhs: f32) {
216
0
        self.l /= rhs;
217
0
        self.c /= rhs;
218
0
        self.h /= rhs;
219
0
    }
220
}
221
222
impl DivAssign<Oklch> for Oklch {
223
    #[inline]
224
0
    fn div_assign(&mut self, rhs: Oklch) {
225
0
        self.l /= rhs.l;
226
0
        self.c /= rhs.c;
227
0
        self.h /= rhs.h;
228
0
    }
229
}
230
231
impl Neg for Oklch {
232
    type Output = Oklch;
233
234
    #[inline]
235
0
    fn neg(self) -> Self::Output {
236
0
        Oklch::new(-self.l, -self.c, -self.h)
237
0
    }
238
}
239
240
impl Pow<f32> for Oklch {
241
    type Output = Oklch;
242
243
    #[inline]
244
0
    fn pow(self, rhs: f32) -> Self::Output {
245
0
        Oklch::new(
246
0
            f_powf(self.l, rhs),
247
0
            f_powf(self.c, rhs),
248
0
            f_powf(self.h, rhs),
249
        )
250
0
    }
251
}
252
253
impl Pow<Oklch> for Oklch {
254
    type Output = Oklch;
255
256
    #[inline]
257
0
    fn pow(self, rhs: Oklch) -> Self::Output {
258
0
        Oklch::new(
259
0
            f_powf(self.l, rhs.l),
260
0
            f_powf(self.c, rhs.c),
261
0
            f_powf(self.h, rhs.h),
262
        )
263
0
    }
264
}
265
266
impl Oklch {
267
    #[inline]
268
0
    pub fn sqrt(&self) -> Oklch {
269
0
        Oklch::new(self.l.sqrt(), self.c.sqrt(), self.h.sqrt())
270
0
    }
271
272
    #[inline]
273
0
    pub fn cbrt(&self) -> Oklch {
274
0
        Oklch::new(f_cbrtf(self.l), f_cbrtf(self.c), f_cbrtf(self.h))
275
0
    }
276
}
277
278
#[cfg(test)]
279
mod tests {
280
    use super::*;
281
282
    #[test]
283
    fn round_trip() {
284
        let xyz = Rgb::new(0.1, 0.2, 0.3);
285
        let lab = Oklch::from_linear_rgb(xyz);
286
        let rolled_back = lab.to_linear_rgb();
287
        let dx = (xyz.r - rolled_back.r).abs();
288
        let dy = (xyz.g - rolled_back.g).abs();
289
        let dz = (xyz.b - rolled_back.b).abs();
290
        assert!(dx < 1e-5);
291
        assert!(dy < 1e-5);
292
        assert!(dz < 1e-5);
293
    }
294
}