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

10

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

Прежде всего, я действительно не уверен, что это можно сделать, используя линейную интерполяцию синего и зеленого. Если это возможно, я не уверен, как это сделать, я нашел документацию по использованию метода 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...

  • 0
    Проверьте эту ссылку о названных цветах. Там есть код, который показывает преобразование между методами спецификации. Я также думаю, что эта ссылка о цветных полос может помочь.
  • 0
    Как вы создали этот пример градиента? Это далеко от линейного.
Показать ещё 3 комментария
Теги:
colors
gradient

6 ответов

40

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

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

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>]

Измените входы на любые цвета, которые вы хотите

  • 3
    Как это работает с matplotlib?
  • 0
    Градиенты, полученные этим методом, проходят через другие цвета, чтобы сформировать градиент - это не настоящий градиент только двух цветов - то есть использование этого для перехода от красного к синему создаст желтый и зеленый цвета.
Показать ещё 1 комментарий
18

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

Изображение 144633

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

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

Изображение 144634

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

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)

Изображение 144635

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

  • 0
    Спасибо Марк, это здорово. Я также экспериментировал с различными кривыми, но, как вы говорите, я изо всех сил пытаюсь найти способ обобщить это для произвольных цветов. Возможно, если посмотреть на то, как могли бы помочь некоторые из стандартных градиентов python, созданных здесь, wiki.scipy.org/Cookbook/Matplotlib/Show_colormaps , хотя я не могу найти код, который показывает, как они были созданы.
5

Первый элемент каждого кортежа (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()

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

Это дает: Изображение 144636

  • 0
    +1 за нестандартное мышление, я никогда не думал о том, чтобы на самом деле пробовать компоненты. Просто из любопытства, почему в словаре должен быть 3-й столбец для каждого компонента, если первое значение в каждом кортеже - это позиция, а второе - это значение. Что представляет собой 3-е?
  • 0
    @Jack: два значения допускают разрыв в цветовой карте. Если точка равна (x0, yleft, yright) , yleft приближается к yleft при увеличении x до x0 и приближается к yright при уменьшении x до x0 .
Показать ещё 1 комментарий
3

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

Этот код нейтрален по размеру изображения (без констант в гауссовском распределении); вы можете изменить его с помощью параметра 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')
3

Это создает цветовой код, управляемый одним параметром, 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:

Изображение 144637

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

  • 0
    Спасибо за этот замечательный пример. Как вы указали, потребуется некоторое сглаживание. Прямо сейчас у нас есть только две кусочно-линейные функции, которые «зеркально отражены» примерно на полпути. Я предполагаю, что идеальным сценарием было бы иметь две нелинейные функции, также отраженные относительно половины пути и пересекающиеся близко к нулю?
  • 0
    Да что-то подобное. У @jcoppens есть более тонкий пример.
1

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

import matplotlib.pyplot as plt
import numpy as np

def fadeColor(c1,c2,mix=0): #fade (linear interpolate) from color c1 (at mix=0) to c2 (mix=1)
    assert len(c1)==len(c2)
    assert mix>=0 and mix<=1, 'mix='+str(mix)
    rgb1=np.array([int(c1[ii:ii+2],16) for ii in range(1,len(c1),2)])
    rgb2=np.array([int(c2[ii:ii+2],16) for ii in range(1,len(c2),2)])   
    rgb=((1-mix)*rgb1+mix*rgb2).astype(int)
    #cOld='#'+''.join([hex(a)[2:] for a in rgb])
    #print(11,[hex(a)[2:].zfill(2) for a in rgb])
    c='#'+('{:}'*3).format(*[hex(a)[2:].zfill(2) for a in rgb])
    #print(rgb1, rgb2, rgb, cOld, c)
    return c

fig, ax = plt.subplots(figsize=(8, 5))
c1='#1f77b4' #blue
c2='#2ca02c' #green

# another set of colors 
#c1='#ff0000' #red 
#c2='#001dff' #blue

n=500
for x in range(n+1):
    ax.axvline(x, color=fadeColor(c1,c2,x/n), linewidth=4) 

plt.show()

результат:

Изображение 144638

  • 1
    это терпит неудачу для c1 = '# ff0000' #red и c2 = '# 001dff' #blue
  • 0
    @PaariVendhan: спасибо и исправил
Показать ещё 3 комментария

Ещё вопросы

Сообщество Overcoder
Наверх
Меню