1# -*- coding: utf-8 -*-
2"""
3Sub-module providing color functions.
4
5References,
6
7- https://en.wikipedia.org/wiki/Color_difference
8- http://www.easyrgb.com/en/math.php
9- Measuring Colour by R.W.G. Hunt and M.R. Pointer
10"""
11
12# std imports
13from math import cos, exp, sin, sqrt, atan2
14
15# isort: off
16try:
17    from functools import lru_cache
18except ImportError:
19    # lru_cache was added in Python 3.2
20    from backports.functools_lru_cache import lru_cache
21
22
23def rgb_to_xyz(red, green, blue):
24    """
25    Convert standard RGB color to XYZ color.
26
27    :arg int red: RGB value of Red.
28    :arg int green: RGB value of Green.
29    :arg int blue: RGB value of Blue.
30    :returns: Tuple (X, Y, Z) representing XYZ color
31    :rtype: tuple
32
33    D65/2° standard illuminant
34    """
35    rgb = []
36    for val in red, green, blue:
37        val /= 255.0
38        if val > 0.04045:
39            val = pow((val + 0.055) / 1.055, 2.4)
40        else:
41            val /= 12.92
42        val *= 100
43        rgb.append(val)
44
45    red, green, blue = rgb  # pylint: disable=unbalanced-tuple-unpacking
46    x_val = red * 0.4124 + green * 0.3576 + blue * 0.1805
47    y_val = red * 0.2126 + green * 0.7152 + blue * 0.0722
48    z_val = red * 0.0193 + green * 0.1192 + blue * 0.9505
49
50    return x_val, y_val, z_val
51
52
53def xyz_to_lab(x_val, y_val, z_val):
54    """
55    Convert XYZ color to CIE-Lab color.
56
57    :arg float x_val: XYZ value of X.
58    :arg float y_val: XYZ value of Y.
59    :arg float z_val: XYZ value of Z.
60    :returns: Tuple (L, a, b) representing CIE-Lab color
61    :rtype: tuple
62
63    D65/2° standard illuminant
64    """
65    xyz = []
66    for val, ref in (x_val, 95.047), (y_val, 100.0), (z_val, 108.883):
67        val /= ref
68        val = pow(val, 1 / 3.0) if val > 0.008856 else 7.787 * val + 16 / 116.0
69        xyz.append(val)
70
71    x_val, y_val, z_val = xyz  # pylint: disable=unbalanced-tuple-unpacking
72    cie_l = 116 * y_val - 16
73    cie_a = 500 * (x_val - y_val)
74    cie_b = 200 * (y_val - z_val)
75
76    return cie_l, cie_a, cie_b
77
78
79@lru_cache(maxsize=256)
80def rgb_to_lab(red, green, blue):
81    """
82    Convert RGB color to CIE-Lab color.
83
84    :arg int red: RGB value of Red.
85    :arg int green: RGB value of Green.
86    :arg int blue: RGB value of Blue.
87    :returns: Tuple (L, a, b) representing CIE-Lab color
88    :rtype: tuple
89
90    D65/2° standard illuminant
91    """
92    return xyz_to_lab(*rgb_to_xyz(red, green, blue))
93
94
95def dist_rgb(rgb1, rgb2):
96    """
97    Determine distance between two rgb colors.
98
99    :arg tuple rgb1: RGB color definition
100    :arg tuple rgb2: RGB color definition
101    :returns: Square of the distance between provided colors
102    :rtype: float
103
104    This works by treating RGB colors as coordinates in three dimensional
105    space and finding the closest point within the configured color range
106    using the formula::
107
108        d^2 = (r2 - r1)^2 + (g2 - g1)^2 + (b2 - b1)^2
109
110    For efficiency, the square of the distance is returned
111    which is sufficient for comparisons
112    """
113    return sum(pow(rgb1[idx] - rgb2[idx], 2) for idx in (0, 1, 2))
114
115
116def dist_rgb_weighted(rgb1, rgb2):
117    """
118    Determine the weighted distance between two rgb colors.
119
120    :arg tuple rgb1: RGB color definition
121    :arg tuple rgb2: RGB color definition
122    :returns: Square of the distance between provided colors
123    :rtype: float
124
125    Similar to a standard distance formula, the values are weighted
126    to approximate human perception of color differences
127
128    For efficiency, the square of the distance is returned
129    which is sufficient for comparisons
130    """
131    red_mean = (rgb1[0] + rgb2[0]) / 2.0
132
133    return ((2 + red_mean / 256) * pow(rgb1[0] - rgb2[0], 2) +
134            4 * pow(rgb1[1] - rgb2[1], 2) +
135            (2 + (255 - red_mean) / 256) * pow(rgb1[2] - rgb2[2], 2))
136
137
138def dist_cie76(rgb1, rgb2):
139    """
140    Determine distance between two rgb colors using the CIE94 algorithm.
141
142    :arg tuple rgb1: RGB color definition
143    :arg tuple rgb2: RGB color definition
144    :returns: Square of the distance between provided colors
145    :rtype: float
146
147    For efficiency, the square of the distance is returned
148    which is sufficient for comparisons
149    """
150    l_1, a_1, b_1 = rgb_to_lab(*rgb1)
151    l_2, a_2, b_2 = rgb_to_lab(*rgb2)
152    return pow(l_1 - l_2, 2) + pow(a_1 - a_2, 2) + pow(b_1 - b_2, 2)
153
154
155def dist_cie94(rgb1, rgb2):
156    # pylint: disable=too-many-locals
157    """
158    Determine distance between two rgb colors using the CIE94 algorithm.
159
160    :arg tuple rgb1: RGB color definition
161    :arg tuple rgb2: RGB color definition
162    :returns: Square of the distance between provided colors
163    :rtype: float
164
165    For efficiency, the square of the distance is returned
166    which is sufficient for comparisons
167    """
168    l_1, a_1, b_1 = rgb_to_lab(*rgb1)
169    l_2, a_2, b_2 = rgb_to_lab(*rgb2)
170
171    s_l = k_l = k_c = k_h = 1
172    k_1 = 0.045
173    k_2 = 0.015
174
175    delta_l = l_1 - l_2
176    delta_a = a_1 - a_2
177    delta_b = b_1 - b_2
178    c_1 = sqrt(a_1 ** 2 + b_1 ** 2)
179    c_2 = sqrt(a_2 ** 2 + b_2 ** 2)
180    delta_c = c_1 - c_2
181    delta_h = sqrt(delta_a ** 2 + delta_b ** 2 + delta_c ** 2)
182    s_c = 1 + k_1 * c_1
183    s_h = 1 + k_2 * c_1
184
185    return ((delta_l / (k_l * s_l)) ** 2 +  # pylint: disable=superfluous-parens
186            (delta_c / (k_c * s_c)) ** 2 +
187            (delta_h / (k_h * s_h)) ** 2)
188
189
190def dist_cie2000(rgb1, rgb2):
191    # pylint: disable=too-many-locals
192    """
193    Determine distance between two rgb colors using the CIE2000 algorithm.
194
195    :arg tuple rgb1: RGB color definition
196    :arg tuple rgb2: RGB color definition
197    :returns: Square of the distance between provided colors
198    :rtype: float
199
200    For efficiency, the square of the distance is returned
201    which is sufficient for comparisons
202    """
203    s_l = k_l = k_c = k_h = 1
204
205    l_1, a_1, b_1 = rgb_to_lab(*rgb1)
206    l_2, a_2, b_2 = rgb_to_lab(*rgb2)
207
208    delta_l = l_2 - l_1
209    l_mean = (l_1 + l_2) / 2
210
211    c_1 = sqrt(a_1 ** 2 + b_1 ** 2)
212    c_2 = sqrt(a_2 ** 2 + b_2 ** 2)
213    c_mean = (c_1 + c_2) / 2
214    delta_c = c_1 - c_2
215
216    g_x = sqrt(c_mean ** 7 / (c_mean ** 7 + 25 ** 7))
217    h_1 = atan2(b_1, a_1 + (a_1 / 2) * (1 - g_x)) % 360
218    h_2 = atan2(b_2, a_2 + (a_2 / 2) * (1 - g_x)) % 360
219
220    if 0 in (c_1, c_2):
221        delta_h_prime = 0
222        h_mean = h_1 + h_2
223    else:
224        delta_h_prime = h_2 - h_1
225        if abs(delta_h_prime) <= 180:
226            h_mean = (h_1 + h_2) / 2
227        else:
228            if h_2 <= h_1:
229                delta_h_prime += 360
230            else:
231                delta_h_prime -= 360
232            h_mean = (h_1 + h_2 + 360) / 2 if h_1 + h_2 < 360 else (h_1 + h_2 - 360) / 2
233
234    delta_h = 2 * sqrt(c_1 * c_2) * sin(delta_h_prime / 2)
235
236    t_x = (1 -
237           0.17 * cos(h_mean - 30) +
238           0.24 * cos(2 * h_mean) +
239           0.32 * cos(3 * h_mean + 6) -
240           0.20 * cos(4 * h_mean - 63))
241
242    s_l = 1 + (0.015 * (l_mean - 50) ** 2) / sqrt(20 + (l_mean - 50) ** 2)
243    s_c = 1 + 0.045 * c_mean
244    s_h = 1 + 0.015 * c_mean * t_x
245    r_t = -2 * g_x * sin(abs(60 * exp(-1 * abs((delta_h - 275) / 25) ** 2)))
246
247    delta_l = delta_l / (k_l * s_l)
248    delta_c = delta_c / (k_c * s_c)
249    delta_h = delta_h / (k_h * s_h)
250
251    return delta_l ** 2 + delta_c ** 2 + delta_h ** 2 + r_t * delta_c * delta_h
252
253
254COLOR_DISTANCE_ALGORITHMS = {'rgb': dist_rgb,
255                             'rgb-weighted': dist_rgb_weighted,
256                             'cie76': dist_cie76,
257                             'cie94': dist_cie94,
258                             'cie2000': dist_cie2000}
259