14

I found this RGB to HSL script over at http://www.mjijackson.com/2008/02/rgb-to-hsl-and-rgb-to-hsv-color-model-conversion-algorithms-in-javascript. I can't find any other small decent ones. The issue is that this code doesn't even really work. Would anybody know why? (I don't know a bit of color math, but maybe it's returning the complementary?)

function rgbToHsl(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];
}

Edit: when I run rgbToHsl(126,210,22) it's giving me [ .24, .81, .45 ], which is the HSL for an orange color.

Matchu
  • 77,193
  • 15
  • 148
  • 158
Kyle Hotchkiss
  • 9,348
  • 18
  • 51
  • 81
  • Question title and function name mis-match, what exactly are you looking for rgb to hsl or hsl to rgb? Also please show code where you are using that function. – Sarfraz Feb 27 '10 at 19:19
  • 1
    What's the problem, what values are wrong? – Pekka Feb 27 '10 at 19:20
  • 4
    It looks like it works to me, and when I try it it certainly returns a good HSL array. What is it that makes you think it doesn't work? You have to invoke it with RGB values expressed as decimal integers between 0 and 255, if that's not clear. – Pointy Feb 27 '10 at 19:20
  • Maybe he thinks it *is* an HSL to RGB function? – Matchu Feb 27 '10 at 19:21
  • The confession, "I don't know a bit of color math," may be telling :-) – Pointy Feb 27 '10 at 19:23
  • (BTW, CSS3 supports HSL/HSLA colors natively. Supported by Gecko, WebKit and Presto (not sure about Trident).) – kennytm Feb 27 '10 at 19:24
  • Gives me decimals for a orange color when I enter green value colors. example: rgb: 126,210,22 is giving me hsl: .24, .81, .45 – Kyle Hotchkiss Feb 27 '10 at 19:24
  • @Kyle: What makes you think 0.24 (= 87 degrees) is orange? – kennytm Feb 27 '10 at 19:28
  • 1
    Do you mean to say that .24 needs to be multiplied by 360? I didn't exactly realize that - I expected it to come out like that. – Kyle Hotchkiss Feb 27 '10 at 19:32
  • 1
    .24 is the ratio 24/100. It naturally follows that if you want it to be out of 360, you'll need to make that conversion yourself. – Matchu Feb 27 '10 at 19:33
  • @Kyle: Maybe. All 3 elements of the returned array are within the range of 0 to 1. – kennytm Feb 27 '10 at 19:34

4 Answers4

23

The resulting HSV array has to be interpreted as three fractions. For some programs, if you want to express HSV as integers, you multiply the "H" value by 360 and the "S" and "V" values by 100. The HSV value you quote for your green shade RGB[126, 210, 22] is HSV [87, 81, 45] in integers. You could change the function to return such integers if you want to:

function rgbToHsl(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 [Math.floor(h * 360), Math.floor(s * 100), Math.floor(l * 100)];
}

[edit] that said, it's still giving me something with a brightness ("L" or "V") that's considerably too dark; Gimp says that the HSV value should be [90, 80, 82], or in fractional terms [.20, .80, .82].

[another edit] well one problem could be that HSL and HSV are different schemes ... still looking around.

