5

Background

Hi All, I 've been playing around with colour (color!) formats and needed to create a class for converting different formats, so I thought I'd share it. It seems to work quite well and accepts a number of different convenient formats.

The class allows the conversion between HSL, HSV, RGB, CMYK, HEX (base16), Integer (base10) and 'HTML color names'. I found the formulae for conversion from pseudocode in the following locations:

http://www.rapidtables.com/convert/color/ (great colour references!)
http://stackoverflow.com/questions/15202079/convert-hex-color (for the sscanf method)

It should be noted that conversions involving CMYK can only be approximate as there is no absolute formula for them. See

http://en.wikipedia.org/wiki/CMYK_color_model ('Conversion')

A number of formats take an array of values, and as an user's format for any one of these can differ, e.g. associative array, indexed array, string (csv type), then I extended the processing to allow for this.

Internally the class converts all input to rgb and then converts onwards from this.

Method and Parameters

The class has only one method: convert() and it takes 3 mandatory parameters $input, $formatIn, $formatOut

The possible values for formatIn (string, case-independent)

  • hsv
  • hsl
  • hex
  • name
  • int
  • rgb
  • cmyk

The possible values for formatOut (string, case-independent)

  • as above for formatIn
  • all

The 'all' format returns (obviously) all formats as an array with the keys as listed for the formats. Any array-based formats are indexed, not associative and where values usually require '%' or '#' symbols, these are included.

input

Note that the names of any associative keys are ignored, order is the important factor

hsl: array(120,'100%','50%') or '120,100%,50%' or array(120,1,0.5) or '120,1,0.5' (for 'lime')
hsv: array(300,'100%','50%') or '300,100%,50%' or array(300,1,0.5) or '300,1,0.5' (for 'purple')
rgb: array(0,128,0) or '0,128,0' or array('red'=>0,'green'=>128,'blue'=>0) (for 'green')
hex: #FF00FFor FF00FF or #F0F or FOF (or lowercase equivalents for 'magenta')
name: case-independent HTML names, e.g. 'teal', 'olive', 'deeppink', 'gainsboro'...
int: a 24-bit integer e.g. 0 ('black') to 16777215 ('white')
cmyk: array(0,0,1,0) or '0,0,1,0' (for 'yellow')

The names 'cyan' and 'aqua' have identical values, therefore when converting to name, 'cyan' will be returned.
The names 'magenta' and 'fuchsia' have identical values, therefore when converting to name, 'magenta' will be returned.
Using 'aqua' or 'fuchsia' from name to something else is fine.

Sample Usage

$col = new colorCon;

print_r($col->convert('olive','name','all'));

print_r($col->convert(array(20,30,249),'rgb','hex'));

print_r($col->convert('#ff23ac','hex','hsv'));

Output

Array
(
    [hex] => #808000
    [int] => 8421376
    [name] => olive
    [hsl] => Array
        (
            [0] => 60
            [1] => 100%
            [2] => 25%
        )

    [cmyk] => Array
        (
            [0] => 0
            [1] => 0
            [2] => 1
            [3] => 0.49803921568627
        )

    [hsv] => Array
        (
            [0] => 60
            [1] => 100%
            [2] => 50%
        )

    [rgb] => Array
        (
            [0] => 128
            [1] => 128
            [2] => 0
        )

)

#141ef9

Array
(
    [0] => 0
    [1] => 86%
    [2] => 100%
)

The Class

