Skip to content

Commit a6063fe

Browse files
committed
Move color_difference to its own module
Export ColorDifference trait in lib.rs Clean up and amend documentation
1 parent f1723e6 commit a6063fe

File tree

5 files changed

+155
-137
lines changed

5 files changed

+155
-137
lines changed

palette/src/color_difference.rs

+129
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
use component::FloatComponent;
2+
use from_f64;
3+
4+
///A trait for calculating the color difference between two colors.
5+
pub trait ColorDifference {
6+
///The type of the calculated color difference
7+
type Scalar: FloatComponent;
8+
9+
///Return the difference or distance between two colors
10+
fn get_color_difference(&self, other: &Self) -> Self::Scalar;
11+
}
12+
13+
/// Container of components necessary to calculate CIEDE color difference
14+
pub struct LabColorDiff<T: FloatComponent> {
15+
/// Lab color lightness
16+
pub l: T,
17+
/// Lab color a* value
18+
pub a: T,
19+
/// Lab color b* value
20+
pub b: T,
21+
/// Lab color chroma value
22+
pub chroma: T,
23+
}
24+
25+
/// Calculate the CIEDE2000 color difference for two colors in Lab color space.
26+
/// There is a "just noticeable difference" between two colors when the delta E
27+
/// is roughly greater than 1. Thus, the color difference is more suited for
28+
/// calculating small distances between colors as opposed to large differences.
29+
#[rustfmt::skip]
30+
pub fn get_ciede_difference<T: FloatComponent>(this: &LabColorDiff<T>, other: &LabColorDiff<T>) -> T {
31+
let c_bar = (this.chroma + other.chroma) / from_f64(2.0);
32+
let c_bar_pow_seven = c_bar * c_bar * c_bar * c_bar * c_bar * c_bar * c_bar;
33+
let twenty_five_pow_seven = from_f64(6103515625.0);
34+
let pi_over_180 = from_f64::<T>(core::f64::consts::PI / 180.0);
35+
36+
let g = from_f64::<T>(0.5)
37+
* (from_f64::<T>(1.0)
38+
- (c_bar_pow_seven / (c_bar_pow_seven + twenty_five_pow_seven)).sqrt());
39+
let a_one_prime = this.a * (from_f64::<T>(1.0) + g);
40+
let a_two_prime = other.a * (from_f64::<T>(1.0) + g);
41+
let c_one_prime = (a_one_prime * a_one_prime + this.b * this.b).sqrt();
42+
let c_two_prime = (a_two_prime * a_two_prime + other.b * other.b).sqrt();
43+
44+
let calc_h_prime = |b: T, a_prime: T| -> T {
45+
if b == T::zero() && a_prime == T::zero() {
46+
from_f64(0.0)
47+
} else {
48+
let result = b.atan2(a_prime).to_degrees();
49+
if result < T::zero() {
50+
result + from_f64(360.0)
51+
} else {
52+
result
53+
}
54+
}
55+
};
56+
let h_one_prime = calc_h_prime(this.b, a_one_prime);
57+
let h_two_prime = calc_h_prime(other.b, a_two_prime);
58+
59+
let h_prime_difference = (h_one_prime - h_two_prime).abs();
60+
61+
let delta_h_prime: T = if c_one_prime == T::zero() || c_two_prime == T::zero() {
62+
from_f64(0.0)
63+
} else {
64+
if h_prime_difference <= from_f64(180.0) {
65+
h_two_prime - h_one_prime
66+
} else {
67+
if h_two_prime <= h_one_prime {
68+
h_two_prime - h_one_prime + from_f64(360.0)
69+
} else {
70+
h_two_prime - h_one_prime - from_f64(360.0)
71+
}
72+
}
73+
};
74+
75+
let delta_big_h_prime = from_f64::<T>(2.0)
76+
* (c_one_prime * c_two_prime).sqrt()
77+
* (delta_h_prime / from_f64(2.0) * pi_over_180).sin();
78+
let h_bar_prime = if c_one_prime == T::zero() || c_two_prime == T::zero() {
79+
h_one_prime + h_two_prime
80+
} else {
81+
if h_prime_difference > from_f64(180.0) {
82+
(h_one_prime + h_two_prime + from_f64(360.0)) / from_f64(2.0)
83+
} else {
84+
(h_one_prime + h_two_prime) / from_f64(2.0)
85+
}
86+
};
87+
88+
let l_bar = (this.l + other.l) / from_f64(2.0);
89+
let c_bar_prime = (c_one_prime + c_two_prime) / from_f64(2.0);
90+
91+
let t: T = from_f64::<T>(1.0)
92+
- from_f64::<T>(0.17) * ((h_bar_prime - from_f64(30.0)) * pi_over_180).cos()
93+
+ from_f64::<T>(0.24) * ((h_bar_prime * from_f64(2.0)) * pi_over_180).cos()
94+
+ from_f64::<T>(0.32) * ((h_bar_prime * from_f64(3.0) + from_f64(6.0)) * pi_over_180).cos()
95+
- from_f64::<T>(0.20) * ((h_bar_prime * from_f64(4.0) - from_f64(63.0)) * pi_over_180).cos();
96+
let s_l = from_f64::<T>(1.0)
97+
+ ((from_f64::<T>(0.015) * (l_bar - from_f64(50.0)) * (l_bar - from_f64(50.0)))
98+
/ ((l_bar - from_f64(50.0)) * (l_bar - from_f64(50.0)) + from_f64(20.0)).sqrt());
99+
let s_c = from_f64::<T>(1.0) + from_f64::<T>(0.045) * c_bar_prime;
100+
let s_h = from_f64::<T>(1.0) + from_f64::<T>(0.015) * c_bar_prime * t;
101+
102+
let delta_theta = from_f64::<T>(30.0)
103+
* (-(((h_bar_prime - from_f64(275.0)) / from_f64(25.0))
104+
* ((h_bar_prime - from_f64(275.0)) / from_f64(25.0))))
105+
.exp();
106+
let c_bar_prime_pow_seven = c_bar_prime
107+
* c_bar_prime
108+
* c_bar_prime
109+
* c_bar_prime
110+
* c_bar_prime
111+
* c_bar_prime
112+
* c_bar_prime;
113+
let r_c: T = from_f64::<T>(2.0)
114+
* (c_bar_prime_pow_seven / (c_bar_prime_pow_seven + twenty_five_pow_seven)).sqrt();
115+
let r_t = -r_c * (from_f64::<T>(2.0) * delta_theta * pi_over_180).sin();
116+
117+
let one = from_f64::<T>(1.0);
118+
let k_l = one;
119+
let k_c = one;
120+
let k_h = one;
121+
let delta_l_prime = other.l - this.l;
122+
let delta_c_prime = c_two_prime - c_one_prime;
123+
124+
((delta_l_prime / (k_l * s_l)) * (delta_l_prime / (k_l * s_l))
125+
+ (delta_c_prime / (k_c * s_c)) * (delta_c_prime / (k_c * s_c))
126+
+ (delta_big_h_prime / (k_h * s_h)) * (delta_big_h_prime / (k_h * s_h))
127+
+ (r_t * delta_c_prime * delta_big_h_prime) / (k_c * s_c * k_h * s_h))
128+
.sqrt()
129+
}

