Darken or lighten a color in matplotlib

PythonMatplotlibColors

Python Problem Overview


Say I have a color in Matplotlib. Maybe it's a string ('k') or an rgb tuple ((0.5, 0.1, 0.8)) or even some hex (#05FA2B). Is there a command / convenience function in Matplotlib that would allow me to darken (or lighten) that color.

I.e. is there matplotlib.pyplot.darken(c, 0.1) or something like that? I guess what I'm hoping for is something that, behind the scenes, would take a color, convert it to HSL, then either multiply the L value by some given factor (flooring at 0 and capping at 1) or explicitly set the L value to a given value and return the modified color.

Python Solutions


Solution 1 - Python

Here is a function from my gist to lighten any color that I think will work with any color format known to matplotlib. I think setting an amount > 1 might darken too.

def lighten_color(color, amount=0.5):
    """
    Lightens the given color by multiplying (1-luminosity) by the given amount.
    Input can be matplotlib color string, hex string, or RGB tuple.

    Examples:
    >> lighten_color('g', 0.3)
    >> lighten_color('#F034A3', 0.6)
    >> lighten_color((.3,.55,.1), 0.5)
    """
    import matplotlib.colors as mc
    import colorsys
    try:
        c = mc.cnames[color]
    except:
        c = color
    c = colorsys.rgb_to_hls(*mc.to_rgb(c))
    return colorsys.hls_to_rgb(c[0], 1 - amount * (1 - c[1]), c[2])

EDIT: Indeed, it does darken as well as lighten:

import matplotlib.pyplot as plt
import numpy as np

xs = np.linspace(-1, 1, 100)
plt.plot(xs, 0 * xs, color='b', lw=3)
plt.plot(xs, xs**2, color=lighten_color('b', 0.4), lw=3)
plt.plot(xs, -xs**2, color=lighten_color('b', 1.6), lw=3)

image with example of darken and lighten

Edit 2: Removed un-needed numpy dependency in the function.

Edit 3: Function modified with improvements from @FLekschas

def adjust_lightness(color, amount=0.5):
    import matplotlib.colors as mc
    import colorsys
    try:
        c = mc.cnames[color]
    except:
        c = color
    c = colorsys.rgb_to_hls(*mc.to_rgb(c))
    return colorsys.hls_to_rgb(c[0], max(0, min(1, amount * c[1])), c[2])

Solution 2 - Python

Using only colorsys which is part of the python standard library it is possible to scale the lighntess with just two lines of code

If you still want the option to pass non rgb values like colornames or HEX you can simply use matplotlib.colors.ColorConverter.to_rgb("#ff0000").

This method scales the lightness of an rgb color

import colorsys

def scale_lightness(rgb, scale_l):
    # convert rgb to hls
    h, l, s = colorsys.rgb_to_hls(*rgb)
    # manipulate h, l, s values and return as rgb
    return colorsys.hls_to_rgb(h, min(1, l * scale_l), s = s)

A simple demonstration. For more details have a look at the old example.

import matplotlib
import seaborn as sns

color = matplotlib.colors.ColorConverter.to_rgb("navy")
rgbs = [scale_lightness(color, scale) for scale in [0, .5, 1, 1.5, 2]]
sns.palplot(rgbs)

enter image description here


Old Answer

There is a Seaborn method, that easily lets you manipulate the lightness. seaborn.set_hls_values() takes a color as RGB-Tuple, HEX or HTML-name and lets you manipulate the hue, lightness and saturation.

The scaled lightness should be between 0 and 1, where 1 < scale increases the lightness and 0 ≤ scale < 1 darkens the color.

from colorsys import rgb_to_hls
import seaborn as sns

color = (1.0, 0.0, 0.0)     # RGB

print(f"Input color:    \t Lightness:  {rgb_to_hls(*color)[1]: .2g}\t RGB:  {color}")

rgbs = []
for scale in [0, .5, 1, 1.5, 2]:

    # scale the lightness (The values should be between 0 and 1)
    lightness = min(1, rgb_to_hls(*color)[1] * scale)

    # manipulate h, l, s channel of a rgb color
    rgb = sns.set_hls_values(color = color, h = None, l = lightness, s = None)

    print(f"Scale factor: {scale: .2g}\t Lightness:  {lightness: .2g}  \t RGB:  {rgb}")
    rgbs.append(rgb)

sns.palplot(rgbs)

In case you want to use other than RGB codes you need to convert your color to RGB. You could use the following command.

color = "red"               # HTML name
color = "#ff0000"           # HEX
color = matplotlib.colors.ColorConverter.to_rgb(color)