class colorCon
{
    public function convert($input,$formatIn,$formatOut)
    {
        $formats = array('hex'=>'Hex','int'=>'Int','name'=>'Name','hsl'=>'HSL','cmyk'=>'CMYK','hsv'=>'HSV');

        $in = strtolower(trim($formatIn));
        $out = strtolower(trim($formatOut));

        if(!is_array($input) && strpos($input, ',') !== false) $input = explode(',',$input);

        $clean = (is_array($input))  ? array_map('trim',$input) : trim($input);

        if((!in_array($in, array_keys($formats)) && $in != 'rgb') || (!in_array($out, array_keys($formats)) && $out != 'all' && $out != 'rgb')) return false;

        if($in != 'rgb')
        {
            $method = 'from' . $formats[$in];
            $rgb = $this->$method($clean);  
        }else{
            $rgb = array_values($clean);    
        }

        if($out != 'rgb')
        {
            if($out == 'all')
            {
                $output = array();
                foreach($formats as $key=>$format)
                {
                    if($key != $in)
                    {
                        $method = 'to' . $format;
                        $output[$key] = $this->$method($rgb);
                    }else{
                        $output[$key] = $clean; 
                    }
                }
                $output['rgb'] = $rgb;
                return $output;
            }
            $method = 'to' . $formats[$out];
            return $this->$method($rgb);

        }else{
            return $rgb;    
        }
    }

    private function toHex($rgb)
    {
        list($r,$g,$b) = $rgb;
        $hex = str_pad(base_convert($r,10,16),2,'0',STR_PAD_LEFT); 
        $hex .= str_pad(base_convert($g,10,16),2,'0',STR_PAD_LEFT); 
        $hex .= str_pad(base_convert($b,10,16),2,'0',STR_PAD_LEFT); 
        return '#' .$hex;
    }

    private function fromHex($hex)
    {
        switch (strlen($hex))
        {
            case 7:
                $clean = $hex;
                break;  
            case 6:
                $clean = '#' . $hex;
                break;
            case 4: 
                $clean = '#' . $hex{1} . $hex{1} . $hex{2} . $hex{2} . $hex{3} . $hex{3}; 
                break;
            case 3:
                $clean = '#' . $hex{0} . $hex{0} . $hex{1} . $hex{1} . $hex{2} . $hex{2};
                break;
            default:
                return false;       
        }
        return sscanf($clean, "#%02x%02x%02x");
    }

    private function toHSL($rgb)
    {
        list($r,$g,$b) = $rgb;

        //Calculation from http://www.rapidtables.com/convert/color/
        $R = $r/255;
        $G = $g/255;
        $B = $b/255;

        $Cx = max(array($R,$G,$B));
        $Cn = min(array($R,$G,$B));
        $D = $Cx - $Cn;

        if($Cx == $B)
        {
            $hue = ($D == 0) ? 0 : ((($R - $G)/$D) + 4) * 60;
        }elseif($Cx == $G){
            $hue = ($D == 0) ? 0 : ((($B - $R)/$D) + 2) * 60;
        }elseif($Cx == $R){
            $hue = ($D == 0) ? 0 : ((($G - $B)/$D) % 6) * 60;
        }
        if($hue < 0) $hue += 360;

        $light = ($Cx + $Cn) / 2;
        $sat = ($D == 0) ? 0 : $D/(1-abs((2*$light)-1));

        return array( $hue, round($sat*100) .'%', round($light*100) .'%' ); 
    }

    private function fromHSL($hsl)
    {
        list($h,$s,$l) = $hsl;
        if($h == 360) $h = 0;

        if(!is_numeric($s))
        {
            $s = intval($s);
        }
        if($s > 1) $s /= 100;
        if(!is_numeric($l))
        {
            $l = intval($l);
        }
        if($l > 1) $l /= 100;

        //Calculation from http://www.rapidtables.com/convert/color/
        $C = (1 - abs(2*$l - 1)) * $s;
        $X = $C * (1 - abs(($h / 60) % 2 - 1));
        $m = $l - $C/2;
        switch (true)
        {
            case $h < 60:
                $rgb = array($C+$m,$X+$m,$m);               
                break;
            case $h < 120:
                $rgb = array($X+$m,$C+$m,$m);
                break;
            case $h < 180:
                $rgb = array($m,$C+$m,$X+$m);
                break;
            case $h < 240:
                $rgb = array($m,$X+$m,$C+$m);
                break;
            case $h < 300:
                $rgb = array($X+$m,$m,$C+$m);
                break;
            case $h < 360:
                $rgb = array($C+$m,$m,$X+$m);
                break;
        }
        return array(round($rgb[0]*255),round($rgb[1]*255),round($rgb[2]*255));
    }

