# vim: set et sw=4 sts=4 fileencoding=utf-8:
#
# The colorzero color library
#
# Copyright (c) 2018 Dave Jones <dave@waveform.org.uk>
#
# SPDX-License-Identifier: BSD-3-Clause
"Defines the various algorithms for :meth:`Color.difference`."
from math import sqrt, atan2, degrees, radians, sin, cos, exp
# Lots of the delta-e functions use single character parameter names and
# variables internally; this is is normal and in keeping with most of the
# referenced sources
# pylint: disable=invalid-name
[docs]def euclid(color1, color2):
"""
Calculates color difference as a simple `Euclidean distance`_ by treating
the three components as spatial dimensions.
.. note::
This function will return considerably different values to the other
difference functions. In particular, the maximum "difference" will be
:math:`\\sqrt{3}` which is much smaller than the output of the CIE
functions.
.. _Euclidean distance: https://en.wikipedia.org/wiki/Euclidean_distance
"""
return sqrt(sum((e1 - e2) ** 2 for e1, e2 in zip(color1, color2)))
[docs]def cie1976(color1, color2):
"""
Calculates color difference according to the `CIE 1976`_ formula.
Effectively this is the Euclidean formula, but with CIE L*a*b* components
instead of RGB.
.. _CIE 1976: https://en.wikipedia.org/wiki/Color_difference#CIE76
"""
return sqrt(sum((e1 - e2) ** 2 for e1, e2 in zip(color1, color2)))
def cie1994(color1, color2, method):
"""
Calculates color difference according to the `CIE 1994`_ formula. The
*method* can be either "cie1994g" for the "graphical" biases, or "cie1994t"
for the "textile" biases. The CIE1994 is also basically the Euclidean
formula (with biases) but in CIE L*C*H* space.
.. _CIE 1994: https://en.wikipedia.org/wiki/Color_difference#CIE94
"""
C1 = sqrt(color1.a ** 2 + color1.b ** 2)
C2 = sqrt(color2.a ** 2 + color2.b ** 2)
dL = color1.l - color2.l
dC = C1 - C2
# Don't bother with the sqrt here as due to limited float precision
# we can wind up with a domain error (because the value is ever so
# slightly negative - try it with black'n'white for an example), and we're
# just going to square the result in the final equation anyway
dH2 = (color1.a - color2.a) ** 2 + (color1.b - color2.b) ** 2 - dC ** 2
kL, K1, K2 = {
'cie1994g': (1, 0.045, 0.015),
'cie1994t': (2, 0.048, 0.014),
}[method]
SC = 1 + K1 * C1
SH = 1 + K2 * C1
return sqrt(
(dL / kL) ** 2 +
(dC / SC) ** 2 +
(dH2 / SH ** 2)
)
[docs]def cie1994g(color1, color2):
"""
Calculates color difference according to the `CIE 1994`_ formula with the
"textile" bias. See :func:`cie1994` for further information.
.. _CIE 1994: https://en.wikipedia.org/wiki/Color_difference#CIE94
"""
return cie1994(color1, color2, 'cie1994g')
[docs]def cie1994t(color1, color2):
"""
Calculates color difference according to the `CIE 1994`_ formula with the
"graphics" bias. See :func:`cie1994` for further information.
.. _CIE 1994: https://en.wikipedia.org/wiki/Color_difference#CIE94
"""
return cie1994(color1, color2, 'cie1994t')
[docs]def ciede2000(color1, color2):
"""
Calculates color difference according to the `CIEDE 2000`_ formula. This is
the most accurate algorithm currently implemented but also the most complex
and slowest. Like CIE1994 it is largely based in CIE L*C*h* space, but with
several modifications to account for perceptual uniformity flaws.
.. _CIEDE 2000: https://en.wikipedia.org/wiki/Color_difference#CIEDE2000
"""
# See WP article and Sharma 2005 for important implementation notes:
# http://www.ece.rochester.edu/~gsharma/ciede2000/ciede2000noteCRNA.pdf
#
# Yes, there's lots of locals; but this is easiest to understand as it's a
# near straight translation of the math
# pylint: disable=too-many-locals
C_ = (
sqrt(color1.a ** 2 + color1.b ** 2) +
sqrt(color2.a ** 2 + color2.b ** 2)
) / 2
G = (1 - sqrt(C_ ** 7 / (C_ ** 7 + 25 ** 7))) / 2
a1_prime = (1 + G) * color1.a
a2_prime = (1 + G) * color2.a
C1_prime = sqrt(a1_prime ** 2 + color1.b ** 2)
C2_prime = sqrt(a2_prime ** 2 + color2.b ** 2)
L_ = (color1.l + color2.l) / 2
C_ = (C1_prime + C2_prime) / 2
h1 = (
0.0 if color1.b == a1_prime == 0 else
degrees(atan2(color1.b, a1_prime)) % 360
)
h2 = (
0.0 if color2.b == a2_prime == 0 else
degrees(atan2(color2.b, a2_prime)) % 360
)
if C1_prime * C2_prime == 0.0:
dh = 0.0
h_ = h1 + h2
elif abs(h1 - h2) <= 180:
dh = h2 - h1
h_ = (h1 + h2) / 2
else:
if h2 > h1:
dh = h2 - h1 - 360
else:
dh = h2 - h1 + 360
if h1 + h2 >= 360:
h_ = (h1 + h2 - 360) / 2
else:
h_ = (h1 + h2 + 360) / 2
dL = color2.l - color1.l
dC = C2_prime - C1_prime
dH = 2 * sqrt(C1_prime * C2_prime) * sin(radians(dh / 2))
T = (
1 -
0.17 * cos(radians(h_ - 30)) +
0.24 * cos(radians(2 * h_)) +
0.32 * cos(radians(3 * h_ + 6)) -
0.20 * cos(radians(4 * h_ - 63))
)
SL = 1 + (0.015 * (L_ - 50) ** 2) / sqrt(20 + (L_ - 50) ** 2)
SC = 1 + 0.045 * C_
SH = 1 + 0.015 * C_ * T
RT = (
-2 * sqrt(C_ ** 7 / (C_ ** 7 + 25 ** 7)) *
sin(radians(60 * exp(-(((h_ - 275) / 25) ** 2))))
)
return sqrt(
(dL / SL) ** 2 +
(dC / SC) ** 2 +
(dH / SH) ** 2 +
RT * (dC / SC) * (dH / SH)
)