HSL Gradients


A few months ago, I wrote a post about Creating Gradients Programmatically in Python. The main use was to generate gradients for things like web site nav bars:

You might try to achieve the glassy look of the default nav button in the first example with a simple linear gradient from #516c7a to #7291a1 and then from #5f7f8f to #87a2af. The problem with dealing with RGB is that it's very difficult to know how to do a variation that differs in hue but has the same saturation and lightness. Or how to have more saturation but keep the hue constant.

So using the formulae from the Wikipedia page on HSL and HSV I implemented a version of gradient.py that takes HSL values instead of RGB.

Instead of specifying #516c7a to #7291a1 and #5f7f8f to #87a2af, this:

is achieved by specifying HSL (200°, 0.2, 0.40) to (200°, 0.2, 0.54) then (200°, 0.2, 0.47) to (200°, 0.2, 0.61). Note that this is much cleaner as the hue stays constant as does the saturation. Only the lightness varies to give the shiny look.

Just by increasing the saturation to 0.5 and keeping everything else the same, we get:

We can then change the hue from 200° to 0°:

With a little bit of experimentation, I found the button looked nicer if it had a slight glow, achieved by increasing the saturation by about 50% at the top and bottom. Here are the same three buttons with saturation 0.3 → 0.2 → 03 (instead of constant 0.2) and 0.7 → 0.5 → 0.7 (instead of constant 0.5):

In every case the lightness gradient was the same from one button to the next.

I found, though, that to get the shiny black, the original lightness didn't work:

Not even varying the lightness by a constant really worked, so the following is achieved with (0, 0.0, 0.40 - 0.25) to (0, 0.0, 0.54 - 0.20) and then from (0, 0.0, 0.47 - 0.25) (0, 0.0, 0.61 - 0.25):

Here is my HSL2RGB function and the reverse RGB2HSL:

[a commenter below has pointed out Python includes functions for this in the standard library already — oh well]

def RGB2HSL(R, G, B):
    fR = float(R / 255.0)
    fG = float(G / 255.0)
    fB = float(B / 255.0)
    ma = max(fR, fG, fB)
    mi = min(fR, fG, fB)
    if ma == mi:
        H = 0
    elif ma == fR:
        H = (60 * ((fG - fB)/(ma - mi))) % 360
    elif ma == fG:
        H = 120 + 60 * ((fB - fR)/(ma - mi))
    elif ma == fB:
        H = 240 + 60 * ((fR - fG)/(ma - mi))
    L = (ma + mi) / 2
    if ma == mi:
        S = 0
    elif L <= 0.5:
        S = (ma - mi) / (2 * L)
    elif L > 0.5:
        S = (ma - mi) / (2 - 2 * L)
    return H, S, L

def HSL2RGB(H, S, L):
    if L < 0.5:
        q = L * (1 + S)
    elif L >= 0.5:
        q = L + S - (L * S)
    p = 2 * L - q
    hk = H / 360.0
    tR = hk + 0.333333
    tG = hk
    tB = hk - 0.333333

    def color(t):
        if t < 0:
            t += 1
        if t > 1:
            t -= 1
        if t < 0.166666:
            c = p + ((q - p) * 6 * t)
        elif 0.166666 <= t < 0.5:
            c = q
        elif 0.5 <= t < 0.666667:
            c = p + ((q - p) * 6 * (0.666667 - t))
        else:
            c = p
        return c

    R = int(color(tR) * 255)
    G = int(color(tG) * 255)
    B = int(color(tB) * 255)
    return R, G, B

The original post was in the categories: python web_design colour_science but I'm still in the process of migrating categories over.

The original post had 9 comments I'm in the process of migrating over.