    private function toName($rgb)
    {
        $int = $this->toInt($rgb);

        $ints = array
        (
            0 => "black",
            128 => "navy",
            139 => "darkblue",
            205 => "mediumblue",
            255 => "blue",
            25600 => "darkgreen",
            32768 => "green",
            32896 => "teal",
            35723 => "darkcyan",
            49151 => "deepskyblue",
            52945 => "darkturquoise",
            64154 => "mediumspringgreen",
            65280 => "lime",
            65407 => "springgreen",
            65535 => "cyan",
            1644912 => "midnightblue",
            2003199 => "dodgerblue",
            2142890 => "lightseagreen",
            2263842 => "forestgreen",
            3050327 => "seagreen",
            3100495 => "darkslategray",
            3329330 => "limegreen",
            3978097 => "mediumseagreen",
            4251856 => "turquoise",
            4286945 => "royalblue",
            4620980 => "steelblue",
            4734347 => "darkslateblue",
            4772300 => "mediumturquoise",
            4915330 => "indigo ",
            5597999 => "darkolivegreen",
            6266528 => "cadetblue",
            6591981 => "cornflowerblue",
            6737322 => "mediumaquamarine",
            6908265 => "dimgray",
            6970061 => "slateblue",
            7048739 => "olivedrab",
            7372944 => "slategray",
            7833753 => "lightslategray",
            8087790 => "mediumslateblue",
            8190976 => "lawngreen",
            8388352 => "chartreuse",
            8388564 => "aquamarine",
            8388608 => "maroon",
            8388736 => "purple",
            8421376 => "olive",
            8421504 => "gray",
            8900331 => "skyblue",
            8900346 => "lightskyblue",
            9055202 => "blueviolet",
            9109504 => "darkred",
            9109643 => "darkmagenta",
            9127187 => "saddlebrown",
            9419919 => "darkseagreen",
            9498256 => "lightgreen",
            9662683 => "mediumpurple",
            9699539 => "darkviolet",
            10025880 => "palegreen",
            10040012 => "darkorchid",
            10145074 => "yellowgreen",
            10506797 => "sienna",
            10824234 => "brown",
            11119017 => "darkgray",
            11393254 => "lightblue",
            11403055 => "greenyellow",
            11529966 => "paleturquoise",
            11584734 => "lightsteelblue",
            11591910 => "powderblue",
            11674146 => "firebrick",
            12092939 => "darkgoldenrod",
            12211667 => "mediumorchid",
            12357519 => "rosybrown",
            12433259 => "darkkhaki",
            12632256 => "silver",
            13047173 => "mediumvioletred",
            13458524 => "indianred ",
            13468991 => "peru",
            13789470 => "chocolate",
            13808780 => "tan",
            13882323 => "lightgray",
            14204888 => "thistle",
            14315734 => "orchid",
            14329120 => "goldenrod",
            14381203 => "palevioletred",
            14423100 => "crimson",
            14474460 => "gainsboro",
            14524637 => "plum",
            14596231 => "burlywood",
            14745599 => "lightcyan",
            15132410 => "lavender",
            15308410 => "darksalmon",
            15631086 => "violet",
            15657130 => "palegoldenrod",
            15761536 => "lightcoral",
            15787660 => "khaki",
            15792383 => "aliceblue",
            15794160 => "honeydew",
            15794175 => "azure",
            16032864 => "sandybrown",
            16113331 => "wheat",
            16119260 => "beige",
            16119285 => "whitesmoke",
            16121850 => "mintcream",
            16316671 => "ghostwhite",
            16416882 => "salmon",
            16444375 => "antiquewhite",
            16445670 => "linen",
            16448210 => "lightgoldenrodyellow",
            16643558 => "oldlace",
            16711680 => "red",
            16711935 => "magenta",
            16716947 => "deeppink",
            16729344 => "orangered",
            16737095 => "tomato",
            16738740 => "hotpink",
            16744272 => "coral",
            16747520 => "darkorange",
            16752762 => "lightsalmon",
            16753920 => "orange",
            16758465 => "lightpink",
            16761035 => "pink",
            16766720 => "gold",
            16767673 => "peachpuff",
            16768685 => "navajowhite",
            16770229 => "moccasin",
            16770244 => "bisque",
            16770273 => "mistyrose",
            16772045 => "blanchedalmond",
            16773077 => "papayawhip",
            16773365 => "lavenderblush",
            16774638 => "seashell",
            16775388 => "cornsilk",
            16775885 => "lemonchiffon",
            16775920 => "floralwhite",
            16775930 => "snow",
            16776960 => "yellow",
            16777184 => "lightyellow",
            16777200 => "ivory",
            16777215 => "white",
        );
        return (isset($ints[$int])) ? $ints[$int] : false;
    }

