web-dev-qa-db-fra.com

Comment créer un dégradé de couleurs en Python?

Je veux créer une nouvelle palette de couleurs qui interpole entre le vert et le bleu (ou deux autres couleurs d'ailleurs). Mon objectif est d'obtenir quelque chose comme: gradient

Tout d'abord, je ne sais vraiment pas si cela peut être fait en utilisant une interpolation linéaire du bleu et du vert. Si c'est le cas, je ne sais pas comment le faire, j'ai trouvé de la documentation sur l'utilisation d'une méthode matplotlib qui interpole les valeurs RVB spécifiées ici

Le vrai problème est de comprendre comment "cdict2" fonctionne ci-dessous. Pour l'exemple, la documentation dit:

"Exemple: supposons que vous vouliez que le rouge passe de 0 à 1 sur la moitié inférieure, le vert pour faire de même sur la moitié centrale et le bleu sur la moitié supérieure. Ensuite, vous utiliseriez:"

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: Je comprends maintenant comment fonctionne l'interpolation, par exemple cela donnera une interpolation du rouge au blanc:

Blanc à rouge: En descendant les colonnes de la "matrice" pour chaque couleur, dans la première colonne, nous avons la coordonnée x de l'endroit où nous voulons que l'interpolation commence et se termine et les deux autres colonnes sont les valeurs réelles de la valeur de couleur à cette coordonnée .

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

Il est évident que le dégradé que je veux sera très difficile à créer en interpolant dans l'espace RVB ...

23
Dipole

Une réponse simple que je n'ai pas encore vue est d'utiliser simplement le paquet de couleurs .

Installer via pip

pip install colour

Utilisez comme suit:

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

Remplacez les entrées par les couleurs de votre choix. Comme indiqué par @zelusp, cela ne se limitera pas à une combinaison fluide de seulement deux couleurs (par exemple, le rouge au bleu aura le jaune + le vert au milieu ), mais sur la base des votes positifs, il est clair qu'un certain nombre de personnes trouvent que c'est une approximation utile

56
Hamy

Il est évident que votre exemple de gradient d'origine est pas linéaire. Jetez un œil à un graphique des valeurs rouges, vertes et bleues moyennes sur l'image:

example gradient graph

Tenter de recréer cela avec une combinaison de dégradés linéaires va être difficile.

Pour moi, chaque couleur ressemble à l'ajout de deux courbes gaussiennes, j'ai donc fait les meilleurs ajustements et j'ai trouvé ceci:

simulated

L'utilisation de ces valeurs calculées me permet de créer un très joli dégradé qui correspond presque exactement au vôtre.

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

Malheureusement, je ne sais pas encore comment le généraliser à des couleurs arbitraires.

27
Mark Ransom

Si vous avez juste besoin d'interpoler entre 2 couleurs , j'ai écrit une fonction simple pour cela. colorFader vous crée un code de couleur hexadécimal à partir de deux autres codes de couleur hexadécimaux.

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

résultat:

simple color mixing in python

mise à jour en raison d'un intérêt élevé:

colorFader fonctionne maintenant pour les couleurs RVB et les chaînes de couleurs comme 'rouge' ou même 'r'.

10
Markus Dutschke

Le premier élément de chaque tuple (0, 0,25, 0,5, etc.) est l'endroit où la couleur doit avoir une certaine valeur. J'ai pris 5 échantillons pour voir les composants RVB (dans GIMP) et les ai placés dans les tableaux. Les composants RVB passent de 0 à 1, j'ai donc dû les diviser par 255,0 pour mettre à l'échelle les valeurs normales de 0 à 255.

Les 5 points sont une approximation assez grossière - si vous voulez une apparence "plus lisse", utilisez plus de valeurs.

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

A noter que le rouge est bien présent. C'est là parce que la zone centrale s'approche du gris - où les trois composants sont nécessaires.

Cela produit: result from the above table

6
jcoppens

Cela crée une palette de couleurs contrôlée par un seul paramètre, 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 monte de 0 à y puis redescend à 0. green monte de 0 à y puis est constant. blue étoiles à y et est constant pour la première moitié, puis descend à 0.

Voici l'intrigue avec y = 0.7:

bluegreen color map

Vous pouvez l'aplanir en ajoutant un ou deux segments supplémentaires.

5
Warren Weckesser

J'en avais également besoin, mais je voulais saisir plusieurs points de couleur arbitraires. Considérez une carte thermique où vous avez besoin de noir, bleu, vert ... jusqu'aux couleurs "chaudes". J'ai emprunté le code de Mark Ransom ci-dessus et l'ai étendu pour répondre à mes besoins. J'en suis très content. Merci à tous, en particulier à Mark.

Ce code est neutre par rapport à la taille de l'image (pas de constantes dans la distribution gaussienne); vous pouvez le changer avec le paramètre width = en pixel (). Il permet également de régler la "propagation" (-> stddev) de la distribution; vous pouvez les embrouiller davantage ou introduire des bandes noires en changeant le paramètre spread = en 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')
4
dgc