0

Below is the function I am using to convert hex colour values to HSL in PHP. This works almost all of the time, however, there are certain colours that produce incorrect values and I cannot figure out why.

function hexToHsl($hex)
{
    $red = hexdec(substr($hex, 0, 2)) / 255;
    $green = hexdec(substr($hex, 2, 2)) / 255;
    $blue = hexdec(substr($hex, 4, 2)) / 255;

    $cmin = min($red, $green, $blue);
    $cmax = max($red, $green, $blue);
    $delta = $cmax - $cmin;

    if ($delta === 0) {
        $hue = 0;
    } elseif ($cmax === $red) {
        $hue = (($green - $blue) / $delta) % 6;
    } elseif ($cmax === $green) {
        $hue = ($blue - $red) / $delta + 2;
    } else {
        $hue = ($red - $green) / $delta + 4;
    }

    $hue = round($hue * 60);
    if ($hue < 0) {
        $hue += 360;
    }

    $lightness = (($cmax + $cmin) / 2) * 100;
    $saturation = $delta === 0 ? 0 : ($delta / (1 - abs(2 * $lightness - 1))) * 100;
    if ($saturation < 0) {
        $saturation += 100;
    }

    $lightness = round($lightness);
    $saturation = round($saturation);

    return array("h" => $hue, "s" => $saturation, "l" => $lightness);
}

Example of a colour that does not work correctly:

Input: #cd5985

expected output: hsl(337, 54%, 58%)

actual output: hsl(0, 58%, 100%)

Kieran
  • 46
  • 3
  • Did you check this out? https://gist.github.com/bedeabza/10463089 – Sandro Vardiashvili Sep 02 '20 at 12:55
  • Yes, that example seems to produce an array of three values between 0 - 1, not an actual hsl() value. As there is no documentation I can find, im not sure what to do with those 3 values to then convert them into Hue, Saturation and Brightness values – Kieran Sep 02 '20 at 14:16
  • you can just implode the returned array, separate it by comma and you'll have your actual hsl() – Sandro Vardiashvili Sep 03 '20 at 06:43
  • No, the values im getting are all between 0 - 1 for all 3 columns. This would make sense for a percentage but not for the Hue value. – Kieran Sep 03 '20 at 08:18

1 Answers1

0

I think your problem is using % integer division rather than fmod() in the line $hue = (($green - $blue) / $delta) % 6;

Try using this instead $hue = fmod( ( ( $green - $blue ) / $delta ), 6 );

If you have a version that is producing values between 0 and 1 in an array then simply multiply the hue by 360 to make it degrees and the saturation and lightness by 100 to make them percents.

Complete version below returning a CSS compatible string

    function  hex2hsl($hexstr) {
        $hexstr = ltrim($hexstr, '#');
        if (strlen($hexstr) == 3) {
            $hexstr = $hexstr[0] . $hexstr[0] . $hexstr[1] . $hexstr[1] . $hexstr[2] . $hexstr[2];
        }
        $R = hexdec($hexstr[0] . $hexstr[1]);
        $G = hexdec($hexstr[2] . $hexstr[3]);
        $B = hexdec($hexstr[4] . $hexstr[5]);
        $RGB = array($R,$G,$B);
//scale value 0 to 255 to floats from 0 to 1
        $r = $RGB[0]/255;
        $g = $RGB[1]/255;
        $b = $RGB[2]/255;
        // using https://gist.github.com/brandonheyer/5254516
        $max = max( $r, $g, $b );
        $min = min( $r, $g, $b );
        // lum
        $l = ( $max + $min ) / 2;
        
        // sat
        $d = $max - $min;
        if( $d == 0 ){
            $h = $s = 0; // achromatic
        } else {
            $s = $d / ( 1 - abs( (2 * $l) - 1 ) );
            // hue
            switch( $max ){
                case $r:
                    $h = 60 * fmod( ( ( $g - $b ) / $d ), 6 );
                    if ($b > $g) {
                        $h += 360;
                    }
                    break;
                case $g:
                    $h = 60 * ( ( $b - $r ) / $d + 2 );
                    break;
                case $b:
                    $h = 60 * ( ( $r - $g ) / $d + 4 );
                    break;
            }
        }
        $hsl = array( round( $h ), round( $s*100 ), round( $l*100 ) );
        $hslstr = 'hsl('.($hsl[0]).','.($hsl[1]).'%,'.($hsl[2]).'%)';
        return $hslstr; 
// or return the $hsl array if you want to make adjustments to values
    }

The above can be simplified/shortened in places, but left expanded for clarity. The core conversion comes from https://gist.github.com/brandonheyer/5254516

RogerCO
  • 21
  • 3