Вы можете думать о цвете как о точке в цветовом пространстве, которое обычно состоит из трех или четырех измерений, таких как RGB или HSL.Для создания линейной интерполяции между двумя точками в этом пространстве необходимо просто следовать линии, созданной этими двумя точками.В зависимости от цветового пространства, вы получите различное продолжение цветов.
Ниже я использую matplotlib
для отображения палитр и colormath
для преобразований, которые вы можете установить с помощью pip install colormath
.Эта библиотека делает эту работу намного проще, чем она была бы в противном случае.
import colormath
import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
from colormath.color_objects import sRGBColor, HSVColor, LabColor, LCHuvColor, XYZColor, LCHabColor
from colormath.color_conversions import convert_color
def hex_to_rgb_color(hex):
return sRGBColor(*[int(hex[i + 1:i + 3], 16) for i in (0, 2 ,4)], is_upscaled=True)
def plot_color_palette(colors, subplot, title, plt_count):
ax = fig.add_subplot(plt_count, 1, subplot)
for sp in ax.spines: ax.spines[sp].set_visible(False)
for x, color in enumerate(colors):
ax.add_patch(mpl.patches.Rectangle((x, 0), 0.95, 1, facecolor=color))
ax.set_xlim((0, len(colors)))
ax.set_ylim((0, 1))
ax.set_xticks([])
ax.set_yticks([])
ax.set_aspect("equal")
plt.title(title)
def create_palette(start_rgb, end_rgb, n, colorspace):
# convert start and end to a point in the given colorspace
start = convert_color(start_rgb, colorspace).get_value_tuple()
end = convert_color(end_rgb, colorspace).get_value_tuple()
# create a set of n points along start to end
points = list(zip(*[np.linspace(start[i], end[i], n) for i in range(3)]))
# create a color for each point and convert back to rgb
rgb_colors = [convert_color(colorspace(*point), sRGBColor) for point in points]
# finally convert rgb colors back to hex
return [color.get_rgb_hex() for color in rgb_colors]
start_color = "#009392"
end_color = "#d0587e"
number_of_colors = 10
colorspaces = (sRGBColor, HSVColor, LabColor, LCHuvColor, LCHabColor, XYZColor)
start_rgb = hex_to_rgb_color(start_color)
end_rgb = hex_to_rgb_color(end_color)
fig = plt.figure(figsize=(number_of_colors, len(colorspaces)), frameon=False)
for index, colorspace in enumerate(colorspaces):
palette = create_palette(start_rgb, end_rgb, number_of_colors, colorspace)
plot_color_palette(palette, index + 1, colorspace.__name__, len(colorspaces))
plt.subplots_adjust(hspace=1.5)
plt.show()
Основная идея линейной экстраполяции заключается в простом расширении определенного векторадвумя цветами.Самая большая проблема заключается в том, что мы попадаем в «стены» цветового пространства.Например, представьте себе цветовое пространство RGB, в котором красный цвет изменяется от 0 до 255. Что должно произойти после того, как наша линия интерполяции достигнет стены 255?Цвет не может стать более красным, чем красный.Я подумал, что вы можете продолжить, рассматривая эту линию как луч света, который может «отражаться» или «отражаться» от стенок пространства rgb.
Интересно, что colormath
не кажетсяна ум, когда параметры его цветовых объектов выходят за их пределы.Продолжается создание цветного объекта с недопустимым шестнадцатеричным значением.Это может иногда происходить во время экстраполяции.Чтобы предотвратить это, мы можем либо ограничить значение RGB:
rgb_colors = np.maximum(np.minimum(rgb, [1, 1, 1]), [0, 0, 0])
, либо заставить его «отражаться», так сказать, от стены.
rgb_colors = []
for color in rgb:
c = list(color)
for i in range(3):
if c[i] > 1:
c[i] = 2 - c[i]
if c[i] < 0:
c[i] *= -1
rgb_colors.append(c)
Уравнения, приведенные выше, должныбыть самоочевиднымКогда канал RGB падает ниже нуля, переверните его знак, чтобы «отразить» от нулевой стены, и аналогично, когда он превысит 1, отразите его обратно к нулю.Вот некоторые результаты экстраполяции с использованием этого метода:
def create_palette(start_rgb, end_rgb, n, colorspace, extrapolation_length):
# convert start and end to a point in the given colorspace
start = np.array(convert_color(start_rgb, colorspace, observer=2).get_value_tuple())
mid = np.array(convert_color(end_rgb, colorspace, observer=2).get_value_tuple())
# extrapolate the end point
end = start + extrapolation_length * (mid - start)
# create a set of n points along start to end
points = list(zip(*[np.linspace(start[i], end[i], n) for i in range(3)]))
# create a color for each point and convert back to rgb
rgb = [convert_color(colorspace(*point), sRGBColor).get_value_tuple() for point in points]
# rgb_colors = np.maximum(np.minimum(rgb, [1, 1, 1]), [0, 0, 0])
rgb_colors = []
for color in rgb:
c = list(color)
for i in range(3):
if c[i] > 1:
c[i] = 2 - c[i]
if c[i] < 0:
c[i] *= -1
rgb_colors.append(c)
# finally convert rgb colors back to hex
return [sRGBColor(*color).get_rgb_hex() for color in rgb_colors]
start_color = "#009392"
end_color = "#d0587e"
number_of_colors = 11
colorspaces = (sRGBColor, HSVColor, LabColor, LCHuvColor, LCHabColor, XYZColor, LuvColor)
start_rgb = hex_to_rgb_color(start_color)
end_rgb = hex_to_rgb_color(end_color)
fig = plt.figure(figsize=(6, len(colorspaces)), frameon=False)
for index, colorspace in enumerate(colorspaces):
palette = create_palette(start_rgb, end_rgb, number_of_colors, colorspace, extrapolation_length=2)
plot_color_palette(palette, index + 1, colorspace.__name__, len(colorspaces))
plt.subplots_adjust(hspace=1.2)
plt.show()
Обратите внимание, что, поскольку Hue является круговой осью, в цветовых пространствах, таких как HSV или HSL, этопереворачивается, и если вы поместите свой конечный цвет в середину палитры, вы, скорее всего, вернетесь обратно к своему начальному цвету.
Тихо увлекательно видеть путь, по которому идут эти интерполяциив цветовом пространстве.Взглянуть.Обратите внимание на эффект отскакивания от стен:
Я мог бы в какой-то момент повернуть этов проект с открытым исходным кодом.