OK in case anybody wants RGB to HSV (like you'd see in Gimp for example) here's a version of that:

function rgbToHsv(r, g, b) {
    var
        min = Math.min(r, g, b),
        max = Math.max(r, g, b),
        delta = max - min,
        h, s, v = max;

    v = Math.floor(max / 255 * 100);
    if ( max != 0 )
        s = Math.floor(delta / max * 100);
    else {
        // black
        return [0, 0, 0];
    }

    if( r == max )
        h = ( g - b ) / delta;         // between yellow & magenta
    else if( g == max )
        h = 2 + ( b - r ) / delta;     // between cyan & yellow
    else
        h = 4 + ( r - g ) / delta;     // between magenta & cyan

    h = Math.floor(h * 60);            // degrees
    if( h < 0 ) h += 360;

    return [h, s, v];
}
Pointy
  • 371,531
  • 55
  • 528
  • 584
  • `rgbToHsv(210, 210, 210)` division by `0` at `h = ( g - b ) / delta;` – BrunoLM Apr 29 '11 at 20:19
  • @BrunoLM it's still the 0.0.1 alpha release :-) I suspect it'd only do that when you give it a neutral (gray) color - I'm not sure what should be done about that. I guess "hue" would be black, saturation would be 100%, and value would be the max/255 maybe? I'd have to play with it some more. – Pointy Apr 29 '11 at 20:24
  • @Pointy That was exactly my test :P. I solved using another var for the division. `var deltadiv = delta == 0 ? 1 : delta;`. The result is `0, 0, 82` and on Photoshop tells me it is `rgb(209, 209, 209)`. Fair enough :) – BrunoLM Apr 29 '11 at 20:36
  • 2
    Here is your code with a fix for this case and also `HsvToRgb` that I found on another website: http://stackoverflow.com/questions/5833624/increase-css-brightness-color-on-click-with-jquery-javascript/5834079#5834079 – BrunoLM Apr 29 '11 at 21:06
  • 3
    @BrunoLM whoa thanks!! Personally I have no present use for code like this (on my current website), but your attention to detail and completeness will certainly improve the experience of future Stackoverflow customers :-) – Pointy Apr 30 '11 at 03:37
  • I'm confused. HSL and HSV are different things right? The asker asked for HSL? – slashdottir Feb 24 '14 at 17:38
  • @slashdottir yes they're different - this answer is (old and) confusing but it's got both conversions. Of course, in modern browsers you can directly set both hsl and hsv colors! – Pointy Feb 24 '14 at 17:41
  • @Pointy The HSV conversion works well, but I think the values need to be rounded rather than floored to match Photoshop values exactly. ( Extensive testing needed) – Mr Mystery Guest Feb 28 '17 at 09:11
  • pretty sure it's `round` and not `floor` – Tom Roggero Oct 18 '17 at 19:01
7

Short but precise

It looks that your code is ok (but it returns hue=0.24 - multiply this by 360 degree to get angle integer value) - however Try this shorter one ( more: hsl2rgb, rgb2hsv, hsv2rgb and sl22sv):

// in: r,g,b in [0,1], out: h in [0,360) and s,l in [0,1]
function rgb2hsl(r,g,b) {
  let v=Math.max(r,g,b), c=v-Math.min(r,g,b), f=(1-Math.abs(v+v-c-1)); 
  let h= c && ((v==r) ? (g-b)/c : ((v==g) ? 2+(b-r)/c : 4+(r-g)/c)); 
  return [60*(h<0?h+6:h), f ? c/f : 0, (v+v-c)/2];
}

function rgb2hsl(r,g,b) {
  let v=Math.max(r,g,b), c=v-Math.min(r,g,b), f=(1-Math.abs(v+v-c-1)); 
  let h= c && ((v==r) ? (g-b)/c : ((v==g) ? 2+(b-r)/c : 4+(r-g)/c)); 
  return [60*(h<0?h+6:h), f ? c/f : 0, (v+v-c)/2];
}

console.log(`rgb: (0.36,0.3,0.24) --> hsl: (${rgb2hsl(0.36,0.3,0.24)})`);


// ---------------
// UX
// ---------------

rgb= [0,0,0];
hs= [0,0,0];

let $ = x => document.querySelector(x);

let hsl2rgb = (h,s,l, a=s*Math.min(l,1-l), f= (n,k=(n+h/30)%12) => l - a*Math.max(Math.min(k-3,9-k,1),-1)) => [f(0),f(8),f(4)];

function changeRGB(i,e) {
  rgb[i]=e.target.value/255;
  hs = rgb2hsl(...rgb);
  refresh();
}

function changeHS(i,e) {
  hs[i]=e.target.value/(i?255:1);
  rgb= hsl2rgb(...hs);
  refresh();
}

