5

Does anyone know a PHP function (for >5.3) which can convert an HSL color to either RGB or Hex? I've tried a dozen Google searches and none of the functions I have found work as expected.

It doesn't matter whether the function converts to RGB or hex because converting between those two is trivial. The inputs are HSL values for CSS (Hue: 0–360, Saturation: 0–100, Lightness: 0–100).

Edit: Specifying the input and output format would be a bonus :)

Rich Jenks
  • 1,723
  • 5
  • 19
  • 32
  • So, what was wrong with the stuff you did find? Are you sure you weren't trying to using them in ways that weren't expected? – enhzflep Dec 06 '13 at 12:23
  • 1
    http://stackoverflow.com/q/3597417/505722 – Jim Dec 06 '13 at 12:27
  • You may need to write your own function, but here is a template from a related answer: http://stackoverflow.com/questions/11804027/farbtastic-convert-hsl-back-to-rgb-or-hex?rq=1 – Marc Audet Dec 06 '13 at 12:30
  • The output was either seemingly-random numbers or, in some cases three identical numbers. I attempted to change the way parameters were provided (named arrays, etc. to resolve three identical number cases) but I was unable to get the expected output. Part of the problem with the functions I found was that the expected parameters and the output format were undocumented and while the output format can be extrapolated from the code, the parameter format is harder to work out from such a hard-math function. – Rich Jenks Dec 06 '13 at 12:31
  • @Jim so the function (HSVtoRGB) expects an array of hue (0–360), saturation (0–1) and lightness (0–1) and returns an array of red, green and blue? – Rich Jenks Dec 06 '13 at 12:38
  • @RichJenks - scroll down further if you want HSL as you asked for. HSV is rather a different beast.. Similar, but different. – enhzflep Dec 06 '13 at 12:43
  • @enhzflep thank! I've tried the function ColorHSLToRGB which expects hue, saturation and lightness in three ints and returns an array with indexes r, G and b. Regardless of input, it returns 13500 for all indexes. The values I'm providing are 209, 75, and 60 (the second and third are percentages. Know what I'm doing wrong? – Rich Jenks Dec 07 '13 at 09:24
  • @RichJenks - No worries. Yup. You're assuming that the values are constrained to [0..359], [0..100], [0..100] - In reality, they're all constrained to the range of [0..1]. How to fix? Divide Hue by 360, saturation and luminance by 100. If you then call var_dump on the return value, you'll get `array(3) { ["r"]=> float(76.5) ["g"]=> float(155.55) ["b"]=> float(229.5) }`. Using the format-specifier for printf, you can output as decimals. `printf("rgb = %d,%d,%d
    ", $result['r'], $result['g'], $result['b']);`
    – enhzflep Dec 07 '13 at 09:34
  • @enhzflep Thanks! Working now :) if you want to submit that as an answer I'll mark it as correct. – Rich Jenks Dec 07 '13 at 10:31
  • See my answer here: http://stackoverflow.com/a/34363975/3437608 for PHP code to and from HSL, from RGB and Hex. – Cullub Dec 19 '15 at 02:34

7 Answers7

10

Taking the code from one of the answers in the link of Jim's comment (PHP HSV to RGB formula comprehension), we can compute it as follows:

<?php    
    $hue = 209;
    $sat = 75;
    $lum = 60;

    $hue /= 360;
    $sat /= 100;
    $lum /= 100;

    $result = ColorHSLToRGB($hue, $sat, $lum);
    var_dump($result); echo '<br>';
    printf("rgb = %d,%d,%d<br>", $result['r'], $result['g'], $result['b']);




