PageRenderTime 48ms CodeModel.GetById 9ms RepoModel.GetById 1ms app.codeStats 0ms

/colorsofimage.class.php

https://github.com/fnx-research/Colors-Of-Image
PHP | 514 lines | 349 code | 119 blank | 46 comment | 51 complexity | 76acca95dbc6e49c114a86d3eec9f0b0 MD5 | raw file
  1. <?php
  2. /**
  3. * Gets the prominent colors in a given image. To get common color matching, all pixels are matched
  4. * against a whitelist color palette.
  5. *
  6. * @author Joe Hoyle joe@hmn.md
  7. *
  8. * Props to the following people who I ripped some of this code from:
  9. *
  10. * Marc Pacheco
  11. *
  12. */
  13. class ColorsOfImage {
  14. var $image;
  15. var $height;
  16. var $width;
  17. var $precision;
  18. var $coinciditions;
  19. var $maxnumcolors;
  20. var $trueper;
  21. var $color_map = array();
  22. var $_palette = array();
  23. var $_min_percentage = 10;
  24. var $_excluded_colors = array( '#FFFFFF' );
  25. static $hit_pixels = 0;
  26. static $missed_pixels = 0;
  27. public function __construct( $image, $precision = 10, $maxnumcolors = 5, $trueper = true ) {
  28. $this->image = $image;
  29. $this->maxnumcolors = $maxnumcolors;
  30. $this->trueper = $trueper;
  31. $this->getImageSize();
  32. $this->precision = $precision;
  33. $this->readPixels();
  34. $this->_excluded_colors[] = $this->getBackgroundColor();
  35. }
  36. public function setMinPercentage( $num ) {
  37. $this->_min_percentage = $num;
  38. }
  39. public function readPixels() {
  40. $image = $this->image;
  41. $width = $this->width;
  42. $height = $this->height;
  43. $arrayex = explode( '.', $image );
  44. $typeOfImage= end( $arrayex );
  45. try {
  46. switch ( $typeOfImage ) {
  47. case "png":
  48. $outputimg = "imagecreatefrompng";
  49. break;
  50. case "jpg":
  51. $outputimg = "imagecreatefromjpeg";
  52. break;
  53. case "gif":
  54. $outputimg = "imagecreatefromgif";
  55. break;
  56. case "bpm":
  57. $outputimg = "imagecreatefrombmp";
  58. break;
  59. default: return;
  60. }
  61. $this->workingImage = $outputimg($image);
  62. } catch (Exception $e) {
  63. echo $e->getMessage()."\n";
  64. exit();
  65. }
  66. for( $x = 0; $x < $width; $x += $this->precision ) {
  67. for ( $y = 0; $y < $height; $y += $this->precision ) {
  68. $index = imagecolorat($this->workingImage, $x, $y);
  69. $rgb = imagecolorsforindex($this->workingImage, $index);
  70. $color = $this->getClosestColor( $rgb["red"], $rgb["green"], $rgb["blue"] );
  71. $hexarray[] = $this->RGBToHex( $color[0], $color[1], $color[2] );
  72. }
  73. }
  74. $coinciditions = array_count_values($hexarray);
  75. $this->coinciditions = $coinciditions;
  76. return true;
  77. }
  78. public function getBackgroundColor( $use_palette = true ) {
  79. $top_left_color = imagecolorsforindex( $this->workingImage, imagecolorat( $this->workingImage, 0, 0) );
  80. $top_left = array( $top_left_color['red'], $top_left_color['green'], $top_left_color['blue'] );
  81. $top_right_color = imagecolorsforindex( $this->workingImage, imagecolorat( $this->workingImage, $this->width - 1, 0 ) );
  82. $top_right = array( $top_right_color['red'], $top_right_color['green'], $top_right_color['blue'] );
  83. $bottom_left_color = imagecolorsforindex( $this->workingImage, imagecolorat( $this->workingImage, 0, $this->height - 1 ) );
  84. $bottom_left = array( $bottom_left_color['red'], $bottom_left_color['green'], $bottom_left_color['blue'] );
  85. $bottom_right_color = imagecolorsforindex( $this->workingImage, imagecolorat( $this->workingImage, $this->width - 1, $this->height - 1 ) );
  86. $bottom_right = array( $bottom_right_color['red'], $bottom_right_color['green'], $bottom_right_color['blue'] );
  87. if ( $use_palette ) {
  88. $top_left = call_user_func_array( array( $this, 'getClosestColor' ), $top_left );
  89. $top_right = call_user_func_array( array( $this, 'getClosestColor' ), $top_right );
  90. $bottom_right = call_user_func_array( array( $this, 'getClosestColor' ), $bottom_right );
  91. $bottom_left = call_user_func_array( array( $this, 'getClosestColor' ), $bottom_left );
  92. }
  93. $colors = array( $top_left, $top_right, $bottom_left, $bottom_right);
  94. if( count( array_unique( $colors[0] ) ) == 1 ) {
  95. return $this->RGBToHex( $top_left[0], $top_left[1], $top_left[2] );
  96. }
  97. return null;
  98. }
  99. static public function RGBToHex($r, $g, $b){
  100. $hex = "#";
  101. $hex.= str_pad( dechex($r), 2, "0", STR_PAD_LEFT );
  102. $hex.= str_pad( dechex($g), 2, "0", STR_PAD_LEFT );
  103. $hex.= str_pad( dechex($b), 2, "0", STR_PAD_LEFT );
  104. return strtoupper($hex);
  105. }
  106. static public function HexToRGB($hex) {
  107. $hex = str_replace("#", "", $hex);
  108. if(strlen($hex) == 3) {
  109. $r = hexdec(substr($hex,0,1).substr($hex,0,1));
  110. $g = hexdec(substr($hex,1,1).substr($hex,1,1));
  111. $b = hexdec(substr($hex,2,1).substr($hex,2,1));
  112. } else {
  113. $r = hexdec(substr($hex,0,2));
  114. $g = hexdec(substr($hex,2,2));
  115. $b = hexdec(substr($hex,4,2));
  116. }
  117. $rgb = array($r, $g, $b);
  118. return $rgb; // returns an array with the rgb values
  119. }
  120. private function getPercentageOfColors(){
  121. $coinciditions = $this->coinciditions;
  122. $total = 0;
  123. foreach ($coinciditions as $color => $cuantity) {
  124. if ( in_array( $color, $this->_excluded_colors ) )
  125. unset( $coinciditions[$color] );
  126. else
  127. $total += $cuantity;
  128. }
  129. foreach ($coinciditions as $color => $cuantity) {
  130. $percentage = (($cuantity/$total)*100);
  131. $finallyarray["$color"] = $percentage;
  132. }
  133. if ( ! $coinciditions )
  134. return array();
  135. asort($finallyarray);
  136. array_keys($finallyarray);
  137. $outputarray = array_slice(array_reverse($finallyarray), 0, $this->maxnumcolors);
  138. $trueper = $this->trueper;
  139. if( $trueper && $outputarray ) {
  140. $total = 0;
  141. foreach ($outputarray as $color => $cuantity) {
  142. $total += $cuantity;
  143. }
  144. foreach ($outputarray as $color => $cuantity) {
  145. $percentage = (($cuantity/$total)*100);
  146. $finallyarrayp["$color"] = $percentage;
  147. }
  148. return $finallyarrayp;
  149. } else {
  150. return $outputarray;
  151. }
  152. }
  153. public function getImageSize() {
  154. $imgsize = getimagesize($this->image);
  155. $height = $imgsize[1];
  156. $width = $imgsize[0];
  157. $this->height = $height;
  158. $this->width = $width;
  159. return "x= ".$width."y= ".$height;
  160. }
  161. public function getProminentColors() {
  162. $pixels = $this->getPercentageOfColors();
  163. $bg_color_hex = $this->getBackgroundColor();
  164. foreach ($pixels as $color => $value) {
  165. if ( $value < $this->_min_percentage )
  166. unset( $pixels[$color] );
  167. }
  168. $_c = array();
  169. foreach ($pixels as $key => $value) {
  170. $_c[] = $key;
  171. }
  172. return $_c;
  173. }
  174. /**
  175. * Function used for testing, will return a map pof all colors to their matching color counter part
  176. */
  177. public function getColorMap() {
  178. $image = $this->image;
  179. $width = $this->width;
  180. $height = $this->height;
  181. $arrayex = explode( '.', $image );
  182. $typeOfImage= end( $arrayex );
  183. for( $x = 0; $x < $width; $x += $this->precision ) {
  184. for ( $y = 0; $y < $height; $y += $this->precision ) {
  185. $index = imagecolorat($this->workingImage, $x, $y);
  186. $rgb = imagecolorsforindex($this->workingImage, $index);
  187. $color = $this->getClosestColor( $rgb["red"], $rgb["green"], $rgb["blue"] );
  188. $hexarray[ $this->RGBToHex( $rgb["red"], $rgb["green"], $rgb["blue"] ) ] = $this->RGBToHex( $color[0], $color[1], $color[2] );
  189. }
  190. }
  191. return $hexarray;
  192. }
  193. private function getClosestColor($r, $g, $b){
  194. if ( isset( $this->color_map[$this->RGBToHex( $r, $g, $b ) ] ) ) {
  195. return $this->color_map[$this->RGBToHex( $r, $g, $b )];
  196. }
  197. $differencearray = array();
  198. $colors = $this->getPalette();
  199. foreach ($colors as $key => $value) {
  200. $value = $value['rgb'];
  201. $differencearray[$key] = self::getDistanceBetweenColors( $value, array( $r, $g, $b ) );
  202. }
  203. $smallest = min( $differencearray );
  204. $key = array_search($smallest, $differencearray);
  205. $color = $this->color_map[$this->RGBToHex( $r, $g, $b )] = $colors[$key]['rgb'];
  206. return $color;
  207. }
  208. private static function getDistanceBetweenColors( $col1, $col2 ) {
  209. $xyz1 = self::rgb_to_xyz( $col1 );
  210. $xyz2 = self::rgb_to_xyz( $col2 );
  211. $lab1 = self::xyz_to_lab( $xyz1 );
  212. $lab2 = self::xyz_to_lab( $xyz2 );
  213. return ciede2000( $lab1, $lab2 );
  214. }
  215. private function getPalette() {
  216. if ( ! empty( $this->_palette ) )
  217. return $this->_palette;
  218. $str = '["#660000", "#990000", "#cc0000", "#cc3333", "#ea4c88", "#993399", "#663399", "#333399", "#0066cc", "#0099cc", "#66cccc", "#77cc33", "#669900", "#336600", "#666600", "#999900", "#cccc33", "#ffff00", "#ffcc33", "#ff9900", "#ff6600", "#cc6633", "#996633", "#663300", "#000000", "#999999", "#cccccc", "#ffffff", "#E7D8B1", "#FDADC7", "#424153", "#ABBCDA", "#F5DD01"]';
  219. $hexs = json_decode( $str );
  220. foreach ( $hexs as $hex )
  221. $this->_palette[] = array( 'rgb' => self::HexToRGB( $hex ), 'hex' => $hex );
  222. return $this->_palette;
  223. }
  224. private static function xyz_to_lab( $xyz ){
  225. $x = $xyz[0];
  226. $y = $xyz[1];
  227. $z = $xyz[2];
  228. $_x = $x/95.047;
  229. $_y = $y/100;
  230. $_z = $z/108.883;
  231. if($_x>0.008856){
  232. $_x = pow($_x,1/3);
  233. }
  234. else{
  235. $_x = 7.787*$_x + 16/116;
  236. }
  237. if($_y>0.008856){
  238. $_y = pow($_y,1/3);
  239. }
  240. else{
  241. $_y = (7.787*$_y) + (16/116);
  242. }
  243. if($_z>0.008856){
  244. $_z = pow($_z,1/3);
  245. }
  246. else{
  247. $_z = 7.787*$_z + 16/116;
  248. }
  249. $l= 116*$_y -16;
  250. $a= 500*($_x-$_y);
  251. $b= 200*($_y-$_z);
  252. return(array( 'l' => $l, 'a' => $a, 'b' => $b));
  253. }
  254. private static function rgb_to_xyz( $rgb ) {
  255. $red = $rgb[0];
  256. $green = $rgb[1];
  257. $blue = $rgb[2];
  258. $_red = $red/255;
  259. $_green = $green/255;
  260. $_blue = $blue/255;
  261. if ( $_red > 0.04045 ) {
  262. $_red = ($_red+0.055)/1.055;
  263. $_red = pow($_red,2.4);
  264. } else{
  265. $_red = $_red/12.92;
  266. }
  267. if ( $_green > 0.04045 ) {
  268. $_green = ($_green+0.055)/1.055;
  269. $_green = pow($_green,2.4);
  270. } else{
  271. $_green = $_green/12.92;
  272. }
  273. if ( $_blue>0.04045 ) {
  274. $_blue = ($_blue+0.055)/1.055;
  275. $_blue = pow($_blue,2.4);
  276. } else {
  277. $_blue = $_blue/12.92;
  278. }
  279. $_red *= 100;
  280. $_green *= 100;
  281. $_blue *= 100;
  282. $x = $_red * 0.4124 + $_green * 0.3576 + $_blue * 0.1805;
  283. $y = $_red * 0.2126 + $_green * 0.7152 + $_blue * 0.0722;
  284. $z = $_red * 0.0193 + $_green * 0.1192 + $_blue * 0.9505;
  285. return(array( $x, $y, $z ));
  286. }
  287. private static function de_1994( $lab1,$lab2 ) {
  288. $c1 = sqrt($lab1[1]*$lab1[1]+$lab1[2]*$lab1[2]);
  289. $c2 = sqrt($lab2[1]*$lab2[1]+$lab2[2]*$lab2[2]);
  290. $dc = $c1-$c2;
  291. $dl = $lab1[0]-$lab2[0];
  292. $da = $lab1[1]-$lab2[1];
  293. $db = $lab1[2]-$lab2[2];
  294. $dh = ( ( $dh_sq = ( ($da*$da)+($db*$db)-($dc*$dc) ) ) < 0 ) ? sqrt( $dh_sq * -1 ) : sqrt( $dh_sq );
  295. $first = $dl;
  296. $second = $dc/(1+0.045*$c1);
  297. $third = $dh/(1+0.015*$c1);
  298. return(sqrt($first*$first+$second*$second+$third*$third));
  299. }
  300. }
  301. /**
  302. * @author Markus N채sman
  303. * @copyright 2012 (c) Markus N채sman <markus at botten dot org >
  304. * @license see COPYING
  305. */
  306. /**
  307. * API FUNCTIONS
  308. */
  309. /**
  310. * Returns diff between c1 and c2 using the CIEDE2000 algorithm
  311. * @return {float} Difference between c1 and c2
  312. */
  313. function ciede2000($c1,$c2) {
  314. /**
  315. * Implemented as in "The CIEDE2000 Color-Difference Formula:
  316. * Implementation Notes, Supplementary Test Data, and Mathematical Observations"
  317. * by Gaurav Sharma, Wencheng Wu and Edul N. Dalal.
  318. */
  319. // Get L,a,b values for color 1
  320. $L1 = $c1['l'];
  321. $a1 = $c1['a'];
  322. $b1 = $c1['b'];
  323. // Get L,a,b values for color 2
  324. $L2 = $c2['l'];
  325. $a2 = $c2['a'];
  326. $b2 = $c2['b'];
  327. // Weight factors
  328. $kL = 1;
  329. $kC = 1;
  330. $kH = 1;
  331. /**
  332. * Step 1: Calculate C1p, C2p, h1p, h2p
  333. */
  334. $C1 = sqrt(pow($a1, 2) + pow($b1, 2)); //(2)
  335. $C2 = sqrt(pow($a2, 2) + pow($b2, 2)); //(2)
  336. $a_C1_C2 = ($C1+$C2)/2.0; //(3)
  337. $G = 0.5 * (1 - sqrt(pow($a_C1_C2 , 7.0) / (pow($a_C1_C2, 7.0) + pow(25.0, 7.0)))); //(4)
  338. $a1p = (1.0 + $G) * $a1; //(5)
  339. $a2p = (1.0 + $G) * $a2; //(5)
  340. $C1p = sqrt(pow($a1p, 2) + pow($b1, 2)); //(6)
  341. $C2p = sqrt(pow($a2p, 2) + pow($b2, 2)); //(6)
  342. $h1p = hp_f($b1, $a1p); //(7)
  343. $h2p = hp_f($b2, $a2p); //(7)
  344. /**
  345. * Step 2: Calculate dLp, dCp, dHp
  346. */
  347. $dLp = $L2 - $L1; //(8)
  348. $dCp = $C2p - $C1p; //(9)
  349. $dhp = dhp_f($C1,$C2, $h1p, $h2p); //(10)
  350. $dHp = 2*sqrt($C1p*$C2p)*sin(radians($dhp)/2.0); //(11)
  351. /**
  352. * Step 3: Calculate CIEDE2000 Color-Difference
  353. */
  354. $a_L = ($L1 + $L2) / 2.0; //(12)
  355. $a_Cp = ($C1p + $C2p) / 2.0; //(13)
  356. $a_hp = a_hp_f($C1,$C2,$h1p,$h2p); //(14)
  357. $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)
  358. $d_ro = 30 * exp(-(pow(($a_hp-275)/25,2))); //(16)
  359. $RC = sqrt((pow($a_Cp, 7.0)) / (pow($a_Cp, 7.0) + pow(25.0, 7.0)));//(17)
  360. $SL = 1 + ((0.015 * pow($a_L - 50, 2)) / sqrt(20 + pow($a_L - 50, 2.0)));//(18)
  361. $SC = 1 + 0.045 * $a_Cp;//(19)
  362. $SH = 1 + 0.015 * $a_Cp * $T;//(20)
  363. $RT = -2 * $RC * sin(radians(2 * $d_ro));//(21)
  364. $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)
  365. return $dE;
  366. }
  367. function hp_f($x,$y) //(7)
  368. {
  369. if($x== 0 && $y == 0) return 0;
  370. else{
  371. $tmphp = degrees(atan2($x,$y));
  372. if($tmphp >= 0) return $tmphp;
  373. else return $tmphp + 360;
  374. }
  375. }
  376. function dhp_f($C1, $C2, $h1p, $h2p) //(10)
  377. {
  378. if($C1*$C2 == 0) return 0;
  379. else if(abs($h2p-$h1p) <= 180) return $h2p-$h1p;
  380. else if(($h2p-$h1p) > 180) return ($h2p-$h1p)-360;
  381. else if(($h2p-$h1p) < -180) return ($h2p-$h1p)+360;
  382. else throw(error);
  383. }
  384. function a_hp_f($C1, $C2, $h1p, $h2p) { //(14)
  385. if($C1*$C2 == 0) return $h1p+$h2p;
  386. else if(abs($h1p-$h2p)<= 180) return ($h1p+$h2p)/2.0;
  387. else if((abs($h1p-$h2p) > 180) && (($h1p+$h2p) < 360)) return ($h1p+$h2p+360)/2.0;
  388. else if((abs($h1p-$h2p) > 180) && (($h1p+$h2p) >= 360)) return ($h1p+$h2p-360)/2.0;
  389. else throw( new Exception( 'd' ));
  390. }
  391. /**
  392. * INTERNAL FUNCTIONS
  393. */
  394. function degrees($n) { return $n*(180/pi()); }
  395. function radians($n) { return $n*(pi()/180); }