Как создать градиент цвета в 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')