<?php

/**
 * Gets the prominent colors in a given image. To get common color matching, all pixels are matched
 * against a whitelist color palette.
 * 
 * @author  Joe Hoyle joe@hmn.md
 * @modified on 18th Aug 2015 by Adam W - mfscripts.com
 * 
 * Props to the following people who I ripped some of this code from:
 * 
 * Marc Pacheco
 * 
 */
class ColorsOfImage {

	var $image;
	var $height;
	var $width;
	var $precision;
	var $coinciditions;
	var $maxnumcolors;
	var $trueper;
	var $color_map = array();
	var $_palette = array();
	var $_min_percentage = 0;
	var $_excluded_colors = array('#FFFFFF');

	static $hit_pixels = 0;
	static $missed_pixels = 0;

	public function __construct( $image, $precision = 10, $maxnumcolors = 10, $trueper = true ) {

		$this->image = $image;
		$this->maxnumcolors = $maxnumcolors;
		$this->trueper = $trueper;
		$this->getImageSize();
		$this->precision = $precision;

		$this->readPixels();

		//$this->_excluded_colors[] = $this->getBackgroundColor();
	}

	public function setMinPercentage( $num ) {
		$this->_min_percentage = $num;
	}

	public function readPixels() {

		$image 		= $this->image;
		$width 		= $this->width;
		$height 	= $this->height;
	
		try {
			switch ( exif_imagetype($image) ) {
				case IMAGETYPE_PNG:
					$outputimg = "imagecreatefrompng";
					break;
				case IMAGETYPE_JPEG:
					$outputimg = "imagecreatefromjpeg";
				break;
				case IMAGETYPE_GIF:
					$outputimg = "imagecreatefromgif";
					break;
				case IMAGETYPE_BMP:
					$outputimg = "imagecreatefrombmp";
					break;
				default: return;
			}
			
			$this->workingImage = $outputimg($image);

		} catch (Exception $e) {
			echo $e->getMessage()."\n";
			exit();
		}
	
		for( $x = 0; $x < $width; $x += $this->precision ) {
			for ( $y = 0; $y < $height; $y += $this->precision ) {
				
				$index = imagecolorat($this->workingImage, $x, $y);
				$rgb = imagecolorsforindex($this->workingImage, $index);

				$color = $this->getClosestColor( $rgb["red"], $rgb["green"], $rgb["blue"] );

				$hexarray[] = $this->RGBToHex( $color[0], $color[1], $color[2] );
			}
		}
		
		$coinciditions = array_count_values($hexarray);
		$this->coinciditions = $coinciditions;

		return true;
	}

	public function getBackgroundColor( $use_palette = true ) {

		$top_left_color = imagecolorsforindex( $this->workingImage, imagecolorat( $this->workingImage, 0, 0) );
		$top_left = array( $top_left_color['red'], $top_left_color['green'], $top_left_color['blue'] );

		$top_right_color = imagecolorsforindex( $this->workingImage, imagecolorat( $this->workingImage, $this->width - 1, 0 ) );
		$top_right = array( $top_right_color['red'], $top_right_color['green'], $top_right_color['blue'] );
		
		$bottom_left_color = imagecolorsforindex( $this->workingImage, imagecolorat( $this->workingImage, 0, $this->height - 1 ) );
		$bottom_left = array( $bottom_left_color['red'], $bottom_left_color['green'], $bottom_left_color['blue'] );

		$bottom_right_color = imagecolorsforindex( $this->workingImage, imagecolorat( $this->workingImage, $this->width - 1, $this->height - 1 ) );
		$bottom_right = array( $bottom_right_color['red'], $bottom_right_color['green'], $bottom_right_color['blue'] );

		if ( $use_palette ) {
			$top_left 		= call_user_func_array( array( $this, 'getClosestColor' ), $top_left );
			$top_right 		= call_user_func_array( array( $this, 'getClosestColor' ), $top_right );
			$bottom_right	 	= call_user_func_array( array( $this, 'getClosestColor' ), $bottom_right );
			$bottom_left 		= call_user_func_array( array( $this, 'getClosestColor' ), $bottom_left );
		}

		$colors = array( $top_left, $top_right, $bottom_left, $bottom_right);

		if( count( array_unique( $colors[0] ) ) == 1 ) {
			return $this->RGBToHex( $top_left[0], $top_left[1], $top_left[2] );
		}

		return null;
	}
	
