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

Last change on this file since 21569 was 6210, checked in by grum, 15 years ago

prepare release 1.0.0

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