Как создать градиент цвета в Python?

Я хочу создать новую цветочную карту, которая интерполирует между зеленым и синим (или любые другие два цвета, если на то пошло). Моя цель - получить что-то вроде: gradient

Прежде всего, я действительно не уверен, что это можно сделать, используя линейную интерполяцию синего и зеленого. Если это возможно, я не уверен, как это сделать, я нашел документацию по использованию метода matplotlib, который интерполирует указанные значения RGB здесь

Настоящая проблема заключается в понимании того, как работает "cdict2". Например, в документации говорится:

"Пример: предположим, что вы хотите, чтобы красный увеличился от 0 до 1 в нижней половине, зеленый, чтобы сделать то же самое в средней половине, а синий - в верхней половине. Затем вы будете использовать:"

from matplotlib import pyplot as plt
import matplotlib 
import numpy as np

plt.figure()
a=np.outer(np.arange(0,1,0.01),np.ones(10))
cdict2 = {'red':   [(0.0,  0.0, 0.0),
                   (0.5,  1.0, 1.0),
                   (1.0,  1.0, 1.0)],
         'green': [(0.0,  0.0, 0.0),
                   (0.25, 0.0, 0.0),
                   (0.75, 1.0, 1.0),
                   (1.0,  1.0, 1.0)],
         'blue':  [(0.0,  0.0, 0.0),
                   (0.5,  0.0, 0.0),
                   (1.0,  1.0, 1.0)]} 
my_cmap2 = matplotlib.colors.LinearSegmentedColormap('my_colormap2',cdict2,256)
plt.imshow(a,aspect='auto', cmap =my_cmap2)                   
plt.show()

EDIT: теперь я понимаю, как работает интерполяция, например, это даст интерполяцию от красного до белого:

От белого до красного: переходя по столбцам "матрицы" для каждого цвета, в первом столбце мы имеем xкоординат, где мы хотим, чтобы интерполяция начиналась и заканчивалась, а два других столбца - это фактические значения для значения цвета на этой координате.

cdict2 = {'red':   [(0.0,  1.0, 1.0),
                    (1.0,  1.0, 1.0),
                    (1.0,  1.0, 1.0)],
         'green': [(0.0,  1.0, 1.0),
                   (1.0, 0.0, 0.0),
                   (1.0,  0.0, 0.0)],
     'blue':  [(0.0,  1.0, 1.0),
               (1.0,  0.0, 0.0),
               (1.0,  0.0, 0.0)]} 

Очевидно, что желаемый градиент будет очень сложно создать путем интерполяции в пространстве RGB...

Ответ 1

Простой ответ, который я еще не видел, это просто использовать цветовой пакет.

Установить через пип

pip install colour

Используйте как есть:

from colour import Color
red = Color("red")
colors = list(red.range_to(Color("green"),10))

# colors is now a list of length 10
# Containing: 
# [<Color red>, <Color #f13600>, <Color #e36500>, <Color #d58e00>, <Color #c7b000>, <Color #a4b800>, <Color #72aa00>, <Color #459c00>, <Color #208e00>, <Color green>]

Измените входные данные на любые цвета, которые вы хотите. Как отмечает @zelusp, это не будет ограничивать себя гладкой комбинацией только двух цветов (например, красный с синим будет иметь желтый + зеленый в середине), но, исходя из откликов, ясно, что многие люди считают, что это полезное приближение

Ответ 2

Очевидно, что ваш оригинальный примерный градиент не является линейным. Посмотрите на график значений красного, зеленого и синего, усредненных по изображению:

example gradient graph

Попытка воссоздать это с помощью комбинации линейных градиентов будет сложной.

Для меня каждый цвет выглядит как добавление двух гауссовских кривых, поэтому я сделал несколько лучших приемов и придумал это:

simulated

Используя эти рассчитанные значения, я могу создать действительно красивый градиент, который точно соответствует вашим.

import math
from PIL import Image
im = Image.new('RGB', (604, 62))
ld = im.load()

def gaussian(x, a, b, c, d=0):
    return a * math.exp(-(x - b)**2 / (2 * c**2)) + d

for x in range(im.size[0]):
    r = int(gaussian(x, 158.8242, 201, 87.0739) + gaussian(x, 158.8242, 402, 87.0739))
    g = int(gaussian(x, 129.9851, 157.7571, 108.0298) + gaussian(x, 200.6831, 399.4535, 143.6828))
    b = int(gaussian(x, 231.3135, 206.4774, 201.5447) + gaussian(x, 17.1017, 395.8819, 39.3148))
    for y in range(im.size[1]):
        ld[x, y] = (r, g, b)

recreated gradient

К сожалению, я еще не знаю, как обобщить его на произвольные цвета.

Ответ 3

Если вам просто нужно интерполировать между двумя цветами, я написал для этого простую функцию. colorFader создает вам шестнадцатеричный цветовой код из двух других шестнадцатеричных цветовых кодов.

import matplotlib as mpl
import matplotlib.pyplot as plt
import numpy as np

def colorFader(c1,c2,mix=0): #fade (linear interpolate) from color c1 (at mix=0) to c2 (mix=1)
    c1=np.array(mpl.colors.to_rgb(c1))
    c2=np.array(mpl.colors.to_rgb(c2))
    return mpl.colors.to_hex((1-mix)*c1 + mix*c2)