	static public function RGBToHex($r, $g, $b){
	
		$hex = "#";
		$hex.= str_pad( dechex($r), 2, "0", STR_PAD_LEFT );
		$hex.= str_pad( dechex($g), 2, "0", STR_PAD_LEFT );
		$hex.= str_pad( dechex($b), 2, "0", STR_PAD_LEFT );

		return strtoupper($hex);
	}

	static public function HexToRGB($hex) {
	   $hex = str_replace("#", "", $hex);
	 
	   if(strlen($hex) == 3) {
		  $r = hexdec(substr($hex,0,1).substr($hex,0,1));
		  $g = hexdec(substr($hex,1,1).substr($hex,1,1));
		  $b = hexdec(substr($hex,2,1).substr($hex,2,1));
	   } else {
		  $r = hexdec(substr($hex,0,2));
		  $g = hexdec(substr($hex,2,2));
		  $b = hexdec(substr($hex,4,2));
	   }
	   $rgb = array($r, $g, $b);

	   return $rgb; // returns an array with the rgb values
	}
	
	private function getPercentageOfColors(){
	
		$coinciditions = $this->coinciditions;

		$total = 0;

		foreach ($coinciditions as $color => $cuantity) {

			if ( in_array( $color, $this->_excluded_colors ) )
				unset( $coinciditions[$color] );

			else
				$total += $cuantity;
		}

		foreach ($coinciditions as $color => $cuantity) {
			$percentage = (($cuantity/$total)*100);
			$finallyarray["$color"] = $percentage;
		}

		if ( ! $coinciditions )
			return array();

		asort($finallyarray);
		array_keys($finallyarray);
		$outputarray = array_slice(array_reverse($finallyarray), 0, $this->maxnumcolors);
	
		$trueper = $this->trueper;
	
		if( $trueper && $outputarray ) {
		
			   $total = 0;
			   foreach ($outputarray as $color => $cuantity) {
				   $total += $cuantity;
			   }
			   foreach ($outputarray as $color => $cuantity) {
				   $percentage = (($cuantity/$total)*100);
				   $finallyarrayp["$color"] = $percentage;
			   }
			   return $finallyarrayp;

		} else {
		
		   return $outputarray;
		}
	}

	public function getImageSize() {
	
		$imgsize 	= getimagesize($this->image);
		$height 	= $imgsize[1];
		$width 		= $imgsize[0];
		$this->height = $height;
		$this->width = $width;

		return "x= ".$width."y= ".$height;
	}

	public function getProminentColors() {

		$pixels 		= $this->getPercentageOfColors();
		$bg_color_hex 	= $this->getBackgroundColor();

		foreach ($pixels as $color => $value) {
			if ( $value < $this->_min_percentage )
				unset( $pixels[$color] );
		}

		$_c = array();

		foreach ($pixels as $key => $value) {
			$_c[] = $key;
		}

		return $_c;
	}

	/**
	 * Function used for testing, will return a map pof all colors to their matching color counter part
	 */
	public function getColorMap() {

		$image 		= $this->image;
		$width 		= $this->width;
		$height 	= $this->height;
		$arrayex 	= explode( '.', $image );
		$typeOfImage = end( $arrayex );

		for( $x = 0; $x < $width; $x += $this->precision ) {
			for ( $y = 0; $y < $height; $y += $this->precision ) {
				
				$index = imagecolorat($this->workingImage, $x, $y);
				$rgb = imagecolorsforindex($this->workingImage, $index);

				$color = $this->getClosestColor( $rgb["red"], $rgb["green"], $rgb["blue"] );

				$hexarray[ $this->RGBToHex( $rgb["red"], $rgb["green"], $rgb["blue"] ) ] = $this->RGBToHex( $color[0], $color[1], $color[2] );
			}
		}

		return $hexarray;
	}

