RGB to HSL and RGB to HSV color model conversion algorithms in JavaScript

Here is a set of additive color model conversion algorithms that I found published on Wikipedia and have implemented in JavaScript. It was surprisingly difficult to find these actually implemented anywhere in compact, efficient, and bug-free code, so I wrote my own. These should be easily portable to other programming languages if desired.

/**
 * Converts an RGB color value to HSL. Conversion formula adapted from
 * http://en.wikipedia.org/wiki/HSL_color_space. Assumes r, g, and b are
 * contained in the set [0, 255] and returns h, s, and l in the set [0, 1].
 *
 * @param   {Number}    r       The red color value
 * @param   {Number}    g       The green color value
 * @param   {Number}    b       The blue color value
 * @return  {Array}             The HSL representation
 */
var rgbToHsl = function(r, g, b){
    r /= 255, g /= 255, b /= 255;
    var max = Math.max(r, g, b), min = Math.min(r, g, b);
    var h, s, l = (max + min) / 2;

    if(max == min){
        h = s = 0; // achromatic
    }else{
        var d = max - min;
        s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
        switch(max){
            case r: h = (g - b) / d + (g < b ? 6 : 0); break;
            case g: h = (b - r) / d + 2; break;
            case b: h = (r - g) / d + 4; break;
        }
        h /= 6;
    }

    return [h, s, l];
};

/**
 * Converts an HSL color value to RGB. Conversion formula adapted from
 * http://en.wikipedia.org/wiki/HSL_color_space. Assumes h, s, and l are
 * contained in the set [0, 1] and returns r, g, and b in the set [0, 255].
 *
 * @param   {Number}    h       The hue
 * @param   {Number}    s       The saturation
 * @param   {Number}    l       The lightness
 * @return  {Array}             The RGB representation
 */
var hslToRgb = function(h, s, l){
    var r, g, b;

    if(s == 0){
        r = g = b = l; // achromatic
    }else{
        function hue2rgb(p, q, t){
            if(t < 0) t += 1;
            if(t > 1) t -= 1;
            if(t < 1/6) return p + (q - p) * 6 * t;
            if(t < 1/2) return q;
            if(t < 2/3) return p + (q - p) * (2/3 - t) * 6;
            return p;
        };

        var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
        var p = 2 * l - q;
        r = hue2rgb(p, q, h + 1/3);
        g = hue2rgb(p, q, h);
        b = hue2rgb(p, q, h - 1/3);
    }

    return [r * 255, g * 255, b * 255];
};

/**
 * Converts an RGB color value to HSV. Conversion formula adapted from
 * http://en.wikipedia.org/wiki/HSV_color_space. Assumes r, g, and b are
 * contained in the set [0, 255] and returns h, s, and v in the set [0, 1].
 *
 * @param   {Number}    r       The red color value
 * @param   {Number}    g       The green color value
 * @param   {Number}    b       The blue color value
 * @return  {Array}             The HSV representation
 */
var rgbToHsv = function(r, g, b){
    r = r/255, g = g/255, b = b/255;
    var max = Math.max(r, g, b), min = Math.min(r, g, b);
    var h, s, v = max;

    var d = max - min;
    s = max == 0 ? 0 : d / max;

    if(max == min){
        h = 0; // achromatic
    }else{
        switch(max){
            case r: h = (g - b) / d + (g < b ? 6 : 0); break;
            case g: h = (b - r) / d + 2; break;
            case b: h = (r - g) / d + 4; break;
        }
        h /= 6;
    }

    return [h, s, v];
};

/**
 * Converts an HSV color value to RGB. Conversion formula adapted from
 * http://en.wikipedia.org/wiki/HSV_color_space. Assumes h, s, and v are
 * contained in the set [0, 1] and returns r, g, and b in the set [0, 255].
 *
 * @param   {Number}    h       The hue
 * @param   {Number}    s       The saturation
 * @param   {Number}    v       The value
 * @return  {Array}             The RGB representation
 */
var hsvToRgb = function(h, s, v){
    var r, g, b;

    var i = Math.floor(h * 6);
    var f = h * 6 - i;
    var p = v * (1 - s);
    var q = v * (1 - f * s);
    var t = v * (1 - (1 - f) * s);

    switch(i){
        case 0: r = v, g = t, b = p; break;
        case 1: r = q, g = v, b = p; break;
        case 2: r = p, g = v, b = t; break;
        case 3: r = p, g = q, b = v; break;
        case 4: r = t, g = p, b = v; break;
        case 5: r = v, g = p, b = q; break;
    }

    return [r * 255, g * 255, b * 255];
};

8 Comments

  • 12 Feb 2008 at 10:55 pm

    If you want to get serious, use the LCh color space instead. The HSL/HSB model does not have much physiological basis. Not important for casual use.

  • Francesco
    3 Jun 2008 at 10:27 am

    I really do appreciate your work, thank you very much for sharing.

  • 13 Jun 2008 at 1:22 pm

    In the rgbToHsl line

    s = l > 0.5 ? d / (2 - max - min) : d / (max (PLUS) min);

    The (2 - max - min) should be (2 - max (PLUS) min).

    (The preview isn’t showing plus signs, so I just wrote them in.)

  • 13 Jun 2008 at 1:47 pm

    Also, for rgbToHsl, shouldn’t you include

    h*=360; s*=100; l*=100;

    before the return statement? Personally, I would also Math.round() each of those.

  • unabacus
    13 Jun 2008 at 3:41 pm

    Hi, nice work…! i’ve spent ages searching for good straight-forward calculations that would help me produce my own “colour” code. Mainly because, as you’ve said, any code i have found has been hideous in either stature (huge), cleverness (complexity) or unexpected features (bugs).. up until now i had failed entirely to get hold of anything that suited my needs..

    Thanks a lot.. really appreciated.. if you have any stipulations for usage please let me know. I do however always mention sources with-in my creations if i have utilised anyone elses code.

  • TopC
    19 Jun 2008 at 2:20 pm

    While trawling for an RGB to HSV solution I stumbled across your code. I’ve written colour modelling code before so knew what I was aiming for, I just wanted a head start. Having put some code together I noticed one issue you might be interested in. The hsvToRgb() method needs to use switch(i%6) otherwise it’ll go wrong when h is 1. This modules is in wikipidia’s formula but has gone astray in your code.

    I came up with a Colour class to supply the behaviour. I was going to include my code but it wouldn’t fit so here’s an extract. Adding 360 (6 in you case) prevents the hue from going negative…

    Colour.prototype._rgbToHsv = function() {
        var r = this._red, g = this._green, b = this._blue;
        var max = this._value = Math.max(r, g, b);
        var min = Math.min(r, g, b);
        var d = max - min;
        this._saturation = (max == 0) ? 0 : d / max;
        switch(max){
            case min: this._hue = 0; break; // achromatic
            case r: this._hue = (60 * ((g - b) / d) + 360) % 360; break;
            case g: this._hue = 60 * ((b - r) / d) + 120; break;
            case b: this._hue = 60 * ((r - g) / d) + 240; break;
        }
    }
    

    Cheers

Leave a comment

Please use Markdown syntax instead of HTML. Thanks.

Comment preview