c1='#1f77b4' #blue
c2='green' #green
n=500

fig, ax = plt.subplots(figsize=(8, 5))
for x in range(n+1):
    ax.axvline(x, color=colorFader(c1,c2,x/n), linewidth=4) 
plt.show()

результат:

simple color mixing in python

обновление из-за высокого интереса:

colorFader теперь работает для rgb-цветов и цветовых строк, таких как 'red' или даже 'r'.

Ответ 4

Первый элемент каждого кортежа (0, 0,25, 0,5 и т.д.) - это место, где цвет должен быть определенным значением. Я взял 5 образцов, чтобы увидеть компоненты RGB (в GIMP) и поместил их в таблицы. Компоненты RGB идут от 0 до 1, поэтому мне пришлось разделить их на 255,0, чтобы масштабировать нормальные значения 0-255.

5 баллов - довольно грубое приближение - если вы хотите "более гладкий" вид, используйте больше значений.

from matplotlib import pyplot as plt
import matplotlib 
import numpy as np

plt.figure()
a=np.outer(np.arange(0,1,0.01),np.ones(10))
fact = 1.0/255.0
cdict2 = {'red':  [(0.0,   22*fact,  22*fact),
                   (0.25, 133*fact, 133*fact),
                   (0.5,  191*fact, 191*fact),
                   (0.75, 151*fact, 151*fact),
                   (1.0,   25*fact,  25*fact)],
         'green': [(0.0,   65*fact,  65*fact),
                   (0.25, 182*fact, 182*fact),
                   (0.5,  217*fact, 217*fact),
                   (0.75, 203*fact, 203*fact),
                   (1.0,   88*fact,  88*fact)],
         'blue':  [(0.0,  153*fact, 153*fact),
                   (0.25, 222*fact, 222*fact),
                   (0.5,  214*fact, 214*fact),
                   (0.75, 143*fact, 143*fact),
                   (1.0,   40*fact,  40*fact)]} 
my_cmap2 = matplotlib.colors.LinearSegmentedColormap('my_colormap2',cdict2,256)
plt.imshow(a,aspect='auto', cmap =my_cmap2)                   
plt.show()

Обратите внимание, что красный цвет присутствует. Это там, потому что центральная область приближается к серой - где нужны три компонента.

Это дает: result from the above table

Ответ 5

Это создает цветовой код, управляемый одним параметром, y:

from matplotlib.colors import LinearSegmentedColormap


def bluegreen(y):
    red = [(0.0, 0.0, 0.0), (0.5, y, y), (1.0, 0.0, 0.0)]
    green = [(0.0, 0.0, 0.0), (0.5, y, y), (1.0, y, y)]
    blue = [(0.0, y, y), (0.5, y, y),(1.0,0.0,0.0)]
    colordict = dict(red=red, green=green, blue=blue)
    bluegreenmap = LinearSegmentedColormap('bluegreen', colordict, 256)
    return bluegreenmap

red нарастает от 0 до y, а затем возвращается к 0. green увеличивается с 0 на y, а затем остается постоянным. blue звезды в y и постоянны для первой половины, затем спускаются до 0.

Здесь график с y = 0.7:

bluegreen color map

Вы можете сгладить его, добавив еще один сегмент или два.

Ответ 6

Мне тоже это нужно, но я хотел ввести несколько произвольных цветовых точек. Рассмотрим карту тепла, где вам нужны черные, синие, зеленые... вплоть до "горячих" цветов. Я позаимствовал код Марка Рэнсом выше и расширил его, чтобы удовлетворить мои потребности. Я очень доволен этим. Спасибо всем, особенно Марк.

Этот код нейтрален по размеру изображения (без констант в гауссовском распределении); вы можете изменить его с помощью параметра width = в pixel(). Он также позволяет настраивать "распространение" (-> stddev) дистрибутива; вы можете запутать их дальше или ввести черные полосы, изменив параметр spread = на pixel().

#!/usr/bin/env python

import math
from PIL import Image
im = Image.new('RGB', (3000, 2000))
ld = im.load()

# A map of rgb points in your distribution
# [distance, (r, g, b)]
# distance is percentage from left edge
heatmap = [
    [0.0, (0, 0, 0)],
    [0.20, (0, 0, .5)],
    [0.40, (0, .5, 0)],
    [0.60, (.5, 0, 0)],
    [0.80, (.75, .75, 0)],
    [0.90, (1.0, .75, 0)],
    [1.00, (1.0, 1.0, 1.0)],
]

def gaussian(x, a, b, c, d=0):
    return a * math.exp(-(x - b)**2 / (2 * c**2)) + d

def pixel(x, width=100, map=[], spread=1):
    width = float(width)
    r = sum([gaussian(x, p[1][0], p[0] * width, width/(spread*len(map))) for p in map])
    g = sum([gaussian(x, p[1][1], p[0] * width, width/(spread*len(map))) for p in map])
    b = sum([gaussian(x, p[1][2], p[0] * width, width/(spread*len(map))) for p in map])
    return min(1.0, r), min(1.0, g), min(1.0, b)

for x in range(im.size[0]):
    r, g, b = pixel(x, width=3000, map=heatmap)
    r, g, b = [int(256*v) for v in (r, g, b)]
    for y in range(im.size[1]):
        ld[x, y] = r, g, b

im.save('grad.png')