The returned values are RGB-Tuples:

Input color:    	Lightness:   0.5	RGB:  (1.0, 0.0, 0.0)

Scale factor:  0	Lightness:   0  	RGB:  (0.0, 0.0, 0.0)  # Black
Scale factor:  0.5	Lightness:   0.25  	RGB:  (0.5, 0.0, 0.0)
Scale factor:  1	Lightness:   0.5  	RGB:  (1.0, 0.0, 0.0)  # Unchanged
Scale factor:  1.5	Lightness:   0.75  	RGB:  (1.0, 0.5, 0.5)
Scale factor:  2	Lightness:   1  	RGB:  (1.0, 1.0, 1.0)  # White

enter image description here enter image description here enter image description here enter image description here enter image description here

EDIT:

Thanks @fhgd! I changed the code to actually scale the lightness and not just set it.

Solution 3 - Python

A few months ago I had to solve that problem. The idea was for the user to choose a color (any color) and the software automatically generated a colormap (this was part of a package for scientific purposes).

In any case here is the code that I used to achieve it. You won't need most of what the object does but it will give you what you ask:

import math

class Color():
    def __init__(self, color, fmt='rgb'):
        self.__initialize__(color, fmt)

    def __initialize__(self, color, fmt='rgb'):
        if fmt == 'rgb':
            self.rgb = (int(color[0]), int(color[1]), int(color[2]))
            self.hex = self._rgb2hex(self.rgb)
            self.hsv = self._rgb2hsv(self.rgb)
            self.rgb0 = self.rgb[0] / 255, self.rgb[1] / 255, self.rgb[2] / 255
        elif fmt == 'rgb0':
            self.rgb = (int(color[0] * 255), int(color[1] * 255), int(color[2] * 255))
            self.hex = self._rgb2hex(self.rgb)
            self.hsv = self._rgb2hsv(self.rgb)
            self.rgb0 = (color[0], color[1], color[2])
        elif fmt == 'hex':
            self.hex = color
            self.rgb = self._hex2rgb(self.hex)
            self.hsv = self._rgb2hsv(self.rgb)
            self.rgb0 = self.rgb[0] / 255, self.rgb[1] / 255, self.rgb[2] / 255
        elif fmt == 'hsv':
            self.hsv = color
            self.rgb = self._hsv2rgb(self.hsv)
            self.hex = self._rgb2hex(self.rgb)
            self.rgb0 = self.rgb[0] / 255, self.rgb[1] / 255, self.rgb[2] / 255
        self.__automaticPalette__()

    def __automaticPalette__(self):
        self.rgbColors = []
        self.hexColors = []
        self.hsvColors = []
        self.rgb0Colors = []
        hsv = self.hsv
        for i in range(255):
            new_hsv = hsv[0], hsv[1], (1 / 255) * i
            self.rgbColors.append(self._hsv2rgb(new_hsv))
            self.hexColors.append(self._rgb2hex(self.rgbColors[-1]))
            self.hsvColors.append(new_hsv)
            r, g, b = self.rgbColors[-1]
            self.rgb0Colors.append((r / 255, g / 255, b / 255))

    def _testPalette(self, o=1):
        from matplotlib import pyplot as plt
        from matplotlib.patches import Rectangle
        if o == 1:
            someX, someY = 0.5, 0.1
            plt.figure()
            s = 1
            currentAxis = plt.gca()
            for x in range(254):
                currentAxis.add_patch(Rectangle((x * s, someY), s, 0.1, alpha=1, color=self.rgb0Colors[x]))
            currentAxis.add_patch(Rectangle((5 * s, someY + 0.07), 30, 0.02, alpha=1, color=self.rgb0))

            plt.ylim(0.1, 0.2)
            plt.xlim(0, (x + 1) * s)
            plt.show()
        elif o == 2:
            local = self.rgb0Colors[90:190][0:-1:10]
            someX, someY = 0.5, 0.1
            plt.figure()
            s = 1
            currentAxis = plt.gca()
            for x in range(len(local)):
                currentAxis.add_patch(Rectangle((x * s, someY), s, 0.1, alpha=1, color=local[x]))
            currentAxis.add_patch(Rectangle((5 * s, someY + 0.07), 30, 0.02, alpha=1, color=self.rgb0))

            plt.ylim(0.1, 0.2)
            plt.xlim(0, (x + 1) * s)
            plt.show()

    def _hex2rgb(self, value):
        # http://stackoverflow.com/questions/214359/converting-hex-color-to-rgb-and-vice-versa
        value = value.lstrip('#')
        lv = len(value)
        return tuple(int(value[i:i + int(lv / 3)], 16) for i in range(0, lv, int(lv / 3)))

    def _rgb2hex(self, rgb):
        # http://stackoverflow.com/questions/214359/converting-hex-color-to-rgb-and-vice-versa
        r = rgb[0]
        g = rgb[1]
        b = rgb[2]
        return '#%02X%02X%02X' % (r, g, b)

    def _hsv2rgb(self, hsv):
        # http://code.activestate.com/recipes/576919-python-rgb-and-hsv-conversion/
        h, s, v = hsv
        h = float(h)
        s = float(s)
        v = float(v)
        h60 = h / 60.0
        h60f = math.floor(h60)
        hi = int(h60f) % 6
        f = h60 - h60f
        p = v * (1 - s)
        q = v * (1 - f * s)
        t = v * (1 - (1 - f) * s)
        r, g, b = 0, 0, 0
        if hi == 0:
            r, g, b = v, t, p
        elif hi == 1:
            r, g, b = q, v, p
        elif hi == 2:
            r, g, b = p, v, t
        elif hi == 3:
            r, g, b = p, q, v
        elif hi == 4:
            r, g, b = t, p, v
        elif hi == 5:
            r, g, b = v, p, q
        r, g, b = int(r * 255), int(g * 255), int(b * 255)
        return r, g, b

    def _rgb2hsv(self, rgb):
        # http://code.activestate.com/recipes/576919-python-rgb-and-hsv-conversion/
        r, g, b = rgb
        r, g, b = r / 255.0, g / 255.0, b / 255.0
        mx = max(r, g, b)
        mn = min(r, g, b)
        df = mx - mn
        if mx == mn:
            h = 0
        elif mx == r:
            h = (60 * ((g - b) / df) + 360) % 360
        elif mx == g:
            h = (60 * ((b - r) / df) + 120) % 360
        elif mx == b:
            h = (60 * ((r - g) / df) + 240) % 360
        if mx == 0:
            s = 0
        else:
            s = df / mx
        v = mx
        return h, s, v

    def getColor(self, fmt='rgb'):
        if fmt == 'rgb':
            return self.rgb
        elif fmt == 'hex':
            return self.hex
        elif fmt == 'rgb0':
            return self.rgb0
        elif fmt == 'hsv':
            return self.hsv

