Interpolate HSL Colours

2.2k views Asked by At

I am building a star visualisation engine, and need to interpolate values received from the API. The HSL colour stops are:

-.63, hsl: 228° 100% 80%,
.165, hsl: 224° 100% 90%,
.33, hsl: 240° 100% 98%,
.495, hsl: 64° 100% 92%,
.66, hsl: 52° 100% 82%,
.825, hsl: 28° 100% 66%,
2.057, hsl: 6° 95% 65%,

Each star will return a colour value between -.63 and 2.057, I need a function that takes that value and gets a colour along the spectrum comprised of the above stops.

I've had some previous help but cannot for the life of me figure this out, and haven't been able to find a concise tutorial or walkthrough of interpolating HSL values. The only way I can figure this out is via an external lib, but that seems a ridiculous solution for something relatively simple. Any help would be appreciated!

2

There are 2 answers

0
Jongware On BEST ANSWER

A straightforward linear interpolation function in HSB space:

function get_color_for(value) {
    var i, h,s,b,stops = [
        [-0.63, [228, 100, 80]],
        [0.165, [224, 100, 90]],
        [0.33, [240, 100, 98]],
        [0.495, [64, 100, 92]],
        [0.66, [52, 100, 82]],
        [0.825, [28, 100, 66]],
        [2.057, [6, 95, 65]]
    ];
    if (value <= stops[0][0]) return stops[0][1];
    if (value >= stops[stops.length - 1][0]) return stops[stops.length - 1][1];
    i = 0;
    while (value > stops[i][0])
       i++;
    value -= stops[i-1][0];
    value /= (stops[i][0]-stops[i-1][0]);
    h = stops[i-1][1][0]+(stops[i][1][0]-stops[i-1][1][0])*value;
    s = stops[i-1][1][1]+(stops[i][1][1]-stops[i-1][1][1])*value;
    b = stops[i-1][1][2]+(stops[i][1][2]-stops[i-1][1][2])*value;
    return [h,s,b];
}

For values lower than the minimum input it always will return the first, and for higher than the maximum, the last color set.

The returned value is an HSL array, which can be used immediately in CSS. If your environment needs RGB colors you can use an hsl-to-rgb conversion function, such as in this earlier SO question. Check what your output device expects for the RGB range: 0.0 to 1.0, 0 to 100, or 0 to 255.

Note that this function cannot be used for hsb in general. The problem is the hue part: this wraps around at 360 (degrees), and so attempting to interpolate between 350 and 10 will make it return cyan (the value around 170) instead of red (the value at 0).

Here is a jsfiddle, displaying the output for a range of numbers.

2
Alexey Lebedev On

RGB interpolation would work better for your color scale, HSL adds a greenish color between blue and yellow:

enter image description here

RGB interpolation:

function lerp(start, end, t) {
    return (1 - t) * start + t * end;
}

function getColor(t) {
    var colors = [
        [153, 173, 255], [204, 218, 255], [245, 245, 255],
        [252, 255, 214], [255, 243, 163], [255, 163, 82],
        [251, 98,  81]
    ];
    var domain = [-0.63, 0.165, 0.33, 0.495, 0.66, 0.825, 2.057];

    if (t <= domain[0]) {
        return colors[0];
    }

    if (t >= domain[domain.length - 1]) {
        return colors[colors.length - 1];
    }

    for (var i = 0; t > domain[i]; i++);

    var from = colors[i - 1];
    var to = colors[i];
    t = (t - domain[i - 1]) / (domain[i] - domain[i - 1]);
    return [
        lerp(from[0], to[0], t),
        lerp(from[1], to[1], t),
        lerp(from[2], to[2], t)
    ];
}

Drawing it:

function rgbToCSS(rgb) {
    return 'rgb(' + Math.round(rgb[0]) + ',' +
                    Math.round(rgb[1]) + ',' +
                    Math.round(rgb[2]) + ')';
}

var canvas = document.createElement('canvas');
canvas.width = 800;
canvas.height = 100;
var context = canvas.getContext('2d');
for (var i = 0; i < canvas.width; i++) {
    context.fillStyle = rgbToCSS(getColor(lerp(-0.63, 2.057, i / canvas.width)));
    context.fillRect(i, 0, 1, canvas.height);
}
document.body.appendChild(canvas);

JSFiddle