Учитывая начальный цвет и средний цвет, как получить остальные цвета?(Python) - PullRequest
3 голосов
/ 07 марта 2019

Я пытаюсь создать цветовую палитру из двух цветов: чирок и роза

Я нашел этот сайт: https://learnui.design/tools/data-color-picker.html#palette Который мог сделать половину того, что я искал, поэтому я хотел попробовать сделать это в Python, используя matplotlib, seaborn, palettable и / или colorsys.

Есть ли способ интерполировать, какие следующие цвета будут заданы серией цветов в градиенте?

Например, с сайта я дал start_color и end_color. Это дало мне 6 цветов в диапазоне от start_color до end_color. Есть ли способ сделать это, кроме как сделать end_color middle_color и продолжить градиент?

from palettable.cartocolors.diverging import TealRose_7
import matplotlib as mpl
import seaborn as sns

start_color = "#009392"
end_color = "#d0587e"

# https://learnui.design/tools/data-color-picker.html#palette
colors = ['#009392', '#0091b2', '#2b89c8', '#7c7ac6', '#b366ac', '#d0587e']

sns.palplot(colors)

enter image description here

Я бы хотел, чтобы чирок start_color оставался первым цветом, сделал розу end_color middle_color (между 3 и 4), а затем обработал цветовую палитру для получения 6 полных цветов.

Я собирался попытаться получить значения RGB, а затем выполнить какое-то моделирование, чтобы выяснить, куда оно пойдет, но я думаю, что, возможно, есть более простой способ сделать это.

Ответы [ 3 ]

2 голосов
/ 07 марта 2019

Вы можете думать о цвете как о точке в цветовом пространстве, которое обычно состоит из трех или четырех измерений, таких как 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()

enter image description here

Основная идея линейной экстраполяции заключается в простом расширении определенного векторадвумя цветами.Самая большая проблема заключается в том, что мы попадаем в «стены» цветового пространства.Например, представьте себе цветовое пространство 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()

enter image description here

Обратите внимание, что, поскольку Hue является круговой осью, в цветовых пространствах, таких как HSV или HSL, этопереворачивается, и если вы поместите свой конечный цвет в середину палитры, вы, скорее всего, вернетесь обратно к своему начальному цвету.


Тихо увлекательно видеть путь, по которому идут эти интерполяциив цветовом пространстве.Взглянуть.Обратите внимание на эффект отскакивания от стен:

enter image description here enter image description here enter image description here enter image description here enter image description here enter image description here enter image description here enter image description here

Я мог бы в какой-то момент повернуть этов проект с открытым исходным кодом.

1 голос
/ 07 марта 2019

Вот решение, которое просто выполняет простую интерполяцию между цветами в цветовом пространстве RGB. Есть проблема с этим ... евклидово расстояние между цветами в RGB не имеет прямого отношения к человеческому восприятию. Так что ... если вы действительно хотите быть вежливым (в хорошем смысле) в том, как воспринимаются ваши цвета, вы можете перейти в Lab или HCL, чтобы сделать что-то подобное.

Это не лучшие рефери, но они предлагают кое-что об этом явлении, я думаю ...

Итак ... с учетом этого предостережения ... вот решение в RGB, но лучше сделать это в Lab или HCL. :)

Помощники / Настройка

import numpy as np

# hex (string) to rgb (tuple3)
def hex2rgb(hex):
    hex_cleaned = hex.lstrip('#')
    return tuple(int(hex_cleaned[i:i+2], 16) for i in (0, 2 ,4))

# rgb (tuple3) to hex (string)
def rgb2hex(rgb):
    return '#' + ''.join([str('0' + hex(hh)[2:])[-2:] for hh in rgb])

# weighted mix of two colors in RGB space (takes and returns hex values)
def color_mixer(hex1, hex2, wt1=0.5):
    rgb1 = hex2rgb(hex1)
    rgb2 = hex2rgb(hex2)
    return rgb2hex(tuple([int(wt1 * tup[0] + (1.0 - wt1) * tup[1]) for tup in zip(rgb1, rgb2)]))

# create full palette
def create_palette(start_color, mid_color, end_color, num_colors):
    # set up steps
    # will create twice as many colors as asked for
    # to allow an explicit "mid_color" with both even and odd number of colors
    num_steps = num_colors  
    steps = np.linspace(0, 1, num_steps)[::-1]

    # create two halves of color values
    pt1 = [color_mixer(first_color, mid_color, wt) for wt in steps]
    pt2 = [color_mixer(mid_color,  last_color, wt) for wt in steps[1:]]

    # combine and subsample to get back down to 'num_colors'
    return (pt1 + pt2)[::2]

Создание палитры

# the 3 colors you specified
first_color = '#009392'
last_color  = '#d0587e'
mid_color   = color_mixer('#2b89c8', '#7c7ac6')

# create hex colors
result = create_pallette(first_color, mid_color, last_color, 5)

result
# ['#009392', '#298aac', '#5381c7', '#916ca2', '#d0587e']

, который выглядит так:

enter image description here

0 голосов
/ 07 марта 2019

Если вы используете цвета RGB, вы можете найти вектор и масштабировать его:

#009392 = (0, 147, 146)
#d0587e = (208, 88, 126)

# slope
(208, 88, 126) - (0, 147, 146) = (208, -59, -20)


k = 4
for n in range(1,k+1):
    color = (0, 147, 146) + (n/k*(208, -59, -20)) 

например (0, 147, 146) + (2/4 * (208, -59, -20))= (104, 117,5, 136)

...