generated from mwc/lab_retro
259 lines
7.3 KiB
Python
259 lines
7.3 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""
|
|
Sub-module providing color functions.
|
|
|
|
References,
|
|
|
|
- https://en.wikipedia.org/wiki/Color_difference
|
|
- http://www.easyrgb.com/en/math.php
|
|
- Measuring Colour by R.W.G. Hunt and M.R. Pointer
|
|
"""
|
|
|
|
# std imports
|
|
from math import cos, exp, sin, sqrt, atan2
|
|
|
|
# isort: off
|
|
try:
|
|
from functools import lru_cache
|
|
except ImportError:
|
|
# lru_cache was added in Python 3.2
|
|
from backports.functools_lru_cache import lru_cache
|
|
|
|
|
|
def rgb_to_xyz(red, green, blue):
|
|
"""
|
|
Convert standard RGB color to XYZ color.
|
|
|
|
:arg int red: RGB value of Red.
|
|
:arg int green: RGB value of Green.
|
|
:arg int blue: RGB value of Blue.
|
|
:returns: Tuple (X, Y, Z) representing XYZ color
|
|
:rtype: tuple
|
|
|
|
D65/2° standard illuminant
|
|
"""
|
|
rgb = []
|
|
for val in red, green, blue:
|
|
val /= 255.0
|
|
if val > 0.04045:
|
|
val = pow((val + 0.055) / 1.055, 2.4)
|
|
else:
|
|
val /= 12.92
|
|
val *= 100
|
|
rgb.append(val)
|
|
|
|
red, green, blue = rgb # pylint: disable=unbalanced-tuple-unpacking
|
|
x_val = red * 0.4124 + green * 0.3576 + blue * 0.1805
|
|
y_val = red * 0.2126 + green * 0.7152 + blue * 0.0722
|
|
z_val = red * 0.0193 + green * 0.1192 + blue * 0.9505
|
|
|
|
return x_val, y_val, z_val
|
|
|
|
|
|
def xyz_to_lab(x_val, y_val, z_val):
|
|
"""
|
|
Convert XYZ color to CIE-Lab color.
|
|
|
|
:arg float x_val: XYZ value of X.
|
|
:arg float y_val: XYZ value of Y.
|
|
:arg float z_val: XYZ value of Z.
|
|
:returns: Tuple (L, a, b) representing CIE-Lab color
|
|
:rtype: tuple
|
|
|
|
D65/2° standard illuminant
|
|
"""
|
|
xyz = []
|
|
for val, ref in (x_val, 95.047), (y_val, 100.0), (z_val, 108.883):
|
|
val /= ref
|
|
val = pow(val, 1 / 3.0) if val > 0.008856 else 7.787 * val + 16 / 116.0
|
|
xyz.append(val)
|
|
|
|
x_val, y_val, z_val = xyz # pylint: disable=unbalanced-tuple-unpacking
|
|
cie_l = 116 * y_val - 16
|
|
cie_a = 500 * (x_val - y_val)
|
|
cie_b = 200 * (y_val - z_val)
|
|
|
|
return cie_l, cie_a, cie_b
|
|
|
|
|
|
@lru_cache(maxsize=256)
|
|
def rgb_to_lab(red, green, blue):
|
|
"""
|
|
Convert RGB color to CIE-Lab color.
|
|
|
|
:arg int red: RGB value of Red.
|
|
:arg int green: RGB value of Green.
|
|
:arg int blue: RGB value of Blue.
|
|
:returns: Tuple (L, a, b) representing CIE-Lab color
|
|
:rtype: tuple
|
|
|
|
D65/2° standard illuminant
|
|
"""
|
|
return xyz_to_lab(*rgb_to_xyz(red, green, blue))
|
|
|
|
|
|
def dist_rgb(rgb1, rgb2):
|
|
"""
|
|
Determine distance between two rgb colors.
|
|
|
|
:arg tuple rgb1: RGB color definition
|
|
:arg tuple rgb2: RGB color definition
|
|
:returns: Square of the distance between provided colors
|
|
:rtype: float
|
|
|
|
This works by treating RGB colors as coordinates in three dimensional
|
|
space and finding the closest point within the configured color range
|
|
using the formula::
|
|
|
|
d^2 = (r2 - r1)^2 + (g2 - g1)^2 + (b2 - b1)^2
|
|
|
|
For efficiency, the square of the distance is returned
|
|
which is sufficient for comparisons
|
|
"""
|
|
return sum(pow(rgb1[idx] - rgb2[idx], 2) for idx in (0, 1, 2))
|
|
|
|
|
|
def dist_rgb_weighted(rgb1, rgb2):
|
|
"""
|
|
Determine the weighted distance between two rgb colors.
|
|
|
|
:arg tuple rgb1: RGB color definition
|
|
:arg tuple rgb2: RGB color definition
|
|
:returns: Square of the distance between provided colors
|
|
:rtype: float
|
|
|
|
Similar to a standard distance formula, the values are weighted
|
|
to approximate human perception of color differences
|
|
|
|
For efficiency, the square of the distance is returned
|
|
which is sufficient for comparisons
|
|
"""
|
|
red_mean = (rgb1[0] + rgb2[0]) / 2.0
|
|
|
|
return ((2 + red_mean / 256) * pow(rgb1[0] - rgb2[0], 2) +
|
|
4 * pow(rgb1[1] - rgb2[1], 2) +
|
|
(2 + (255 - red_mean) / 256) * pow(rgb1[2] - rgb2[2], 2))
|
|
|
|
|
|
def dist_cie76(rgb1, rgb2):
|
|
"""
|
|
Determine distance between two rgb colors using the CIE94 algorithm.
|
|
|
|
:arg tuple rgb1: RGB color definition
|
|
:arg tuple rgb2: RGB color definition
|
|
:returns: Square of the distance between provided colors
|
|
:rtype: float
|
|
|
|
For efficiency, the square of the distance is returned
|
|
which is sufficient for comparisons
|
|
"""
|
|
l_1, a_1, b_1 = rgb_to_lab(*rgb1)
|
|
l_2, a_2, b_2 = rgb_to_lab(*rgb2)
|
|
return pow(l_1 - l_2, 2) + pow(a_1 - a_2, 2) + pow(b_1 - b_2, 2)
|
|
|
|
|
|
def dist_cie94(rgb1, rgb2):
|
|
# pylint: disable=too-many-locals
|
|
"""
|
|
Determine distance between two rgb colors using the CIE94 algorithm.
|
|
|
|
:arg tuple rgb1: RGB color definition
|
|
:arg tuple rgb2: RGB color definition
|
|
:returns: Square of the distance between provided colors
|
|
:rtype: float
|
|
|
|
For efficiency, the square of the distance is returned
|
|
which is sufficient for comparisons
|
|
"""
|
|
l_1, a_1, b_1 = rgb_to_lab(*rgb1)
|
|
l_2, a_2, b_2 = rgb_to_lab(*rgb2)
|
|
|
|
s_l = k_l = k_c = k_h = 1
|
|
k_1 = 0.045
|
|
k_2 = 0.015
|
|
|
|
delta_l = l_1 - l_2
|
|
delta_a = a_1 - a_2
|
|
delta_b = b_1 - b_2
|
|
c_1 = sqrt(a_1 ** 2 + b_1 ** 2)
|
|
c_2 = sqrt(a_2 ** 2 + b_2 ** 2)
|
|
delta_c = c_1 - c_2
|
|
delta_h = sqrt(delta_a ** 2 + delta_b ** 2 + delta_c ** 2)
|
|
s_c = 1 + k_1 * c_1
|
|
s_h = 1 + k_2 * c_1
|
|
|
|
return ((delta_l / (k_l * s_l)) ** 2 + # pylint: disable=superfluous-parens
|
|
(delta_c / (k_c * s_c)) ** 2 +
|
|
(delta_h / (k_h * s_h)) ** 2)
|
|
|
|
|
|
def dist_cie2000(rgb1, rgb2):
|
|
# pylint: disable=too-many-locals
|
|
"""
|
|
Determine distance between two rgb colors using the CIE2000 algorithm.
|
|
|
|
:arg tuple rgb1: RGB color definition
|
|
:arg tuple rgb2: RGB color definition
|
|
:returns: Square of the distance between provided colors
|
|
:rtype: float
|
|
|
|
For efficiency, the square of the distance is returned
|
|
which is sufficient for comparisons
|
|
"""
|
|
s_l = k_l = k_c = k_h = 1
|
|
|
|
l_1, a_1, b_1 = rgb_to_lab(*rgb1)
|
|
l_2, a_2, b_2 = rgb_to_lab(*rgb2)
|
|
|
|
delta_l = l_2 - l_1
|
|
l_mean = (l_1 + l_2) / 2
|
|
|
|
c_1 = sqrt(a_1 ** 2 + b_1 ** 2)
|
|
c_2 = sqrt(a_2 ** 2 + b_2 ** 2)
|
|
c_mean = (c_1 + c_2) / 2
|
|
delta_c = c_1 - c_2
|
|
|
|
g_x = sqrt(c_mean ** 7 / (c_mean ** 7 + 25 ** 7))
|
|
h_1 = atan2(b_1, a_1 + (a_1 / 2) * (1 - g_x)) % 360
|
|
h_2 = atan2(b_2, a_2 + (a_2 / 2) * (1 - g_x)) % 360
|
|
|
|
if 0 in (c_1, c_2):
|
|
delta_h_prime = 0
|
|
h_mean = h_1 + h_2
|
|
else:
|
|
delta_h_prime = h_2 - h_1
|
|
if abs(delta_h_prime) <= 180:
|
|
h_mean = (h_1 + h_2) / 2
|
|
else:
|
|
if h_2 <= h_1:
|
|
delta_h_prime += 360
|
|
else:
|
|
delta_h_prime -= 360
|
|
h_mean = (h_1 + h_2 + 360) / 2 if h_1 + h_2 < 360 else (h_1 + h_2 - 360) / 2
|
|
|
|
delta_h = 2 * sqrt(c_1 * c_2) * sin(delta_h_prime / 2)
|
|
|
|
t_x = (1 -
|
|
0.17 * cos(h_mean - 30) +
|
|
0.24 * cos(2 * h_mean) +
|
|
0.32 * cos(3 * h_mean + 6) -
|
|
0.20 * cos(4 * h_mean - 63))
|
|
|
|
s_l = 1 + (0.015 * (l_mean - 50) ** 2) / sqrt(20 + (l_mean - 50) ** 2)
|
|
s_c = 1 + 0.045 * c_mean
|
|
s_h = 1 + 0.015 * c_mean * t_x
|
|
r_t = -2 * g_x * sin(abs(60 * exp(-1 * abs((delta_h - 275) / 25) ** 2)))
|
|
|
|
delta_l = delta_l / (k_l * s_l)
|
|
delta_c = delta_c / (k_c * s_c)
|
|
delta_h = delta_h / (k_h * s_h)
|
|
|
|
return delta_l ** 2 + delta_c ** 2 + delta_h ** 2 + r_t * delta_c * delta_h
|
|
|
|
|
|
COLOR_DISTANCE_ALGORITHMS = {'rgb': dist_rgb,
|
|
'rgb-weighted': dist_rgb_weighted,
|
|
'cie76': dist_cie76,
|
|
'cie94': dist_cie94,
|
|
'cie2000': dist_cie2000}
|