    private function fromName($name)
    {
        $colors = array
        (
            "aliceblue" => array(240,248,255),
            "antiquewhite" => array(250,235,215),
            "aqua" => array(0,255,255),
            "aquamarine" => array(127,255,212),
            "azure" => array(240,255,255),
            "beige" => array(245,245,220),
            "bisque" => array(255,228,196),
            "black" => array(0,0,0),
            "blanchedalmond" => array(255,235,205),
            "blue" => array(0,0,255),
            "blueviolet" => array(138,43,226),
            "brown" => array(165,42,42),
            "burlywood" => array(222,184,135),
            "cadetblue" => array(95,158,160),
            "chartreuse" => array(127,255,0),
            "chocolate" => array(210,105,30),
            "coral" => array(255,127,80),
            "cornflowerblue" => array(100,149,237),
            "cornsilk" => array(255,248,220),
            "crimson" => array(220,20,60),
            "cyan" => array(0,255,255),
            "darkblue" => array(0,0,139),
            "darkcyan" => array(0,139,139),
            "darkgoldenrod" => array(184,134,11),
            "darkgray" => array(169,169,169),
            "darkgreen" => array(0,100,0),
            "darkkhaki" => array(189,183,107),
            "darkmagenta" => array(139,0,139),
            "darkolivegreen" => array(85,107,47),
            "darkorange" => array(255,140,0),
            "darkorchid" => array(153,50,204),
            "darkred" => array(139,0,0),
            "darksalmon" => array(233,150,122),
            "darkseagreen" => array(143,188,143),
            "darkslateblue" => array(72,61,139),
            "darkslategray" => array(47,79,79),
            "darkturquoise" => array(0,206,209),
            "darkviolet" => array(148,0,211),
            "deeppink" => array(255,20,147),
            "deepskyblue" => array(0,191,255),
            "dimgray" => array(105,105,105),
            "dodgerblue" => array(30,144,255),
            "firebrick" => array(178,34,34),
            "floralwhite" => array(255,250,240),
            "forestgreen" => array(34,139,34),
            "fuchsia" => array(255,0,255),
            "gainsboro" => array(220,220,220),
            "ghostwhite" => array(248,248,255),
            "gold" => array(255,215,0),
            "goldenrod" => array(218,165,32),
            "gray" => array(128,128,128),
            "green" => array(0,128,0),
            "greenyellow" => array(173,255,47),
            "honeydew" => array(240,255,240),
            "hotpink" => array(255,105,180),
            "indianred " => array(205,92,92),
            "indigo " => array(75,0,130),
            "ivory" => array(255,255,240),
            "khaki" => array(240,230,140),
            "lavender" => array(230,230,250),
            "lavenderblush" => array(255,240,245),
            "lawngreen" => array(124,252,0),
            "lemonchiffon" => array(255,250,205),
            "lightblue" => array(173,216,230),
            "lightcoral" => array(240,128,128),
            "lightcyan" => array(224,255,255),
            "lightgoldenrodyellow" => array(250,250,210),
            "lightgray" => array(211,211,211),
            "lightgreen" => array(144,238,144),
            "lightpink" => array(255,182,193),
            "lightsalmon" => array(255,160,122),
            "lightseagreen" => array(32,178,170),
            "lightskyblue" => array(135,206,250),
            "lightslategray" => array(119,136,153),
            "lightsteelblue" => array(176,196,222),
            "lightyellow" => array(255,255,224),
            "lime" => array(0,255,0),
            "limegreen" => array(50,205,50),
            "linen" => array(250,240,230),
            "magenta" => array(255,0,255),
            "maroon" => array(128,0,0),
            "mediumaquamarine" => array(102,205,170),
            "mediumblue" => array(0,0,205),
            "mediumorchid" => array(186,85,211),
            "mediumpurple" => array(147,112,219),
            "mediumseagreen" => array(60,179,113),
            "mediumslateblue" => array(123,104,238),
            "mediumspringgreen" => array(0,250,154),
            "mediumturquoise" => array(72,209,204),
            "mediumvioletred" => array(199,21,133),
            "midnightblue" => array(25,25,112),
            "mintcream" => array(245,255,250),
            "mistyrose" => array(255,228,225),
            "moccasin" => array(255,228,181),
            "navajowhite" => array(255,222,173),
            "navy" => array(0,0,128),
            "oldlace" => array(253,245,230),
            "olive" => array(128,128,0),
            "olivedrab" => array(107,142,35),
            "orange" => array(255,165,0),
            "orangered" => array(255,69,0),
            "orchid" => array(218,112,214),
            "palegoldenrod" => array(238,232,170),
            "palegreen" => array(152,251,152),
            "paleturquoise" => array(175,238,238),
            "palevioletred" => array(219,112,147),
            "papayawhip" => array(255,239,213),
            "peachpuff" => array(255,218,185),
            "peru" => array(205,133,63),
            "pink" => array(255,192,203),
            "plum" => array(221,160,221),
            "powderblue" => array(176,224,230),
            "purple" => array(128,0,128),
            "red" => array(255,0,0),
            "rosybrown" => array(188,143,143),
            "royalblue" => array(65,105,225),
            "saddlebrown" => array(139,69,19),
            "salmon" => array(250,128,114),
            "sandybrown" => array(244,164,96),
            "seagreen" => array(46,139,87),
            "seashell" => array(255,245,238),
            "sienna" => array(160,82,45),
            "silver" => array(192,192,192),
            "skyblue" => array(135,206,235),
            "slateblue" => array(106,90,205),
            "slategray" => array(112,128,144),
            "snow" => array(255,250,250),
            "springgreen" => array(0,255,127),
            "steelblue" => array(70,130,180),
            "tan" => array(210,180,140),
            "teal" => array(0,128,128),
            "thistle" => array(216,191,216),
            "tomato" => array(255,99,71),
            "turquoise" => array(64,224,208),
            "violet" => array(238,130,238),
            "wheat" => array(245,222,179),
            "white" => array(255,255,255),
            "whitesmoke" => array(245,245,245),
            "yellow" => array(255,255,0),
            "yellowgreen" => array(154,205,50),
        );
        $n = strtolower(trim($name));
        if(in_array($n,array_keys($colors)))
        {
            return $colors[$n]; 
        }
        return false;
    }

