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