<?php
/*
Copyright 2011 Mikael Jacobson. All rights reserved.

Redistribution and use in source and binary forms, with or without modification, are
permitted provided that the following conditions are met:

   1. Redistributions of source code must retain the above copyright notice, this list of
      conditions and the following disclaimer.

   2. Redistributions in binary form must reproduce the above copyright notice, this list
      of conditions and the following disclaimer in the documentation and/or other materials
      provided with the distribution.

THIS SOFTWARE IS PROVIDED BY Mikael Jacobson ''AS IS'' AND ANY EXPRESS OR IMPLIED
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL Mikael Jacobson OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

The views and conclusions contained in the software and documentation are those of the
authors and should not be interpreted as representing official policies, either expressed
or implied, of Mikael Jacobson.
*/

/**
 * Used for manipulating colors and switching between various color spaces,
 * colors are stored as HSL internally.
 * @author Mikael Jacobson <mikael@pantburk.info>
 * @copyright Copyright (c) 2011 Mikael Jacobson
 * @license http://pantburk.info/colorcreator/LICENSE
 * @version 1.2.6
 */
class ColorCreator {
    private 
$hue// Stored internally as ∈ [0,1]
    
private $saturation;
    private 
$lightness;
    
    
/**
     * Default constructor, defaults to setting the color to black.
     */
    
public function __construct() {
        
$this->hue $this->saturation $this->lightness 0;
    }

    
/**
     * Acts as constructor for instancing an object using HSL values
     * @param mixed $hue Hue as ∈ [0,360]
     * @param float $sat Saturation as ∈ [0,1]
     * @param float $lightness Lightness as ∈ [0,1]
     * @return ColorCreator New instance
     */
    
public static function fromHSL($hue=0$sat=0$lightness=0) {
        if(
$sat || $sat 1.0 || $lightness || $lightness 1.0) {
            
error_log($hue." - ".$sat." - ".$lightness);
            throw new 
InvalidArgumentException('Argument out of range');
        }

        
$instance = new self();
        
$instance->setHue($hue);
        
$instance->setSaturation($sat);
        
$instance->setLightness($lightness);
        return 
$instance;
    }

    
/**
     * Acts as constructor for instancing an object using red, green and blue values
     * @param int $red Value for red as ∈ [0,255]
     * @param int $green Value for green as ∈ [0,255]
     * @param int $blue Value for blue as ∈ [0,255]
     * @return ColorCreator New instance
     */
    
public static function fromRGB($red$green$blue) {
        if(
$red || $red 255 || $green || $green 255 || $blue || $blue 255) {
            throw new 
InvalidArgumentException('Argument out of range');
        }

        
$hsl self::rgbToHSL($red$green$blue);
        return 
self::fromHSL($hsl['h'], $hsl['s'], $hsl['l']);
    }

    
/**
     * Acts as constructor for instancing an object using a hex string with
     * the format "rrggbb".
     * WARNING: Appears to be broken (creating objects through this path has
     * proven itself to give erronous output colors).
     * @param string $hex Input string
     * @return ColorCreator New instance
     */
    
public static function fromHex($hex) {
        
$hsl self::hexToHSL($hex);
        return 
self::fromHSL($hsl['h'], $hsl['s'], $hsl['l']);
    }

    
/**
     * Converts RGB values to an array of HSL values
     * WARNING: Possibly broken and causing the fromHex() problem...
     * @param int $red Value for red as ∈ [0,255]
     * @param int $green Value for green as ∈ [0,255]
     * @param int $blue Value for blue as ∈ [0,255]
     * @return array An array with HSL values
     */
    
private static function rgbToHSL($red$green$blue) {
        
$result = array("h" => 0"s" => 0"l" => 0);
        
$colors = array("r" => ($red 255), "g" => ($green 255), "b" => ($blue 255));

        
$maxcolor 0.0;
        
$mincolor 1.0;
        foreach(
$colors as $color) {
            if(
$color $maxcolor) { $maxcolor $color; };
            if(
$color $mincolor) { $mincolor $color; };
        }
        
        
// Lightness
        
$result['l'] = ($maxcolor $mincolor)/2;

        
// Saturation
        
if($maxcolor != $mincolor) {
            if(
$result['l'] < 0.5) {
                
$result['s'] = ($maxcolor $mincolor) / ($maxcolor $mincolor);
            }
            else {
                
$result['s'] = ($maxcolor $mincolor) / (2.0 $maxcolor $mincolor);
            }

            
// Hue
            
if($colors['r'] == $maxcolor) {
                
$result['h'] = ($colors['g'] - $colors['b']) / ($maxcolor $mincolor);
            }
            else if(
$colors['g']) {
                
$result['h'] = 2.0 + (($colors['b'] - $colors['r']) / ($maxcolor $mincolor));
            }
            else if(
$colors['b']) {
                
$result['h'] = 4.0 + (($colors['r'] - $colors['g']) / ($maxcolor $mincolor));
            }
        }


        
$result['h'] = fmod((60.0 $result['h']), 360);
        if(
$result['h'] < 0) {
            
$result['h'] += 360;
        }

        return 
$result;
    }

    
/**
     * Converts an HSL color to an array of RGB values
     * Uses the algorithm described by the book Fundamentals of Interactive
     * Computer Graphics by Foley and van Dam.
     * @param mixed $hue Hue
     * @param float $saturation Saturation
     * @param float $lightness Lightness
     * @return array An array with RGB values
     */
    
private static function hslToRGB($hue$saturation$lightness) {
        
$r $g $b 0;
        if((float)
$lightness === 0.0) {
            
$g $g $b 0;
        }
        else {
            if((float)
$saturation === 0) {
                
$r $g $b = (float)$lightness;
            }
            else {
                
$temp2 0;
                if(
$lightness 0.5) {
                    
$temp2 $lightness * (1.0 $saturation);
                }
                else {
                    
$temp2 $lightness $saturation - ($lightness $saturation);
                }
                
$temp1 2.0 $lightness $temp2;
                
                
$temp3 = array($hue 1.0/3.0,
                               
$hue,
                               
$hue 1.0/3.0);
                
$clr = array(0.00.00.0);
                
                for(
$i 0$i 3$i++) {
                    if(
$temp3[$i] < 0) {
                        
$temp3[$i] += 1.0;
                    }
                    if(
$temp3[$i] > 1) {
                        
$temp3[$i] -= 1.0;
                    }
                    
                    if(
6.0 $temp3[$i] < 1) {
                        
$clr[$i] = $temp1 + (($temp2-$temp1) * $temp3[$i] * 6.0);
                    }
                    else if(
2.0 $temp3[$i] < 1) {
                        
$clr[$i] = $temp2;
                    }
                    else if(
3.0 $temp3[$i] < 2) {
                        
$clr[$i] = $temp1 + ($temp2-$temp1) * ((2.0/3.0) - $temp3[$i]) * 6.0;
                    }
                    else {
                        
$clr[$i] = $temp1;
                    }
                }
                
                
$r $clr[0];
                
$g $clr[1];
                
$b $clr[2];
            }
        }        

        
$r $r 255;
        
$g $g 255;
        
$b $b 255;
        
        return array(
"r" => $r"g" => $g"b" => $b);
    }

    
/**
     * Converts a color as a hex string in the format rrggbb into an
     * RGB array.
     * @param string $hex Hex string
     * @return array Array of three ints in ∈ [0,255]
     */
    
private static function hexToRGB($hex) {
        
$hex strtolower($hex);
        if(
preg_match('/[^0-9abcdef]/'$hex) || strlen($hex) != 6) {
            throw new 
InvalidArgumentException('Invalid input given to fromHex!');
        }

        
$chunks str_split($hex2);
        
$red hexdec($chunks[0]);
        
$green hexdec($chunks[1]);
        
$blue hexdec($chunks[2]);
        
        return array(
'r' => $red'g' => $green'b' => $blue);        
    }

    
/**
     * Converts a color as a hex string in the format rrggbb into an
     * HSL array.
     * @param string $hex Hex string
     * @return array Array with HSL values
     */
    
private static function hexToHSL($hex) {
        
$rgb self::hexToRGB($hex);
        return 
self::rgbToHSL($rgb['r'], $rgb['g'], $rgb['b']);
    }

    
/**
     * Returns the current color as a HSL array.
     * @return array Array of Hue ∈ [0,360], Saturation ∈ [0,1] and Lightness ∈ [0,1]
     */
    
public function toHSL() {
         return array(
"h" => $this->getHue(), "s" => $this->getSaturation(), "l" => $this->getLightness());
    }

    
/**
     * Returns the current color as an RGB value
     * @return array Array of three ints in ∈ [0,255]
     */
    
public function toRGB() {
        return 
self::hslToRGB($this->hue$this->saturation$this->lightness);
    }

    
/**
     * Returns the current color as a hex string
     * @return array Array of three hex strings representing Red, Green and Blue color channels.
     */
    
public function toHex() {
        
$rgb self::hslToRGB($this->hue$this->saturation$this->lightness);

        
$r str_pad(dechex(round($rgb['r'], 0)), 2"0"STR_PAD_LEFT);
        
$g str_pad(dechex(round($rgb['g'], 0)), 2"0"STR_PAD_LEFT);
        
$b str_pad(dechex(round($rgb['b'], 0)), 2"0"STR_PAD_LEFT);
        
        return array(
"r" => $r"g" => $g"b" => $b);
    }

    
/**
     * Fetches the complementary color as RGB
     * @return array Array of three values representing the Red, Green and Blue color channels
     */
     
public function complementaryToRGB() {
         
$comp self::fromHSL($this->getHue() + 180$this->saturation$this->lightness);
         return 
$comp->toRGB();
     }

    
/**
     * Fetches the complementary color as RGB hex strings
     * @return array Array of three hex strings representing the Red, Green and Blue color channels
     */
     
public function complementaryToHex() {
         
$comp self::fromHSL($this->getHue() + 180$this->saturation$this->lightness);
         return 
$comp->toHex();         
     }
    
    
/**
     * Rotates the hue by $angle
     * @param mixed $angle The angle by which to rotate the hue.
     */
    
public function rotateHue($angle) {
        
$hue fmod(360 $this->hue $angle360);
        
$this->hue = (float)$hue 360;
    }
    
    
/**
     * Sets hue
     * @param mixed $value Hue as angle in degrees
     */
    
public function setHue($value) {
        
$_hue fmod($value360);
        
$this->hue = (float)$_hue 360;
    }

    
/**
     * Gets hue
     * @return float Hue
     */
    
public function getHue() {
        return 
fmod(360 $this->hue360);
    }

    
/**
     * Sets saturation
     * @param float $value Saturation as ∈ [0,1]
     */
    
public function setSaturation($value) {
        
$this->saturation $value;
    }

    
/**
     * Gets saturation
     * @return float Saturation
     */
    
public function getSaturation() {
        return 
$this->saturation;
    }

    
/**
     * Sets lightness
     * @param float $value Lightness as ∈ [0,1]
     */
    
public function setLightness($value) {
        
$this->lightness $value;
    }

    
/**
     * @return float Lightness
     */
    
public function getLightness() {
        return 
$this->lightness;
    }
}
?>