convert Hsl to rgb and hex
Asked Answered
H

9

40

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?

Hein answered 19/4, 2016 at 14:47 Comment(0)
B
74

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
Bourbonism answered 23/5, 2017 at 12:3 Comment(3)
Thank you, this works perfectly. But can you explain the nature of the n variable in the f function?Gobo
I can't :(. The wiki article explains it a bit, but this is way too advanced math+trigonometry for me :)Bourbonism
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.Gobo
I
35

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>
Incest answered 2/1, 2019 at 23:17 Comment(5)
rgb2hex expects values between 0 and 1? Most common formats are 0-255 and % (That is CSS colors)Thymic
@Thymic 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.Predicant
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.Thymic
This function code is way unreadable. Please prefer to write clean code and let the bundler minify it when it needs to.Ruvalcaba
@ChristosLytras I used formulas and notations in accordance with wikipedia.Predicant
M
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

Monochasium answered 19/4, 2016 at 15:16 Comment(0)
P
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

Pul answered 30/1, 2019 at 22:32 Comment(0)
S
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.

Saccule answered 19/4, 2016 at 14:52 Comment(1)
If you want to give people another option besides NPM since this appears to be isomorphic, you could publish it on bower.Dropsy
L
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

Luker answered 26/6, 2017 at 20:27 Comment(0)
O
1

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.

Outage answered 24/8, 2022 at 15:58 Comment(0)
R
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}`;
} 
Relique answered 4/1, 2023 at 10:19 Comment(0)
S
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'
Sethrida answered 21/6, 2023 at 12:6 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.