# Source code for colormath.color_conversions

```"""
Conversion between color spaces.

.. note:: This module makes extensive use of imports within functions.
That stinks.
"""

import math
import logging

import numpy

from colormath import color_constants
from colormath import spectral_constants
from colormath.color_objects import ColorBase, XYZColor, sRGBColor, LCHabColor, \
LCHuvColor, LabColor, xyYColor, LuvColor, HSVColor, HSLColor, CMYColor, \
CMYKColor, BaseRGBColor
from colormath.color_exceptions import InvalidIlluminantError, UndefinedConversionError

logger = logging.getLogger(__name__)

# noinspection PyPep8Naming
def apply_RGB_matrix(var1, var2, var3, rgb_type, convtype="xyz_to_rgb"):
"""
Applies an RGB working matrix to convert from XYZ to RGB.
The arguments are tersely named var1, var2, and var3 to allow for the passing
of XYZ _or_ RGB values. var1 is X for XYZ, and R for RGB. var2 and var3
"""

convtype = convtype.lower()
# Retrieve the appropriate transformation matrix from the constants.
rgb_matrix = rgb_type.conversion_matrices[convtype]

logger.debug("  \* Applying RGB conversion matrix: %s->%s",
rgb_type.__class__.__name__, convtype)
# Stuff the RGB/XYZ values into a NumPy matrix for conversion.
var_matrix = numpy.array((
var1, var2, var3
))
# Perform the adaptation via matrix multiplication.
result_matrix = numpy.dot(var_matrix, rgb_matrix)
return result_matrix[0], result_matrix[1], result_matrix[2]

# noinspection PyPep8Naming,PyUnusedLocal
def Spectral_to_XYZ(cobj, illuminant_override=None, *args, **kwargs):
"""
"""

# If the user provides an illuminant_override numpy array, use it.
if illuminant_override:
reference_illum = illuminant_override
else:
# Otherwise, look up the illuminant from known standards based
# on the value of 'illuminant' pulled from the SpectralColor object.
try:
reference_illum = spectral_constants.REF_ILLUM_TABLE[cobj.illuminant]
except KeyError:
raise InvalidIlluminantError(cobj.illuminant)

# Get the spectral distribution of the selected standard observer.
if cobj.observer == '10':
std_obs_x = spectral_constants.STDOBSERV_X10
std_obs_y = spectral_constants.STDOBSERV_Y10
std_obs_z = spectral_constants.STDOBSERV_Z10
else:
# Assume 2 degree, since it is theoretically the only other possibility.
std_obs_x = spectral_constants.STDOBSERV_X2
std_obs_y = spectral_constants.STDOBSERV_Y2
std_obs_z = spectral_constants.STDOBSERV_Z2

# This is a NumPy array containing the spectral distribution of the color.
sample = cobj.get_numpy_array()

# The denominator is constant throughout the entire calculation for X,
# Y, and Z coordinates. Calculate it once and re-use.
denom = std_obs_y * reference_illum

# This is also a common element in the calculation whereby the sample
# NumPy array is multiplied by the reference illuminant's power distribution
# (which is also a NumPy array).
sample_by_ref_illum = sample * reference_illum

# Calculate the numerator of the equation to find X.
x_numerator = sample_by_ref_illum * std_obs_x
y_numerator = sample_by_ref_illum * std_obs_y
z_numerator = sample_by_ref_illum * std_obs_z

xyz_x = x_numerator.sum() / denom.sum()
xyz_y = y_numerator.sum() / denom.sum()
xyz_z = z_numerator.sum() / denom.sum()

return XYZColor(
xyz_x, xyz_y, xyz_z, observer=cobj.observer, illuminant=cobj.illuminant)

# noinspection PyPep8Naming,PyUnusedLocal
def Lab_to_LCHab(cobj, *args, **kwargs):
"""
Convert from CIE Lab to LCH(ab).
"""

lch_l = cobj.lab_l
lch_c = math.sqrt(math.pow(float(cobj.lab_a), 2) + math.pow(float(cobj.lab_b), 2))
lch_h = math.atan2(float(cobj.lab_b), float(cobj.lab_a))

if lch_h > 0:
lch_h = (lch_h / math.pi) * 180
else:
lch_h = 360 - (math.fabs(lch_h) / math.pi) * 180

return LCHabColor(
lch_l, lch_c, lch_h, observer=cobj.observer, illuminant=cobj.illuminant)

# noinspection PyPep8Naming,PyUnusedLocal
def Lab_to_XYZ(cobj, *args, **kwargs):
"""
Convert from Lab to XYZ
"""

illum = cobj.get_illuminant_xyz()
xyz_y = (cobj.lab_l + 16.0) / 116.0
xyz_x = cobj.lab_a / 500.0 + xyz_y
xyz_z = xyz_y - cobj.lab_b / 200.0

if math.pow(xyz_y, 3) > color_constants.CIE_E:
xyz_y = math.pow(xyz_y, 3)
else:
xyz_y = (xyz_y - 16.0 / 116.0) / 7.787

if math.pow(xyz_x, 3) > color_constants.CIE_E:
xyz_x = math.pow(xyz_x, 3)
else:
xyz_x = (xyz_x - 16.0 / 116.0) / 7.787

if math.pow(xyz_z, 3) > color_constants.CIE_E:
xyz_z = math.pow(xyz_z, 3)
else:
xyz_z = (xyz_z - 16.0 / 116.0) / 7.787

xyz_x = (illum["X"] * xyz_x)
xyz_y = (illum["Y"] * xyz_y)
xyz_z = (illum["Z"] * xyz_z)

return XYZColor(
xyz_x, xyz_y, xyz_z, observer=cobj.observer, illuminant=cobj.illuminant)

# noinspection PyPep8Naming,PyUnusedLocal
def Luv_to_LCHuv(cobj, *args, **kwargs):
"""
Convert from CIE Luv to LCH(uv).
"""

lch_l = cobj.luv_l
lch_c = math.sqrt(math.pow(cobj.luv_u, 2.0) + math.pow(cobj.luv_v, 2.0))
lch_h = math.atan2(float(cobj.luv_v), float(cobj.luv_u))

if lch_h > 0:
lch_h = (lch_h / math.pi) * 180
else:
lch_h = 360 - (math.fabs(lch_h) / math.pi) * 180
return LCHuvColor(
lch_l, lch_c, lch_h, observer=cobj.observer, illuminant=cobj.illuminant)

# noinspection PyPep8Naming,PyUnusedLocal
def Luv_to_XYZ(cobj, *args, **kwargs):
"""
Convert from Luv to XYZ.
"""

illum = cobj.get_illuminant_xyz()
# Without Light, there is no color. Short-circuit this and avoid some
# zero division errors in the var_a_frac calculation.
if cobj.luv_l <= 0.0:
xyz_x = 0.0
xyz_y = 0.0
xyz_z = 0.0
return XYZColor(
xyz_x, xyz_y, xyz_z, observer=cobj.observer, illuminant=cobj.illuminant)

# Various variables used throughout the conversion.
cie_k_times_e = color_constants.CIE_K * color_constants.CIE_E
u_sub_0 = (4.0 * illum["X"]) / (illum["X"] + 15.0 * illum["Y"] + 3.0 * illum["Z"])
v_sub_0 = (9.0 * illum["Y"]) / (illum["X"] + 15.0 * illum["Y"] + 3.0 * illum["Z"])
var_u = cobj.luv_u / (13.0 * cobj.luv_l) + u_sub_0
var_v = cobj.luv_v / (13.0 * cobj.luv_l) + v_sub_0

# Y-coordinate calculations.
if cobj.luv_l > cie_k_times_e:
xyz_y = math.pow((cobj.luv_l + 16.0) / 116.0, 3.0)
else:
xyz_y = cobj.luv_l / color_constants.CIE_K

# X-coordinate calculation.
xyz_x = xyz_y * 9.0 * var_u / (4.0 * var_v)
# Z-coordinate calculation.
xyz_z = xyz_y * (12.0 - 3.0 * var_u - 20.0 * var_v) / (4.0 * var_v)

return XYZColor(
xyz_x, xyz_y, xyz_z, illuminant=cobj.illuminant, observer=cobj.observer)

# noinspection PyPep8Naming,PyUnusedLocal
def LCHab_to_Lab(cobj, *args, **kwargs):
"""
Convert from LCH(ab) to Lab.
"""

lab_l = cobj.lch_l
return LabColor(
lab_l, lab_a, lab_b, illuminant=cobj.illuminant, observer=cobj.observer)

# noinspection PyPep8Naming,PyUnusedLocal
def LCHuv_to_Luv(cobj, *args, **kwargs):
"""
Convert from LCH(uv) to Luv.
"""

luv_l = cobj.lch_l
return LuvColor(
luv_l, luv_u, luv_v, illuminant=cobj.illuminant, observer=cobj.observer)

# noinspection PyPep8Naming,PyUnusedLocal
def xyY_to_XYZ(cobj, *args, **kwargs):
"""
Convert from xyY to XYZ.
"""

xyz_x = (cobj.xyy_x * cobj.xyy_Y) / cobj.xyy_y
xyz_y = cobj.xyy_Y
xyz_z = ((1.0 - cobj.xyy_x - cobj.xyy_y) * xyz_y) / cobj.xyy_y

return XYZColor(
xyz_x, xyz_y, xyz_z, illuminant=cobj.illuminant, observer=cobj.observer)

# noinspection PyPep8Naming,PyUnusedLocal
def XYZ_to_xyY(cobj, *args, **kwargs):
"""
Convert from XYZ to xyY.
"""

xyy_x = cobj.xyz_x / (cobj.xyz_x + cobj.xyz_y + cobj.xyz_z)
xyy_y = cobj.xyz_y / (cobj.xyz_x + cobj.xyz_y + cobj.xyz_z)
xyy_Y = cobj.xyz_y

return xyYColor(
xyy_x, xyy_y, xyy_Y, observer=cobj.observer, illuminant=cobj.illuminant)

# noinspection PyPep8Naming,PyUnusedLocal
def XYZ_to_Luv(cobj, *args, **kwargs):
"""
Convert from XYZ to Luv
"""

temp_x = cobj.xyz_x
temp_y = cobj.xyz_y
temp_z = cobj.xyz_z

luv_u = (4.0 * temp_x) / (temp_x + (15.0 * temp_y) + (3.0 * temp_z))
luv_v = (9.0 * temp_y) / (temp_x + (15.0 * temp_y) + (3.0 * temp_z))

illum = cobj.get_illuminant_xyz()
temp_y = temp_y / illum["Y"]
if temp_y > color_constants.CIE_E:
temp_y = math.pow(temp_y, (1.0 / 3.0))
else:
temp_y = (7.787 * temp_y) + (16.0 / 116.0)

ref_U = (4.0 * illum["X"]) / (illum["X"] + (15.0 * illum["Y"]) + (3.0 * illum["Z"]))
ref_V = (9.0 * illum["Y"]) / (illum["X"] + (15.0 * illum["Y"]) + (3.0 * illum["Z"]))

luv_l = (116.0 * temp_y) - 16.0
luv_u = 13.0 * luv_l * (luv_u - ref_U)
luv_v = 13.0 * luv_l * (luv_v - ref_V)

return LuvColor(
luv_l, luv_u, luv_v, observer=cobj.observer, illuminant=cobj.illuminant)

# noinspection PyPep8Naming,PyUnusedLocal
def XYZ_to_Lab(cobj, *args, **kwargs):
"""
Converts XYZ to Lab.
"""

illum = cobj.get_illuminant_xyz()
temp_x = cobj.xyz_x / illum["X"]
temp_y = cobj.xyz_y / illum["Y"]
temp_z = cobj.xyz_z / illum["Z"]

if temp_x > color_constants.CIE_E:
temp_x = math.pow(temp_x, (1.0 / 3.0))
else:
temp_x = (7.787 * temp_x) + (16.0 / 116.0)

if temp_y > color_constants.CIE_E:
temp_y = math.pow(temp_y, (1.0 / 3.0))
else:
temp_y = (7.787 * temp_y) + (16.0 / 116.0)

if temp_z > color_constants.CIE_E:
temp_z = math.pow(temp_z, (1.0 / 3.0))
else:
temp_z = (7.787 * temp_z) + (16.0 / 116.0)

lab_l = (116.0 * temp_y) - 16.0
lab_a = 500.0 * (temp_x - temp_y)
lab_b = 200.0 * (temp_y - temp_z)
return LabColor(
lab_l, lab_a, lab_b, observer=cobj.observer, illuminant=cobj.illuminant)

# noinspection PyPep8Naming,PyUnusedLocal
def XYZ_to_RGB(cobj, target_rgb, *args, **kwargs):
"""
XYZ to RGB conversion.
"""

temp_X = cobj.xyz_x
temp_Y = cobj.xyz_y
temp_Z = cobj.xyz_z

logger.debug("  \- Target RGB space: %s", target_rgb)
target_illum = target_rgb.native_illuminant
logger.debug("  \- Target native illuminant: %s", target_illum)
logger.debug("  \- XYZ color's illuminant: %s", cobj.illuminant)

# If the XYZ values were taken with a different reference white than the
# native reference white of the target RGB space, a transformation matrix
# must be applied.
if cobj.illuminant != target_illum:
logger.debug("  \* Applying transformation from %s to %s ",
cobj.illuminant, target_illum)
temp_X, temp_Y, temp_Z,
orig_illum=cobj.illuminant, targ_illum=target_illum)
logger.debug("  \*   New values: %.3f, %.3f, %.3f",
temp_X, temp_Y, temp_Z)

# Apply an RGB working space matrix to the XYZ values (matrix mul).
rgb_r, rgb_g, rgb_b = apply_RGB_matrix(
temp_X, temp_Y, temp_Z,
rgb_type=target_rgb, convtype="xyz_to_rgb")

# v
linear_channels = dict(r=rgb_r, g=rgb_g, b=rgb_b)
# V
nonlinear_channels = {}
if target_rgb == sRGBColor:
for channel in ['r', 'g', 'b']:
v = linear_channels[channel]
if v <= 0.0031308:
nonlinear_channels[channel] = v * 12.92
else:
nonlinear_channels[channel] = 1.055 * math.pow(v, 1 / 2.4) - 0.055
else:
# If it's not sRGB...
for channel in ['r', 'g', 'b']:
v = linear_channels[channel]
nonlinear_channels[channel] = math.pow(v, 1 / target_rgb.rgb_gamma)

return target_rgb(
nonlinear_channels['r'], nonlinear_channels['g'], nonlinear_channels['b'])

# noinspection PyPep8Naming,PyUnusedLocal
def RGB_to_XYZ(cobj, target_illuminant=None, *args, **kwargs):
"""
RGB to XYZ conversion. Expects 0-255 RGB values.

Based off of: http://www.brucelindbloom.com/index.html?Eqn_RGB_to_XYZ.html
"""

# Will contain linearized RGB channels (removed the gamma func).
linear_channels = {}

if isinstance(cobj, sRGBColor):
for channel in ['r', 'g', 'b']:
V = getattr(cobj, 'rgb_' + channel)
if V <= 0.04045:
linear_channels[channel] = V / 12.92
else:
linear_channels[channel] = math.pow((V + 0.055) / 1.055, 2.4)
else:
# If it's not sRGB...
gamma = cobj.rgb_gamma

for channel in ['r', 'g', 'b']:
V = getattr(cobj, 'rgb_' + channel)
linear_channels[channel] = math.pow(V, gamma)

# Apply an RGB working space matrix to the XYZ values (matrix mul).
xyz_x, xyz_y, xyz_z = apply_RGB_matrix(
linear_channels['r'], linear_channels['g'], linear_channels['b'],
rgb_type=cobj, convtype="rgb_to_xyz")

if target_illuminant is None:
target_illuminant = cobj.native_illuminant

# The illuminant of the original RGB object. This will always match
# the RGB colorspace's native illuminant.
illuminant = cobj.native_illuminant
xyzcolor = XYZColor(xyz_x, xyz_y, xyz_z, illuminant=illuminant)
# This will take care of any illuminant changes for us (if source
# illuminant != target illuminant).

return xyzcolor

# noinspection PyPep8Naming,PyUnusedLocal
def __RGB_to_Hue(var_R, var_G, var_B, var_min, var_max):
"""
For RGB_to_HSL and RGB_to_HSV, the Hue (H) component is calculated in
the same way.
"""

if var_max == var_min:
return 0.0
elif var_max == var_R:
return (60.0 * ((var_G - var_B) / (var_max - var_min)) + 360) % 360.0
elif var_max == var_G:
return 60.0 * ((var_B - var_R) / (var_max - var_min)) + 120
elif var_max == var_B:
return 60.0 * ((var_R - var_G) / (var_max - var_min)) + 240.0

# noinspection PyPep8Naming,PyUnusedLocal
def RGB_to_HSV(cobj, *args, **kwargs):
"""
Converts from RGB to HSV.

H values are in degrees and are 0 to 360.
S values are a percentage, 0.0 to 1.0.
V values are a percentage, 0.0 to 1.0.
"""

var_R = cobj.rgb_r
var_G = cobj.rgb_g
var_B = cobj.rgb_b

var_max = max(var_R, var_G, var_B)
var_min = min(var_R, var_G, var_B)

var_H = __RGB_to_Hue(var_R, var_G, var_B, var_min, var_max)

if var_max == 0:
var_S = 0
else:
var_S = 1.0 - (var_min / var_max)

var_V = var_max

hsv_h = var_H
hsv_s = var_S
hsv_v = var_V

return HSVColor(
var_H, var_S, var_V)

# noinspection PyPep8Naming,PyUnusedLocal
def RGB_to_HSL(cobj, *args, **kwargs):
"""
Converts from RGB to HSL.

H values are in degrees and are 0 to 360.
S values are a percentage, 0.0 to 1.0.
L values are a percentage, 0.0 to 1.0.
"""

var_R = cobj.rgb_r
var_G = cobj.rgb_g
var_B = cobj.rgb_b

var_max = max(var_R, var_G, var_B)
var_min = min(var_R, var_G, var_B)

var_H = __RGB_to_Hue(var_R, var_G, var_B, var_min, var_max)
var_L = 0.5 * (var_max + var_min)

if var_max == var_min:
var_S = 0
elif var_L <= 0.5:
var_S = (var_max - var_min) / (2.0 * var_L)
else:
var_S = (var_max - var_min) / (2.0 - (2.0 * var_L))

return HSLColor(
var_H, var_S, var_L)

# noinspection PyPep8Naming,PyUnusedLocal
def __Calc_HSL_to_RGB_Components(var_q, var_p, C):
"""
This is used in HSL_to_RGB conversions on R, G, and B.
"""

if C < 0:
C += 1.0
if C > 1:
C -= 1.0

# Computing C of vector (Color R, Color G, Color B)
if C < (1.0 / 6.0):
return var_p + ((var_q - var_p) * 6.0 * C)
elif (1.0 / 6.0) <= C < 0.5:
return var_q
elif 0.5 <= C < (2.0 / 3.0):
return var_p + ((var_q - var_p) * 6.0 * ((2.0 / 3.0) - C))
else:
return var_p

# noinspection PyPep8Naming,PyUnusedLocal
def HSV_to_RGB(cobj, target_rgb, *args, **kwargs):
"""
HSV to RGB conversion.

H values are in degrees and are 0 to 360.
S values are a percentage, 0.0 to 1.0.
V values are a percentage, 0.0 to 1.0.
"""

H = cobj.hsv_h
S = cobj.hsv_s
V = cobj.hsv_v

h_floored = int(math.floor(H))
h_sub_i = int(h_floored / 60) % 6
var_f = (H / 60.0) - (h_floored // 60)
var_p = V * (1.0 - S)
var_q = V * (1.0 - var_f * S)
var_t = V * (1.0 - (1.0 - var_f) * S)

if h_sub_i == 0:
rgb_r = V
rgb_g = var_t
rgb_b = var_p
elif h_sub_i == 1:
rgb_r = var_q
rgb_g = V
rgb_b = var_p
elif h_sub_i == 2:
rgb_r = var_p
rgb_g = V
rgb_b = var_t
elif h_sub_i == 3:
rgb_r = var_p
rgb_g = var_q
rgb_b = V
elif h_sub_i == 4:
rgb_r = var_t
rgb_g = var_p
rgb_b = V
elif h_sub_i == 5:
rgb_r = V
rgb_g = var_p
rgb_b = var_q
else:
raise ValueError("Unable to convert HSL->RGB due to value error.")

# In the event that they define an HSV color and want to convert it to
# a particular RGB space, let them override it here.
if target_rgb is not None:
rgb_type = target_rgb
else:
rgb_type = cobj.rgb_type

return target_rgb(rgb_r, rgb_g, rgb_b)

# noinspection PyPep8Naming,PyUnusedLocal
def HSL_to_RGB(cobj, target_rgb, *args, **kwargs):
"""
HSL to RGB conversion.
"""

H = cobj.hsl_h
S = cobj.hsl_s
L = cobj.hsl_l

if L < 0.5:
var_q = L * (1.0 + S)
else:
var_q = L + S - (L * S)

var_p = 2.0 * L - var_q

# H normalized to range [0,1]
h_sub_k = (H / 360.0)

t_sub_R = h_sub_k + (1.0 / 3.0)
t_sub_G = h_sub_k
t_sub_B = h_sub_k - (1.0 / 3.0)

rgb_r = __Calc_HSL_to_RGB_Components(var_q, var_p, t_sub_R)
rgb_g = __Calc_HSL_to_RGB_Components(var_q, var_p, t_sub_G)
rgb_b = __Calc_HSL_to_RGB_Components(var_q, var_p, t_sub_B)

# In the event that they define an HSV color and want to convert it to
# a particular RGB space, let them override it here.
if target_rgb is not None:
rgb_type = target_rgb
else:
rgb_type = cobj.rgb_type

return target_rgb(rgb_r, rgb_g, rgb_b)

# noinspection PyPep8Naming,PyUnusedLocal
def RGB_to_CMY(cobj, *args, **kwargs):
"""
RGB to CMY conversion.

NOTE: CMYK and CMY values range from 0.0 to 1.0
"""

cmy_c = 1.0 - cobj.rgb_r
cmy_m = 1.0 - cobj.rgb_g
cmy_y = 1.0 - cobj.rgb_b

return CMYColor(cmy_c, cmy_m, cmy_y)

# noinspection PyPep8Naming,PyUnusedLocal
def CMY_to_RGB(cobj, target_rgb, *args, **kwargs):
"""
Converts CMY to RGB via simple subtraction.

NOTE: Returned values are in the range of 0-255.
"""

rgb_r = 1.0 - cobj.cmy_c
rgb_g = 1.0 - cobj.cmy_m
rgb_b = 1.0 - cobj.cmy_y

return target_rgb(rgb_r, rgb_g, rgb_b)

# noinspection PyPep8Naming,PyUnusedLocal
def CMY_to_CMYK(cobj, *args, **kwargs):
"""
Converts from CMY to CMYK.

NOTE: CMYK and CMY values range from 0.0 to 1.0
"""

var_k = 1.0
if cobj.cmy_c < var_k:
var_k = cobj.cmy_c
if cobj.cmy_m < var_k:
var_k = cobj.cmy_m
if cobj.cmy_y < var_k:
var_k = cobj.cmy_y

if var_k == 1:
cmyk_c = 0.0
cmyk_m = 0.0
cmyk_y = 0.0
else:
cmyk_c = (cobj.cmy_c - var_k) / (1.0 - var_k)
cmyk_m = (cobj.cmy_m - var_k) / (1.0 - var_k)
cmyk_y = (cobj.cmy_y - var_k) / (1.0 - var_k)
cmyk_k = var_k

return CMYKColor(cmyk_c, cmyk_m, cmyk_y, cmyk_k)

# noinspection PyPep8Naming,PyUnusedLocal
def CMYK_to_CMY(cobj, *args, **kwargs):
"""
Converts CMYK to CMY.

NOTE: CMYK and CMY values range from 0.0 to 1.0
"""

cmy_c = cobj.cmyk_c * (1.0 - cobj.cmyk_k) + cobj.cmyk_k
cmy_m = cobj.cmyk_m * (1.0 - cobj.cmyk_k) + cobj.cmyk_k
cmy_y = cobj.cmyk_y * (1.0 - cobj.cmyk_k) + cobj.cmyk_k

return CMYColor(cmy_c, cmy_m, cmy_y)

CONVERSION_TABLE = {
"SpectralColor": {
"SpectralColor": [None],
"XYZColor": [Spectral_to_XYZ],
"xyYColor": [Spectral_to_XYZ, XYZ_to_xyY],
"LabColor": [Spectral_to_XYZ, XYZ_to_Lab],
"LCHabColor": [Spectral_to_XYZ, XYZ_to_Lab, Lab_to_LCHab],
"LCHuvColor": [Spectral_to_XYZ, XYZ_to_Luv, Luv_to_LCHuv],
"LuvColor": [Spectral_to_XYZ, Lab_to_XYZ, XYZ_to_Luv],
"sRGBColor": [Spectral_to_XYZ, XYZ_to_RGB],
"HSLColor": [Spectral_to_XYZ, XYZ_to_RGB, RGB_to_HSL],
"HSVColor": [Spectral_to_XYZ, XYZ_to_RGB, RGB_to_HSV],
"CMYColor": [Spectral_to_XYZ, XYZ_to_RGB, RGB_to_CMY],
"CMYKColor": [Spectral_to_XYZ, XYZ_to_RGB, RGB_to_CMY, CMY_to_CMYK],
},
"LabColor": {
"LabColor": [None],
"XYZColor": [Lab_to_XYZ],
"xyYColor": [Lab_to_XYZ, XYZ_to_xyY],
"LCHabColor": [Lab_to_LCHab],
"LCHuvColor": [Lab_to_XYZ, XYZ_to_Luv, Luv_to_LCHuv],
"LuvColor": [Lab_to_XYZ, XYZ_to_Luv],
"sRGBColor": [Lab_to_XYZ, XYZ_to_RGB],
"HSLColor": [Lab_to_XYZ, XYZ_to_RGB, RGB_to_HSL],
"HSVColor": [Lab_to_XYZ, XYZ_to_RGB, RGB_to_HSV],
"CMYColor": [Lab_to_XYZ, XYZ_to_RGB, RGB_to_CMY],
"CMYKColor": [Lab_to_XYZ, XYZ_to_RGB, RGB_to_CMY, CMY_to_CMYK],
},
"LCHabColor": {
"LCHabColor": [None],
"XYZColor": [LCHab_to_Lab, Lab_to_XYZ],
"xyYColor": [LCHab_to_Lab, Lab_to_XYZ, XYZ_to_xyY],
"LabColor": [LCHab_to_Lab],
"LCHuvColor": [LCHab_to_Lab, Lab_to_XYZ, XYZ_to_Luv, Luv_to_LCHuv],
"LuvColor": [LCHab_to_Lab, Lab_to_XYZ, XYZ_to_Luv],
"sRGBColor": [LCHab_to_Lab, Lab_to_XYZ, XYZ_to_RGB],
"HSLColor": [LCHab_to_Lab, Lab_to_XYZ, XYZ_to_RGB, RGB_to_HSL],
"HSVColor": [LCHab_to_Lab, Lab_to_XYZ, XYZ_to_RGB, RGB_to_HSV],
"CMYColor": [LCHab_to_Lab, Lab_to_XYZ, XYZ_to_RGB, RGB_to_CMY],
"CMYKColor": [LCHab_to_Lab, Lab_to_XYZ, XYZ_to_RGB, RGB_to_CMY, CMY_to_CMYK],
},
"LCHuvColor": {
"LCHuvColor": [None],
"XYZColor": [LCHuv_to_Luv, Luv_to_XYZ],
"xyYColor": [LCHuv_to_Luv, Luv_to_XYZ, XYZ_to_xyY],
"LabColor": [LCHuv_to_Luv, Luv_to_XYZ, XYZ_to_Lab],
"LuvColor": [LCHuv_to_Luv],
"LCHabColor": [LCHuv_to_Luv, Luv_to_XYZ, XYZ_to_Lab, Lab_to_LCHab],
"sRGBColor": [LCHuv_to_Luv, Luv_to_XYZ, XYZ_to_RGB],
"HSLColor": [LCHuv_to_Luv, Luv_to_XYZ, XYZ_to_RGB, RGB_to_HSL],
"HSVColor": [LCHuv_to_Luv, Luv_to_XYZ, XYZ_to_RGB, RGB_to_HSV],
"CMYColor": [LCHuv_to_Luv, Luv_to_XYZ, XYZ_to_RGB, RGB_to_CMY],
"CMYKColor": [LCHuv_to_Luv, Luv_to_XYZ, XYZ_to_RGB, RGB_to_CMY, CMY_to_CMYK],
},
"LuvColor": {
"LuvColor": [None],
"XYZColor": [Luv_to_XYZ],
"xyYColor": [Luv_to_XYZ, XYZ_to_xyY],
"LabColor": [Luv_to_XYZ, XYZ_to_Lab],
"LCHabColor": [Luv_to_XYZ, XYZ_to_Lab, Lab_to_LCHab],
"LCHuvColor": [Luv_to_LCHuv],
"sRGBColor": [Luv_to_XYZ, XYZ_to_RGB],
"HSLColor": [Luv_to_XYZ, XYZ_to_RGB, RGB_to_HSL],
"HSVColor": [Luv_to_XYZ, XYZ_to_RGB, RGB_to_HSV],
"CMYColor": [Luv_to_XYZ, XYZ_to_RGB, RGB_to_CMY],
"CMYKColor": [Luv_to_XYZ, XYZ_to_RGB, RGB_to_CMY, CMY_to_CMYK],
},
"XYZColor": {
"XYZColor": [None],
"xyYColor": [XYZ_to_xyY],
"LabColor": [XYZ_to_Lab],
"LCHabColor": [XYZ_to_Lab, Lab_to_LCHab],
"LCHuvColor": [XYZ_to_Lab, Luv_to_LCHuv],
"LuvColor": [XYZ_to_Luv],
"sRGBColor": [XYZ_to_RGB],
"HSLColor": [XYZ_to_RGB, RGB_to_HSL],
"HSVColor": [XYZ_to_RGB, RGB_to_HSV],
"CMYColor": [XYZ_to_RGB, RGB_to_CMY],
"CMYKColor": [XYZ_to_RGB, RGB_to_CMY, CMY_to_CMYK],
},
"xyYColor": {
"xyYColor": [None],
"XYZColor": [xyY_to_XYZ],
"LabColor": [xyY_to_XYZ, XYZ_to_Lab],
"LCHabColor": [xyY_to_XYZ, XYZ_to_Lab, Lab_to_LCHab],
"LCHuvColor": [xyY_to_XYZ, XYZ_to_Luv, Luv_to_LCHuv],
"LuvColor": [xyY_to_XYZ, XYZ_to_Luv],
"sRGBColor": [xyY_to_XYZ, XYZ_to_RGB],
"HSLColor": [xyY_to_XYZ, XYZ_to_RGB, RGB_to_HSL],
"HSVColor": [xyY_to_XYZ, XYZ_to_RGB, RGB_to_HSV],
"CMYColor": [xyY_to_XYZ, XYZ_to_RGB, RGB_to_CMY],
"CMYKColor": [xyY_to_XYZ, XYZ_to_RGB, RGB_to_CMY, CMY_to_CMYK],
},
"HSLColor": {
"HSLColor": [None],
"HSVColor": [HSL_to_RGB, RGB_to_HSV],
"sRGBColor": [HSL_to_RGB],
"CMYColor": [HSL_to_RGB, RGB_to_CMY],
"CMYKColor": [HSL_to_RGB, RGB_to_CMY, CMY_to_CMYK],
"XYZColor": [HSL_to_RGB, RGB_to_XYZ],
"xyYColor": [HSL_to_RGB, RGB_to_XYZ, XYZ_to_xyY],
"LabColor": [HSL_to_RGB, RGB_to_XYZ, XYZ_to_Lab],
"LCHabColor": [HSL_to_RGB, RGB_to_XYZ, XYZ_to_Lab, Lab_to_LCHab],
"LCHuvColor": [HSL_to_RGB, RGB_to_XYZ, XYZ_to_Luv, Luv_to_LCHuv],
"LuvColor": [HSL_to_RGB, RGB_to_XYZ, XYZ_to_RGB],
},
"HSVColor": {
"HSVColor": [None],
"HSLColor": [HSV_to_RGB, RGB_to_HSL],
"sRGBColor": [HSV_to_RGB],
"CMYColor": [HSV_to_RGB, RGB_to_CMY],
"CMYKColor": [HSV_to_RGB, RGB_to_CMY, CMY_to_CMYK],
"XYZColor": [HSV_to_RGB, RGB_to_XYZ],
"xyYColor": [HSV_to_RGB, RGB_to_XYZ, XYZ_to_xyY],
"LabColor": [HSV_to_RGB, RGB_to_XYZ, XYZ_to_Lab],
"LCHabColor": [HSV_to_RGB, RGB_to_XYZ, XYZ_to_Lab, Lab_to_LCHab],
"LCHuvColor": [HSV_to_RGB, RGB_to_XYZ, XYZ_to_Luv, Luv_to_LCHuv],
"LuvColor": [HSV_to_RGB, RGB_to_XYZ, XYZ_to_RGB],
},
"CMYColor": {
"CMYColor": [None],
"CMYKColor": [CMY_to_CMYK],
"HSLColor": [CMY_to_RGB, RGB_to_HSL],
"HSVColor": [CMY_to_RGB, RGB_to_HSV],
"sRGBColor": [CMY_to_RGB],
"XYZColor": [CMY_to_RGB, RGB_to_XYZ],
"xyYColor": [CMY_to_RGB, RGB_to_XYZ, XYZ_to_xyY],
"LabColor": [CMY_to_RGB, RGB_to_XYZ, XYZ_to_Lab],
"LCHabColor": [CMY_to_RGB, RGB_to_XYZ, XYZ_to_Lab, Lab_to_LCHab],
"LCHuvColor": [CMY_to_RGB, RGB_to_XYZ, XYZ_to_Luv, Luv_to_LCHuv],
"LuvColor": [CMY_to_RGB, RGB_to_XYZ, XYZ_to_RGB],
},
"CMYKColor": {
"CMYKColor": [None],
"CMYColor": [CMYK_to_CMY],
"HSLColor": [CMYK_to_CMY, CMY_to_RGB, RGB_to_HSL],
"HSVColor": [CMYK_to_CMY, CMY_to_RGB, RGB_to_HSV],
"sRGBColor": [CMYK_to_CMY, CMY_to_RGB],
"XYZColor": [CMYK_to_CMY, CMY_to_RGB, RGB_to_XYZ],
"xyYColor": [CMYK_to_CMY, CMY_to_RGB, RGB_to_XYZ, XYZ_to_xyY],
"LabColor": [CMYK_to_CMY, CMY_to_RGB, RGB_to_XYZ, XYZ_to_Lab],
"LCHabColor": [CMYK_to_CMY, CMY_to_RGB, RGB_to_XYZ, XYZ_to_Lab, Lab_to_LCHab],
"LCHuvColor": [CMYK_to_CMY, CMY_to_RGB, RGB_to_XYZ, XYZ_to_Luv, Luv_to_LCHuv],
"LuvColor": [CMYK_to_CMY, CMY_to_RGB, RGB_to_XYZ, XYZ_to_RGB],
},
}

# We use this as a template conversion dict for each RGB color space. They
# are all identical.
_RGB_CONVERSION_DICT_TEMPLATE = {
"HSLColor": [RGB_to_HSL],
"HSVColor": [RGB_to_HSV],
"CMYColor": [RGB_to_CMY],
"CMYKColor": [RGB_to_CMY, CMY_to_CMYK],
"XYZColor": [RGB_to_XYZ],
"xyYColor": [RGB_to_XYZ, XYZ_to_xyY],
"LabColor": [RGB_to_XYZ, XYZ_to_Lab],
"LCHabColor": [RGB_to_XYZ, XYZ_to_Lab, Lab_to_LCHab],
"LCHuvColor": [RGB_to_XYZ, XYZ_to_Luv, Luv_to_LCHuv],
"LuvColor": [RGB_to_XYZ, XYZ_to_Luv],
}

# Avoid the repetition, since the conversion tables for the various RGB
# spaces are the same.
for rgb_space in _RGB_SPACES:
if rgb_space != "sRGBColor":
# This is a bit strange, but wherever we see sRGBColor in a conversion
# dict, duplicate it for each additional color space. Keeps us from
# having to manually type/update this for every single RGB space.
for key in CONVERSION_TABLE:
CONVERSION_TABLE[key][rgb_space] = CONVERSION_TABLE[key]["sRGBColor"]
# Avoid modifying the original template dict.
conv_dict = _RGB_CONVERSION_DICT_TEMPLATE.copy()
# No-ops conversions to self.
conv_dict[rgb_space] = [None]
# The new RGB color space is now a part of the conversion table.
CONVERSION_TABLE[rgb_space] = conv_dict

[docs]def convert_color(color, target_cs, through_rgb_type=sRGBColor,
target_illuminant=None, *args, **kwargs):
"""
Converts the color to the designated color space.

:param color: A Color instance to convert.
:param target_cs: The Color class to convert to. Note that this is not
an instance, but a class.
:keyword BaseRGBColor through_rgb_type: If during your conversion between
your original and target color spaces you have to pass through RGB,
this determines which kind of RGB to use. For example, XYZ->HSL.
You probably don't need to specify this unless you have a special
usage case.
:type target_illuminant: None or str
:keyword target_illuminant: If during conversion from RGB to a reflective
color space you want to explicitly end up with a certain illuminant,
pass this here. Otherwise the RGB space's native illuminant will be used.
:returns: An instance of the type passed in as ``target_cs``.
:raises: :py:exc:`colormath.color_exceptions.UndefinedConversionError`
if conversion between the two color spaces isn't possible.
"""

if isinstance(target_cs, str):
raise ValueError("target_cs parameter must be a Color object.")
if not issubclass(target_cs, ColorBase):
raise ValueError("target_cs parameter must be a Color object.")

# Find the origin color space's conversion table.
cs_table = CONVERSION_TABLE[color.__class__.__name__]
try:
# Look up the conversion path for the specified color space.
conversions = cs_table[target_cs.__name__]
except KeyError:
raise UndefinedConversionError(
color.__class__.__name__,
target_cs.__name__,
)

logger.debug('Converting %s to %s', color, target_cs)
logger.debug(' @ Conversion path: %s', conversions)

# Start with original color in case we convert to the same color space.
new_color = color

if issubclass(target_cs, BaseRGBColor):
# If the target_cs is an RGB color space of some sort, then we
# have to set our through_rgb_type to make sure the conversion returns
# the expected RGB colorspace (instead of defaulting to sRGBColor).
through_rgb_type = target_cs

# We have to be careful to use the same RGB color space that created
# an object (if it was created by a conversion) in order to get correct
# results. For example, XYZ->HSL via Adobe RGB should default to Adobe
# RGB when taking that generated HSL object back to XYZ.
# noinspection PyProtectedMember
if through_rgb_type != sRGBColor:
# User overrides take priority over everything.
# noinspection PyProtectedMember
target_rgb = through_rgb_type
elif color._through_rgb_type:
# Otherwise, a value on the color object is the next best thing,
# when available.
# noinspection PyProtectedMember
target_rgb = color._through_rgb_type
else:
# We could collapse this into a single if statement above,
# but I think this reads better.
target_rgb = through_rgb_type

# Iterate through the list of functions for the conversion path, storing
# the results in a dictionary via update(). This way the user has access
# to all of the variables involved in the conversion.
for func in conversions:
# Execute the function in this conversion step and store the resulting
# Color object.
logger.debug(' * Conversion: %s passed to %s()',
new_color.__class__.__name__, func)
logger.debug(' |->  in %s', new_color)

if func:
# This can be None if you try to convert a color to the color
# space that is already in. IE: XYZ->XYZ.
new_color = func(
new_color,
target_rgb=target_rgb,
target_illuminant=target_illuminant,
*args, **kwargs)

logger.debug(' |-< out %s', new_color)

# If this conversion had something other than the default sRGB color space
# requested,
if through_rgb_type != sRGBColor:
new_color._through_rgb_type = through_rgb_type

return new_color
```