Skip to content

Commit c0f13d8

Browse files
committed
Add traits for delta E and the improvemed formulae from Huang et al
1 parent bae6ab5 commit c0f13d8

File tree

4 files changed

+283
-21
lines changed

4 files changed

+283
-21
lines changed

benchmarks/benches/cie.rs

+71-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
use codspeed_criterion_compat::{black_box, criterion_group, criterion_main, Criterion};
2-
use palette::convert::FromColorUnclamped;
2+
use palette::{
3+
color_difference::{Ciede2000, DeltaE},
4+
convert::FromColorUnclamped,
5+
};
36
use palette::{Lab, Lch, Xyz, Yxy};
47

58
#[path = "../../integration_tests/tests/convert/data_color_mine.rs"]
@@ -116,5 +119,71 @@ fn cie_conversion(c: &mut Criterion) {
116119
group.finish();
117120
}
118121

119-
criterion_group!(benches, cie_conversion);
122+
fn cie_delta_e(c: &mut Criterion) {
123+
let mut group = c.benchmark_group("Cie delta E");
124+
let colormine: Vec<ColorMine<f32>> = load_data();
125+
126+
let lab: Vec<Lab> = colormine
127+
.iter()
128+
.map(|x| Lab::from_color_unclamped(x.xyz))
129+
.collect();
130+
let lch: Vec<Lch> = colormine
131+
.iter()
132+
.map(|x| Lch::from_color_unclamped(x.xyz))
133+
.collect();
134+
135+
group.bench_with_input("Lab delta E", &lab, |b, lab| {
136+
b.iter(|| {
137+
for &lhs in lab {
138+
for &rhs in lab.iter().rev() {
139+
black_box(lhs.delta_e(rhs));
140+
}
141+
}
142+
})
143+
});
144+
145+
group.bench_with_input("Lch delta E", &lch, |b, lch| {
146+
b.iter(|| {
147+
for &lhs in lch {
148+
for &rhs in lch.iter().rev() {
149+
black_box(lhs.delta_e(rhs));
150+
}
151+
}
152+
})
153+
});
154+
155+
group.bench_with_input("Lch delta E via Lab", &lch, |b, lch| {
156+
b.iter(|| {
157+
for &lhs in lch {
158+
for &rhs in lch.iter().rev() {
159+
black_box(
160+
Lab::from_color_unclamped(lhs).delta_e(Lab::from_color_unclamped(rhs)),
161+
);
162+
}
163+
}
164+
})
165+
});
166+
167+
group.bench_with_input("Lab CIEDE2000", &lab, |b, lab| {
168+
b.iter(|| {
169+
for &lhs in lab {
170+
for &rhs in lab.iter().rev() {
171+
black_box(lhs.difference(rhs));
172+
}
173+
}
174+
})
175+
});
176+
177+
group.bench_with_input("Lch CIEDE2000", &lch, |b, lch| {
178+
b.iter(|| {
179+
for &lhs in lch {
180+
for &rhs in lch.iter().rev() {
181+
black_box(lhs.difference(rhs));
182+
}
183+
}
184+
})
185+
});
186+
}
187+
188+
criterion_group!(benches, cie_conversion, cie_delta_e);
120189
criterion_main!(benches);

palette/src/color_difference.rs

+76-14
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,13 @@
22
//!
33
//! ## Selecting an algorithm
44
//!
5-
//! Different distance/difference algorithms and formulae are good for different
5+
//! Different distance/difference algorithms and formulas are good for different
66
//! situations. Some are faster but less accurate and some may only be suitable
77
//! for certain color spaces. This table may help navigating the options a bit
88
//! by summarizing the difference between the traits in this module.
99
//!
10-
//! **Disclaimer:** _This is not an actual benchmark! It's always best to test and
11-
//! evaluate the differences in an actual application, when possible._
10+
//! **Disclaimer:** _This is not an actual benchmark! It's always best to test
11+
//! and evaluate the differences in an actual application, when possible._
1212
//!
1313
//! Property explanations:
1414
//! - **Complexity:** Low complexity options are generally faster than high
@@ -19,18 +19,22 @@
1919
//! | Trait | Complexity | Accuracy | Notes |
2020
//! |-------|------------|----------|-------|
2121
//! | [`Ciede2000`] | High | High for small differences, lower for large differences | The de-facto standard, but requires complex calculations to compensate for increased errors in certain areas of the CIE L\*a\*b\* (CIELAB) space.
22+
//! | [`ImprovedCiede2000`] | High | High for small differences, lower for large differences | A general improvement of [`Ciede2000`], using a formula by Huang et al.
23+
//! | [`DeltaE`] | Usually low | Medium to high | The formula differs between color spaces and may not always be the best. Other formulas, such as [`Ciede2000`], may be preferred for some spaces.
24+
//! | [`ImprovedDeltaE`] | Usually low | Medium to high | A general improvement of [`DeltaE`], using a formula by Huang et al.
2225
//! | [`EuclideanDistance`] | Low | Medium to high for perceptually uniform spaces, otherwise low | Can be good enough for perceptually uniform spaces or as a "quick and dirty" check.
2326
//! | [`HyAb`] | Low | High accuracy for medium to large differences. Less accurate than CIEDE2000 for small differences, but still performs well and is much less computationally expensive. | Similar to Euclidean distance, but separates lightness and chroma more. Limited to Cartesian spaces with a lightness axis and a chroma plane.
2427
//! | [`Wcag21RelativeContrast`] | Low | Low and only compares lightness | Meant for checking contrasts in computer graphics (such as between text and background colors), assuming sRGB. Mostly useful as a hint or for checking WCAG 2.1 compliance, considering the criticism it has received.
2528
26-
use core::ops::{Add, BitAnd, BitOr, Div};
29+
use core::ops::{Add, BitAnd, BitOr, Div, Mul};
2730

