1 #![doc(html_root_url = "https://docs.rs/lab")]
2 
3 /*!
4 
5 # Lab
6 
7 Tools for converting RGB colors to L\*a\*b\* measurements.
8 
9 RGB colors, for this crate at least, are considered to be composed of `u8`
10 values from 0 to 255, while L\*a\*b\* colors are represented by its own struct
11 that uses `f32` values.
12 
13 # Usage
14 
15 ## Converting single values
16 
17 To convert a single value, use one of the functions
18 
19 * `lab::Lab::from_rgb(rgb: &[u8; 3]) -> Lab`
20 * `lab::Lab::from_rgba(rgba: &[u8; 4]) -> Lab` (drops the fourth alpha byte)
21 * `lab::Lab::to_rgb(&self) -> [u8; 3]`
22 
23 ```rust
24 extern crate lab;
25 use lab::Lab;
26 
27 let pink_in_lab = Lab::from_rgb(&[253, 120, 138]);
28 // Lab { l: 66.639084, a: 52.251457, b: 14.860654 }
29 ```
30 
31 ## Converting multiple values
32 
33 To convert slices of values
34 
35 * `lab::rgbs_to_labs(rgbs: &[[u8; 3]]) -> Vec<Lab>`
36 * `lab::labs_to_rgbs(labs: &[Lab]) -> Vec<[u8; 3]>`
37 * `lab::rgb_bytes_to_labs(bytes: &[u8]) -> Vec<Lab>`
38 * `lab::labs_to_rgb_bytes(labs: &[Lab]) -> Vec<u8>`
39 
40 ```rust
41 extern crate lab;
42 use lab::rgbs_to_labs;
43 
44 let rgbs = vec![
45     [0xFF, 0x69, 0xB6],
46     [0xE7, 0x00, 0x00],
47     [0xFF, 0x8C, 0x00],
48     [0xFF, 0xEF, 0x00],
49     [0x00, 0x81, 0x1F],
50     [0x00, 0xC1, 0xC1],
51     [0x00, 0x44, 0xFF],
52     [0x76, 0x00, 0x89],
53 ];
54 
55 let labs = rgbs_to_labs(&rgbs);
56 ```
57 
58 ```rust
59 extern crate lab;
60 use lab::rgb_bytes_to_labs;
61 
62 let rgbs = vec![
63     0xFF, 0x69, 0xB6,
64     0xE7, 0x00, 0x00,
65     0xFF, 0x8C, 0x00,
66     0xFF, 0xEF, 0x00,
67     0x00, 0x81, 0x1F,
68     0x00, 0xC1, 0xC1,
69     0x00, 0x44, 0xFF,
70     0x76, 0x00, 0x89,
71 ];
72 
73 let labs = rgb_bytes_to_labs(&rgbs);
74 ```
75 
76 These functions will use x86_64 AVX2 instructions if compiled to a supported target.
77 
78 ## Minimum Rust version
79 
80 Lab 0.7.0 requires Rust >= 1.31.0 for the [chunks_exact](https://doc.rust-lang.org/std/primitive.slice.html#method.chunks_exact) slice method
81 
82 */
83 
84 #[cfg(test)]
85 #[macro_use]
86 extern crate pretty_assertions;
87 #[cfg(test)]
88 extern crate approx;
89 #[cfg(test)]
90 extern crate lazy_static;
91 #[cfg(test)]
92 extern crate rand;
93 
94 #[cfg(test)]
95 mod approx_impl;
96 
97 #[cfg(all(target_arch = "x86_64", target_feature = "avx2"))]
98 mod simd;
99 
100 /// Struct representing a color in CIALab, a.k.a. L\*a\*b\*, color space
101 #[derive(Debug, PartialEq, Copy, Clone, Default)]
102 pub struct Lab {
103     pub l: f32,
104     pub a: f32,
105     pub b: f32,
106 }
107 
108 /// Struct representing a color in cylindrical CIELCh color space
109 #[derive(Debug, PartialEq, Copy, Clone, Default)]
110 pub struct LCh {
111     pub l: f32,
112     pub c: f32,
113     pub h: f32,
114 }
115 
116 // κ and ε parameters used in conversion between XYZ and La*b*.  See
117 // http://www.brucelindbloom.com/LContinuity.html for explanation as to why
118 // those are different values than those provided by CIE standard.
119 pub(crate) const KAPPA: f32 = 24389.0 / 27.0;
120 pub(crate) const EPSILON: f32 = 216.0 / 24389.0;
121 pub(crate) const CBRT_EPSILON: f32 = 6.0 / 29.0;
122 
123 // S₀ and E₀ thresholds used in sRGB gamma.  The latter is scaled for encoded
124 // value in the range 0..255.
125 pub(crate) const S_0: f32 = 0.003130668442500564;
126 pub(crate) const E_0_255: f32 = 3294.6 * S_0;
127 
128 // Generated by srgb-matrices.py
129 const WHITE_X: f32 = 0.9504492182750991;
130 const WHITE_Z: f32 = 1.0889166484304715;
131 
rgb_to_lab(r: u8, g: u8, b: u8) -> Lab132 fn rgb_to_lab(r: u8, g: u8, b: u8) -> Lab {
133     xyz_to_lab(rgb_to_xyz(r, g, b))
134 }
135 
rgb_to_xyz(r: u8, g: u8, b: u8) -> [f32; 3]136 fn rgb_to_xyz(r: u8, g: u8, b: u8) -> [f32; 3] {
137     rgb_to_xyz_inner(r as f32, g as f32, b as f32)
138 }
139 
rgb_to_xyz_normalized(rgb: &[f32; 3]) -> [f32; 3]140 fn rgb_to_xyz_normalized(rgb: &[f32; 3]) -> [f32; 3] {
141     rgb_to_xyz_inner(rgb[0] * 255.0, rgb[1] * 255.0, rgb[2] * 255.0)
142 }
143 
144 #[inline(always)]
145 #[cfg(any(target_feature = "fma", test))]
mul3(a0: f32, a1: f32, a2: f32, b0: f32, b1: f32, b2: f32) -> f32146 fn mul3(a0: f32, a1: f32, a2: f32, b0: f32, b1: f32, b2: f32) -> f32 {
147     a2.mul_add(b2, a1.mul_add(b1, a0 * b0))
148 }
149 
150 #[inline(always)]
151 #[cfg(not(any(target_feature = "fma", test)))]
mul3(a0: f32, a1: f32, a2: f32, b0: f32, b1: f32, b2: f32) -> f32152 fn mul3(a0: f32, a1: f32, a2: f32, b0: f32, b1: f32, b2: f32) -> f32 {
153     a0 * b0 + a1 * b1 + a2 * b2
154 }
155 
156 #[inline]
rgb_to_xyz_inner(r: f32, g: f32, b: f32) -> [f32; 3]157 fn rgb_to_xyz_inner(r: f32, g: f32, b: f32) -> [f32; 3] {
158     #[inline]
rgb_to_xyz_map(c: f32) -> f32159     fn rgb_to_xyz_map(c: f32) -> f32 {
160         if c > E_0_255 {
161             const A: f32 = 0.055 * 255.0;
162             const D: f32 = 1.055 * 255.0;
163             ((c + A) / D).powf(2.4)
164         } else {
165             const D: f32 = 12.92 * 255.0;
166             c / D
167         }
168     }
169 
170     let r = rgb_to_xyz_map(r);
171     let g = rgb_to_xyz_map(g);
172     let b = rgb_to_xyz_map(b);
173 
174     // Generated by srgb-matrices.py
175     let x = mul3(
176         r,
177         g,
178         b,
179         0.4124108464885388,
180         0.3575845678529519,
181         0.18045380393360833,
182     );
183     let y = mul3(
184         r,
185         g,
186         b,
187         0.21264934272065283,
188         0.7151691357059038,
189         0.07218152157344333,
190     );
191     let z = mul3(
192         r,
193         g,
194         b,
195         0.019331758429150258,
196         0.11919485595098397,
197         0.9503900340503373,
198     );
199 
200     [x, y, z]
201 }
202 
xyz_to_lab(xyz: [f32; 3]) -> Lab203 fn xyz_to_lab(xyz: [f32; 3]) -> Lab {
204     #[inline]
xyz_to_lab_map(c: f32) -> f32205     fn xyz_to_lab_map(c: f32) -> f32 {
206         if c > EPSILON {
207             c.powf(1.0 / 3.0)
208         } else {
209             (KAPPA * c + 16.0) / 116.0
210         }
211     }
212 
213     // It’s tempting to replace the division with a multiplication by inverse,
214     // however that results in slightly worse test_grey_error benchmark.
215     let x = xyz_to_lab_map(xyz[0] / WHITE_X);
216     let y = xyz_to_lab_map(xyz[1]);
217     let z = xyz_to_lab_map(xyz[2] / WHITE_Z);
218 
219     Lab {
220         l: (116.0 * y) - 16.0,
221         a: 500.0 * (x - y),
222         b: 200.0 * (y - z),
223     }
224 }
225 
lab_to_xyz(lab: &Lab) -> [f32; 3]226 fn lab_to_xyz(lab: &Lab) -> [f32; 3] {
227     let fy = (lab.l + 16.0) / 116.0;
228     let fx = (lab.a / 500.0) + fy;
229     let fz = fy - (lab.b / 200.0);
230     let xr = if fx > CBRT_EPSILON {
231         fx.powi(3)
232     } else {
233         ((fx * 116.0) - 16.0) / KAPPA
234     };
235     let yr = if lab.l > EPSILON * KAPPA {
236         fy.powi(3)
237     } else {
238         lab.l / KAPPA
239     };
240     let zr = if fz > CBRT_EPSILON {
241         fz.powi(3)
242     } else {
243         ((fz * 116.0) - 16.0) / KAPPA
244     };
245 
246     [xr * WHITE_X, yr, zr * WHITE_Z]
247 }
248 
xyz_to_rgb(xyz: [f32; 3]) -> [u8; 3]249 fn xyz_to_rgb(xyz: [f32; 3]) -> [u8; 3] {
250     let rgb = xyz_to_rgb_normalized(xyz);
251     [
252         (rgb[0] * 255.0).round() as u8,
253         (rgb[1] * 255.0).round() as u8,
254         (rgb[2] * 255.0).round() as u8,
255     ]
256 }
257 
xyz_to_rgb_normalized(xyz: [f32; 3]) -> [f32; 3]258 fn xyz_to_rgb_normalized(xyz: [f32; 3]) -> [f32; 3] {
259     let x = xyz[0];
260     let y = xyz[1];
261     let z = xyz[2];
262 
263     // Generated by srgb-matrices.py
264     let r = mul3(
265         x,
266         y,
267         z,
268         3.240812398895283,
269         -1.5373084456298136,
270         -0.4985865229069666,
271     );
272     let g = mul3(
273         x,
274         y,
275         z,
276         -0.9692430170086407,
277         1.8759663029085742,
278         0.04155503085668564,
279     );
280     let b = mul3(
281         x,
282         y,
283         z,
284         0.055638398436112804,
285         -0.20400746093241362,
286         1.0571295702861434,
287     );
288 
289     #[inline]
xyz_to_rgb_map(c: f32) -> f32290     fn xyz_to_rgb_map(c: f32) -> f32 {
291         (if c > S_0 {
292             1.055 * c.powf(1.0 / 2.4) - 0.055
293         } else {
294             12.92 * c
295         })
296         .min(1.0)
297         .max(0.0)
298     }
299 
300     [xyz_to_rgb_map(r), xyz_to_rgb_map(g), xyz_to_rgb_map(b)]
301 }
302 
303 /// Convenience function to map a slice of RGB values to Lab values in serial
304 ///
305 /// # Example
306 /// ```
307 /// # extern crate lab;
308 /// # use lab::{Lab, rgbs_to_labs};
309 /// let rgbs = &[[255u8, 0, 0], [255, 0, 255], [0, 255, 255]];
310 /// let labs = lab::rgbs_to_labs(rgbs);
311 /// assert_eq!(labs, vec![
312 ///     Lab { l: 53.238235, a: 80.09231, b: 67.202095 },
313 ///     Lab { l: 60.322693, a: 98.23698, b: -60.827957 },
314 ///     Lab { l: 91.11428, a: -48.08274, b: -14.12958 }
315 /// ]);
316 /// ```
317 #[inline]
rgbs_to_labs(rgbs: &[[u8; 3]]) -> Vec<Lab>318 pub fn rgbs_to_labs(rgbs: &[[u8; 3]]) -> Vec<Lab> {
319     #[cfg(all(target_arch = "x86_64", target_feature = "avx2"))]
320     let labs = simd::rgbs_to_labs(rgbs);
321 
322     #[cfg(not(all(target_arch = "x86_64", target_feature = "avx2")))]
323     let labs = __scalar::rgbs_to_labs(rgbs);
324 
325     labs
326 }
327 
328 /// RGB to Lab conversion that operates on a flat `&[u8]` of consecutive RGB triples.
329 ///
330 /// # Example
331 /// ```
332 /// # extern crate lab;
333 /// # use lab::{Lab, rgb_bytes_to_labs};
334 /// let rgbs = &[255u8, 0, 0, 255, 0, 255, 0, 255, 255];
335 /// let labs = lab::rgb_bytes_to_labs(rgbs);
336 /// assert_eq!(labs, vec![
337 ///     Lab { l: 53.238235, a: 80.09231, b: 67.202095 },
338 ///     Lab { l: 60.322693, a: 98.23698, b: -60.827957 },
339 ///     Lab { l: 91.11428, a: -48.08274, b: -14.12958 }
340 /// ]);
341 /// ```
rgb_bytes_to_labs(bytes: &[u8]) -> Vec<Lab>342 pub fn rgb_bytes_to_labs(bytes: &[u8]) -> Vec<Lab> {
343     #[cfg(all(target_arch = "x86_64", target_feature = "avx2"))]
344     let labs = simd::rgb_bytes_to_labs(bytes);
345 
346     #[cfg(not(all(target_arch = "x86_64", target_feature = "avx2")))]
347     let labs = __scalar::rgb_bytes_to_labs(bytes);
348 
349     labs
350 }
351 
352 /// Convenience function to map a slice of Lab values to RGB values in serial
353 ///
354 /// # Example
355 /// ```
356 /// # extern crate lab;
357 /// # use lab::{Lab, labs_to_rgbs};
358 /// let labs = &[
359 ///     Lab { l: 91.11321, a: -48.08751, b: -14.131201 },
360 ///     Lab { l: 60.32421, a: 98.23433, b: -60.824894 },
361 ///     Lab { l: 97.13926, a: -21.553724, b: 94.47797 },
362 /// ];
363 /// let rgbs = lab::labs_to_rgbs(labs);
364 /// assert_eq!(rgbs, vec![[0u8, 255, 255], [255, 0, 255], [255, 255, 0]]);
365 /// ```
366 #[inline]
labs_to_rgbs(labs: &[Lab]) -> Vec<[u8; 3]>367 pub fn labs_to_rgbs(labs: &[Lab]) -> Vec<[u8; 3]> {
368     #[cfg(all(target_arch = "x86_64", target_feature = "avx2"))]
369     let rgbs = simd::labs_to_rgbs(labs);
370 
371     #[cfg(not(all(target_arch = "x86_64", target_feature = "avx2")))]
372     let rgbs = __scalar::labs_to_rgbs(labs);
373 
374     rgbs
375 }
376 
377 /// Lab to RGB conversion that returns RGB triples flattened into a `Vec<u8>`
378 ///
379 /// # Example
380 /// ```
381 /// # extern crate lab;
382 /// # use lab::{Lab, labs_to_rgb_bytes};
383 /// let labs = &[
384 ///     Lab { l: 91.11321, a: -48.08751, b: -14.131201 },
385 ///     Lab { l: 60.32421, a: 98.23433, b: -60.824894 },
386 ///     Lab { l: 97.13926, a: -21.553724, b: 94.47797 },
387 /// ];
388 /// let rgb_bytes = lab::labs_to_rgb_bytes(labs);
389 /// assert_eq!(rgb_bytes, vec![0, 255, 255, 255, 0, 255, 255, 255, 0]);
390 /// ```
391 #[inline]
labs_to_rgb_bytes(labs: &[Lab]) -> Vec<u8>392 pub fn labs_to_rgb_bytes(labs: &[Lab]) -> Vec<u8> {
393     #[cfg(all(target_arch = "x86_64", target_feature = "avx2"))]
394     let bytes = simd::labs_to_rgb_bytes(labs);
395 
396     #[cfg(not(all(target_arch = "x86_64", target_feature = "avx2")))]
397     let bytes = __scalar::labs_to_rgb_bytes(labs);
398 
399     bytes
400 }
401 
402 #[doc(hidden)]
403 pub mod __scalar {
404     use rgb_to_lab;
405     use Lab;
406 
407     #[inline]
labs_to_rgbs(labs: &[Lab]) -> Vec<[u8; 3]>408     pub fn labs_to_rgbs(labs: &[Lab]) -> Vec<[u8; 3]> {
409         labs.iter().map(Lab::to_rgb).collect()
410     }
411 
412     #[inline]
labs_to_rgb_bytes(labs: &[Lab]) -> Vec<u8>413     pub fn labs_to_rgb_bytes(labs: &[Lab]) -> Vec<u8> {
414         labs.iter()
415             .map(Lab::to_rgb)
416             .fold(Vec::with_capacity(labs.len() * 3), |mut acc, rgb| {
417                 acc.extend_from_slice(&rgb);
418                 acc
419             })
420     }
421 
422     #[inline]
rgbs_to_labs(rgbs: &[[u8; 3]]) -> Vec<Lab>423     pub fn rgbs_to_labs(rgbs: &[[u8; 3]]) -> Vec<Lab> {
424         rgbs.iter().map(Lab::from_rgb).collect()
425     }
426 
427     #[inline]
rgb_bytes_to_labs(bytes: &[u8]) -> Vec<Lab>428     pub fn rgb_bytes_to_labs(bytes: &[u8]) -> Vec<Lab> {
429         bytes
430             .chunks_exact(3)
431             .map(|rgb| rgb_to_lab(rgb[0], rgb[1], rgb[2]))
432             .collect()
433     }
434 }
435 
436 impl Lab {
437     /// Constructs a new `Lab` from a three-element array of `u8`s
438     ///
439     /// # Examples
440     ///
441     /// ```
442     /// let lab = lab::Lab::from_rgb(&[240, 33, 95]);
443     /// assert_eq!(lab::Lab { l: 52.334686, a: 75.55157, b: 19.995684 }, lab);
444     /// ```
from_rgb(rgb: &[u8; 3]) -> Self445     pub fn from_rgb(rgb: &[u8; 3]) -> Self {
446         rgb_to_lab(rgb[0], rgb[1], rgb[2])
447     }
448 
449     #[doc(hidden)]
from_rgb_normalized(rgb: &[f32; 3]) -> Self450     pub fn from_rgb_normalized(rgb: &[f32; 3]) -> Self {
451         xyz_to_lab(rgb_to_xyz_normalized(rgb))
452     }
453 
454     /// Constructs a new `Lab` from a four-element array of `u8`s
455     ///
456     /// The `Lab` struct does not store alpha channel information, so the last
457     /// `u8` representing alpha is discarded. This convenience method exists
458     /// in order to easily measure colors already stored in an RGBA array.
459     ///
460     /// # Examples
461     ///
462     /// ```
463     /// let lab = lab::Lab::from_rgba(&[240, 33, 95, 255]);
464     /// assert_eq!(lab::Lab { l: 52.334686, a: 75.55157, b: 19.995684 }, lab);
465     /// ```
from_rgba(rgba: &[u8; 4]) -> Self466     pub fn from_rgba(rgba: &[u8; 4]) -> Self {
467         Lab::from_rgb(&[rgba[0], rgba[1], rgba[2]])
468     }
469 
470     #[doc(hidden)]
from_rgba_normalized(rgba: &[f32; 4]) -> Self471     pub fn from_rgba_normalized(rgba: &[f32; 4]) -> Self {
472         Lab::from_rgb_normalized(&[rgba[0], rgba[1], rgba[2]])
473     }
474 
475     /// Returns the `Lab`'s color in RGB, in a 3-element array.
476     ///
477     /// # Examples
478     ///
479     /// ```
480     /// let lab = lab::Lab { l: 52.330193, a: 75.56704, b: 19.989174 };
481     /// let rgb = lab.to_rgb();
482     /// assert_eq!([240, 33, 95], rgb);
483     /// ```
to_rgb(&self) -> [u8; 3]484     pub fn to_rgb(&self) -> [u8; 3] {
485         xyz_to_rgb(lab_to_xyz(self))
486     }
487 
488     #[doc(hidden)]
to_rgb_normalized(&self) -> [f32; 3]489     pub fn to_rgb_normalized(&self) -> [f32; 3] {
490         xyz_to_rgb_normalized(lab_to_xyz(self))
491     }
492 
493     /// Measures the perceptual distance between the colors of one `Lab`
494     /// and an `other`.
495     ///
496     /// # Examples
497     ///
498     /// ```
499     /// # use lab::Lab;
500     /// let pink = Lab { l: 66.6348, a: 52.260696, b: 14.850557 };
501     /// let websafe_pink = Lab { l: 64.2116, a: 62.519463, b: 2.8871894 };
502     /// assert_eq!(254.23636, pink.squared_distance(&websafe_pink));
503     /// ```
squared_distance(&self, other: &Lab) -> f32504     pub fn squared_distance(&self, other: &Lab) -> f32 {
505         (self.l - other.l).powi(2) + (self.a - other.a).powi(2) + (self.b - other.b).powi(2)
506     }
507 }
508 
509 impl LCh {
510     /// Constructs a new `LCh` from a three-element array of `u8`s
511     ///
512     /// # Examples
513     ///
514     /// ```
515     /// let lch = lab::LCh::from_rgb(&[240, 33, 95]);
516     /// assert_eq!(lab::LCh { l: 52.334686, c: 78.15284, h: 0.25873056 }, lch);
517     /// ```
from_rgb(rgb: &[u8; 3]) -> Self518     pub fn from_rgb(rgb: &[u8; 3]) -> Self {
519         LCh::from_lab(Lab::from_rgb(rgb))
520     }
521 
522     /// Constructs a new `LCh` from a four-element array of `u8`s
523     ///
524     /// The `LCh` struct does not store alpha channel information, so the last
525     /// `u8` representing alpha is discarded. This convenience method exists
526     /// in order to easily measure colors already stored in an RGBA array.
527     ///
528     /// # Examples
529     ///
530     /// ```
531     /// let lch = lab::LCh::from_rgba(&[240, 33, 95, 255]);
532     /// assert_eq!(lab::LCh { l: 52.334686, c: 78.15284, h: 0.25873056 }, lch);
533     /// ```
from_rgba(rgba: &[u8; 4]) -> Self534     pub fn from_rgba(rgba: &[u8; 4]) -> Self {
535         LCh::from_lab(Lab::from_rgba(rgba))
536     }
537 
538     /// Constructs a new `LCh` from a `Lab`
539     ///
540     /// # Examples
541     ///
542     /// ```
543     /// let lab = lab::Lab { l: 52.33686, a: 75.5516, b: 19.998878 };
544     /// let lch = lab::LCh::from_lab(lab);
545     /// assert_eq!(lab::LCh { l: 52.33686, c: 78.15369, h: 0.25877 }, lch);
546     ///
547     /// let lab = lab::Lab { l: 52.33686, a: 0.0, b: 0.0 };
548     /// let lch = lab::LCh::from_lab(lab);
549     /// assert_eq!(lab::LCh { l: 52.33686, c: 0.0, h: 0.0 }, lch);
550     /// ```
from_lab(lab: Lab) -> Self551     pub fn from_lab(lab: Lab) -> Self {
552         LCh {
553             l: lab.l,
554             c: lab.a.hypot(lab.b),
555             h: lab.b.atan2(lab.a),
556         }
557     }
558 
559     /// Returns the `LCh`'s color in RGB, in a 3-element array
560     ///
561     /// # Examples
562     ///
563     /// ```
564     /// let mut lch = lab::LCh { l: 52.33686, c: 78.15369, h: 0.25877 };
565     /// assert_eq!([240, 33, 95], lch.to_rgb());
566     ///
567     /// lch.h += std::f32::consts::TAU;
568     /// assert_eq!([240, 33, 95], lch.to_rgb());
569     /// ```
to_rgb(&self) -> [u8; 3]570     pub fn to_rgb(&self) -> [u8; 3] {
571         self.to_lab().to_rgb()
572     }
573 
574     /// Returns the `LCh`'s color in `Lab`
575     ///
576     /// Note that due to imprecision of floating point arithmetic, conversions
577     /// between Lab and LCh are not stable.  A chain of Lab→LCh→Lab or
578     /// LCh→Lab→LCh operations isn’t guaranteed to give back the source colour.
579     ///
580     /// # Examples
581     ///
582     /// ```
583     /// let lch = lab::LCh { l: 52.33686, c: 78.15369, h: 0.25877 };
584     /// let lab = lch.to_lab();
585     /// assert_eq!(lab::Lab { l: 52.33686, a: 75.5516, b: 19.998878 }, lab);
586     ///
587     /// let lch = lab::LCh { l: 52.33686, c: 0.0, h: 0.25877 };
588     /// let lab = lch.to_lab();
589     /// assert_eq!(lab::Lab { l: 52.33686, a: 0.0, b: 0.0 }, lab);
590     ///
591     /// let inp = lab::Lab { l: 29.52658, a: 58.595745, b: -36.281406 };
592     /// let lch = lab::LCh { l: 29.52658, c: 68.91881,  h: -0.5544043 };
593     /// let out = lab::Lab { l: 29.52658, a: 58.59575,  b: -36.281406 };
594     /// assert_eq!(lch, lab::LCh::from_lab(inp));
595     /// assert_eq!(out, lch.to_lab());
596     /// ```
to_lab(&self) -> Lab597     pub fn to_lab(&self) -> Lab {
598         Lab {
599             l: self.l,
600             a: self.c * self.h.cos(),
601             b: self.c * self.h.sin(),
602         }
603     }
604 }
605 
606 #[cfg(test)]
607 mod tests {
608     use super::{labs_to_rgbs, rgbs_to_labs, LCh, Lab};
609     use approx::assert_relative_eq;
610     use rand::Rng;
611 
612     const PINK: Lab = Lab {
613         l: 66.637695,
614         a: 52.250145,
615         b: 14.858591,
616     };
617 
618     #[rustfmt::skip]
619     static COLOURS: [([u8; 3], Lab, LCh); 17] = [
620         ([253, 120, 138],
621          PINK,
622          LCh { l: 66.637695, c: 54.321777, h: 0.2770602 }),
623 
624         ([127,   0,   0],
625          Lab { l: 25.299877, a: 47.77421, b: 37.752514 },
626          LCh { l: 25.299877, c: 60.890293, h: 0.66875386 }),
627         ([  0, 127,   0],
628          Lab { l: 45.87715, a: -51.405922, b: 49.61748 },
629          LCh { l: 45.87715, c: 71.445526, h: 2.373896 }),
630         ([  0,   0, 127],
631          Lab { l: 12.809523, a: 47.237186, b: -64.33636 },
632          LCh { l: 12.809523, c: 79.81553, h: -0.93746966 }),
633         ([  0, 127, 127],
634          Lab { l: 47.892532, a: -28.680845, b: -8.428156 },
635          LCh { l: 47.892532, c: 29.893557, h: -2.8557782 }),
636         ([127,   0, 127],
637          Lab { l: 29.525677, a: 58.597298, b: -36.28323 },
638          LCh { l: 29.525677, c: 68.92109, h: -0.554415 }),
639         ([255,   0,   0],
640          Lab { l: 53.238235, a: 80.09231, b: 67.202095 },
641          LCh { l: 53.238235, c: 104.55094, h: 0.6981073 }),
642         ([  0, 255,   0],
643          Lab { l: 87.73554, a: -86.18078, b: 83.18251 },
644          LCh { l: 87.73554, c: 119.776695, h: 2.373896 }),
645         ([  0,   0, 255],
646          Lab { l: 32.298466, a: 79.192, b: -107.858345 },
647          LCh { l: 32.298466, c: 133.8088, h: -0.93746966 }),
648         ([  0, 255, 255],
649          Lab { l: 91.11428, a: -48.08274, b: -14.12958 },
650          LCh { l: 91.11428, c: 50.115814, h: -2.8557787 }),
651         ([255,   0, 255],
652          Lab { l: 60.322693, a: 98.23698, b: -60.827957 },
653          LCh { l: 60.322693, c: 115.544556, h: -0.55441487 }),
654         ([255, 255,   0],
655          Lab { l: 97.139, a: -21.556675, b: 94.48001 },
656          LCh { l: 97.139, c: 96.90801, h: 1.7951176 }),
657 
658         ([  0,   0,   0],
659          Lab { l: 0.0, a: 0.0, b: 0.0 },
660          LCh { l: 0.0, c: 0.0, h: 0.0 }),
661         ([ 64,  64,  64],
662          Lab { l: 27.09341, a: 0.0, b: 0.0 },
663          LCh { l: 27.09341, c: 0.0, h: 0.0 }),
664         ([127, 127, 127],
665          Lab { l: 53.192772, a: 0.0, b: 0.0 },
666          LCh { l: 53.192772, c: 0.0, h: 0.0 }),
667         ([196, 196, 196],
668          Lab { l: 79.15698, a: 0.0, b: 0.0 },
669          LCh { l: 79.15698, c: 0.0, h: 0.0 }),
670         ([255, 255, 255],
671          Lab { l: 100.0, a: 0.0, b: 0.0 },
672          LCh { l: 100.0, c: 0.0, h: 0.0 }),
673     ];
674 
675     #[test]
test_lab_from_rgb()676     fn test_lab_from_rgb() {
677         let expected: Vec<_> = COLOURS.iter().map(|(_, lab, _)| *lab).collect();
678         let actual: Vec<_> = COLOURS
679             .iter()
680             .map(|(rgb, _, _)| Lab::from_rgb(rgb))
681             .collect();
682         assert_eq!(expected, actual);
683     }
684 
685     #[test]
test_lab_to_rgb()686     fn test_lab_to_rgb() {
687         let expected: Vec<_> = COLOURS.iter().map(|(rgb, _, _)| *rgb).collect();
688         let actual: Vec<_> = COLOURS.iter().map(|(_, lab, _)| lab.to_rgb()).collect();
689         assert_eq!(expected, actual);
690     }
691 
692     #[test]
test_lch_from_rgb()693     fn test_lch_from_rgb() {
694         let expected: Vec<_> = COLOURS.iter().map(|(_, _, lch)| *lch).collect();
695         let actual: Vec<_> = COLOURS
696             .iter()
697             .map(|(rgb, _, _)| LCh::from_rgb(rgb))
698             .collect();
699         assert_relative_eq!(expected.as_slice(), actual.as_slice());
700     }
701 
702     #[test]
test_lch_to_rgb()703     fn test_lch_to_rgb() {
704         let expected: Vec<_> = COLOURS.iter().map(|(rgb, _, _)| *rgb).collect();
705         let actual: Vec<_> = COLOURS.iter().map(|(_, _, lch)| lch.to_rgb()).collect();
706         assert_eq!(expected, actual);
707     }
708 
709     #[test]
test_lch_from_lab()710     fn test_lch_from_lab() {
711         let expected: Vec<_> = COLOURS.iter().map(|(_, _, lch)| *lch).collect();
712         let actual: Vec<_> = COLOURS
713             .iter()
714             .map(|(_, lab, _)| LCh::from_lab(*lab))
715             .collect();
716         assert_relative_eq!(expected.as_slice(), actual.as_slice());
717     }
718 
719     #[test]
test_lch_to_lab()720     fn test_lch_to_lab() {
721         let mut expected: Vec<_> = COLOURS.iter().map(|(_, lab, _)| *lab).collect();
722         let mut actual: Vec<_> = COLOURS.iter().map(|(_, _, lch)| lch.to_lab()).collect();
723 
724         // Floating point arithmetic is hard.  Due to accumulation of errors (or
725         // perhaps imprecision of trig functions) the Lab→LCh→Lab conversion
726         // produces slightly different colour than what the source.  Round a*
727         // and b* to four decimal places to work around this.
728         fn round(vec: &mut Vec<Lab>) {
729             for lab in vec.iter_mut() {
730                 lab.a = (lab.a * 100000.0).round() / 100000.0;
731                 lab.b = (lab.b * 100000.0).round() / 100000.0;
732             }
733         }
734         round(&mut expected);
735         round(&mut actual);
736 
737         assert_eq!(expected, actual);
738     }
739 
740     #[test]
test_distance()741     fn test_distance() {
742         let ugly_websafe_pink = Lab {
743             l: 64.2116,
744             a: 62.519463,
745             b: 2.8871894,
746         };
747         assert_eq!(254.65927, PINK.squared_distance(&ugly_websafe_pink));
748     }
749 
750     #[test]
test_send()751     fn test_send() {
752         fn assert_send<T: Send>() {}
753         assert_send::<Lab>();
754     }
755 
756     #[test]
test_sync()757     fn test_sync() {
758         fn assert_sync<T: Sync>() {}
759         assert_sync::<Lab>();
760     }
761 
762     #[test]
test_rgb_to_lab_to_rgb()763     fn test_rgb_to_lab_to_rgb() {
764         let rgbs: Vec<[u8; 3]> = {
765             let rand_seed = [1u8; 32];
766             let rng: rand::rngs::StdRng = rand::SeedableRng::from_seed(rand_seed);
767             rng.sample_iter(&rand::distributions::Standard)
768                 .take(2048)
769                 .collect()
770         };
771         let labs = rgbs_to_labs(&rgbs);
772         let rgbs2 = labs_to_rgbs(&labs);
773         assert_eq!(rgbs2, rgbs);
774     }
775 
776     #[test]
test_grey_error()777     fn test_grey_error() {
778         // Grey colours have a* and b* components equal to zero.  This test goes
779         // through all 8-bit greys and calculates squared error.  If it goes up,
780         // a change might have worsen the precision of the calculations.  If it
781         // goes down, calculations got better.
782         let mut error: f64 = 0.0;
783         let mut count: usize = 0;
784         for i in 0..=255_u32 {
785             let lab = Lab::from_rgb(&[i as u8, i as u8, i as u8]);
786             if lab.a != 0.0 || lab.b != 0.0 {
787                 error = (lab.a as f64).mul_add(lab.a as f64, error);
788                 error = (lab.b as f64).mul_add(lab.b as f64, error);
789                 count += 1;
790             }
791         }
792         assert_eq!((23, 10.627054791711998), (count, error * 1e9));
793     }
794 }
795