38

I need a color converter to convert from hsl to rgb and hex value. I am going to do similar like this. I am using jquery and jquery ui range slider for this. Here is my code:

$(function() {
    $( "#hsl_hue_range" ).slider({
        min: 0,
        max: 100,
        value: 0,
        range: false,
        animate:"slow",
        orientation: "horizontal",
        slide: function( event, ui ) {
            var hsl_hue = ui.value;
        }
    });
});

$(function() {
    $( "#hsl_saturation_range" ).slider({
        min: 0,
        max: 100,
        value: 0,
        range: false,
        animate:"slow",
        orientation: "horizontal",
        slide: function( event, ui ) {
            var hsl_saturation = ui.value;
        }
    });
});

$(function() {
    $( "#hsl_light_range" ).slider({
        min: 0,
        max: 100,
        value: 0,
        range: false,
        animate:"slow",
        orientation: "horizontal",
        slide: function( event, ui ) {
            var hsl_light = ui.value;
        }
    });
});

I want the solution like this:

the input to converter can be given by the variables. like hsl_hue hsl_saturation hsl_light.

Is there any way to do this?
if no way, what can I do?

Shohidul Alam
  • 653
  • 1
  • 6
  • 19

9 Answers9

68

New approach (inspired by @Kamil-Kiełczewski solution)
Takes degree, percentage, percentage and returns css hex color:

function hslToHex(h, s, l) {
  l /= 100;
  const a = s * Math.min(l, 1 - l) / 100;
  const f = n => {
    const k = (n + h / 30) % 12;
    const color = l - a * Math.max(Math.min(k - 3, 9 - k, 1), -1);
    return Math.round(255 * color).toString(16).padStart(2, '0');   // convert to Hex and prefix "0" if needed
  };
  return `#${f(0)}${f(8)}${f(4)}`;
}

Example:

hslToHex(360, 100, 50)  // "#ff0000" -> red

Original version: (still OK, just longer)

Takes degree, percentage, percentage and returns css hex color:

function hslToHex(h, s, l) {
  h /= 360;
  s /= 100;
  l /= 100;
  let r, g, b;
  if (s === 0) {
    r = g = b = l; // achromatic
  } else {
    const 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;
    };
    const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
    const p = 2 * l - q;
    r = hue2rgb(p, q, h + 1 / 3);
    g = hue2rgb(p, q, h);
    b = hue2rgb(p, q, h - 1 / 3);
  }
  const toHex = x => {
    const hex = Math.round(x * 255).toString(16);
    return hex.length === 1 ? '0' + hex : hex;
  };
  return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
}

Example:

hslToHex(360, 100, 50)  // "#ff0000" -> red
icl7126
  • 5,740
  • 4
  • 53
  • 51
  • Thank you, this works perfectly. But can you explain the nature of the `n` variable in the `f` function? – MattWeiler Mar 24 '23 at 23:45
  • 1
    I can't :(. The [wiki article](https://en.wikipedia.org/wiki/HSL_and_HSV#HSL_to_RGB_alternative) explains it a bit, but this is way too advanced math+trigonometry for me :) – icl7126 Mar 25 '23 at 08:46
  • Fair enough, I'm in the same boat. But it does seem to work well, I was just hoping to document the function in case someone needs to tweak it later. Thank for the article link. – MattWeiler Mar 25 '23 at 18:39
30

Shortest

Try this (wiki, error analysis, more: rgb2hsl, hsv2rgb rgb2hsv and hsl2hsv)

// input: h in [0,360] and s,v in [0,1] - output: r,g,b in [0,1]
function hsl2rgb(h,s,l) 
{
  let a= s*Math.min(l,1-l);
  let f= (n,k=(n+h/30)%12) => l - a*Math.max(Math.min(k-3,9-k,1),-1);
  return [f(0),f(8),f(4)];
}   

To calc hsl2hex use rgb2hex(...hsl2rgb(30,1,0.5)). To convert string from format e.g. rgb(255, 255, 255) to hex use rgbStrToHex (which handle empty string case) as follows

// oneliner version
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)];

// r,g,b are in [0-1], result e.g. #0812fa.
let rgb2hex = (r,g,b) => "#" + [r,g,b].map(x=>Math.round(x*255).toString(16).padStart(2,0) ).join('');

// hexStr e.g #abcdef, result "rgb(171,205,239)"
let hexStr2rgb  = (hexStr) => `rgb(${hexStr.substr(1).match(/../g).map(x=>+`0x${x}`)})`;