2831
use crate::{
2932
angle::RealAngle,
3033
bool_mask::{HasBoolMask, LazySelect},
3134
convert::IntoColorUnclamped,
3235
num::{
33-
Abs, Arithmetics, Exp, Hypot, MinMax, One, PartialCmp, Powi, Real, Sqrt, Trigonometry, Zero,
36+
Abs, Arithmetics, Exp, Hypot, MinMax, One, PartialCmp, Powf, Powi, Real, Sqrt,
37+
Trigonometry, Zero,
3438
},
3539
white_point::D65,
3640
Lab, Lch, LinLuma,
@@ -50,25 +54,53 @@ pub trait ColorDifference {
5054
fn get_color_difference(self, other: Self) -> Self::Scalar;
5155
}
5256

53-
/// Calculate the CIEDE2000 color difference between two colors.
57+
/// Calculate the CIEDE2000 Δ*E\** (Delta E) color difference between two
58+
/// colors.
5459
///
55-
/// CIEDE2000 is a formula by the CIE that calculates a distance metric, ΔE\*
60+
/// CIEDE2000 is a formula by the CIE that calculates a distance metric, Δ*E\**
5661
/// (also known as Delta E), as an estimate of perceived color distance or
57-
/// difference.
62+
/// difference. CIEDE2000 is an improvement over Δ*E* (see [`DeltaE`]) for CIE
63+
/// L\*a\*b\* and CIE L\*C\*h° (see [`Lab`] and [`Lch`]).
5864
///
59-
/// There is a "just noticeable difference" between two colors when the ΔE\*
60-
/// (Delta E) is roughly greater than 1. Thus, the color difference is more
61-
/// suited for calculating small distances between colors as opposed to large
62-
/// differences.
65+
/// There is a "just noticeable difference" between two colors when the Δ*E\**
66+
/// is roughly greater than 1. Thus, the color difference is more suited for
67+
/// calculating small distances between colors as opposed to large differences.
6368
#[doc(alias = "ColorDifference")]
6469
pub trait Ciede2000 {
65-
/// The type for the ΔE\* (Delta E).
70+
/// The type for the Δ*E\** (Delta E).
6671
type Scalar;
6772

68-
/// Calculate the CIEDE2000 ΔE\* (Delta E) color difference between `self` and `other`.
73+
/// Calculate the CIEDE2000 Δ*E\** (Delta E) color difference between `self` and `other`.
74+
#[must_use]
6975
fn difference(self, other: Self) -> Self::Scalar;
7076
}
7177

78+
/// Calculate the CIEDE2000 Δ*E'* (improved IEDE2000 Δ*E\**) color difference.
79+
///
80+
/// The "improved CIEDE2000" uses the output of [`Ciede2000`] and enhances it
81+
/// according to *Power functions improving the performance of color-difference
82+
/// formulas* by Huang et al.
83+
pub trait ImprovedCiede2000: Ciede2000 {
84+
/// Calculate the CIEDE2000 Δ*E'* (improved IEDE2000 Δ*E\**) color
85+
/// difference between `self` and `other`.
86+
#[must_use]
87+
fn improved_difference(self, other: Self) -> Self::Scalar;
88+
}
89+
90+
impl<C> ImprovedCiede2000 for C
91+
where
92+
C: Ciede2000,
93+
C::Scalar: Real + Mul<C::Scalar, Output = C::Scalar> + Powf,
94+
{
95+
#[inline]
96+
fn improved_difference(self, other: Self) -> Self::Scalar {
97+
// Coefficients from "Power functions improving the performance of
98+
// color-difference formulas" by Huang et al.
99+
// https://opg.optica.org/oe/fulltext.cfm?uri=oe-23-1-597&id=307643
100+
C::Scalar::from_f64(1.43) * self.difference(other).powf(C::Scalar::from_f64(0.7))
101+
}
102+
}
103+
72104
/// Container of components necessary to calculate CIEDE color difference
73105
pub(crate) struct LabColorDiff<T> {
74106
/// Lab color lightness
@@ -431,9 +463,39 @@ pub trait HyAb {
431463
///
432464
/// This returns the sum of the absolute lightness difference and the
433465
/// distance on the chroma plane.
466+
#[must_use]
434467
fn hybrid_distance(self, other: Self) -> Self::Scalar;
435468
}
436469

470+
/// Calculate the Δ*E* color difference between two colors.
471+
///
472+
/// This represents the original Δ*E* formula for a color space. It's often a
473+
/// Euclidean distance for perceptually uniform color spaces and may not always
474+
/// be the best option. See the [`color_difference`](self) module for more
475+
/// details and options.
476+
pub trait DeltaE {
477+
/// The type for the distance value.
478+
type Scalar;
479+
480+
/// Calculate the Δ*E* color difference metric for `self` and `other`,
481+
/// according to the color space's specification.
482+
#[must_use]
483+
fn delta_e(self, other: Self) -> Self::Scalar;
484+
}
485+
486+
/// Calculate the Δ*E'* (improved Δ*E*) color difference between two colors.
487+
///
488+
/// The Δ*E'* uses the output of [`DeltaE`] and enhances it according to *Power
489+
/// functions improving the performance of color-difference formulas* by Huang
490+
/// et al. Only spaces with specified coefficients implement this trait.
491+
pub trait ImprovedDeltaE: DeltaE {
492+
/// Calculate the Δ*E'* (improved Δ*E*) color difference metric for `self`
493+
/// and `other`, according to the color space's specification and later
494+
/// improvements by Huang et al.
495+
#[must_use]
496+
fn improved_delta_e(self, other: Self) -> Self::Scalar;
497+
}
498+
437499
#[cfg(test)]
438500
mod test {
439501
use core::str::FromStr;

palette/src/lab.rs

+32-2
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,14 @@ use crate::{
2121
blend::{PreAlpha, Premultiply},
2222
bool_mask::{HasBoolMask, LazySelect},
2323
clamp, clamp_assign,
24-
color_difference::{get_ciede2000_difference, Ciede2000, LabColorDiff},
24+
color_difference::{
25+
get_ciede2000_difference, Ciede2000, DeltaE, EuclideanDistance, ImprovedDeltaE,
26+
LabColorDiff,
27+
},
2528
convert::FromColorUnclamped,
2629
num::{
2730
self, Abs, Arithmetics, Cbrt, Exp, FromScalarArray, Hypot, IntoScalarArray, IsValidDivisor,
28-
MinMax, One, PartialCmp, Powi, Real, Sqrt, Trigonometry, Zero,
31+
MinMax, One, PartialCmp, Powf, Powi, Real, Sqrt, Trigonometry, Zero,
2932
},
3033
stimulus::Stimulus,
3134
white_point::{WhitePoint, D65},
@@ -310,6 +313,33 @@ where
310313
}
311314
}
312315

316+
impl<Wp, T> DeltaE for Lab<Wp, T>
317+
where
318+
Self: EuclideanDistance<Scalar = T>,
319+
T: Sqrt,
320+
{
321+
type Scalar = T;
322+
323+
#[inline]
324+
fn delta_e(self, other: Self) -> Self::Scalar {
325+
self.distance(other)
326+
}
327+
}
328+
329+
impl<Wp, T> ImprovedDeltaE for Lab<Wp, T>
330+
where
331+
Self: DeltaE<Scalar = T>,
332+
T: Real + Mul<T, Output = T> + Powf + Sqrt,
333+
{
334+
#[inline]
335+
fn improved_delta_e(self, other: Self) -> Self::Scalar {
336+
// Coefficients from "Power functions improving the performance of
337+
// color-difference formulas" by Huang et al.
338+
// https://opg.optica.org/oe/fulltext.cfm?uri=oe-23-1-597&id=307643
339+
T::from_f64(1.26) * self.delta_e(other).powf(T::from_f64(0.55))
340+
}
341+
}
342+
313343
#[allow(deprecated)]
314344
impl<Wp, T> crate::ColorDifference for Lab<Wp, T>
315345
where

0 commit comments

Comments
 (0)