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