    private function fromInt($int)
    {
        $clean = (int) $int;
        $b =  $clean & 255;
        $g = ($clean >> 8) & 255;
        $r = ($clean >> 16) & 255;
        return array($r,$g,$b);     
    }

    private function toInt($rgb)
    {
        list($r,$g,$b) = $rgb;
        return 65536 * $r + 256 * $g + $b;
    }

    private function toCMYK($rgb)
    {
        list($r,$g,$b) = $rgb;

        //Calculation from http://www.rapidtables.com/convert/color/
        $R = $r/255;
        $G = $g/255;
        $B = $b/255;

        $k = 1 - max($R,$G,$B);
        $c = (1 - $R - $k) / (1 - $k);
        $m = (1 - $G - $k) / (1 - $k);
        $y = (1 - $B - $k) / (1 - $k);  

        return array($c,$m,$y,$k);
    }

    private function fromCMYK($cmyk)
    {
        list($c,$m,$y,$k) = $cmyk;
        $r = 255 * (1 - $c) * (1 - $k);
        $g = 255 * (1 - $m) * (1 - $k);
        $b = 255 * (1 - $y) * (1 - $k);

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

    private function toHSV($rgb)
    {
        list($r,$g,$b) = $rgb;

        //Calculation from http://www.rapidtables.com/convert/color/
        $R = $r/255;
        $G = $g/255;
        $B = $b/255;

        $Cx = max(array($R,$G,$B));
        $Cn = min(array($R,$G,$B));
        $D = $Cx - $Cn;

        if($Cx == $B)
        {
            $hue = ($D == 0) ? 0 : ((($R - $G)/$D) + 4) * 60;
        }elseif($Cx == $G){
            $hue = ($D == 0) ? 0 : ((($B - $R)/$D) + 2) * 60;
        }elseif($Cx == $R){
            $hue = ($D == 0) ? 0 : ((($G - $B)/$D) % 6) * 60;
        }
        if($hue < 0) $hue += 360;

        $value = $Cx;
        $sat = ($D == 0) ? 0 : $D/$Cx;

        return array( $hue, round($sat*100) .'%', round($value*100) .'%' );

    }

 /**
  *
  * @param mixed $hsv (csv string or indexed/associative array)
  * 
  * @return array ((int) red, (int) green, (int) blue) where int 0 - 255
  */    
    private function fromHSV($hsv)
    {
        list($h,$s,$v) = $hsv;
        if($h == 360) $h = 0;

        //To get rid of % signs
        if(!is_numeric($s))
        {
            $s = intval($s);
        }
        //If value in %age -> decimal
        if($s > 1) $s /= 100;
        //To get rid of % signs
        if(!is_numeric($v))
        {
            $v = intval($v);
        }
        //If value as %age -> decimal
        if($v > 1) $v /= 100;

        //Calculation from http://www.rapidtables.com/convert/color/
        $C = $v * $s;
        $X = $C * (1 - abs(($h / 60) % 2 - 1));
        $m = $v - $C;

        switch (true)
        {
            case $h < 60:
                $rgb = array($C+$m,$X+$m,$m);               
                break;
            case $h < 120:
                $rgb = array($X+$m,$C+$m,$m);
                break;
            case $h < 180:
                $rgb = array($m,$C+$m,$X+$m);
                break;
            case $h < 240:
                $rgb = array($m,$X+$m,$C+$m);
                break;
            case $h < 300:
                $rgb = array($X+$m,$m,$C+$m);
                break;
            case $h < 360:
                $rgb = array($C+$m,$m,$X+$m);
                break;
        }

        return array(round($rgb[0]*255),round($rgb[1]*255),round($rgb[2]*255));
    }
}

Feedback

Any feeback including possible improvements most welcome :)
I am thinking about extending it to include opacity for HSLa and RGBa. Maybe other formats too.

