1 // Modified version of https://github.com/elliotekj/DeltaE
2 
3 use lab::Lab;
4 use std::f32;
5 use std::f32::consts::PI;
6 
7 pub struct DE2000;
8 
9 pub struct KSubArgs {
10     pub l: f32,
11     pub c: f32,
12     pub h: f32,
13 }
14 
15 #[allow(clippy::needless_doctest_main)]
16 impl DE2000 {
17     // Returns the difference between two `Lab` colors.
18     //
19     // ### Example
20     //
21     // ```ignore
22     // extern crate delta_e;
23     // extern crate lab;
24     //
25     // use delta_e::DE2000;
26     // use lab::Lab;
27     //
28     // fn main() {
29     //     let color_1 = Lab {
30     //         l: 38.972,
31     //         a: 58.991,
32     //         b: 37.138,
33     //     };
34     //
35     //     let color_2 = Lab {
36     //         l: 54.528,
37     //         a: 42.416,
38     //         b: 54.497,
39     //     };
40     //
41     //     let delta_e = DE2000::new(color_1, color_2);
42     //     println!("The color difference is: {}", delta_e);
43     // }
44     // ```
45 
46     #[allow(clippy::new_ret_no_self)]
new(color_1: Lab, color_2: Lab, ksub: KSubArgs) -> f3247     pub fn new(color_1: Lab, color_2: Lab, ksub: KSubArgs) -> f32 {
48         let delta_l_prime = color_2.l - color_1.l;
49 
50         let l_bar = (color_1.l + color_2.l) / 2.0;
51 
52         let c1 = (color_1.a.powi(2) + color_1.b.powi(2)).sqrt();
53         let c2 = (color_2.a.powi(2) + color_2.b.powi(2)).sqrt();
54 
55         let (a_prime_1, a_prime_2) = {
56             let c_bar = (c1 + c2) / 2.0;
57 
58             let tmp = 1.0 - (c_bar.powi(7) / (c_bar.powi(7) + 25f32.powi(7))).sqrt();
59             (
60                 color_1.a + (color_1.a / 2.0) * tmp,
61                 color_2.a + (color_2.a / 2.0) * tmp,
62             )
63         };
64 
65         let c_prime_1 = (a_prime_1.powi(2) + color_1.b.powi(2)).sqrt();
66         let c_prime_2 = (a_prime_2.powi(2) + color_2.b.powi(2)).sqrt();
67 
68         let c_bar_prime = (c_prime_1 + c_prime_2) / 2.0;
69 
70         let delta_c_prime = c_prime_2 - c_prime_1;
71 
72         let s_sub_l =
73             1.0 + ((0.015 * (l_bar - 50.0).powi(2)) / (20.0 + (l_bar - 50.0).powi(2)).sqrt());
74 
75         let s_sub_c = 1.0 + 0.045 * c_bar_prime;
76 
77         let h_prime_1 = get_h_prime_fn(color_1.b, a_prime_1);
78         let h_prime_2 = get_h_prime_fn(color_2.b, a_prime_2);
79 
80         let delta_h_prime = get_delta_h_prime(c1, c2, h_prime_1, h_prime_2);
81 
82         let delta_upcase_h_prime =
83             2.0 * (c_prime_1 * c_prime_2).sqrt() * ((delta_h_prime) / 2.0).sin();
84 
85         let upcase_h_bar_prime = get_upcase_h_bar_prime(h_prime_1, h_prime_2);
86 
87         let upcase_t = get_upcase_t(upcase_h_bar_prime);
88 
89         let s_sub_upcase_h = 1.0 + 0.015 * c_bar_prime * upcase_t;
90 
91         let r_sub_t = get_r_sub_t(c_bar_prime, upcase_h_bar_prime);
92 
93         let lightness: f32 = delta_l_prime / (ksub.l * s_sub_l);
94 
95         let chroma: f32 = delta_c_prime / (ksub.c * s_sub_c);
96 
97         let hue: f32 = delta_upcase_h_prime / (ksub.h * s_sub_upcase_h);
98 
99         (lightness.powi(2) + chroma.powi(2) + hue.powi(2) + r_sub_t * chroma * hue).sqrt()
100     }
101 }
102 
get_h_prime_fn(x: f32, y: f32) -> f32103 fn get_h_prime_fn(x: f32, y: f32) -> f32 {
104     let mut hue_angle;
105 
106     if x == 0.0 && y == 0.0 {
107         return 0.0;
108     }
109 
110     hue_angle = x.atan2(y);
111 
112     if hue_angle < 0.0 {
113         hue_angle += 2. * PI;
114     }
115 
116     hue_angle
117 }
118 
get_delta_h_prime(c1: f32, c2: f32, h_prime_1: f32, h_prime_2: f32) -> f32119 fn get_delta_h_prime(c1: f32, c2: f32, h_prime_1: f32, h_prime_2: f32) -> f32 {
120     if 0.0 == c1 || 0.0 == c2 {
121         return 0.0;
122     }
123 
124     if (h_prime_1 - h_prime_2).abs() <= PI {
125         return h_prime_2 - h_prime_1;
126     }
127 
128     if h_prime_2 <= h_prime_1 {
129         h_prime_2 - h_prime_1 + 2. * PI
130     } else {
131         h_prime_2 - h_prime_1 - 2. * PI
132     }
133 }
134 
get_upcase_h_bar_prime(h_prime_1: f32, h_prime_2: f32) -> f32135 fn get_upcase_h_bar_prime(h_prime_1: f32, h_prime_2: f32) -> f32 {
136     if (h_prime_1 - h_prime_2).abs() > PI {
137         return (h_prime_1 + h_prime_2 + 2.0 * PI) / 2.0;
138     }
139 
140     (h_prime_1 + h_prime_2) / 2.0
141 }
142 
get_upcase_t(upcase_h_bar_prime: f32) -> f32143 fn get_upcase_t(upcase_h_bar_prime: f32) -> f32 {
144     1.0 - 0.17 * (upcase_h_bar_prime - PI / 6.0).cos()
145         + 0.24 * (2.0 * upcase_h_bar_prime).cos()
146         + 0.32 * (3.0 * upcase_h_bar_prime + PI / 30.0).cos()
147         - 0.20 * (4.0 * upcase_h_bar_prime - 7.0 * PI / 20.0).cos()
148 }
149 
get_r_sub_t(c_bar_prime: f32, upcase_h_bar_prime: f32) -> f32150 fn get_r_sub_t(c_bar_prime: f32, upcase_h_bar_prime: f32) -> f32 {
151     let degrees = (radians_to_degrees(upcase_h_bar_prime) - 275.0) * (1.0 / 25.0);
152     -2.0 * (c_bar_prime.powi(7) / (c_bar_prime.powi(7) + 25f32.powi(7))).sqrt()
153         * (degrees_to_radians(60.0 * (-(degrees.powi(2))).exp())).sin()
154 }
155 
radians_to_degrees(radians: f32) -> f32156 fn radians_to_degrees(radians: f32) -> f32 {
157     radians * (180.0 / f32::consts::PI)
158 }
159 
degrees_to_radians(degrees: f32) -> f32160 fn degrees_to_radians(degrees: f32) -> f32 {
161     degrees * (f32::consts::PI / 180.0)
162 }
163