// rgb - color str e.g."rgb(12,233,43)", result color hex e.g. "#0ce92b"
let rgbStrToHex= rgb=> '#'+rgb.match(/\d+/g).map(x=>(+x).toString(16).padStart(2,0)).join``


console.log(`hsl: (30,0.2,0.3) --> rgb: (${hsl2rgb(30,0.2,0.3)}) --> hex: ${rgb2hex(...hsl2rgb(30,0.2,0.3))}`);
console.log(`rgb: ${hexStr2rgb("#ff647b")} --> hex: ${rgbStrToHex("rgb(255,100, 123)")}`)


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

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

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

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(', ')
  hh = rgb2hex(...rgb);
  tr = `RGB: ${rr}`
  th = `HSL: ${hs.map((x,i)=>i? (x*100).toFixed(2)+'%':x|0).join(', ')}`
  thh= `HEX: ${hh}`
  $('.box').style.backgroundColor=`rgb(${rr})`;  
  $('.infoRGB').innerHTML=`${tr}`;  
  $('.infoHS').innerHTML =`${th}\n${thh}`;  
  
  $('#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;  
}

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

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>
Kamil Kiełczewski
  • 85,173
  • 29
  • 368
  • 345
  • 1
    rgb2hex expects values between 0 and 1? Most common formats are 0-255 and % (That is CSS colors) – Kaiido Jan 03 '19 at 08:53
  • 1
    @Kaiido OP wants to convert hsl to hex - he can easily do it by e.g. `rgb2hex(...hsl2rgb(30,1,0.5))` however it is also easy to change that function to process [0-255] values by change `Math.ceil(x*255)` to `Math.ceil(x)` in above code. – Kamil Kiełczewski Jan 03 '19 at 09:23
  • 3
    Sure I got it, my point is that calling it `rgb2hex` is a bit misleading when you can't pass directly what usual values for `rgb` are. – Kaiido Jan 03 '19 at 09:30
21

HSL to RGB:

/**
     * 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
     */
    function hslToRgb(h, s, l){
        var r, g, b;

        if(s == 0){
            r = g = b = l; // achromatic
        }else{
            var hue2rgb = 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 [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)];
    }


You can find more information here - HSL to RGB color conversion

Community
  • 1
  • 1
luke mclean
  • 358
  • 1
  • 7
6

Another way to solve this problem is to leverage the window.getComputedStyle capability of modern browsers:

  1. Create an element on the page (it can be hidden, e.g. with display:none, but that appears to suppress output of the opacity / "A" value)

  2. Set a color-valued property of that element using the representation of your choice, e.g. e.style.color = 'hsla(100, 50%, 75%, 0.8)'; (or even named colors like 'rebeccapurple')

  3. Read the value back using window.getComputedStyle(e).color. It will be a string of the form rgb(r,g,b) or rgba(r,g,b,a).

Live demo on CodePen

jacobq
  • 11,209
  • 4
  • 40
  • 71
2

I've made a small library that can easily convert colors.

This is my HSL to RGB method, which uses a few other utility methods from the library:

Color.hslToRgb = function(hsl, formatted) {
  var a, b, g, h, l, p, q, r, ref, s;
  if (isString(hsl)) {
    if (!hsl.match(Color.HSL_REGEX)) {
      return;
    }
    ref = hsl.match(/hsla?\((.+?)\)/)[1].split(',').map(function(value) {
      value.trim();
      return parseFloat(value);
    }), h = ref[0], s = ref[1], l = ref[2], a = ref[3];
  } else if ((isObject(hsl)) && (hasKeys(hsl, ['h', 's', 'l']))) {
    h = hsl.h, s = hsl.s, l = hsl.l, a = hsl.a;
  } else {
    return;
  }
  h /= 360;
  s /= 100;
  l /= 100;
  if (s === 0) {
    r = g = b = l;
  } else {
    q = l < 0.5 ? l * (1 + s) : l + s - l * s;
    p = 2 * l - q;
    r = Color.hueToRgb(p, q, h + 1 / 3);
    g = Color.hueToRgb(p, q, h);
    b = Color.hueToRgb(p, q, h - 1 / 3);
  }
  return getRgb(Math.round(r * 255), Math.round(g * 255), Math.round(b * 255), a, formatted);
};

If you don't want to use npm, the lib can also be found on GitHub.