	private function getClosestColor($r, $g, $b){

		if ( isset( $this->color_map[$this->RGBToHex( $r, $g, $b ) ] ) ) {
			return $this->color_map[$this->RGBToHex( $r, $g, $b )];
		}

		$differencearray = array();
		$colors = $this->getPalette();

		foreach ($colors as $key => $value) {
			$value = $value['rgb'];
			$differencearray[$key] = self::getDistanceBetweenColors( $value, array( $r, $g, $b ) );
		}

		$smallest = min( $differencearray );

		$key = array_search($smallest, $differencearray);

		$color = $this->color_map[$this->RGBToHex( $r, $g, $b )] = $colors[$key]['rgb'];

		return $color;
	}

	private static function getDistanceBetweenColors( $col1, $col2 ) {

		$xyz1 = self::rgb_to_xyz( $col1 );
		$xyz2 = self::rgb_to_xyz( $col2 );

		$lab1 = self::xyz_to_lab( $xyz1 );
		$lab2 = self::xyz_to_lab( $xyz2 );

		return ciede2000( $lab1, $lab2 );
	} 

	private function getPalette() {

		if ( ! empty( $this->_palette ) )
			return $this->_palette;

		$str = '["#F0F8FF", "#FAEBD7", "#00FFFF", "#7FFFD4", "#F0FFFF", "#F5F5DC", "#FFE4C4", "#000000", "#FFEBCD", "#0000FF", "#8A2BE2", "#A52A2A", "#DEB887", "#5F9EA0", "#7FFF00", "#D2691E", "#FF7F50", "#6495ED", "#FFF8DC", "#DC143C", "#00FFFF", "#00008B", "#008B8B", "#B8860B", "#A9A9A9", "#006400", "#BDB76B", "#8B008B", "#556B2F", "#FF8C00", "#9932CC", "#8B0000", "#E9967A", "#8FBC8F", "#483D8B", "#2F4F4F", "#00CED1", "#9400D3", "#FF1493", "#00BFFF", "#696969", "#1E90FF", "#B22222", "#FFFAF0", "#228B22", "#FF00FF", "#DCDCDC", "#F8F8FF", "#FFD700", "#DAA520", "#808080", "#008000", "#ADFF2F", "#F0FFF0", "#FF69B4", "#CD5C5C", "#4B0082", "#FFFFF0", "#F0E68C", "#E6E6FA", "#FFF0F5", "#7CFC00", "#FFFACD", "#ADD8E6", "#F08080", "#E0FFFF", "#FAFAD2", "#D3D3D3", "#90EE90", "#FFB6C1", "#FFA07A", "#20B2AA", "#87CEFA", "#778899", "#B0C4DE", "#FFFFE0", "#00FF00", "#32CD32", "#FAF0E6", "#FF00FF", "#800000", "#66CDAA", "#0000CD", "#BA55D3", "#9370DB", "#3CB371", "#7B68EE", "#00FA9A", "#48D1CC", "#C71585", "#191970", "#F5FFFA", "#FFE4E1", "#FFE4B5", "#FFDEAD", "#000080", "#FDF5E6", "#808000", "#6B8E23", "#FFA500", "#FF4500", "#DA70D6", "#EEE8AA", "#98FB98", "#AFEEEE", "#DB7093", "#FFEFD5", "#FFDAB9", "#CD853F", "#FFC0CB", "#DDA0DD", "#B0E0E6", "#800080", "#663399", "#FF0000", "#BC8F8F", "#4169E1", "#8B4513", "#FA8072", "#F4A460", "#2E8B57", "#FFF5EE", "#A0522D", "#C0C0C0", "#87CEEB", "#6A5ACD", "#708090", "#FFFAFA", "#00FF7F", "#4682B4", "#D2B48C", "#008080", "#D8BFD8", "#FF6347", "#40E0D0", "#EE82EE", "#F5DEB3", "#FFFFFF", "#F5F5F5", "#FFFF00", "#9ACD32"]';

		$hexs = json_decode( $str );

		foreach ( $hexs as $hex )
			$this->_palette[] = array( 'rgb' => self::HexToRGB( $hex ), 'hex' => $hex );

		return $this->_palette;
	}
	
