This was a small for fun experiment done on a lazy Saturday. Check the original at https://github.com/dreavjr/colorinterp.
(c̸) 2017 Eduardo Valle. This software is in Public Domain; it is provided "as is" without any warranties. Please check https://github.com/dreavjr/colorinterp/blob/master/LICENSE.md
In [19]:
import re
import colorlover as cl
from IPython.display import HTML
import numpy as np
num_original = 4
num_interpolated = 19
colors = cl.scales[str(num_original).strip()]['qual']['Set1']
HTML(cl.to_html( colors ))
Out[19]:
In [20]:
colors_list = [ re.search('rgb\(([0-9]+),([0-9]+),([0-9]+)\)', c).groups() for c in colors ]
colors_array = np.asarray([ [ float(p) for p in c ] for c in colors_list ])
# DEBUG : forces sRGB correspondence on my hardware
# colors = np.asarray([ [172, 205, 229], [59, 117, 184], [181, 225, 128] ], dtype='float')
colors_array
Out[20]:
In [21]:
Scale = 255.0
srgb = colors_array/Scale
srgb_thres=0.04045
srgb_linear = np.empty(srgb.shape)
srgb_linear[srgb<=srgb_thres] = (srgb/12.92)[srgb<=srgb_thres]
srgb_linear[srgb>srgb_thres] = np.power(((srgb+0.055)/1.055),2.4)[srgb>srgb_thres]
srgb2xyz = np.asarray([ [ 0.4124, 0.3576, 0.1805 ],
[ 0.2126, 0.7152, 0.0722 ],
[ 0.0193, 0.1192, 0.9505 ] ])
# xyz = srgb2xyz.dot(srgb_linear[i])
xyz = srgb_linear.dot(srgb2xyz.T)
xyz
Out[21]:
The formulas copied from Wikipedia article on L*a*b* and Bruce Lind Bloom's page on color conversion.
In [22]:
Lab_thresh = 216.0/24389.0
Lab_kappa = 24389.0/27.0
def xyz2lab(t):
t2 = np.empty(t.shape)
cond = t<=Lab_thresh
t2[cond] = (Lab_kappa*t+16.0)[cond]
t2[np.logical_not(cond)] = np.power(t,(1.0/3.0))[np.logical_not(cond)]
return t2
WhiteD65 = np.asarray([ 0.95047, 1.00000, 1.08883 ])
xyz_ratio = xyz/WhiteD65
pre_lab = xyz2lab(xyz_ratio)
ell = 116.0*pre_lab[:,1] - 16.0
a = 500.0*(pre_lab[:,0]-pre_lab[:,1])
b = 200.0*(pre_lab[:,1]-pre_lab[:,2])
lab = np.asarray([ell, a, b]).T
lab
Out[22]:
In [23]:
originals = np.arange(ell.shape[0])
interpolated = np.linspace(originals[0], originals[-1], num=num_interpolated)
ell_interp = np.interp(interpolated, originals, ell)
a_interp = np.interp(interpolated, originals, a)
b_interp = np.interp(interpolated, originals, b)
# DEBUG: disables interpolation
# ell_interp = ell
# a_interp = a
# b_interp = b
# Demonstration: also interpolates in the rgb space ---
rgb = colors_array.T
red_interp = np.interp(interpolated, originals, rgb[0])
green_interp = np.interp(interpolated, originals, rgb[1])
blue_interp = np.interp(interpolated, originals, rgb[2])
rgb_interp = np.asarray([red_interp, green_interp, blue_interp]).T
# --- this is not needed for the perceptual interpolation, only for comparison
np.asarray([ell_interp, a_interp, b_interp]).T
Out[23]:
The formulas copied from Wikipedia article on L*a*b*. and Bruce Lind Bloom's page on color conversion.
In [24]:
def lab2xyz(t):
t2 = np.empty(t.shape)
cond =t<=Lab_thresh
t2[cond] = ((t-16.0)/Lab_kappa)[cond]
t2[np.logical_not(cond)] = np.power(t,3.0)[np.logical_not(cond)]
return t2
ell_interp_scaled = (ell_interp+16.0)/116.0
a_interp_scaled = a_interp/500.0
b_interp_scaled = b_interp/200.0
pre_x_interp = lab2xyz(ell_interp_scaled+a_interp_scaled)
pre_y_interp = lab2xyz(ell_interp_scaled)
pre_z_interp = lab2xyz(ell_interp_scaled-b_interp_scaled)
pre_xyz_interp = np.asarray([pre_x_interp, pre_y_interp, pre_z_interp]).T
xyz_interp = pre_xyz_interp*WhiteD65
xyz_interp #, xyz
Out[24]:
In [25]:
xyz2srgb = np.asarray([ [ 3.2406, -1.5372, -0.4986 ],
[ -0.9689, 1.8758, 0.0415 ],
[ 0.0557, -0.2040, 1.0570 ] ])
# srgb_linear_interp = xyz2srgb.dot(xyz_interp[i])
srgb_linear_interp = xyz_interp.dot(xyz2srgb.T)
srgb_interp = np.empty(srgb_linear_interp.shape)
srgb_interp[srgb_linear_interp<=0.0031308] = (12.92*srgb_linear_interp)[srgb_linear_interp<=0.0031308]
srgb_interp[srgb_linear_interp>0.0031308] = ((1.055)*np.power(srgb_linear_interp,(1.0/2.4)) - 0.055)[srgb_linear_interp>0.0031308]
srgb_interp[srgb_interp<0.0] = 0.0
srgb_interp[srgb_interp>1.0] = 1.0
srgb_device_interp = np.round(srgb_interp*Scale)
# print(srgb_linear_interp, srgb_interp, srgb_device_interp, colors, sep='\n\n')
srgb_device_interp
Out[25]:
In [26]:
# This --- finally --- is the desired output
colors_interpolated = [ 'rgb(%d,%d,%d)' % tuple(c) for c in srgb_device_interp ]
after_hsl_and_back = cl.to_rgb(cl.to_hsl(colors_interpolated))
rgb_interpolated = [ 'rgb(%d,%d,%d)' % tuple(c) for c in rgb_interp ]
print(colors)
HTML(cl.to_html( colors ))
Out[26]:
In [27]:
print(colors_interpolated)
print(after_hsl_and_back)
print(rgb_interpolated)
HTML(cl.to_html(after_hsl_and_back))
HTML('<div>'+
'<div style="height:20px;width:110px;display:inline-block;">Int. on L*a*b*:</div>'+
cl.to_html( colors_interpolated )+'<br/>'+
'<div style="height:20px;width:110px;display:inline-block;">HSL and back:</div>'+
cl.to_html( after_hsl_and_back )+'<br/>'+
'<div style="height:20px;width:110px;display:inline-block;">Int. on RGB:</div>'+
cl.to_html( rgb_interpolated ))
Out[27]: