source: extensions/ColorStat/cstat_colorstat.class.inc.php @ 6107

Last change on this file since 6107 was 6107, checked in by grum, 14 years ago

Plugin is now in a usable state (color analysis is not yet tuned)

File size: 16.6 KB
Line 
1<?php
2/* -----------------------------------------------------------------------------
3  Plugin     : ColorStat
4  Author     : Grum
5    email    : grum@grum.fr
6    website  : http://photos.grum.fr
7    PWG user : http://forum.piwigo.org/profile.php?id=3706
8
9    << May the Little SpaceFrog be with you ! >>
10  ------------------------------------------------------------------------------
11  See main.inc.php for release information
12
13  Provided classes :
14  * RGB
15    public functions :
16      __construct($R, $G, $B)
17      set($R, $G, $B)
18      setInt($value)
19      get($floatValue=false)
20      getHexString()
21      getInt()
22      getHSV()
23      setHSV(HSV $hsv)
24    private functions :
25      setProperty($property, $value)
26
27  * HSV
28    public functions :
29      __construct($H, $S, $V)
30      set($H, $S, $V)
31      get()
32      getRGB()
33      setRGB(RGB $rgb)
34    private functions :
35      setProperty($property, $value)
36
37  * ColorStat
38    public functions :
39    static getFileColors($fileName, $colorTable, $quality=1)
40    static RGBtoHSV(RGB $RGB)
41    static HSVtoRGB(HSV $HSV)
42    static IntToRGB($rgb)
43    static getColorTable($huePrec=10, $prec=10, $returnedType='HSV')
44    static getColorFromTable(HSV $hsvObject, $colorTable)
45
46  --------------------------------------------------------------------------- */
47
48  if(!defined('PHPWG_ROOT_PATH')) die('Hacking attempt!');
49
50/**
51 * The RGB class allows to read & write a RGB value
52 */
53class RGB {
54  protected $RGB = Array('R' => 0, 'G' => 0, 'B' => 0);
55
56  public function __construct($R=0, $G=0, $B=0)
57  {
58    $this->set($R, $G, $B);
59  }
60
61  public function set($R, $G, $B)
62  {
63    $this->setProperty('R', $R);
64    $this->setProperty('G', $G);
65    $this->setProperty('B', $B);
66    return($this->get());
67  }
68
69  public function setInt($value)
70  {
71    $tmp=ColorStat::IntToRGB($value);
72    $tmp2=$tmp->get();
73    $this->set($tmp2['R'], $tmp2['G'], $tmp2['B']);
74    unset($tmp2);
75    unset($tmp);
76  }
77
78  public function get($floatValue=false)
79  {
80    if($floatValue)
81    {
82      return(
83        Array(
84          'R' => $this->RGB['R']/255,
85          'G' => $this->RGB['G']/255,
86          'B' => $this->RGB['B']/255,
87        )
88      );
89    }
90    else
91    {
92      return($this->RGB);
93    }
94  }
95
96  public function getHexString()
97  {
98    return(sprintf('%02x%02x%02x', $this->RGB['R'], $this->RGB['G'], $this->RGB['B']));
99  }
100
101  public function getInt()
102  {
103    return(  $this->RGB['R']<<16 + $this->RGB['G']<<8 + $this->RGB['B']);
104  }
105
106  public function getHSV()
107  {
108    return(ColorStat::RGBtoHSV($this));
109  }
110
111  public function setHSV(HSV $hsv)
112  {
113    $tmp=ColorStat::HSVtoRGB($hsv);
114    $tmp2=$tmp->get();
115    $this->set($tmp2['R'], $tmp2['G'], $tmp2['B']);
116    unset($tmp2);
117    unset($tmp);
118  }
119
120  private function setProperty($property, $value)
121  {
122    if($property=='R' or $property=='G' or $property=='B')
123    {
124      if($value<0) $value=0;
125      if($value>255) $value=255;
126
127      $this->RGB[$property]=$value;
128      return($this->RGB[$property]);
129    }
130  }
131}
132
133
134/**
135 * The HSV class allows to read & write a HSV value
136 */
137class HSV {
138  protected $HSV = Array('H' => 0, 'S' => 0, 'V' => 0);
139
140  public function __construct($H=0, $S=0, $V=0)
141  {
142    $this->set($H, $S, $V);
143  }
144
145  public function set($H, $S, $V)
146  {
147    $this->setProperty('H', $H);
148    $this->setProperty('S', $S);
149    $this->setProperty('V', $V);
150    return($this->get());
151  }
152
153  public function get()
154  {
155    return($this->HSV);
156  }
157
158  public function getRGB()
159  {
160    return(ColorStat::HSVtoRGB($this));
161  }
162
163  public function setRGB(RGB $rgb)
164  {
165    $tmp=ColorStat::RGBtoHSV($rgb);
166    $tmp2=$tmp->get();
167    $this->set($tmp2['H'], $tmp2['S'], $tmp2['V']);
168    unset($tmp2);
169    unset($tmp);
170  }
171
172  private function setProperty($property, $value)
173  {
174    if($property=='H')
175    {
176      $this->HSV['H']=$value%360;
177      return($this->HSV['H']);
178    }
179    elseif($property=='S' or $property=='V')
180    {
181      if($value<0) $value=0;
182      if($value>100) $value=100;
183
184      $this->HSV[$property]=$value;
185      return($this->HSV[$property]);
186    }
187    return(false);
188  }
189}
190
191
192
193
194class ColorStat {
195  static public $fileColorsStat = Array(
196    'pixels' => 0,
197    'analyzed' => 0,
198    'time' => 0,
199    'colors' =>0,
200    'fileName' => "",
201    'quality' => 0,
202  );
203
204  /**
205   *
206   * @param String $fileName : the name of picture to scan
207   * @param String $colorTable : the color table model
208   * @param String $options : Array of options
209   *                            'quality' : set the quality for analyze
210   *                            'maxTime' : set the maximum time for analyze
211   *                            'pps'     : pixel per second analyzed
212   *                          if 'maxTime' and 'pps' are greater than zero, the
213   *                          quality parameter is computed automatically
214   * @return Array : an array of HSV objects
215   */
216  static function getFileColors($fileName, $colorTable, $options=array())
217  {
218    $options=self::checkOptions($options);
219
220    self::$fileColorsStat=Array(
221      'pixels'   => 0,
222      'analyzed' => 0,
223      'time' => 0,
224      'pps' =>0,
225    );
226
227    if(file_exists($fileName))
228    {
229      $time=microtime(true);
230
231      if(preg_match('/.*\.gif$/i', $fileName))
232      {
233        $image = imagecreatefromgif($fileName);
234      }
235      elseif(preg_match('/.*\.(jpg|jpeg)$/i', $fileName))
236      {
237        $image = imagecreatefromjpeg($fileName);
238      }
239      elseif(preg_match('/.*\.png$/i', $fileName))
240      {
241        $image = imagecreatefrompng($fileName);
242      }
243      else
244      {
245        return(-2);
246      }
247
248      $imageWidth=imagesx($image);
249      $imageHeight=imagesy($image);
250
251      if($options['pps']>0 && $options['maxTime']>0)
252      {
253        $quality=round(sqrt($imageWidth*$imageHeight/($options['pps']*$options['maxTime'])), 0);
254      }
255      else
256      {
257        $quality=$options['quality'];
258      }
259
260      $imageWorkWidth=round($imageWidth/$quality,0);
261      $imageWorkHeight=round($imageHeight/$quality,0);
262      $imageWork=imagecreatetruecolor($imageWorkWidth,$imageWorkHeight);
263      imagecopyresampled($imageWork, $image, 0, 0, 0, 0, $imageWorkWidth, $imageWorkHeight, $imageWidth, $imageHeight);
264      //imagecopyresized($imageWork, $image, 0, 0, 0, 0, $imageWorkWidth, $imageWorkHeight, $imageWidth, $imageHeight);
265      imagedestroy($image);
266
267      $returned=Array();
268
269      $i=0;
270      for($px=0;$px<$imageWorkWidth;$px++)
271      {
272        for($py=0;$py<$imageWorkHeight;$py++)
273        {
274          $i++;
275          $value=imagecolorat($imageWork, $px, $py);
276
277          $rgb=self::IntToRGB($value);
278          //echo sprintf("%06x", $value)." => ".$rgb->getHexString();
279
280          $color=self::getColorFromTable($rgb->getHSV(), $colorTable)->getHexString();
281          //echo " ($i) $color<br>";
282
283          if(array_key_exists($color, $returned))
284          {
285            $returned[$color]['num']=$returned[$color]['num']+1;
286          }
287          else
288          {
289            $returned[$color]=Array(
290              'hsv' => $rgb->getHSV()->get(),
291              'num' => 1,
292              'pct' => 0,
293            );
294          }
295          unset($rgb);
296        }
297      }
298
299      /*
300      $fName="q".$quality."_c".$options['numColors']."_".$fileName.".png";
301      imagepng($imageWork, $fName);
302      */
303
304      imagedestroy($imageWork);
305      uasort($returned, Array('ColorStat', 'sortTones'));
306
307      if($options['numColors']>0)
308      {
309        foreach($returned as $key=>$val)
310        {
311          $returnedColors[$key]=$val;
312          $options['numColors']--;
313          if($options['numColors']<=0) break;
314        }
315      }
316      else
317      {
318        $returnedColors=$returned;
319      }
320
321      self::$fileColorsStat=Array(
322        'pixels'   => $imageWidth*$imageHeight,
323        'analyzed' => $i,
324        'time'     => microtime(true)-$time,
325        'colors'   => count($returned),
326        'pps'      => $i/(microtime(true)-$time),
327        'quality'  => $quality,
328        //'fileName' => $fName,
329      );
330
331      unset($returned);
332
333      foreach($returnedColors as $key => $val)
334      {
335        $returnedColors[$key]['pct']=round(100*$val['num']/self::$fileColorsStat['analyzed'],2);
336      }
337
338      return($returnedColors);
339    }
340    else
341    {
342      return(-1);
343    }
344  }
345
346  /**
347   *  Calculate the HSV value from a RGB value
348   *
349   * @param RGB $RGB : RGB object
350   * @return HSV : new HSV object
351   */
352  static public function RGBtoHSV(RGB $RGB)
353  {
354    $rgbValues=$RGB->get(true);
355    $max=self::max($rgbValues);
356    $min=self::min($rgbValues);
357
358    if($max['value']==$min['value'])
359    {
360      $H=0;
361    }
362    elseif($max['key']=='R')
363    {
364      $H=(60*($rgbValues['G']-$rgbValues['B'])/($max['value']-$min['value'])+360)%360;
365    }
366    elseif($max['key']=='G')
367    {
368      $H=(60*($rgbValues['B']-$rgbValues['R'])/($max['value']-$min['value'])+120);
369    }
370    elseif($max['key']=='B')
371    {
372      $H=(60*($rgbValues['R']-$rgbValues['G'])/($max['value']-$min['value'])+240);
373    }
374
375    $S=round(100*(($max['value']==0)?0:1-$min['value']/$max['value']),0);
376    $V=round(100*$max['value'],0);
377
378    return(new HSV($H, $S, $V));
379  }
380
381  /**
382   *  Calculate the RGB value from a HSV value
383   *
384   * @param HSV $HSV : HSV object
385   * @return RGB : new RGB object
386   */
387  static public function HSVtoRGB(HSV $HSV)
388  {
389    $hsvValues=$HSV->get();
390
391    $h=abs($hsvValues['H']/60)%6;
392    $f=$hsvValues['H']/60-$h;
393    $l=round(2.55*$hsvValues['V']*(1-$hsvValues['S']/100),0);
394    $m=round(2.55*$hsvValues['V']*(1-$f*$hsvValues['S']/100),0);
395    $n=round(2.55*$hsvValues['V']*(1-(1-$f)*$hsvValues['S']/100),0);
396
397    $v=round(2.55*$hsvValues['V'],0);
398
399    switch($h)
400    {
401      case 0:
402        return(new RGB($v, $n, $l));
403        break;
404      case 1:
405        return(new RGB($m, $v, $l));
406        break;
407      case 2:
408        return(new RGB($l, $v, $n));
409        break;
410      case 3:
411        return(new RGB($l, $m, $v));
412        break;
413      case 4:
414        return(new RGB($n, $l, $v));
415        break;
416      case 5:
417        return(new RGB($v, $l, $m));
418        break;
419    }
420  }
421
422  /**
423   *
424   * @param Int $rgb : an integer &hRRGGBB
425   * @return RGB : a RGB object
426   */
427  static public function IntToRGB($rgb)
428  {
429    return(new RGB(($rgb >> 16) & 0xFF, ($rgb >> 8) & 0xFF, $rgb & 0xFF, true));
430  }
431
432  /**
433   * return a color table
434   *  $table = Array(
435   *              Array(),
436   *              Array(),
437   *              ...
438   *              Array(),
439   *              Array(),
440   *           )
441   *  $table[n]    => Array of all colors for a given hue value
442   *  $table[n][m] => HSV object or a color string RRGGBB
443   *
444   *
445   * @param Int $huePrec : degree of precision for hue [1..360]
446   * @param Float $prec    : precision step for saturation & value [1..100]
447   * @param String $returnedType : 'HSV'   => return a HSV object
448   *                               'color' => return a color string RRGGBB
449   * @return Array : the color table
450   */
451  static public function getColorTable($huePrec=10, $prec=10, $returnedType='HSV')
452  {
453    $returned=Array();
454    for($hue=0;$hue<=360;$hue+=$huePrec)
455    {
456      $hueValues=Array();
457
458      $saturation=100;
459      for($value=0;$value<100;$value=$value+=$prec)
460      {
461        $hsv=new HSV($hue, $saturation, $value);
462        if($returnedType=='HSV')
463        {
464          $hueValues[]=$hsv;
465        }
466        else
467        {
468          $hueValues[]=$hsv->getRGB()->getHexString();
469        }
470        unset($hsv);
471      }
472
473      $value=100;
474      for($saturation=100;$saturation>=0;$saturation-=$prec)
475      {
476        $hsv=new HSV($hue, $saturation, $value);
477        if($returnedType=='HSV')
478        {
479          $hueValues[]=$hsv;
480        }
481        else
482        {
483          $hueValues[]=$hsv->getRGB()->getHexString();
484        }
485        unset($hsv);
486      }
487
488
489      for($tmp=$prec;$tmp<=100;$tmp+=$prec)
490      {
491        $hsv=new HSV($hue, $tmp, 100-$tmp);
492        if($returnedType=='HSV')
493        {
494          $hueValues[]=$hsv;
495        }
496        else
497        {
498          $hueValues[]=$hsv->getRGB()->getHexString();
499        }
500        unset($hsv);
501      }
502      for($tmp=$prec;$tmp<=100;$tmp+=$prec)
503      {
504        $hsv=new HSV($hue, $tmp, $tmp);
505        if($returnedType=='HSV')
506        {
507          $hueValues[]=$hsv;
508        }
509        else
510        {
511          $hueValues[]=$hsv->getRGB()->getHexString();
512        }
513        unset($hsv);
514      }
515
516      $returned[]=$hueValues;
517      unset($hueValues);
518    }
519
520    $hue=0;
521    $saturation=0;
522    $hueValues=Array();
523    //$prec=1/(count($returned[0])-2);
524    for($value=0;$value<=100;$value+=$prec/4)
525    {
526      $hsv=new HSV($hue, $saturation, round($value,0));
527      if($returnedType=='HSV')
528      {
529        $hueValues[]=$hsv;
530      }
531      else
532      {
533        $hueValues[]=$hsv->getRGB()->getHexString();
534      }
535      unset($hsv);
536    }
537
538    $returned[]=$hueValues;
539    unset($hueValues);
540
541    return($returned);
542  }
543
544  /**
545   * @param Array : an array
546   * @return Array : an array, giving the minimum value and the related key
547   */
548  static protected function min($values)
549  {
550    $minKey="";
551    $minValue=0;
552    foreach($values as $key => $val)
553    {
554      if($minKey=='' or $val<$minValue)
555      {
556        $minKey=$key;
557        $minValue=$val;
558      }
559    }
560    return(Array('key' => $minKey, 'value' => $minValue));
561  }
562
563  /**
564   * reverted sort color
565   *
566   */
567  static protected function sortTones($a, $b)
568  {
569    if($a['num'] == $b['num'])
570    {
571      return(0);
572    }
573    return ($a['num'] > $b['num']) ? -1 : 1;
574  }
575
576  /**
577   * @param Array : an array
578   * @return Array : an array, giving the maximum value and the related key
579   */
580  static protected function max($values)
581  {
582    $maxKey="";
583    $maxValue=0;
584    foreach($values as $key => $val)
585    {
586      if($maxKey=='' or $val>$maxValue)
587      {
588        $maxKey=$key;
589        $maxValue=$val;
590      }
591    }
592    return(Array('key' => $maxKey, 'value' => $maxValue));
593  }
594
595  /**
596   * check the validity for getFileColors() options
597   * if no parameters are given, return the default options values
598   *
599   * @param Array $options : an array with given options values
600   * @return Array : an array with valid options values
601   */
602  static protected function checkOptions($options=Array())
603  {
604    if(!is_array($options))
605    {
606      $options=Array();
607    }
608
609    if(!array_key_exists('quality', $options)) $options['quality']=1;
610    if(!array_key_exists('maxTime', $options)) $options['maxTime']=0;
611    if(!array_key_exists('pps', $options)) $options['pps']=0;
612    if(!array_key_exists('numColors', $options)) $options['numColors']=0;
613
614    if($options['quality']<=0) $options['quality']=1;
615    if($options['quality']>20) $options['quality']=20;
616    if($options['maxTime']<0) $options['maxTime']=0;
617    if($options['pps']<0) $options['pps']=0;
618    if($options['numColors']<0) $options['numColors']=0;
619
620    return($options);
621  }
622
623  static public function getColorFromTable(HSV $hsvObject, $colorTable)
624  {
625    $hue=360/(count($colorTable)-2);
626    $step=count($colorTable[0]);
627//echo "*hue:$hue<br>";
628//echo "*step:$step<br>";
629
630    $hsv=$hsvObject->get();
631
632    $hueNumber=round($hsv['H']/$hue,0);
633//echo "*hueNumber:$hueNumber<br>";
634
635//echo "*H:".$hsv['H']."<br>";
636//echo "*S:".$hsv['S']."<br>";
637//echo "*V:".$hsv['V']."<br>";
638
639    if($hsv['V']==0)
640    {
641//echo "-black-<br>";
642      // black
643      return(new RGB(0,0,0));
644    }
645    elseif($hsv['S']==0 && $hsv['V']==100)
646    {
647//echo "-white-<br>";
648      // white
649      return(new RGB(255,255,255));
650    }
651    elseif($hsv['S']==0 && $hsv['V']<100 && $hsv['V']>0)
652    {
653//echo "-grey-<br>";
654      // grey
655      $stepNumber=round($hsv['V']*($step-1)/100,0);
656      return($colorTable[count($colorTable)-1][$stepNumber]->getRGB());
657    }
658    elseif($hsv['S']==100 && $hsv['V']<100 && $hsv['V']>0)
659    {
660//echo "-value-<br>";
661      //color 'value'
662      $stepNumber=round($hsv['V']*$step/400,0);
663      return($colorTable[$hueNumber][$stepNumber]->getRGB());
664    }
665    elseif($hsv['V']==100 && $hsv['S']<100 && $hsv['S']>0)
666    {
667//echo "-saturation-<br>";
668      //color 'saturation'
669      $stepNumber=round(($step/4)+(100-$hsv['S'])*($step/400),0);
670      return($colorTable[$hueNumber][$stepNumber]->getRGB());
671    }
672    elseif($hsv['V']==100 && $hsv['S']==100)
673    {
674//echo "-pure-<br>";
675      //color 'pure'
676      $stepNumber=$step/4;
677      return($colorTable[$hueNumber][$stepNumber]->getRGB());
678    }
679    elseif(
680      ($hsv['V']<100 && $hsv['V']>50 && $hsv['S']<50 && $hsv['S']>0) ||
681      ($hsv['S']<100 && $hsv['S']>50 && $hsv['V']<50 && $hsv['V']>0))
682    {
683//echo "-desaturated 1-<br>";
684      //color 'desaturated'
685      $stepNumber=($step/2)+(100-$hsv['V'])*$step/400;
686      return($colorTable[$hueNumber][$stepNumber]->getRGB());
687    }
688    elseif($hsv['V']<100 && $hsv['V']>0 && $hsv['S']<100 && $hsv['S']>0)
689    {
690//echo "-desaturated 2-<br>";
691      //color 'desaturated'
692      $stepNumber=($step*0.75)+$hsv['V']*$step/400;
693      return($colorTable[$hueNumber][$stepNumber]->getRGB());
694    }
695//echo "-none-<br>";
696    return(new RGB(0,0,0));
697  }
698
699}
700
701
702
703
704
705
706?>
Note: See TracBrowser for help on using the repository browser.