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

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

Improve algorythm for colors analysis + use GPCRequestBuilder interface instead of a local interface

File size: 17.7 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['pps']>0 && $options['maxTime']>0)
359        {
360          $quality=round(sqrt($imageWidth*$imageHeight/($options['pps']*$options['maxTime'])), 0);
361        }
362        else
363        {
364          $quality=$options['quality'];
365        }
366
367        $imageWorkWidth=round($imageWidth/$quality,0);
368        $imageWorkHeight=round($imageHeight/$quality,0);
369        $imageWork=imagecreatetruecolor($imageWorkWidth,$imageWorkHeight);
370        imagecopyresampled($imageWork, $image, 0, 0, 0, 0, $imageWorkWidth, $imageWorkHeight, $imageWidth, $imageHeight);
371        //imagecopyresized($imageWork, $image, 0, 0, 0, 0, $imageWorkWidth, $imageWorkHeight, $imageWidth, $imageHeight);
372        imagedestroy($image);
373
374        $returned=Array();
375
376        $i=0;
377        for($px=0;$px<$imageWorkWidth;$px++)
378        {
379          for($py=0;$py<$imageWorkHeight;$py++)
380          {
381            $i++;
382            $value=imagecolorat($imageWork, $px, $py);
383
384            $rgb=self::IntToRGB($value);
385            //echo sprintf("%06x", $value)." => ".$rgb->getHexString();
386
387
388            //echo " ($i) ".$color->getHexString()."<br>";
389
390            if(DEBUG_MODE)
391            {
392              $color=self::getColorFromTable($rgb->getHSV(), $colorTable);
393              $newRGB=$color->get();
394              $col=imagecolorallocate($imageWork, $newRGB['R'], $newRGB['G'], $newRGB['B']);
395              imagesetpixel($imageWork, $px, $py, $col);
396              imagecolordeallocate($imageWork, $col);
397              $color=$color->getHexString();
398              unset($newRGB);
399            }
400            else
401            {
402              $color=self::getColorFromTable($rgb->getHSV(), $colorTable)->getHexString();
403            }
404
405
406            if(array_key_exists($color, $returned))
407            {
408              $returned[$color]['num']++;
409            }
410            else
411            {
412              $returned[$color]=Array(
413                'hsv' => $rgb->getHSV()->get(),
414                'num' => 1,
415                'pct' => 0,
416              );
417            }
418            unset($rgb);
419          }
420        }
421
422
423        if(DEBUG_MODE)
424        {
425          $fName="q".$quality."_c".$options['numColors']."_nb".count($returned)."_".$fileName.".png";
426          imagepng($imageWork, $fName);
427        }
428
429
430        imagedestroy($imageWork);
431        uasort($returned, Array('ColorStat', 'sortTones'));
432
433        if($options['numColors']>0)
434        {
435          foreach($returned as $key=>$val)
436          {
437            $returnedColors[$key]=$val;
438            $options['numColors']--;
439            if($options['numColors']<=0) break;
440          }
441        }
442        else
443        {
444          $returnedColors=$returned;
445        }
446
447        self::$fileColorsStat=Array(
448          'pixels'   => $imageWidth*$imageHeight,
449          'analyzed' => $i,
450          'time'     => microtime(true)-$time,
451          'colors'   => count($returned),
452          'pps'      => $i/(microtime(true)-$time),
453          'quality'  => $quality,
454        );
455
456        if(DEBUG_MODE)
457        {
458          self::$fileColorsStat['fileName']=$fName;
459        }
460
461        unset($returned);
462
463        foreach($returnedColors as $key => $val)
464        {
465          $returnedColors[$key]['pct']=round(100*$val['num']/self::$fileColorsStat['analyzed'],2);
466        }
467
468        return($returnedColors);
469      }
470      catch (Exception $e)
471      {
472        echo "ERROR!<br>".print_r($e, true);
473        return(-3);
474      }
475
476    }
477    else
478    {
479      return(-1);
480    }
481  }
482
483  /**
484   *  Calculate the HSV value from a RGB value
485   *
486   * @param RGB $RGB : RGB object
487   * @return HSV : new HSV object
488   */
489  static public function RGBtoHSV(RGB $RGB)
490  {
491    $rgbValues=$RGB->get(true);
492    $max=self::max($rgbValues);
493    $min=self::min($rgbValues);
494
495    if($max['value']==$min['value'])
496    {
497      $H=0;
498    }
499    elseif($max['key']=='R')
500    {
501      $H=(60*($rgbValues['G']-$rgbValues['B'])/($max['value']-$min['value'])+360)%360;
502    }
503    elseif($max['key']=='G')
504    {
505      $H=(60*($rgbValues['B']-$rgbValues['R'])/($max['value']-$min['value'])+120);
506    }
507    elseif($max['key']=='B')
508    {
509      $H=(60*($rgbValues['R']-$rgbValues['G'])/($max['value']-$min['value'])+240);
510    }
511
512    $S=round(100*(($max['value']==0)?0:1-$min['value']/$max['value']),0);
513    $V=round(100*$max['value'],0);
514
515    return(new HSV($H, $S, $V));
516  }
517
518  /**
519   *  Calculate the RGB value from a HSV value
520   *
521   * @param HSV $HSV : HSV object
522   * @return RGB : new RGB object
523   */
524  static public function HSVtoRGB(HSV $HSV)
525  {
526    $hsvValues=$HSV->get();
527
528    $h=abs($hsvValues['H']/60)%6;
529    $f=$hsvValues['H']/60-$h;
530    $l=round(2.55*$hsvValues['V']*(1-$hsvValues['S']/100),0);
531    $m=round(2.55*$hsvValues['V']*(1-$f*$hsvValues['S']/100),0);
532    $n=round(2.55*$hsvValues['V']*(1-(1-$f)*$hsvValues['S']/100),0);
533
534    $v=round(2.55*$hsvValues['V'],0);
535
536    switch($h)
537    {
538      case 0:
539        return(new RGB($v, $n, $l));
540        break;
541      case 1:
542        return(new RGB($m, $v, $l));
543        break;
544      case 2:
545        return(new RGB($l, $v, $n));
546        break;
547      case 3:
548        return(new RGB($l, $m, $v));
549        break;
550      case 4:
551        return(new RGB($n, $l, $v));
552        break;
553      case 5:
554        return(new RGB($v, $l, $m));
555        break;
556    }
557  }
558
559  /**
560   *
561   * @param Int $rgb : an integer &hRRGGBB
562   * @return RGB : a RGB object
563   */
564  static public function IntToRGB($rgb)
565  {
566    return(new RGB(($rgb >> 16) & 0xFF, ($rgb >> 8) & 0xFF, $rgb & 0xFF, true));
567  }
568
569  /**
570   * return a color table
571   *  $table[h][s][v] => HSV object or string 'RRGGBB'
572   *
573   * @param Int $huePrec : degree of precision for hue [1..360]
574   * @param Float $prec    : precision step for saturation & value [1..100]
575   * @param String $returnedType : 'HSV'   => return a HSV object
576   *                               'color' => return a color string 'RRGGBB'
577   * @return Array : the color table
578   */
579  static public function getColorTable($huePrec=10, $prec=10, $returnedType='HSV')
580  {
581    $returned=Array();
582    for($hue=0;$hue<360;$hue+=$huePrec)
583    {
584      $hueValues=Array();
585
586      for($saturation=0;$saturation<=100;$saturation+=$prec)
587      {
588        $saturationValues=Array();
589
590        for($value=0;$value<=100;$value+=$prec)
591        {
592          $hsv=new HSV($hue, $saturation, $value);
593          if($returnedType=='HSV')
594          {
595            $saturationValues[$value]=$hsv;
596          }
597          else
598          {
599            $saturationValues[$value]=$hsv->getRGB()->getHexString();
600          }
601          unset($hsv);
602        }
603
604        $hueValues[$saturation]=$saturationValues;
605        unset($saturationValues);
606      }
607      $returned[$hue]=$hueValues;
608      unset($hueValues);
609    }
610
611
612    return($returned);
613  }
614
615  /**
616   * @param Array : an array
617   * @return Array : an array, giving the minimum value and the related key
618   */
619  static protected function min($values)
620  {
621    $minKey="";
622    $minValue=0;
623    foreach($values as $key => $val)
624    {
625      if($minKey=='' or $val<$minValue)
626      {
627        $minKey=$key;
628        $minValue=$val;
629      }
630    }
631    return(Array('key' => $minKey, 'value' => $minValue));
632  }
633
634  /**
635   * reverted sort color
636   *
637   */
638  static protected function sortTones($a, $b)
639  {
640    if($a['num'] == $b['num'])
641    {
642      return(0);
643    }
644    return ($a['num'] > $b['num']) ? -1 : 1;
645  }
646
647  /**
648   * @param Array : an array
649   * @return Array : an array, giving the maximum value and the related key
650   */
651  static protected function max($values)
652  {
653    $maxKey="";
654    $maxValue=0;
655    foreach($values as $key => $val)
656    {
657      if($maxKey=='' or $val>$maxValue)
658      {
659        $maxKey=$key;
660        $maxValue=$val;
661      }
662    }
663    return(Array('key' => $maxKey, 'value' => $maxValue));
664  }
665
666  /**
667   * check the validity for getFileColors() options
668   * if no parameters are given, return the default options values
669   *
670   * @param Array $options : an array with given options values
671   * @return Array : an array with valid options values
672   */
673  static protected function checkOptions($options=Array())
674  {
675    if(!is_array($options))
676    {
677      $options=Array();
678    }
679
680    if(!array_key_exists('quality', $options)) $options['quality']=1;
681    if(!array_key_exists('maxTime', $options)) $options['maxTime']=0;
682    if(!array_key_exists('pps', $options)) $options['pps']=0;
683    if(!array_key_exists('numColors', $options)) $options['numColors']=0;
684
685    if($options['quality']<=0) $options['quality']=1;
686    if($options['quality']>20) $options['quality']=20;
687    if($options['maxTime']<0) $options['maxTime']=0;
688    if($options['pps']<0) $options['pps']=0;
689    if($options['numColors']<0) $options['numColors']=0;
690
691    return($options);
692  }
693
694  /**
695   * for the given color, return the nearest color of the color table
696   *
697   * @param HSV $hsvObject    : the color, as HSV object
698   * @param Array $colorTable : the colorTable
699   * @param String $mode      : 'HSV' or 'RGB' to define the returned object
700   * @return HSV or RGB       : the color
701   */
702  static public function getColorFromTable(HSV $hsvObject, $colorTable, $mode='RGB')
703  {
704    $hsv=$hsvObject->get();
705//echo "*H:".$hsv['H']." ";
706//echo "*S:".$hsv['S']." ";
707//echo "*V:".$hsv['V']."<br>";
708
709    $hue=360/count($colorTable);
710    $hueNumber=round($hsv['H']/$hue,0);
711    $step=100/(count($colorTable[0])-1);
712//echo "*H_Step:$hue<br>";
713//echo "*SV_Step:$step<br>";
714
715    $hsv['S']=round((100/$step)*$hsv['S']/100,0)*$step;
716    $hsv['V']=round((100/$step)*$hsv['V']/100,0)*$step;
717    $hsv['H']=($hueNumber*$hue)%360;
718//echo "*H:".$hsv['H']." ";
719//echo "*S:".$hsv['S']." ";
720//echo "*V:".$hsv['V']."<br>";
721
722    if($mode=='RGB')
723    {
724      return($colorTable[$hsv['H']][$hsv['S']][$hsv['V']]->getRGB());
725    }
726    else
727    {
728      return($colorTable[$hsv['H']][$hsv['S']][$hsv['V']]);
729    }
730  }
731
732}
733
734
735
736
737
738
739?>
Note: See TracBrowser for help on using the repository browser.