function refresh() {
  rr = rgb.map(x=>x*255|0).join(', ')
  tr = `RGB: ${rr}`
  th = `HSL: ${hs.map((x,i)=>i? (x*100).toFixed(2)+'%':x|0).join(', ')}`
  $('.box').style.backgroundColor=`rgb(${rr})`;  
  $('.infoRGB').innerHTML=`${tr}`;  
  $('.infoHS').innerHTML =`${th}`;  
  
  $('#r').value=rgb[0]*255;
  $('#g').value=rgb[1]*255;
  $('#b').value=rgb[2]*255;
  
  $('#h').value=hs[0];
  $('#s').value=hs[1]*255;
  $('#l').value=hs[2]*255;  
}

refresh();
.box {
  width: 50px;
  height: 50px;
  margin: 20px;
}

body {
    display: flex;
}
<div>
<input id="r" type="range" min="0" max="255" oninput="changeRGB(0,event)">R<br>
<input id="g" type="range" min="0" max="255" oninput="changeRGB(1,event)">G<br>
<input id="b" type="range" min="0" max="255" oninput="changeRGB(2,event)">B<br>
<pre class="infoRGB"></pre>
</div> 

<div>
<div class="box hsl"></div>

</div>

<div>
<input id="h" type="range" min="0" max="360" oninput="changeHS(0,event)">H<br>
<input id="s" type="range" min="0" max="255" oninput="changeHS(1,event)">S<br>
<input id="l" type="range" min="0" max="255" oninput="changeHS(2,event)">L<br>
<pre class="infoHS"></pre><br>
</div>

I develop S_HSL wiki formulas (marked by green border) - where MAX=max(r,g,b) and MIN=min(r,g,b) - and in above code I make some improvements and make analysis which shows that results are correct. This allows me to get quite short code at the end

enter image description here

Kamil Kiełczewski
  • 53,729
  • 20
  • 259
  • 241
1

Function below converts RGB color into Hue Saturation Brightness color like Photoshop color picker, results are in the ranges:

  • Hue 0-360 (degrees)
  • Saturation: 0-100 (%)
  • Brightness: 0-100 (%)

I still don't understand why people use the term HSV (Hue Saturation Value) instead of HSB (Hue Saturation Brightness), anyway it's a matter fo terminology, the results are the same

   //Converts to color HSB object (code from here http://www.csgnetwork.com/csgcolorsel4.html with some improvements)
   function rgb2hsb(r, g, b)
   {    
    r /= 255; g /= 255; b /= 255; // Scale to unity.   
    var minVal = Math.min(r, g, b),
    maxVal = Math.max(r, g, b),
    delta = maxVal - minVal,
    HSB = {hue:0, sat:0, bri:maxVal},
    del_R, del_G, del_B;

    if( delta !== 0 )
    {
        HSB.sat = delta / maxVal;
        del_R = (((maxVal - r) / 6) + (delta / 2)) / delta;
        del_G = (((maxVal - g) / 6) + (delta / 2)) / delta;
        del_B = (((maxVal - b) / 6) + (delta / 2)) / delta;

        if (r === maxVal) {HSB.hue = del_B - del_G;}
        else if (g === maxVal) {HSB.hue = (1 / 3) + del_R - del_B;}
        else if (b === maxVal) {HSB.hue = (2 / 3) + del_G - del_R;}

        if (HSB.hue < 0) {HSB.hue += 1;}
        if (HSB.hue > 1) {HSB.hue -= 1;}
    }

    HSB.hue *= 360;
    HSB.sat *= 100;
    HSB.bri *= 100;

    return HSB;
   }

Usage example:

var hsb = rgb2hsb(126,210,22);
alert("hue = " + hsb.hue + "saturation = " + hsb.sat + "brightness = " + hsb.bri);
Marco Demaio
  • 30,990
  • 33
  • 122
  • 155
0

You have to change to the hsl format just like hsl(155,100%,30%), that can be use in HTML.

//this change hsl to :  "hsl(155,100%,30%)"
function hsl2str({h,s,l,a=1}) {
    return "hsl("+h+","+s*100+"%,"+l*100+"%,"+a+")";
}
//for html, the h is 0-360, so you need this function
function rgb2hsl({r,g,b,a=1}) {
    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;
        }
    }

    return {h: h*60,s,l,a};
}
Azametzin
  • 4,342
  • 12
  • 22
  • 38
defend orca
  • 175
  • 1
  • 11