GMchris
  • 5,439
  • 4
  • 22
  • 40
  • 1
    If you want to give people another option besides NPM since this appears to be isomorphic, you could publish it on [bower](http://bower.io). – Patrick Roberts Apr 19 '16 at 15:18
2

I recently had cause to solve this problem and came up with a canvas-based solution. I'm logging it here for posterity only.

In my case I also needed to account for the cumulative effects on the conversion given a range of background colors and a semi-transparent alpha channel...

var HSL2COLOR = function () {
    return function (hsl, bg) {
        function checkHex(v) {
            return 1 === v.length ? '0'+v : v;
        }
        var data, r, g, b, a,
        cnv = document.createElement('canvas'),
        ctx = cnv.getContext('2d'),
        alpha = /a\(/.test(hsl),
        output = {};

        return cnv.width = cnv.height = 1,
        bg && (ctx.fillStyle = bg, ctx.fillRect(0, 0, 1, 1)),
        ctx.fillStyle = hsl,
        ctx.fillRect(0, 0, 1, 1),

        data = ctx.getImageData(0, 0, 1, 1).data,
        r = data[0],
        g = data[1],
        b = data[2],
        a = (data[3] / 255).toFixed(2),

        alpha ? (output.hsla = hsl, bg ? output.rgb = 'rgb('+r+','+g+','+b+')' : output.rgba = 'rgb('+r+','+g+','+b+','+a+')')  : (output.hsl = hsl, output.rgb = 'rgb('+r+','+g+','+b+')'),
        output.hex = '#'+checkHex(r.toString(16))+checkHex(g.toString(16))+checkHex(b.toString(16)),
        output;
    };
}();

// hsl: no alpha-channel + no background color
console.log(HSL2COLOR('hsl(170, 60%, 45%)'));
/*=> { 
        hsl: "hsl(170, 60%, 45%)", 
        rgb: "rgb(45,183,160)", 
        hex: "#2db7a0" 
     }
*/
// hsla: alpha-channel + no background color 
console.log(HSL2COLOR('hsla(170, 60%, 45%, 0.35)'));
/*=> {
        hsla: "hsla(170, 60%, 45%, 0.35)",
        rgba: "rgb(42,183,160,0.35)", 
        hex: "#2ab7a0" 
     }
*/
// hsla: alpha-channel + background color
console.log(HSL2COLOR('hsla(170, 60%, 45%, 0.35)','#f00'));
/*=> {
        hsla: "hsla(170, 60%, 45%, 0.35)",
        rgb: "rgb(181,64,56)", 
        hex: "#b54038" 
     }
*/

As you can see from the results above, HEX values are not particularly representative when there is an alpha-channel in the input but no background color specified - as the canvas basically sees a transparent background as black. Nonetheless, the rgba value remained coherent.

Anyway, I achieved what I needed to, and perhaps this will be of some use to someone, sometime.

BP

Brian Peacock
  • 1,801
  • 16
  • 24
0

Or you could npm install color-convert and not re-invent the wheel.

const convert = require('color-convert')
const hex = '#' + convert.hsl.hex(96, 48, 59) // convert hsl to hex
const rgb = convert.hsl.rgb(96, 48, 59) // convert hsl to rgb

Life can be simple at times.

Martin Braun
  • 10,906
  • 9
  • 64
  • 105
0

from @icl7126 's answer,

you can add another function param to include alpha

function hsl2hex(h,s,l,alpha) {
    l /= 100;
    const a = s * Math.min(l, 1 - l) / 100;
    const f = n => {
      const k = (n + h / 30) % 12;
      const color = l - a * Math.max(Math.min(k - 3, 9 - k, 1), -1);
      return Math.round(255 * color).toString(16).padStart(2, '0');   
      // convert to Hex and prefix "0" if needed
    };
  //alpha conversion
  alpha = Math.round(alpha * 255).toString(16).padStart(2, '0');

  return `#${f(0)}${f(8)}${f(4)}${alpha}`;
} 
vinu iyer
  • 31
  • 5
0

Final solution:

function stringAsColor(string, saturation = 100, lightness = 50) {
    let hash = 0;
    for (let i = 0; i < string.length; i++) {
        hash = string.charCodeAt(i) + ((hash << 5) - hash);
        hash = hash & hash;
    }
    const hslToHex = function (h, s, l) {
        l /= 100;
        const a = s * Math.min(l, 1 - l) / 100;
        const f = n => {
            const k = (n + h / 30) % 12;
            const color = l - a * Math.max(Math.min(k - 3, 9 - k, 1), -1);
            return Math.round(255 * color).toString(16).padStart(2, '0');
        };
        return ['#', f(0), f(8), f(4)].join('');
    };
    return hslToHex(Math.abs(hash % 360), saturation, lightness);
}

Ussage:

stringAsColor('abc');          // -> '#0019ff'
stringAsColor('abc', 100, 20); // -> '#000a66'
Dmitry Shashurov
  • 1,148
  • 13
  • 11