	private static function xyz_to_lab( $xyz ){
		 $x = $xyz[0];
		 $y = $xyz[1];
		 $z = $xyz[2];
		 $_x = $x/95.047;
		 $_y = $y/100;
		 $_z = $z/108.883;
		 if($_x>0.008856){
			  $_x = pow($_x,1/3);
		 }
		 else{
			  $_x = 7.787*$_x + 16/116;
		 }
		 if($_y>0.008856){
			  $_y = pow($_y,1/3);
		 }
		 else{
			  $_y = (7.787*$_y) + (16/116);
		 }
		 if($_z>0.008856){
			  $_z = pow($_z,1/3);
		 }
		 else{
			  $_z = 7.787*$_z + 16/116;
		 }
		 $l= 116*$_y -16;
		 $a= 500*($_x-$_y);
		 $b= 200*($_y-$_z);
		 
		 return(array( 'l' => $l, 'a' => $a, 'b' => $b));
	}


	private static function rgb_to_xyz( $rgb ) {
		$red = $rgb[0];
		$green = $rgb[1];
		$blue = $rgb[2]; 
		$_red = $red/255;
		$_green = $green/255;
		$_blue = $blue/255;

		if ( $_red > 0.04045 ) {
			$_red = ($_red+0.055)/1.055;
			$_red = pow($_red,2.4);
		} else{
			$_red = $_red/12.92;
		}

		if ( $_green > 0.04045 ) {
		  $_green = ($_green+0.055)/1.055;
		  $_green = pow($_green,2.4);     
		} else{
		  $_green = $_green/12.92;
		}

		if ( $_blue>0.04045 ) {
		  $_blue = ($_blue+0.055)/1.055;
		  $_blue = pow($_blue,2.4);     
		} else {
		  $_blue = $_blue/12.92;
		}

		$_red *= 100;
		$_green *= 100;
		$_blue *= 100;
		$x = $_red * 0.4124 + $_green * 0.3576 + $_blue * 0.1805;
		$y = $_red * 0.2126 + $_green * 0.7152 + $_blue * 0.0722;
		$z = $_red * 0.0193 + $_green * 0.1192 + $_blue * 0.9505;
		return(array( $x, $y, $z ));
	}


	private static function de_1994( $lab1,$lab2 ) {
		$c1 = sqrt($lab1[1]*$lab1[1]+$lab1[2]*$lab1[2]);
		$c2 = sqrt($lab2[1]*$lab2[1]+$lab2[2]*$lab2[2]);
		$dc = $c1-$c2;
		$dl = $lab1[0]-$lab2[0];
		$da = $lab1[1]-$lab2[1];
		$db = $lab1[2]-$lab2[2];

		$dh = ( ( $dh_sq = ( ($da*$da)+($db*$db)-($dc*$dc) ) ) < 0 ) ? sqrt( $dh_sq * -1  ) : sqrt( $dh_sq );

		$first = $dl;
		$second = $dc/(1+0.045*$c1);
		$third = $dh/(1+0.015*$c1);
		return(sqrt($first*$first+$second*$second+$third*$third));
	}
}

/**
 * @author Markus Näsman
 * @copyright 2012 (c) Markus Näsman <markus at botten dot org >
 * @license see COPYING
 */

/**
 * API FUNCTIONS
 */