function ColorHSLToRGB($h, $s, $l){

        $r = $l;
        $g = $l;
        $b = $l;
        $v = ($l <= 0.5) ? ($l * (1.0 + $s)) : ($l + $s - $l * $s);
        if ($v > 0){
              $m;
              $sv;
              $sextant;
              $fract;
              $vsf;
              $mid1;
              $mid2;

              $m = $l + $l - $v;
              $sv = ($v - $m ) / $v;
              $h *= 6.0;
              $sextant = floor($h);
              $fract = $h - $sextant;
              $vsf = $v * $sv * $fract;
              $mid1 = $m + $vsf;
              $mid2 = $v - $vsf;

              switch ($sextant)
              {
                    case 0:
                          $r = $v;
                          $g = $mid1;
                          $b = $m;
                          break;
                    case 1:
                          $r = $mid2;
                          $g = $v;
                          $b = $m;
                          break;
                    case 2:
                          $r = $m;
                          $g = $v;
                          $b = $mid1;
                          break;
                    case 3:
                          $r = $m;
                          $g = $mid2;
                          $b = $v;
                          break;
                    case 4:
                          $r = $mid1;
                          $g = $m;
                          $b = $v;
                          break;
                    case 5:
                          $r = $v;
                          $g = $m;
                          $b = $mid2;
                          break;
              }
        }
        return array('r' => $r * 255.0, 'g' => $g * 255.0, 'b' => $b * 255.0);
}
?>

Output:

array(3) { ["r"]=> float(76.5) ["g"]=> float(155.55) ["b"]=> float(229.5) } 
rgb = 76,155,229
Community
  • 1
  • 1
enhzflep
  • 12,927
  • 2
  • 32
  • 51
  • Thanks for putting this together. I used in on one of my sites, and it seemed to work because I was using random colors anyway. However, I noticed if I just go through hue values of 0 through 359, I also get completely random numbers. Is this function really correct? – Swiss Mister Oct 17 '16 at 20:26
  • No problem, you're welcome. Yup, while the relationship between hsl and rgb is entirely unintuitive and smoothly varying H while maintainin the S and L values causes the rgb values to change wildly. Have a play with an image editing program and drag the eyedropper over a pic of an hsl colour selection swatch - the rgb values will change in just the same way. ;) - ive used this code for years for generating them. – enhzflep Oct 19 '16 at 01:22
  • That's really weird. I see indeed that most google results for HSL to RGB look strange to esoteric almost. But when I am in the Chrome inspector, click on a CSS color setting, and switch to HSL, I can just click from 0 to 359 and get exactly what I expect: that I go through the entire color wheel continuously. Now what is wrong? – Swiss Mister Oct 19 '16 at 18:22
  • @SwissMister - Sorry for the delay. I'm afraid I dont understand what you're getting-at here. **(0)** I see no colour wheel in Chrome's tools. **(1)** I see nowhere to click 0-360 (or 359) **(2)** I know (I think) what you're expecting, but have no idea what you're getting - explicitly stating each is very rarely a problem. Failing to do so usually or at least often, is. – enhzflep Oct 24 '16 at 10:22
  • 0: http://imgur.com/a/s0MFC -> 1: scroll through the values of the "H" field -> 2: I still am curious why there is no HSL2RGB function that does this. Do you have any idea? – Swiss Mister Nov 08 '16 at 10:39
  • I posted this answer: http://stackoverflow.com/a/40490546/1598477 - this code works and produces exactly what I want (see test code at the bottom in that answer code) – Swiss Mister Nov 08 '16 at 15:23
  • @SwissMister - uh-huh. It appears that your code fails to reproduce the colours that the browser does. I have constructed a jsfiddle example that utilizes the conversion you've provided and the one the browser contains. The produced images do not match. (Chrome 53.0.2785.143 m (64-bit) ) What do _you_ make of this? https://jsfiddle.net/aeh6vhuw/ (yours is the top image) – enhzflep Nov 08 '16 at 23:11
  • I can't see what you mean. jsfiddle is for javascript, but I posted and talk about a php function. – Swiss Mister Nov 15 '16 at 15:24
3

Putting this together (which helped me produce this chart)

/**
 * convert a HSL colorscheme to either Hexadecimal (default) or RGB.
 * 
 * We want a method where we can programmatically generate a series of colors
 * between two values (eg. red to green) which is easy to do with HSL because
 * you just change the hue. (0 = red, 120 = green).  You can use this function
 * to convert those hsl color values to either the rgb or hexadecimal color scheme.
 * e.g. You have
 *   hsl(50, 100%, 50%)
 * To convert,
 * $hex = convertHSL(50,100,50);  // returns #ffd500
 * or 
 * $rgb = convertHSL(50,100,50, false);  // returns rgb(255, 213, 0)
 *  
 * see https://coderwall.com/p/dvsxwg/smoothly-transition-from-green-to-red
 * @param int $h the hue
 * @param int $s the saturation
 * @param int $l the luminance
 * @param bool $toHex whether you want hexadecimal equivalent or rgb equivalent
 * @return string usable in HTML or CSS
 */