So if you call it like this:

c = Color((51, 153, 255))
# c = Color((0.5, 0.1, 0.8), fmt='rgb0') # It should work with rgb0
# c = Color('#05d4fa', fmt='hex')        # and hex but I don't remember if it was well tested so be careful (the conversions might be messy).
c._testPalette(1)
print(c.rgbColors)

It will return you this:

Automatically building a colormap from a single color

, and this:

[(0, 0, 0), (0, 0, 1), (0, 1, 2), (0, 1, 3), (0, 2, 4), (0, 3, 5), (1, 3, 6), (1, 4, 7), (1, 4, 8), (1, 5, 9), (1, 6, 10), (2, 6, 11), (2, 7, 12), (2, 7, 13), (2, 8, 14), (2, 9, 15), (3, 9, 16), (3, 10, 17), (3, 10, 18), (3, 11, 19), (3, 12, 20), (4, 12, 21), (4, 13, 22), (4, 13, 23), (4, 14, 24), (4, 15, 25), (5, 15, 26), (5, 16, 27), (5, 16, 28), (5, 17, 29), (5, 18, 30), (6, 18, 31), (6, 19, 32), (6, 19, 32), (6, 20, 34), (6, 21, 35), (7, 21, 36), (7, 22, 36), (7, 22, 38), (7, 23, 39), (7, 24, 40), (8, 24, 40), (8, 25, 42), (8, 25, 43), (8, 26, 44), (8, 26, 44), (9, 27, 46), (9, 28, 47), (9, 28, 48), (9, 29, 48), (9, 30, 50), (10, 30, 51), (10, 31, 52), (10, 31, 52), (10, 32, 54), (10, 33, 55), (11, 33, 56), (11, 34, 56), (11, 34, 58), (11, 35, 59), (11, 36, 60), (12, 36, 60), (12, 37, 62), (12, 37, 63), (12, 38, 64), (12, 38, 65), (13, 39, 65), (13, 40, 67), (13, 40, 68), (13, 41, 69), (13, 42, 70), (14, 42, 71), (14, 43, 72), (14, 43, 73), (14, 44, 73), (14, 45, 75), (15, 45, 76), (15, 46, 77), (15, 46, 78), (15, 47, 79), (15, 48, 80), (16, 48, 81), (16, 49, 81), (16, 49, 83), (16, 50, 84), (16, 50, 85), (17, 51, 86), (17, 52, 87), (17, 52, 88), (17, 53, 89), (17, 53, 89), (18, 54, 91), (18, 55, 92), (18, 55, 93), (18, 56, 94), (18, 57, 95), (19, 57, 96), (19, 58, 97), (19, 58, 97), (19, 59, 99), (19, 60, 100), (20, 60, 101), (20, 61, 102), (20, 61, 103), (20, 62, 104), (20, 62, 105), (21, 63, 105), (21, 64, 107), (21, 64, 108), (21, 65, 109), (21, 66, 110), (22, 66, 111), (22, 67, 112), (22, 67, 113), (22, 68, 113), (22, 69, 115), (23, 69, 116), (23, 70, 117), (23, 70, 118), (23, 71, 119), (23, 72, 120), (24, 72, 121), (24, 73, 121), (24, 73, 123), (24, 74, 124), (24, 74, 125), (25, 75, 126), (25, 76, 127), (25, 76, 128), (25, 77, 129), (25, 77, 130), (26, 78, 131), (26, 79, 131), (26, 79, 133), (26, 80, 134), (26, 81, 135), (27, 81, 136), (27, 82, 137), (27, 82, 138), (27, 83, 139), (27, 84, 140), (28, 84, 141), (28, 85, 142), (28, 85, 143), (28, 86, 144), (28, 86, 145), (29, 87, 146), (29, 88, 147), (29, 88, 147), (29, 89, 149), (29, 90, 150), (30, 90, 151), (30, 91, 152), (30, 91, 153), (30, 92, 154), (30, 93, 155), (31, 93, 156), (31, 94, 157), (31, 94, 158), (31, 95, 159), (31, 96, 160), (32, 96, 161), (32, 97, 162), (32, 97, 163), (32, 98, 163), (32, 99, 165), (33, 99, 166), (33, 100, 167), (33, 100, 168), (33, 101, 169), (33, 101, 170), (34, 102, 171), (34, 103, 172), (34, 103, 173), (34, 104, 174), (34, 105, 175), (35, 105, 176), (35, 106, 177), (35, 106, 178), (35, 107, 179), (35, 107, 179), (36, 108, 181), (36, 109, 182), (36, 109, 183), (36, 110, 184), (36, 110, 185), (37, 111, 186), (37, 112, 187), (37, 112, 188), (37, 113, 189), (37, 114, 190), (38, 114, 191), (38, 115, 192), (38, 115, 193), (38, 116, 194), (38, 116, 195), (39, 117, 195), (39, 118, 197), (39, 118, 198), (39, 119, 199), (39, 120, 200), (40, 120, 201), (40, 121, 202), (40, 121, 203), (40, 122, 204), (40, 123, 205), (41, 123, 206), (41, 124, 207), (41, 124, 208), (41, 125, 209), (41, 125, 210), (42, 126, 211), (42, 127, 211), (42, 127, 213), (42, 128, 214), (42, 129, 215), (43, 129, 216), (43, 130, 217), (43, 130, 218), (43, 131, 219), (43, 132, 220), (44, 132, 221), (44, 133, 222), (44, 133, 223), (44, 134, 224), (44, 135, 225), (45, 135, 226), (45, 136, 227), (45, 136, 227), (45, 137, 229), (45, 138, 230), (46, 138, 231), (46, 139, 232), (46, 139, 233), (46, 140, 234), (46, 140, 235), (47, 141, 236), (47, 142, 237), (47, 142, 238), (47, 143, 239), (47, 144, 240), (48, 144, 241), (48, 145, 242), (48, 145, 243), (48, 146, 243), (48, 147, 245), (49, 147, 246), (49, 148, 247), (49, 148, 248), (49, 149, 249), (49, 149, 250), (50, 150, 251), (50, 151, 252), (50, 151, 253), (50, 152, 254)]

Which is a list of all the color generated to create that color map. It's custom though, matplotlib was just used to plot it.

EDIT: Just a note to explain how this is achieved. RGB gives you a value for Red, Green and Blue. HSL (HSV) on the other hand gives you hue, saturation, and lightness (value). So if you convert your color from RGB into HSL and than run the whole spectrum of lightness you'll obtain the dark to light values of a color (for example blue will always remain blue, although lighter and darker).

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
Question8one6View Question on Stackoverflow
Solution 1 - PythonIan HincksView Answer on Stackoverflow
Solution 2 - PythonNight TrainView Answer on Stackoverflow
Solution 3 - PythonarmatitaView Answer on Stackoverflow