/**
* Returns diff between c1 and c2 using the CIEDE2000 algorithm
* @return {float}   Difference between c1 and c2
*/
function ciede2000($c1,$c2) {
  /**
   * Implemented as in "The CIEDE2000 Color-Difference Formula:
   * Implementation Notes, Supplementary Test Data, and Mathematical Observations"
   * by Gaurav Sharma, Wencheng Wu and Edul N. Dalal.
   */

  // Get L,a,b values for color 1
  $L1 = $c1['l'];
  $a1 = $c1['a'];
  $b1 = $c1['b'];

  // Get L,a,b values for color 2
  $L2 = $c2['l'];
  $a2 = $c2['a'];
  $b2 = $c2['b'];

  // Weight factors
  $kL = 1;
  $kC = 1;
  $kH = 1;

  /**
   * Step 1: Calculate C1p, C2p, h1p, h2p
   */
  $C1 = sqrt(pow($a1, 2) + pow($b1, 2)); //(2)
  $C2 = sqrt(pow($a2, 2) + pow($b2, 2)); //(2)

  $a_C1_C2 = ($C1+$C2)/2.0;             //(3)

  $G = 0.5 * (1 - sqrt(pow($a_C1_C2 , 7.0) / (pow($a_C1_C2, 7.0) + pow(25.0, 7.0)))); //(4)

  $a1p = (1.0 + $G) * $a1; //(5)
  $a2p = (1.0 + $G) * $a2; //(5)

  $C1p = sqrt(pow($a1p, 2) + pow($b1, 2)); //(6)
  $C2p = sqrt(pow($a2p, 2) + pow($b2, 2)); //(6)

  

  $h1p = hp_f($b1, $a1p); //(7)
  $h2p = hp_f($b2, $a2p); //(7)

  /**
   * Step 2: Calculate dLp, dCp, dHp
   */
  $dLp = $L2 - $L1; //(8)
  $dCp = $C2p - $C1p; //(9)

 
  $dhp = dhp_f($C1,$C2, $h1p, $h2p); //(10)
  $dHp = 2*sqrt($C1p*$C2p)*sin(radians($dhp)/2.0); //(11)

  /**
   * Step 3: Calculate CIEDE2000 Color-Difference
   */
  $a_L = ($L1 + $L2) / 2.0; //(12)
  $a_Cp = ($C1p + $C2p) / 2.0; //(13)

   
  
  $a_hp = a_hp_f($C1,$C2,$h1p,$h2p); //(14)

  $T = 1-0.17*cos(radians($a_hp-30))+0.24*cos(radians(2*$a_hp))+0.32*cos(radians(3*$a_hp+6))-0.20*cos(radians(4*$a_hp-63)); //(15)
  $d_ro = 30 * exp(-(pow(($a_hp-275)/25,2))); //(16)
  $RC = sqrt((pow($a_Cp, 7.0)) / (pow($a_Cp, 7.0) + pow(25.0, 7.0)));//(17)
  $SL = 1 + ((0.015 * pow($a_L - 50, 2)) / sqrt(20 + pow($a_L - 50, 2.0)));//(18)
  $SC = 1 + 0.045 * $a_Cp;//(19)
  $SH = 1 + 0.015 * $a_Cp * $T;//(20)
  $RT = -2 * $RC * sin(radians(2 * $d_ro));//(21)
  $dE = sqrt(pow($dLp /($SL * $kL), 2) + pow($dCp /($SC * $kC), 2) + pow($dHp /($SH * $kH), 2) + $RT * ($dCp /($SC * $kC)) * ($dHp / ($SH * $kH))); //(22)
  return $dE;
}

function hp_f($x,$y) //(7)
  {
	if($x== 0 && $y == 0) return 0;
	else{
	  $tmphp = degrees(atan2($x,$y));
	  if($tmphp >= 0) return $tmphp;
	  else           return $tmphp + 360;
	}
  }
 function dhp_f($C1, $C2, $h1p, $h2p) //(10)
  {
	if($C1*$C2 == 0)               return 0;
	else if(abs($h2p-$h1p) <= 180) return $h2p-$h1p;
	else if(($h2p-$h1p) > 180)     return ($h2p-$h1p)-360;
	else if(($h2p-$h1p) < -180)    return ($h2p-$h1p)+360;
	else                         throw(error);
  }
  function a_hp_f($C1, $C2, $h1p, $h2p) { //(14)
	if($C1*$C2 == 0)                                      return $h1p+$h2p;
	else if(abs($h1p-$h2p)<= 180)                         return ($h1p+$h2p)/2.0;
	else if((abs($h1p-$h2p) > 180) && (($h1p+$h2p) < 360))  return ($h1p+$h2p+360)/2.0;
	else if((abs($h1p-$h2p) > 180) && (($h1p+$h2p) >= 360)) return ($h1p+$h2p-360)/2.0;
	else                                                throw( new Exception( 'd' ));
  }

/**
 * INTERNAL FUNCTIONS
 */
function degrees($n) { return $n*(180/pi()); }
function radians($n) { return $n*(pi()/180); }