Edited by diafol

Votes + Comments
nice job!
really interesting!
Cool
3
Contributors
4
Replies
37
Views
3 Years
Discussion Span
Last Post by diafol
1

Nice job. Haven't tested the class but after scanning the code it is obvious that this is a nice example of clean coding / useful functionality. Quite some work must have been put into this to gather all the information an put it into this little tool. I remember myself struggling a couple of times needing to convert between formats.

Adding suppport for transparency might be also a very good idea.

Thank you for sharing.

0

No problem :)

I've added css output for those formats that support it (string as opposed to array) and I've got HSLa sorted - but as RGBa is related to the internal workings (rgb), I'm looking at a major bit of refactoring -especially the constructor. Never mind - it's all good practice :)

1

You could use array_flip to remove the need for the two constant arrays. Although a conversion is needed then.

Votes + Comments
good idea
0

Yes I'm aware of the array_flip. It may be an idea - I'd then store the array in class scope - probably where it need to be actually. The additional conversion from int to rgb/a shouldn't be a problem. Good idea :)

BTW - I've added support for opacity and css strings. However, on further testing I've found that the HSL conversion is acting strangely - it works 'most of the time', but then it has a problem with others. I'll have to investigate to see if I've got a small error in the formula somewhere.

I'll post an updated version ... sometime :)

Edited by diafol

This topic has been dead for over six months. Start a new discussion instead.
Have something to contribute to this discussion? Please be thoughtful, detailed and courteous, and be sure to adhere to our posting rules.