palette/src/lab.rs

+8-116
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@ use encoding::pixel::RawPixel;
55
use white_point::{WhitePoint, D65};
66
use {clamp, from_f64};
77
use {Alpha, LabHue, Lch, Xyz};
8-
use {
9-
ColorDifference, Component, ComponentWise, FloatComponent, GetHue, Limited, Mix, Pixel, Shade,
10-
};
8+
use {Component, ComponentWise, FloatComponent, GetHue, Limited, Mix, Pixel, Shade};
9+
10+
use color_difference::ColorDifference;
11+
use color_difference::{get_ciede_difference, LabColorDiff};
1112

1213
/// CIE L\*a\*b\* (CIELAB) with an alpha component. See the [`Laba`
1314
/// implementation in `Alpha`](struct.Alpha.html#Laba).
@@ -305,13 +306,6 @@ where
305306
}
306307
}
307308

308-
/// CIEDE2000 distance metric for color difference.
309-
pub struct ColorDiff<T: FloatComponent> {
310-
pub l: T,
311-
pub a: T,
312-
pub b: T,
313-
pub chroma: T,
314-
}
315309
impl<Wp, T> ColorDifference for Lab<Wp, T>
316310
where
317311
T: FloatComponent,
@@ -322,126 +316,24 @@ where
322316
fn get_color_difference(&self, other: &Lab<Wp, T>) -> Self::Scalar {
323317
// Color difference calculation requires Lab and chroma components. This
324318
// function handles the conversion into those components which are then
325-
// passed to `calculate_color_difference()`
326-
let self_params = ColorDiff {
319+
// passed to `get_ciede_difference()` where calculation is completed.
320+
let self_params = LabColorDiff {
327321
l: self.l,
328322
a: self.a,
329323
b: self.b,
330324
chroma: (self.a * self.a + self.b * self.b).sqrt(),
331325
};
332-
let other_params = ColorDiff {
326+
let other_params = LabColorDiff {
333327
l: other.l,
334328
a: other.a,
335329
b: other.b,
336330
chroma: (other.a * other.a + other.b * other.b).sqrt(),
337331
};
338332

339-
calculate_color_difference(&self_params, &other_params)
333+
get_ciede_difference(&self_params, &other_params)
340334
}
341335
}
342336

343-
#[rustfmt::skip]
344-
pub fn calculate_color_difference<T: FloatComponent>(this: &ColorDiff<T>, other: &ColorDiff<T>) -> T {
345-
let c_bar = (this.chroma + other.chroma) / from_f64(2.0);
346-
let c_bar_pow_seven = c_bar * c_bar * c_bar * c_bar * c_bar * c_bar * c_bar;
347-
let twenty_five_pow_seven = from_f64(6103515625.0);
348-
let pi_over_180 = from_f64::<T>(core::f64::consts::PI / 180.0);
349-
350-
let g = from_f64::<T>(0.5)
351-
* (from_f64::<T>(1.0)
352-
- (c_bar_pow_seven / (c_bar_pow_seven + twenty_five_pow_seven)).sqrt());
353-
let a_one_prime = this.a * (from_f64::<T>(1.0) + g);
354-
let a_two_prime = other.a * (from_f64::<T>(1.0) + g);
355-
let c_one_prime = (a_one_prime * a_one_prime + this.b * this.b).sqrt();
356-
let c_two_prime = (a_two_prime * a_two_prime + other.b * other.b).sqrt();
357-
358-
let calc_h_prime = |b: T, a_prime: T| -> T {
359-
if b == T::zero() && a_prime == T::zero() {
360-
from_f64(0.0)
361-
} else {
362-
let result = b.atan2(a_prime).to_degrees();
363-
if result < T::zero() {
364-
result + from_f64(360.0)
365-
} else {
366-
result
367-
}
368-
}
369-
};
370-
let h_one_prime = calc_h_prime(this.b, a_one_prime);
371-
let h_two_prime = calc_h_prime(other.b, a_two_prime);
372-
373-
let h_prime_difference = (h_one_prime - h_two_prime).abs();
374-
375-
let delta_h_prime: T = if c_one_prime == T::zero() || c_two_prime == T::zero() {
376-
from_f64(0.0)
377-
} else {
378-
if h_prime_difference <= from_f64(180.0) {
379-
h_two_prime - h_one_prime
380-
} else {
381-
if h_two_prime <= h_one_prime {
382-
h_two_prime - h_one_prime + from_f64(360.0)
383-
} else {
384-
h_two_prime - h_one_prime - from_f64(360.0)
385-
}
386-
}
387-
};
388-
389-
let delta_big_h_prime = from_f64::<T>(2.0)
390-
* (c_one_prime * c_two_prime).sqrt()
391-
* (delta_h_prime / from_f64(2.0) * pi_over_180).sin();
392-
let h_bar_prime = if c_one_prime == T::zero() || c_two_prime == T::zero() {
393-
h_one_prime + h_two_prime
394-
} else {
395-
if h_prime_difference > from_f64(180.0) {
396-
(h_one_prime + h_two_prime + from_f64(360.0)) / from_f64(2.0)
397-
} else {
398-
(h_one_prime + h_two_prime) / from_f64(2.0)
399-
}
400-
};
401-
402-
let l_bar = (this.l + other.l) / from_f64(2.0);
403-
let c_bar_prime = (c_one_prime + c_two_prime) / from_f64(2.0);
404-
405-
let t: T = from_f64::<T>(1.0)
406-
- from_f64::<T>(0.17) * ((h_bar_prime - from_f64(30.0)) * pi_over_180).cos()
407-
+ from_f64::<T>(0.24) * ((h_bar_prime * from_f64(2.0)) * pi_over_180).cos()
408-
+ from_f64::<T>(0.32) * ((h_bar_prime * from_f64(3.0) + from_f64(6.0)) * pi_over_180).cos()
409-
- from_f64::<T>(0.20) * ((h_bar_prime * from_f64(4.0) - from_f64(63.0)) * pi_over_180).cos();
410-
let s_l = from_f64::<T>(1.0)
411-
+ ((from_f64::<T>(0.015) * (l_bar - from_f64(50.0)) * (l_bar - from_f64(50.0)))
412-
/ ((l_bar - from_f64(50.0)) * (l_bar - from_f64(50.0)) + from_f64(20.0)).sqrt());
413-
let s_c = from_f64::<T>(1.0) + from_f64::<T>(0.045) * c_bar_prime;
414-
let s_h = from_f64::<T>(1.0) + from_f64::<T>(0.015) * c_bar_prime * t;
415-
416-
let delta_theta = from_f64::<T>(30.0)
417-
* (-(((h_bar_prime - from_f64(275.0)) / from_f64(25.0))
418-
* ((h_bar_prime - from_f64(275.0)) / from_f64(25.0))))
419-
.exp();
420-
let c_bar_prime_pow_seven = c_bar_prime
421-
* c_bar_prime
422-
* c_bar_prime
423-
* c_bar_prime
424-
* c_bar_prime
425-
* c_bar_prime
426-
* c_bar_prime;
427-
let r_c: T = from_f64::<T>(2.0)
428-
* (c_bar_prime_pow_seven / (c_bar_prime_pow_seven + twenty_five_pow_seven)).sqrt();
429-
let r_t = -r_c * (from_f64::<T>(2.0) * delta_theta * pi_over_180).sin();
430-
431-
let one = from_f64::<T>(1.0);
432-
let k_l = one;
433-
let k_c = one;
434-
let k_h = one;
435-
let delta_l_prime = other.l - this.l;
436-
let delta_c_prime = c_two_prime - c_one_prime;
437-
438-
((delta_l_prime / (k_l * s_l)) * (delta_l_prime / (k_l * s_l))
439-
+ (delta_c_prime / (k_c * s_c)) * (delta_c_prime / (k_c * s_c))
440-
+ (delta_big_h_prime / (k_h * s_h)) * (delta_big_h_prime / (k_h * s_h))
441-
+ (r_t * delta_c_prime * delta_big_h_prime) / (k_c * s_c * k_h * s_h))
442-
.sqrt()
443-
}
444-
445337
impl<Wp, T> ComponentWise for Lab<Wp, T>
446338
where
447339
T: FloatComponent,

palette/src/lch.rs

+9-9
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
11
use core::marker::PhantomData;
22
use core::ops::{Add, AddAssign, Sub, SubAssign};
33

4+
use color_difference::ColorDifference;
5+
use color_difference::{get_ciede_difference, LabColorDiff};
46
use encoding::pixel::RawPixel;
5-
use lab::calculate_color_difference;
6-
use lab::ColorDiff;
77
use white_point::{WhitePoint, D65};
88
use {clamp, from_f64};
99
use {Alpha, Hue, Lab, LabHue, Xyz};
1010
use {
11-
ColorDifference, Component, FloatComponent, FromColor, GetHue, IntoColor, Limited, Mix, Pixel,
12-
Saturate, Shade,
11+
Component, FloatComponent, FromColor, GetHue, IntoColor, Limited, Mix, Pixel, Saturate, Shade,
1312
};
1413

1514
/// CIE L\*C\*h° with an alpha component. See the [`Lcha` implementation in
@@ -315,6 +314,7 @@ where
315314
type Scalar = T;
316315

317316
fn get_color_difference(&self, other: &Lch<Wp, T>) -> Self::Scalar {
317+
// Prepare a* and b* from Lch components to calculate color difference
318318
let self_a = clamp(
319319
self.chroma.max(T::zero()) * self.hue.to_radians().cos(),
320320
from_f64(-128.0),
@@ -335,20 +335,20 @@ where
335335
from_f64(-128.0),
336336
from_f64(127.0),
337337
);
338-
let self_params = ColorDiff {
338+
let self_params = LabColorDiff {
339339
l: self.l,
340340
a: self_a,
341341
b: self_b,
342-
chroma: (self_a * self_a + self_b * self_b).sqrt(),
342+
chroma: self.chroma,
343343
};
344-
let other_params = ColorDiff {
344+
let other_params = LabColorDiff {
345345
l: other.l,
346346
a: other_a,
347347
b: other_b,
348-
chroma: (other_a * other_a + other_b * other_b).sqrt(),
348+
chroma: other.chroma,
349349
};
350350

351-
calculate_color_difference(&self_params, &other_params)
351+
get_ciede_difference(&self_params, &other_params)
352352
}
353353
}
354354

palette/src/lib.rs

+2-9
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,7 @@ pub use rgb::{GammaSrgb, GammaSrgba, LinSrgb, LinSrgba, Srgb, Srgba};
183183
pub use xyz::{Xyz, Xyza};
184184
pub use yxy::{Yxy, Yxya};
185185

186+
pub use color_difference::ColorDifference;
186187
pub use component::*;
187188
pub use convert::{ConvertFrom, ConvertInto, FromColor, IntoColor, OutOfBounds};
188189
pub use encoding::pixel::Pixel;
@@ -364,6 +365,7 @@ mod yxy;
364365
mod hues;
365366

366367
pub mod chromatic_adaptation;
368+
mod color_difference;
367369
mod component;
368370
mod convert;
369371
pub mod encoding;
@@ -498,15 +500,6 @@ pub trait Hue: GetHue {
498500
fn shift_hue<H: Into<Self::Hue>>(&self, amount: H) -> Self;
499501
}
500502

501-
///A trait for calculating the color difference between two colors.
502-
pub trait ColorDifference {
503-
///The type of the calculated color difference
504-
type Scalar: Float;
505-
506-
///Return the difference or distance between two colors
507-
fn get_color_difference(&self, other: &Self) -> Self::Scalar;
508-
}
509-
510503
/// A trait for colors where the saturation (or chroma) can be manipulated
511504
/// without conversion.
512505
///

palette/tests/convert/data_ciede_2000.rs

+7-3
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,18 @@ Data from http://www2.ece.rochester.edu/~gsharma/ciede2000/
33
44
Tests Lab color differences with expected delta E*
55
6-
Note: The test uses `f64` because `f32` failed Travis CI builds on Linux.
7-
MacOS and Windows passed the tests.
6+
Note: Test uses `f64` because `f32` failed Travis CI builds on Linux for Lch on
7+
case 13 or 14 which is noted in the paper as testing accuracy of hue
8+
angle and atan calcuation (calculated: 4.7460666, expected: 4.8045).
9+
MacOS and Windows passed the tests so be wary when using f32 on Linux.
810
*/
911

1012
extern crate approx;
13+
1114
use csv;
1215
use palette::white_point::D65;
13-
use palette::{ColorDifference, Lab, Lch};
16+
use palette::ColorDifference;
17+
use palette::{Lab, Lch};
1418

1519
#[derive(Deserialize, PartialEq)]
1620
struct Cie2000Raw {

0 commit comments

Comments
 (0)