如何在 Python 中创建颜色渐变?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/25668828/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-18 23:27:57  来源:igfitidea点击:

How to create colour gradient in Python?

pythoncolorsgradient

提问by Dipole

I want to create a new colormap which interpolates between green and blue (or any other two colours for that matter). My goal is to get something like: gradient

我想创建一个新的颜色图,它在绿色和蓝色(或任何其他两种颜色)之间进行插值。我的目标是得到类似的东西:坡度

First of all I am really not sure if this can be done using linear interpolation of blue and green. If it can, I'm not sure how to do so, I found some documentation on using a matplotlib method that interpolates specified RGB values here

首先,我真的不确定这是否可以使用蓝色和绿色的线性插值来完成。如果可以,我不知道该怎么做,我找到了一些关于使用 matplotlib 方法插入指定 RGB 值的文档here

The real trouble is understanding how "cdict2" works below. For the example the documentation says:

真正的麻烦是理解下面的“cdict2”是如何工作的。例如,文档说:

"Example: suppose you want red to increase from 0 to 1 over the bottom half, green to do the same over the middle half, and blue over the top half. Then you would use:"

“示例:假设您希望红色在下半部分从 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: I now understand how the interpolation works, for example this will give a red to white interpolation:

编辑:我现在了解插值是如何工作的,例如这将给出红色到白色的插值:

White to red: Going down the columns of the "matrix" for each colour, in column one we have the xcoordinate of where we want the interpolation to start and end and the two other columns are the actual values for the colour value at that coordinate.

白色到红色:沿着每种颜色的“矩阵”的列向下移动,在第一列中,我们有我们希望插值开始和结束的位置的 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)]} 

It is evident that the gradient I want will be very difficult to create by interpolating in RGB space...

很明显,通过在 RGB 空间中插值来创建我想要的渐变将非常困难......

回答by Warren Weckesser

This creates a colormap controlled by a single parameter, y:

这将创建一个由单个参数控制的颜色图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

redramps up from 0 to yand then back down to 0. greenramps up from 0 to yand then is constant. bluestars at yand is constant for the first half, then ramps down to 0.

red从 0 上升到 0y然后再下降到 0。 green从 0 上升到y然后是恒定的。 上半场的blue星数为y并且保持不变,然后下降到 0。

Here's the plot with y = 0.7:

这是情节y = 0.7

bluegreen color map

蓝绿色地图

You could smooth it out by using adding another segment or two.

您可以通过添加另一个或两个部分来平滑它。

回答by jcoppens

The first element of each tuple (0, 0.25, 0.5, etc) is the place where the color should be a certain value. I took 5 samples to see the RGB components (in GIMP), and placed them in the tables. The RGB components go from 0 to 1, so I had to divide them by 255.0 to scale the normal 0-255 values.

每个元组的第一个元素(0、0.25、0.5 等)是颜色应该是某个值的地方。我取了 5 个样本来查看 RGB 分量(在 GIMP 中),并将它们放在表格中。RGB 分量从 0 到 1,因此我必须将它们除以 255.0 以缩放正常的 0-255 值。

The 5 points are a rather coarse approximation - if you want a 'smoother' appearance, use more values.

这 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()

Note that red is quite present. It's there because the center area approaches gray - where the three components are necessary.

请注意,红色非常存在。它在那里是因为中心区域接近灰色 - 三个组件是必需的。

This produces: result from the above table

这产生: 上表的结果

回答by Mark Ransom

It's obvious that your original example gradient is notlinear. Have a look at a graph of the red, green, and blue values averaged across the image:

很明显,您的原始示例梯度不是线性的。看一下图像中平均红色、绿色和蓝色值的图表:

example gradient graph

示例梯度图

Attempting to recreate this with a combination of linear gradients is going to be difficult.

尝试用线性渐变的组合重新创建它会很困难。

To me each color looks like the addition of two gaussian curves, so I did some best fits and came up with this:

对我来说,每种颜色看起来都像是两条高斯曲线的相加,所以我做了一些最佳拟合并想出了这个:

simulated

模拟的

Using these calculated values lets me create a really pretty gradient that matches yours almost exactly.

使用这些计算值可以让我创建一个非常漂亮的渐变,几乎完全匹配你的渐变。

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

重新创建的渐变

Unfortunately I don't yet know how to generalize it to arbitrary colors.

不幸的是,我还不知道如何将它概括为任意颜色。

回答by Hamy

A simple answer I have not seen yet is to just use the colour package.

我还没有看到的一个简单答案是只使用color 包

Install via pip

通过 pip 安装

pip install colour

Use as so:

像这样使用:

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

Change the inputs to any colors you want. As noted by @zelusp, this will notrestrict itself to a smooth combination of only two colors (e.g. red to blue will have yellow+green in the middle), but based on the upvotes it's clear a number of folks find this to be a useful approximation

将输入更改为您想要的任何颜色。正如@zelusp 所指出的,这不会将自身限制为仅两种颜色的平滑组合(例如,红色到蓝色中间会有黄色+绿色),但是根据投票结果,很明显很多人发现这是一个有用的近似

回答by dgc

I needed this as well, but I wanted to enter multiple arbitrary color points. Consider a heat map where you need black, blue, green... all the way up to "hot" colors. I borrowed Mark Ransom's code above and extended it to meet my needs. I'm very happy with it. My thanks to all, especially Mark.

我也需要这个,但我想输入多个任意色点。考虑一个热图,您需要黑色、蓝色、绿色……一直到“热”颜色。我借用了上面Mark Ransom的代码并对其进行了扩展以满足我的需求。我很高兴。感谢所有人,尤其是马克。

This code is neutral to the size of the image (no constants in the gaussian distribution); you can change it with the width= parameter to pixel(). It also allows tuning the "spread" (-> stddev) of the distribution; you can muddle them up further or introduce black bands by changing the spread= parameter to pixel().

此代码与图像大小无关(高斯分布中没有常数);您可以使用像素()的 width= 参数更改它。它还允许调整分布的“传播”(-> 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')

回答by Markus Dutschke

If you just need to interpolate in between 2 colors, I wrote a simple function for that. colorFadercreates you a hex color code out of two other hex color codes.

如果您只需要在 2 种颜色之间进行插值,我为此编写了一个简单的函数。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()

result:

结果:

simple color mixing in python

python中的简单颜色混合

update due to high interest:

由于兴趣高涨更新:

colorFaderworks now for rgb-colors and color-strings like 'red' or even 'r'.

colorFader现在适用于 rgb 颜色和颜色字符串,如“红色”甚至“r”。

回答by Markus Dutschke

enter image description here

在此处输入图片说明

This is a very compact way to create a colormap. See also the documentation of LinearSegmentedColormap.

这是创建颜色图的一种非常紧凑的方式。另请参阅LinearSegmentedColormap的文档。

import matplotlib as mpl
import matplotlib.pylab as plt

cmap0 = mpl.colors.LinearSegmentedColormap.from_list(
        'green2red', ['green', 'orangered'])
cmap1 = mpl.colors.LinearSegmentedColormap.from_list(
        'unevently divided', [(0, 'b'), (.3, 'gray'), (1, 'green')])

# plot
fig, axs = plt.subplots(2, 1)
norm = mpl.colors.Normalize(vmin=0, vmax=1)
cbar = axs[0].figure.colorbar(
            mpl.cm.ScalarMappable(norm=norm, cmap=cmap0),
            ax=axs[0], fraction=.1)
cbar = axs[1].figure.colorbar(
            mpl.cm.ScalarMappable(norm=norm, cmap=cmap1),
            ax=axs[1], fraction=.1)
plt.show()