function convertHSL($h, $s, $l, $toHex=true){
    $h /= 360;
    $s /=100;
    $l /=100;

    $r = $l;
    $g = $l;
    $b = $l;
    $v = ($l <= 0.5) ? ($l * (1.0 + $s)) : ($l + $s - $l * $s);
    if ($v > 0){
          $m;
          $sv;
          $sextant;
          $fract;
          $vsf;
          $mid1;
          $mid2;

          $m = $l + $l - $v;
          $sv = ($v - $m ) / $v;
          $h *= 6.0;
          $sextant = floor($h);
          $fract = $h - $sextant;
          $vsf = $v * $sv * $fract;
          $mid1 = $m + $vsf;
          $mid2 = $v - $vsf;

          switch ($sextant)
          {
                case 0:
                      $r = $v;
                      $g = $mid1;
                      $b = $m;
                      break;
                case 1:
                      $r = $mid2;
                      $g = $v;
                      $b = $m;
                      break;
                case 2:
                      $r = $m;
                      $g = $v;
                      $b = $mid1;
                      break;
                case 3:
                      $r = $m;
                      $g = $mid2;
                      $b = $v;
                      break;
                case 4:
                      $r = $mid1;
                      $g = $m;
                      $b = $v;
                      break;
                case 5:
                      $r = $v;
                      $g = $m;
                      $b = $mid2;
                      break;
          }
    }
    $r = round($r * 255, 0);
    $g = round($g * 255, 0);
    $b = round($b * 255, 0);

    if ($toHex) {
        $r = ($r < 15)? '0' . dechex($r) : dechex($r);
        $g = ($g < 15)? '0' . dechex($g) : dechex($g);
        $b = ($b < 15)? '0' . dechex($b) : dechex($b);
        return "#$r$g$b";
    } else {
        return "rgb($r, $g, $b)";    
    }
Greg Rundlett
  • 1,056
  • 9
  • 11
3

My tests of all other implementations showed only weird and possibly unplausible results. Here is a PHP implementation of @Mohsen's code from https://stackoverflow.com/a/9493060/1598477. Plus a test to show the full beauty...

Sorry to cross-post this. But I really haven't seen any other implementation that gives the quality I needed.

/**
 * 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 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;
        }
function hslToRgb($h, $s, $l){
    if($s == 0){
        $r = $l;
        $g = $l;
        $b = $l; // achromatic
    }else{
        $q = $l < 0.5 ? $l * (1 + $s) : $l + $s - $l * $s;
        $p = 2 * $l - $q;
        $r = hue2rgb($p, $q, $h + 1/3);
        $g = hue2rgb($p, $q, $h);
        $b = hue2rgb($p, $q, $h - 1/3);
    }

    return array(round($r * 255), round($g * 255), round($b * 255));
}

/* Uncomment to test * /
for ($i=0;$i<360;$i++) {
  $rgb=hslToRgb($i/360, 1, .9);
  echo '<div style="background-color:rgb(' .$rgb[0] . ', ' . $rgb[1] . ', ' . $rgb[2] . ');padding:2px;"></div>';
}
/* End Test */
Community
  • 1
  • 1
Swiss Mister
  • 3,260
  • 2
  • 20
  • 42
1

The PEAR package Image_Color2 has methods to transform between color models - see convertTo.

cweiske
  • 30,033
  • 14
  • 133
  • 194
0

Here is my solution

HSV value restrictions: $H [0-359], $S [0-100], $V [0-100]

function hsv_to_rgb($iH, $iS, $iV) {

    if($iH < 0)   $iH = 0;
    if($iH > 360) $iH = 360;
    if($iS < 0)   $iS = 0;
    if($iS > 100) $iS = 100;
    if($iV < 0)   $iV = 0;
    if($iV > 100) $iV = 100;
    $dS = $iS/100.0;
    $dV = $iV/100.0;
    $dC = $dV*$dS;
    $dH = $iH/60.0;
    $dT = $dH;
    while($dT >= 2.0) $dT -= 2.0; // php modulus does not work with float
    $dX = $dC*(1-abs($dT-1));     // as used in the Wikipedia link
    switch($dH) {
      case($dH >= 0.0 && $dH < 1.0):
        $dR = $dC; $dG = $dX; $dB = 0.0; break;
      case($dH >= 1.0 && $dH < 2.0):
        $dR = $dX; $dG = $dC; $dB = 0.0; break;
      case($dH >= 2.0 && $dH < 3.0):
        $dR = 0.0; $dG = $dC; $dB = $dX; break;
      case($dH >= 3.0 && $dH < 4.0):
        $dR = 0.0; $dG = $dX; $dB = $dC; break;
      case($dH >= 4.0 && $dH < 5.0):
        $dR = $dX; $dG = 0.0; $dB = $dC; break;
      case($dH >= 5.0 && $dH < 6.0):
        $dR = $dC; $dG = 0.0; $dB = $dX; break;
      default:
        $dR = 0.0; $dG = 0.0; $dB = 0.0; break;
    }
    $dM  = $dV - $dC;
    $dR += $dM; $dG += $dM; $dB += $dM;
    $dR *= 255; $dG *= 255; $dB *= 255;
    return array(round($dR), round($dG), round($dB));
  }
0

Copied from this SO Answer by Cullub.

This code worked perfectly to convert a user email to user avatar colour. After long hours of searching, I got the correct hex value similar to the one generated using javascript on the front end.

TL;DR: The full code can be found here on Pastebin.

/**
 * convert user email to hsl for user avatar
 * @param string $string
 * @return string HEX color code
 */
function stringToColor($string)
{
    $hash = 0;
    $l = 70;
    $s = 60;
    for ($i = 0; $i < strlen($string); $i++) {
        $hash = ord($string[$i]) + (($hash << 5) - $hash);
    }
    $h = fmod($hash, 360);
    return $this->hslToHex($h, $s, $l, true);
}

/**
 * Converts HSL to Hex by converting it to 
 * RGB, then converting that to hex.
 * 
 * string hslToHex($h, $s, $l[, $prependPound = true]
 * 
 * $h is the Degrees value of the Hue
 * $s is the Percentage value of the Saturation
 * $l is the Percentage value of the Lightness
 * $prependPound is a bool, whether you want a pound 
 *  sign prepended. (optional - default=true)
 *
 * Calls: 
 *   hslToRgb
 *
 * Output: Hex in the format: #00ff88 (with 
 * pound sign).  Rounded to the nearest whole
 * number.
 */
function hslToHex($h, $s, $l, $prependPound = true)
{
    //convert hsl to rgb
    $rgb = $this->hslToRgb($h, $s, $l);

    //convert rgb to hex
    $hexR = $rgb['r'];
    $hexG = $rgb['g'];
    $hexB = $rgb['b'];

    //round to the nearest whole number
    $hexR = round($hexR);
    $hexG = round($hexG);
    $hexB = round($hexB);

    //convert to hex
    $hexR = dechex($hexR);
    $hexG = dechex($hexG);
    $hexB = dechex($hexB);

    //check for a non-two string length
    //if it's 1, we can just prepend a
    //0, but if it is anything else non-2,
    //it must return false, as we don't 
    //know what format it is in.
    if (strlen($hexR) != 2) {
        if (strlen($hexR) == 1) {
            //probably in format #0f4, etc.
            $hexR = "0" . $hexR;
        } else {
            //unknown format
            return false;
        }
    }
    if (strlen($hexG) != 2) {
        if (strlen($hexG) == 1) {
            $hexG = "0" . $hexG;
        } else {
            return false;
        }
    }
    if (strlen($hexB) != 2) {
        if (strlen($hexB) == 1) {
            $hexB = "0" . $hexB;
        } else {
            return false;
        }
    }

    //if prependPound is set, will prepend a
    //# sign to the beginning of the hex code.
    //(default = true)
    $hex = "";
    if ($prependPound) {
        $hex = "#";
    }

    $hex = $hex . $hexR . $hexG . $hexB;

    return $hex;
}
/**
 * Converts an HSL color value to RGB. Conversion formula
 * adapted from http://www.niwa.nu/2013/05/math-behind-colorspace-conversions-rgb-hsl/.
 * Assumes h, s, and l are in the format Degrees,
 * Percent, Percent, and returns r, g, and b in 
 * the range [0 - 255].
 *
 * Called by hslToHex by default.
 *
 * Calls: 
 *   degPercPercToHsl
 *   hueToRgb
 *
 * @param   Number  h       The hue value
 * @param   Number  s       The saturation level
 * @param   Number  l       The luminence
 * @return  Array           The RGB representation
 */
function hslToRgb($h, $s, $l)
{
    //convert the hue's 360 degrees in a circle to 1
    $h /= 360;
    //convert the saturation and lightness to the 0-1 
    //range by multiplying by 100
    $s /= 100;
    $l /= 100;

    //If there's no saturation, the color is a greyscale,
    //so all three RGB values can be set to the lightness.
    //(Hue doesn't matter, because it's grey, not color)
    if ($s == 0) {
        $r = $l * 255;
        $g = $l * 255;
        $b = $l * 255;
    } else {
        //calculate some temperary variables to make the 
        //calculation eaisier.
        if ($l < 0.5) {
            $temp2 = $l * (1 + $s);
        } else {
            $temp2 = ($l + $s) - ($s * $l);
        }
        $temp1 = 2 * $l - $temp2;

        //run the calculated vars through hueToRgb to
        //calculate the RGB value.  Note that for the Red
        //value, we add a third (120 degrees), to adjust 
        //the hue to the correct section of the circle for
        //red.  Simalarly, for blue, we subtract 1/3.
        $r = 255 * $this->hueToRgb($temp1, $temp2, $h + (1 / 3));
        $g = 255 * $this->hueToRgb($temp1, $temp2, $h);
        $b = 255 * $this->hueToRgb($temp1, $temp2, $h - (1 / 3));
    }
    $rgb['r'] = $r;
    $rgb['g'] = $g;
    $rgb['b'] = $b;
    return $rgb;
    // return "rgb($r, $g, $b)";
}
/**
 * Converts an HSL hue to it's RGB value.  
 *
 * Input: $temp1 and $temp2 - temperary vars based on 
 * whether the lumanence is less than 0.5, and 
 * calculated using the saturation and luminence
 * values.
 *  $hue - the hue (to be converted to an RGB 
 * value)  For red, add 1/3 to the hue, green 
 * leave it alone, and blue you subtract 1/3 
 * from the hue.
 *
 * Output: One RGB value.
 *
 * Thanks to Easy RGB for this function (Hue_2_RGB).
 * http://www.easyrgb.com/index.php?X=MATH&$h=19#text19
 *
 */
function hueToRgb($temp1, $temp2, $hue)
{
    if ($hue < 0) {
        $hue += 1;
    }
    if ($hue > 1) {
        $hue -= 1;
    }

    if ((6 * $hue) < 1) {
        return ($temp1 + ($temp2 - $temp1) * 6 * $hue);
    } elseif ((2 * $hue) < 1) {
        return $temp2;
    } elseif ((3 * $hue) < 2) {
        return ($temp1 + ($temp2 - $temp1) * ((2 / 3) - $hue) * 6);
    }
    return $temp1;
}
SMJ
  • 716
  • 1
  • 9
  • 23
-2

If you have decimal RGB values (enhzflep showed how to get them), you can easily get the #ab01cd hex web string:

$rgb['r'] = ($t = round($rgb['r'] * 255, 0)) < 15 ? '0'.dechex($t) : dechex($t);
$rgb['g'] = ($t = round($rgb['g'] * 255, 0)) < 15 ? '0'.dechex($t) : dechex($t);
$rgb['b'] = ($t = round($rgb['b'] * 255, 0)) < 15 ? '0'.dechex($t) : dechex($t);
$hexweb = "#".$rgb['r'].$rgb['g'].$rgb['b'];
Martin Thoma
  • 124,992
  • 159
  • 614
  • 958
  • 2
    This is potentially useful once he gets the RGB, but it doesn't answer the question. Btw, this can be simplified down to `'#'.str_pad(dechex(($r<<16) + ($g<<8) + $b),6,'0',STR_PAD_LEFT);` – Chinoto Vokro Mar 09 '16 at 17:51