source: spip-zone/_plugins_/adaptive_images/trunk/lib/AdaptiveImages/AdaptiveImages.php @ 79131

Last change on this file since 79131 was 79131, checked in by cedric@…, 6 years ago

mise a jour de la lib AdaptiveImages?

File size: 39.2 KB
Line 
1<?php
2/**
3 * AdaptiveImages
4 *
5 * @version    1.0.2
6 * @copyright  2013
7 * @author     Nursit
8 * @licence    GNU/GPL3
9 * @source     https://github.com/nursit/AdaptiveImages
10 */
11
12
13class AdaptiveImages {
14        /**
15         * @var AdaptiveImages
16         */
17        static protected $instance;
18
19        /**
20         * Use progressive rendering for PNG and GIF when JS disabled ?
21         * @var boolean
22         */
23        protected $nojsPngGifProgressiveRendering = false;
24
25        /**
26         * Background color for JPG lowsrc generation
27         * (if source has transparency layer)
28         * @var string
29         */
30        protected $lowsrcJpgBgColor = '#ffffff';
31
32
33        /**
34         * JPG compression quality for JPG lowsrc
35         * @var int
36         */
37        protected $lowsrcJpgQuality = 10;
38
39        /**
40         * JPG compression quality for 1x JPG images
41         * @var int
42         */
43        protected $x10JpgQuality = 75;
44
45        /**
46         * JPG compression quality for 1.5x JPG images
47         * @var int
48         */
49        protected $x15JpgQuality = 65;
50
51        /**
52         * JPG compression quality for 2x JPG images
53         * @var int
54         */
55        protected $x20JpgQuality = 45;
56
57        /**
58         * Breakpoints width for image generation
59         * @var array
60         */
61        protected $defaultBkpts = array(160,320,480,640,960,1440);
62
63        /**
64         * Maximum display width for images
65         * @var int
66         */
67        protected $maxWidth1x = 640;
68
69        /**
70         * Minimum display width for adaptive images (smaller will be unchanged)
71         * @var int
72         */
73        protected $minWidth1x = 320;
74
75        /**
76         * Maximum width for delivering mobile version in data-src-mobile=""
77         * @var int
78         */
79        protected $maxWidthMobileVersion = 320;
80
81        /**
82         * Set to true to generate adapted image only at first request from users
83         * (speed up initial page generation)
84         * @var int
85         */
86        protected $onDemandImages = false;
87
88
89        /**
90         * Allowed format images to be adapted
91         * @var array
92         */
93        protected $acceptedFormats = array('gif','png','jpeg','jpg');
94
95        /**
96         * directory for storing adaptive images
97         * @var string
98         */
99        protected $destDirectory = "local/adapt-img/";
100
101        /**
102         * Maximum number of px for image that can be loaded in memory by GD
103         * can be used to avoid Fatal Memory Error on large image if PHP memory limited
104         * @var string
105         */
106        protected $maxImagePxGDMemoryLimit = 0;
107
108        /**
109         * Constructor
110         */
111        protected function __construct(){
112        }
113
114        /**
115         * get
116         * @param $property
117         * @return mixed
118         * @throws InvalidArgumentException
119         */
120        public function __get($property){
121                if(!property_exists($this,$property) OR $property=="instance") {
122      throw new InvalidArgumentException("Property {$property} doesn't exist");
123    }
124                return $this->{$property};
125        }
126
127        /**
128         * set
129         * @param $property
130         * @param $value
131         * @return mixed
132         * @throws InvalidArgumentException
133         */
134        public function __set($property, $value){
135                if(!property_exists($this,$property) OR $property=="instance") {
136      throw new InvalidArgumentException("Property {$property} doesn't exist");
137    }
138                if (in_array($property,array("nojsPngGifProgressiveRendering","onDemandImages"))){
139                        if (!is_bool($value))
140                                throw new InvalidArgumentException("Property {$property} needs a bool value");
141                }
142                elseif (in_array($property,array("lowsrcJpgBgColor","destDirectory"))){
143                        if (!is_string($value))
144                                throw new InvalidArgumentException("Property {$property} needs a string value");
145                }
146                elseif (in_array($property,array("defaultBkpts","acceptedFormats"))){
147                        if (!is_array($value))
148                                throw new InvalidArgumentException("Property {$property} needs an array value");
149                }
150                elseif (!is_int($value)){
151                        throw new InvalidArgumentException("Property {$property} needs an int value");
152                }
153                if ($property=="defaultBkpts"){
154                        sort($value);
155                }
156
157                return ($this->{$property} = $value);
158        }
159
160        /**
161         * Disable cloning
162         */
163        protected function __clone() {
164         trigger_error("Cannot clone a singleton class", E_USER_ERROR);
165        }
166
167        /**
168         * Retrieve the AdaptiveImages object
169         *
170         * @return AdaptiveImages
171         */
172        static public function getInstance() {
173         if (!(self::$instance instanceof self)) {
174           self::$instance = new self;
175         }
176         return self::$instance;
177        }
178
179        /**
180         * Log function for internal warning if we can avoid to throw an Exception
181         * Do nothing, should be overriden with your personal log function
182         * @param $message
183         */
184        protected function log($message){
185
186        }
187
188
189        /**
190         * Process the full HTML page :
191         *  - adapt all <img> in the HTML
192         *  - collect all inline <style> and put in the <head>
193         *  - add necessary JS
194         *
195         * @param string $html
196         *   HTML source page
197         * @param int $maxWidth1x
198         *   max display width for images 1x
199         * @return string
200         *  HTML modified page
201         */
202        public function adaptHTMLPage($html,$maxWidth1x=null){
203                // adapt all images that need it, if not already
204                $html = $this->adaptHTMLPart($html, $maxWidth1x);
205
206                // if there is adapted images in the page, add the necessary CSS and JS
207                if (strpos($html,"adapt-img-wrapper")!==false){
208                        // Common styles for all adaptive images during loading
209                        $ins = "<style type='text/css'>"."img.adapt-img{opacity:0.70;max-width:100%;height:auto;}"
210                        ."span.adapt-img-wrapper,span.adapt-img-wrapper:after{display:inline-block;max-width:100%;position:relative;-webkit-background-size:100% auto;background-size:100% auto;background-repeat:no-repeat;line-height:1px;}"
211                        ."span.adapt-img-wrapper:after{position:absolute;top:0;left:0;right:0;bottom:0;content:\"\"}"
212                        ."</style>\n";
213                        // JS that evaluate connection speed and add a aislow class on <html> if slow connection
214                        // and onload JS that adds CSS to finish rendering
215                        $async_style = "html img.adapt-img{opacity:0.01}html span.adapt-img-wrapper:after{display:none;}";
216                        $length = strlen($html)+2000; // ~2000 pour le JS qu'on va inserer
217                        $ins .= "<script type='text/javascript'>/*<![CDATA[*/"
218                                ."function adaptImgFix(n){var i=window.getComputedStyle(n.parentNode).backgroundImage.replace(/\W?\)$/,'').replace(/^url\(\W?|/,'');n.src=(i&&i!='none'?i:n.src);}"
219                                ."(function(){function hAC(c){(function(H){H.className=H.className+' '+c})(document.documentElement)}"
220                                // Android 2 media-queries bad support workaround
221                                // muliple rules = multiples downloads : put .android2 on <html>
222                                // use with simple css without media-queries and send compressive image
223                                ."var android2 = (/android 2[.]/i.test(navigator.userAgent.toLowerCase()));"
224                                ."if (android2) {hAC('android2');}\n"
225                                // slowConnection detection
226                                ."var slowConnection = false;"
227                                ."if (typeof window.performance!==\"undefined\"){"
228                                ."var perfData = window.performance.timing;"
229                                ."var speed = ~~($length/(perfData.responseEnd - perfData.connectStart));" // approx, *1000/1024 to be exact
230                                //."console.log(speed);"
231                                ."slowConnection = (speed && speed<50);" // speed n'est pas seulement une bande passante car prend en compte la latence de connexion initiale
232                                ."}else{"
233                                //https://github.com/Modernizr/Modernizr/blob/master/feature-detects/network/connection.js
234                                ."var connection = navigator.connection || navigator.mozConnection || navigator.webkitConnection;"
235                                ."if (typeof connection!==\"undefined\") slowConnection = (connection.type == 3 || connection.type == 4 || /^[23]g$/.test(connection.type));"
236                                ."}"
237                                //."console.log(slowConnection);"
238                                ."if(slowConnection) {hAC('aislow');}\n"
239                                // injecter async style async after images are loaded
240                          // in order to hide 2 top layers and show only lower one
241                                ."var adaptImg_onload = function(){"
242                          ."var sa = document.createElement('style'); sa.type = 'text/css';"
243                          ."sa.innerHTML = '$async_style';"
244                          ."var s = document.getElementsByTagName('style')[0]; s.parentNode.insertBefore(sa, s);};"
245                                // http://www.webreference.com/programming/javascript/onloads/index.html
246                                ."function addLoadEvent(func){var oldol=window.onload;if (typeof oldol != 'function'){window.onload=func;}else{window.onload=function(){if (oldol){oldol();} func();}}}"
247                                ."if (typeof jQuery!=='undefined') jQuery(function(){jQuery(window).load(adaptImg_onload)}); else addLoadEvent(adaptImg_onload);"
248                          ."})();/*]]>*/</script>\n";
249                        // alternative noscript if no js (to de-activate progressive rendering on PNG and GIF)
250                        if (!$this->nojsPngGifProgressiveRendering)
251                                $ins .= "<noscript><style type='text/css'>.png img.adapt-img,.gif img.adapt-img{opacity:0.01}span.adapt-img-wrapper.png:after,span.adapt-img-wrapper.gif:after{display:none;}</style></noscript>";
252
253                        // collect all adapt-img <style> in order to put it in the <head>
254                        preg_match_all(",<!--\[if !IE\]><!-->.*(<style[^>]*>.*</style>).*<!--<!\[endif\]-->,Ums",$html,$matches);
255                        if (count($matches[1])){
256                                $html = str_replace($matches[1],"",$html);
257                                $ins .= implode("\n",$matches[1]);
258                        }
259                        // insert before first <script or <link
260                        if ($p = strpos($html,"<link") OR $p = strpos($html,"<script") OR $p = strpos($html,"</head"))
261                                $html = substr_replace($html,"<!--[if !IE]-->$ins\n<!--[endif]-->\n",$p,0);
262                }
263                return $html;
264        }
265
266
267        /**
268         * Adapt each <img> from HTML part
269         *
270         * @param string $html
271         *   HTML source page
272         * @param int $maxWidth1x
273         *   max display width for images 1x
274         * @return string
275         */
276        public function adaptHTMLPart($html,$maxWidth1x=null){
277                static $bkpts = array();
278                if (is_null($maxWidth1x) OR !intval($maxWidth1x))
279                        $maxWidth1x = $this->maxWidth1x;
280
281                if ($maxWidth1x AND !isset($bkpts[$maxWidth1x])){
282                        $b = $this->defaultBkpts;
283                        while (count($b) AND end($b)>$maxWidth1x) array_pop($b);
284                        // la largeur maxi affichee
285                        if (!count($b) OR end($b)<$maxWidth1x) $b[] = $maxWidth1x;
286                        $bkpts[$maxWidth1x] = $b;
287                }
288                $bkpt = (isset($bkpts[$maxWidth1x])?$bkpts[$maxWidth1x]:null);
289
290                $replace = array();
291                preg_match_all(",<img\s[^>]*>,Uims",$html,$matches,PREG_SET_ORDER);
292                if (count($matches)){
293                        foreach($matches as $m){
294                                $ri = $this->processImgTag($m[0], $bkpt, $maxWidth1x);
295                                if ($ri!==$m[0]){
296                                        $replace[$m[0]] = $ri;
297                                }
298                        }
299                        if (count($replace)){
300                                $html = str_replace(array_keys($replace),array_values($replace),$html);
301                        }
302                }
303
304                return $html;
305        }
306
307
308
309        /**
310         * OnDemand production and delivery of BkptImage from it's URL
311         * @param string path
312         *   local/adapt-img/w/x/file
313         *   ex : 320/20x/file
314         *   w is the display width
315         *   x is the dpi resolution (10x => 1, 15x => 1.5, 20x => 2)
316         *   file is the original source image file path
317         * @throws Exception
318         */
319        public function deliverBkptImage($path){
320
321                try {
322                        $file = $this->processBkptImageFromPath($path, $mime);
323                }
324                catch (Exception $e){
325                        $file = "";
326                }
327                if (!$file
328                  OR !$mime){
329                        http_status(404);
330                        throw new InvalidArgumentException("Unable to find {$path} image");
331                }
332
333                header("Content-Type: ". $mime);
334                #header("Expires: 3600"); // set expiration time
335
336                if ($cl = filesize($file))
337                        header("Content-Length: ". $cl);
338
339                readfile($file);
340        }
341
342
343        /**
344         * Build an image variant for a resolution breakpoint
345         * file path of image is constructed from source file, width and resolution on scheme :
346         * bkptwidth/resolution/full/path/to/src/image/file
347         * it allows to reverse-build the image variant from the path
348         *
349         * if $force==false and $this->onDemandImages==true we only compute the file path
350         * and the image variant will be built on first request
351         *
352         * @param string $src
353         *   source image
354         * @param int $wkpt
355         *   breakpoint width (display width) for which the image is built
356         * @param int $wx
357         *   real width in px of image
358         * @param string $x
359         *   resolution 10x 15x 20x
360         * @param string $extension
361         *   extension
362         * @param bool $force
363         *   true to force immediate image building if not existing or if too old
364         * @return string
365         *   name of image file
366         * @throws Exception
367         */
368        protected function processBkptImage($src, $wkpt, $wx, $x, $extension, $force=false){
369                $dir_dest = $this->destDirectory."$wkpt/$x/";
370                $dest = $dir_dest.$src;
371
372                if (($exist=file_exists($dest)) AND filemtime($dest)>=filemtime($src))
373                        return $dest;
374
375                $force = ($force?true:!$this->onDemandImages);
376
377                // if file already exists but too old, delete it if we don't want to generate it now
378                // it will be generated on first request
379                if ($exist AND !$force)
380                        @unlink($dest);
381
382                if (!$force)
383                        return $dest;
384
385                switch($x){
386                        case '10x':
387                                $quality = $this->x10JpgQuality;
388                                break;
389                        case '15x':
390                                $quality = $this->x15JpgQuality;
391                                break;
392                        case '20x':
393                                $quality = $this->x20JpgQuality;
394                                break;
395                }
396
397                $i = $this->imgSharpResize($src,$dir_dest,$wx,10000,$quality);
398                if ($i AND $i!==$dest AND $i!==$src AND $i!==preg_replace(",\.gif$,",".png",$dest)){
399                        throw new Exception("Error in imgSharpResize : return \"$i\" whereas \"$dest\" expected");
400                }
401                return $i;
402        }
403
404
405        /**
406         * Build an image variant from it's URL
407         * this function is used when $this->onDemandImages==true
408         * needs a RewriteRule such as following and a router to call this function on first request
409         *
410         * RewriteRule \badapt-img/(\d+/\d\dx/.*)$ spip.php?action=adapt_img&arg=$1 [QSA,L]
411         *
412         * @param string $URLPath
413         * @param string $mime
414         * @return string
415         * @throws Exception
416         */
417        protected function processBkptImageFromPath($URLPath,&$mime){
418                $base = $this->destDirectory;
419                $path = $URLPath;
420                // if base path is provided, remove it
421                if (strncmp($path,$base,strlen($base))==0)
422                        $path = substr($path,strlen($base));
423
424                $path = explode("/",$path);
425                $wkpt = intval(array_shift($path));
426                $x = array_shift($path);
427                $src = implode("/",$path);
428
429                $parts = pathinfo($src);
430                $extension = strtolower($parts['extension']);
431                $mime = $this->extensionToMimeType($extension);
432                $dpi = array('10x'=>1,'15x'=>1.5,'20x'=>2);
433
434                // check that path is well formed
435                if (!$wkpt
436                  OR !isset($dpi[$x])
437                  OR !file_exists($src)
438                  OR !$mime){
439                        throw new Exception("Unable to build adapted image $URLPath");
440                }
441                $wx = intval(round($wkpt * $dpi[$x]));
442
443                $file = $this->processBkptImage($src, $wkpt, $wx, $x, $extension, true);
444                return $file;
445        }
446
447
448        /**
449         * Process one single <img> tag :
450         * extract informations of src attribute
451         * and data-src-mobile attribute if provided
452         * compute images versions for provided breakpoints
453         *
454         * Don't do anything if img width is lower than $this->minWidth1x
455         *
456         * @param string $img
457         *   html img tag
458         * @param array $bkpt
459         *   breakpoints
460         * @param int $maxWidth1x
461         *   max display with of image (in 1x)
462         * @return string
463         *   html markup : original markup or adapted markup
464         */
465        protected function processImgTag($img, $bkpt, $maxWidth1x){
466                if (!$img) return $img;
467
468                // don't do anyting if has adapt-img (already adaptive) or no-adapt-img class (no adaptative needed)
469                if (strpos($img, "adapt-img")!==false)
470                        return $img;
471                if (is_null($bkpt) OR !is_array($bkpt))
472                        $bkpt = $this->defaultBkpts;
473
474                list($w,$h) = $this->imgSize($img);
475                // Don't do anything if img is to small or unknown width
476                if (!$w OR $w<=$this->minWidth1x) return $img;
477
478                $src = trim($this->tagAttribute($img, 'src'));
479                if (strlen($src)<1){
480                        $src = $img;
481                        $img = "<img src='".$src."' />";
482                }
483                $srcMobile = $this->tagAttribute($img, 'data-src-mobile');
484
485                // don't do anyting of data-URI images
486                if (strncmp($src, "data:", 5)==0)
487                        return $img;
488
489                $images = array();
490                if ($w<end($bkpt))
491                        $images[$w] = array(
492                                '10x' => $src,
493                                '15x' => $src,
494                                '20x' => $src,
495                        );
496                $src = preg_replace(',[?][0-9]+$,', '', $src);
497
498                // don't do anyting if we can't find file
499                if (!file_exists($src))
500                        return $img;
501
502                $parts = pathinfo($src);
503                $extension = $parts['extension'];
504
505                // don't do anyting if it's an animated GIF
506                if ($extension=="gif" AND $this->isAnimatedGif($src))
507                        return $img;
508
509                // build images (or at least URLs of images) on breakpoints
510                $fallback = $src;
511                $wfallback = $w;
512                $dpi = array('10x' => 1, '15x' => 1.5, '20x' => 2);
513                $wk = 0;
514                foreach ($bkpt as $wk){
515                        if ($wk>$w) break;
516                        $is_mobile = (($srcMobile AND $wk<=$this->maxWidthMobileVersion) ? true : false);
517                        foreach ($dpi as $k => $x){
518                                $wkx = intval(round($wk*$x));
519                                if ($wkx>$w)
520                                        $images[$wk][$k] = $src;
521                                else {
522                                        $images[$wk][$k] = $this->processBkptImage($is_mobile ? $srcMobile : $src, $wk, $wkx, $k, $extension);
523                                }
524                        }
525                        if ($wk<=$maxWidth1x AND ($is_mobile OR !$srcMobile)){
526                                $fallback = $images[$wk]['10x'];
527                                $wfallback = $wk;
528                        }
529                }
530
531                // Build the fallback img : High-compressed JPG
532                // Start from the mobile version if available or from the larger version otherwise
533                if ($wk>$w && $w<$maxWidth1x){
534                        $fallback = $images[$w]['10x'];
535                        $wfallback = $w;
536                }
537
538
539                // if $this->onDemandImages == true image has not been built yet
540                // in this case ask for immediate generation
541                if (!file_exists($fallback)){
542                        $mime = ""; // not used here
543                        $this->processBkptImageFromPath($fallback, $mime);
544                }
545
546                // $this->lowsrcJpgQuality give a base quality for a 450kpx image size
547                // quality is varying around this value (+/- 50%) depending of image pixel size
548                // in order to limit the weight of fallback (empirical rule)
549                $q = round($this->lowsrcJpgQuality-((min($maxWidth1x, $wfallback)*$h/$w*min($maxWidth1x, $wfallback))/75000-6));
550                $q = min($q, round($this->lowsrcJpgQuality)*1.5);
551                $q = max($q, round($this->lowsrcJpgQuality)*0.5);
552                $images["fallback"] = $this->img2JPG($fallback, $this->destDirectory."fallback/", $this->lowsrcJpgBgColor, $q);
553
554                // limit $src image width to $maxWidth1x for old IE
555                $src = $this->processBkptImage($src,$maxWidth1x,$maxWidth1x,'10x',$extension,true);
556                list($w,$h) = $this->imgSize($src);
557                $img = $this->setTagAttribute($img,"src",$src);
558                $img = $this->setTagAttribute($img,"width",$w);
559                $img = $this->setTagAttribute($img,"height",$h);
560
561                // ok, now build the markup
562                return $this->imgAdaptiveMarkup($img, $images, $w, $h, $extension, $maxWidth1x);
563        }
564
565
566        /**
567         * Build html markup with CSS rules in <style> tag
568         * from provided img tag an array of bkpt images
569         *
570         * @param string $img
571         *   source img tag
572         * @param array $bkptImages
573         *     falbback => file
574         *     width =>
575         *        10x => file
576         *        15x => file
577         *        20x => file
578         * @param int $width
579         * @param int $height
580         * @param string $extension
581         * @param int $maxWidth1x
582         * @return string
583         */
584        function imgAdaptiveMarkup($img, $bkptImages, $width, $height, $extension, $maxWidth1x){
585                $class = $this->tagAttribute($img,"class");
586                if (strpos($class,"adapt-img")!==false) return $img;
587                ksort($bkptImages);
588                $cid = "c".crc32(serialize($bkptImages));
589                $style = "";
590                if ($class) $class = " $class";
591                $class = "$cid$class";
592                $img = $this->setTagAttribute($img,"class","adapt-img-ie $class");
593
594                // provided fallback image?
595                $fallback_file = "";
596                if (isset($bkptImages['fallback'])){
597                        $fallback_file = $bkptImages['fallback'];
598                        unset($bkptImages['fallback']);
599                }
600                // else we use the smallest one
601                if (!$fallback_file){
602                        $fallback_file = reset($bkptImages);
603                        $fallback_file = $fallback_file['10x'];
604                }
605                // embed fallback as a DATA URI if not more than 32ko
606                $fallback_file = $this->base64EmbedFile($fallback_file);
607
608                $prev_width = 0;
609                $medias = array();
610                $lastw = array_keys($bkptImages);
611                $lastw = end($lastw);
612                $wandroid = 0;
613                foreach ($bkptImages as $w=>$files){
614                        if ($w==$lastw) {$islast = true;}
615                        if ($w<=$this->maxWidthMobileVersion) $wandroid = $w;
616                        // use min-width and max-width in order to avoid override
617                        if ($prev_width<$maxWidth1x){
618                                $hasmax = (($islast OR $w>=$maxWidth1x)?false:true);
619                                $mw = ($prev_width?"and (min-width:{$prev_width}px)":"").($hasmax?" and (max-width:{$w}px)":"");
620                                $htmlsel = "html:not(.android2)";
621                                $htmlsel = array(
622                                        '10x' => "$htmlsel",
623                                        '15x' => "$htmlsel:not(.aislow)",
624                                        '20x' => "$htmlsel:not(.aislow)",
625                                );
626                        }
627                        $mwdpi = array(
628                                '10x' => "screen $mw",
629                                '15x' => "screen and (-webkit-min-device-pixel-ratio: 1.5) and (-webkit-max-device-pixel-ratio: 1.99) $mw,screen and (min--moz-device-pixel-ratio: 1.5) and (max--moz-device-pixel-ratio: 1.99) $mw",
630                                '20x' => "screen and (-webkit-min-device-pixel-ratio: 2) $mw,screen and (min--moz-device-pixel-ratio: 2) $mw",
631                        );
632                        foreach($files as $kx=>$file){
633                                if (isset($mwdpi[$kx])){
634                                        $mw = $mwdpi[$kx];
635                                        $not = $htmlsel[$kx];
636                                        $medias[$mw] = "@media $mw{{$not} span.$cid,{$not} span.$cid:after{background-image:url($file);}}";
637                                }
638                        }
639                        $prev_width = $w+1;
640                }
641
642                // One single CSS rule for old android browser (<3) which isn't able to manage override properly
643                // we chose JPG 320px width - 1.5x as a compromise
644                if ($wandroid){
645                        $file = $bkptImages[$wandroid]['15x'];
646                        $medias['android2'] = "html.android2 span.$cid,html.android2 span.$cid:after{background-image:url($file);}";
647                }
648
649                // Media-Queries
650                $style .= implode("",$medias);
651
652                $out = "<!--[if IE]>$img<![endif]-->\n";
653                $img = $this->setTagAttribute($img,"src",$fallback_file);
654                $img = $this->setTagAttribute($img,"class","adapt-img $class");
655                $img = $this->setTagAttribute($img,"onmousedown","adaptImgFix(this)");
656                // $img = setTagAttribute($img,"onkeydown","adaptImgFix(this)"); // usefull ?
657                $out .= "<!--[if !IE]><!--><span class=\"adapt-img-wrapper $cid $extension\">$img</span>\n<style>".$style."</style><!--<![endif]-->";
658
659                return $out;
660        }
661
662
663
664        /**
665         * Get height and width from an image file or <img> tag
666         * use width and height attributes of provided <img> tag if possible
667         * store getimagesize result in static to avoid multiple disk access if needed
668         *
669         * @param string $img
670         * @return array
671         *  (width,height)
672         */
673        protected function imgSize($img) {
674
675                static $largeur_img =array(), $hauteur_img= array();
676                $srcWidth = 0;
677                $srcHeight = 0;
678
679                $source = $this->tagAttribute($img,'src');
680
681                if (!$source) $source = $img;
682                else {
683                        $srcWidth = $this->tagAttribute($img,'width');
684                        $srcHeight = $this->tagAttribute($img,'height');
685                }
686
687                // never process on remote img
688                if (preg_match(';^(\w{3,7}://);', $source)){
689                        return array(0,0);
690                }
691                // remove timestamp on URL
692                if (($p=strpos($source,'?'))!==FALSE)
693                        $source=substr($source,0,$p);
694
695                if (isset($largeur_img[$source]))
696                        $srcWidth = $largeur_img[$source];
697                if (isset($hauteur_img[$source]))
698                        $srcHeight = $hauteur_img[$source];
699                if (!$srcWidth OR !$srcHeight){
700                        if (file_exists($source)
701                                AND $srcsize = @getimagesize($source)){
702                                if (!$srcWidth) $largeur_img[$source] = $srcWidth = $srcsize[0];
703                                if (!$srcHeight)        $hauteur_img[$source] = $srcHeight = $srcsize[1];
704                        }
705                }
706                return array($srcWidth,$srcHeight);
707        }
708
709
710        /**
711         * Find and get attribute value in an HTML tag
712         * Regexp from function extraire_attribut() in
713         * http://core.spip.org/projects/spip/repository/entry/spip/ecrire/inc/filtres.php#L2013
714         * @param $tag
715         *   html tag
716         * @param $attribute
717         *   attribute we look for
718         * @param $full
719         *   if true the function also returns the regexp match result
720         * @return array|string
721         */
722        protected function tagAttribute($tag, $attribute, $full = false) {
723                if (preg_match(
724                ',(^.*?<(?:(?>\s*)(?>[\w:.-]+)(?>(?:=(?:"[^"]*"|\'[^\']*\'|[^\'"]\S*))?))*?)(\s+'
725                .$attribute
726                .'(?:=\s*("[^"]*"|\'[^\']*\'|[^\'"]\S*))?)()([^>]*>.*),isS',
727
728                $tag, $r)) {
729                        if ($r[3][0] == '"' || $r[3][0] == "'") {
730                                $r[4] = substr($r[3], 1, -1);
731                                $r[3] = $r[3][0];
732                        } elseif ($r[3]!=='') {
733                                $r[4] = $r[3];
734                                $r[3] = '';
735                        } else {
736                                $r[4] = trim($r[2]);
737                        }
738                        $att = str_replace("&#39;", "'", $r[4]);
739                }
740                else
741                        $att = NULL;
742
743                if ($full)
744                        return array($att, $r);
745                else
746                        return $att;
747        }
748
749
750        /**
751         * change or insert an attribute of an html tag
752         *
753         * @param string $tag
754         *   html tag
755         * @param string $attribute
756         *   attribute name
757         * @param string $value
758         *   new value
759         * @param bool $protect
760         *   protect value if true (remove newlines and convert quotes)
761         * @param bool $removeEmpty
762         *   if true remove attribute from html tag if empty
763         * @return string
764         *   modified tag
765         */
766        protected function setTagAttribute($tag, $attribute, $value, $protect=true, $removeEmpty=false) {
767                // preparer l'attribut
768                // supprimer les &nbsp; etc mais pas les balises html
769                // qui ont un sens dans un attribut value d'un input
770                if ($protect) {
771                        $value = preg_replace(array(",\n,",",\s(?=\s),msS"),array(" ",""),strip_tags($value));
772                        $value = str_replace(array("'",'"',"<",">"),array('&#039;','&#034;','&lt;','&gt;'), $value);
773                }
774
775                // echapper les ' pour eviter tout bug
776                $value = str_replace("'", "&#039;", $value);
777                if ($removeEmpty AND strlen($value)==0)
778                        $insert = '';
779                else
780                        $insert = " $attribute='$value'";
781
782                list($old, $r) = $this->tagAttribute($tag, $attribute, true);
783
784                if ($old !== NULL) {
785                        // Remplacer l'ancien attribut du meme nom
786                        $tag = $r[1].$insert.$r[5];
787                }
788                else {
789                        // preferer une balise " />" (comme <img />)
790                        if (preg_match(',/>,', $tag))
791                                $tag = preg_replace(",\s?/>,S", $insert." />", $tag, 1);
792                        // sinon une balise <a ...> ... </a>
793                        else
794                                $tag = preg_replace(",\s?>,S", $insert.">", $tag, 1);
795                }
796
797                return $tag;
798        }
799
800        /**
801         * Provide Mime Type for Image file Extension
802         * @param $extension
803         * @return string
804         */
805        protected function extensionToMimeType($extension){
806                static $MimeTable = array(
807                        'jpg' => 'image/jpeg',
808                        'jpeg' => 'image/jpeg',
809                        'png' => 'image/png',
810                        'gif' => 'image/gif',
811                );
812
813                return (isset($MimeTable[$extension])?$MimeTable[$extension]:'image/jpeg');
814        }
815
816
817        /**
818         * Detect animated GIF : don't touch it
819         * http://it.php.net/manual/en/function.imagecreatefromgif.php#59787
820         *
821         * @param string $filename
822         * @return bool
823         */
824        protected function isAnimatedGif($filename){
825                $filecontents = file_get_contents($filename);
826
827                $str_loc = 0;
828                $count = 0;
829                while ($count<2) # There is no point in continuing after we find a 2nd frame
830                {
831
832                        $where1 = strpos($filecontents, "\x00\x21\xF9\x04", $str_loc);
833                        if ($where1===FALSE){
834                                break;
835                        } else {
836                                $str_loc = $where1+1;
837                                $where2 = strpos($filecontents, "\x00\x2C", $str_loc);
838                                if ($where2===FALSE){
839                                        break;
840                                } else {
841                                        if ($where1+8==$where2){
842                                                $count++;
843                                        }
844                                        $str_loc = $where2+1;
845                                }
846                        }
847                }
848
849                if ($count>1){
850                        return (true);
851
852                } else {
853                        return (false);
854                }
855        }
856
857        /**
858         * Embed image file in Base 64 URI
859         *
860         * @param string $filename
861         * @param int $maxsize
862         * @return string
863         *     URI Scheme of base64 if possible,
864         *     or URL from source file
865         */
866        function base64EmbedFile ($filename, $maxsize = 32768) {
867                $extension = substr(strrchr($filename,'.'),1);
868
869                if (!file_exists($filename)
870                        OR filesize($filename)>$maxsize
871                        OR !$content = file_get_contents($filename))
872                        return $filename;
873
874                $base64 = base64_encode($content);
875                $encoded = 'data:'.$this->extensionToMimeType($extension).';base64,'.$base64;
876
877                return $encoded;
878        }
879
880
881        /**
882         * Convert image to JPG and replace transparency with a background color
883         *
884         * @param string $source
885         *   source file name (or img tag)
886         * @param string $destDir
887         *   destination directory
888         * @param string $bgColor
889         *   hexa color
890         * @param int $quality
891         *   JPG quality
892         * @return string
893         *   file name of the resized image (or source image if fail)
894         * @throws Exception
895         */
896        function img2JPG($source, $destDir, $bgColor='#000000', $quality=85) {
897                $infos = $this->readSourceImage($source, $destDir, 'jpg', true);
898
899                if (!$infos) return $source;
900
901                $couleurs = $this->colorHEX2RGB($bgColor);
902                $dr= $couleurs["red"];
903                $dv= $couleurs["green"];
904                $db= $couleurs["blue"];
905
906                $srcWidth = $infos["largeur"];
907                $srcHeight = $infos["hauteur"];
908
909                if ($infos["creer"]) {
910                        if ($this->maxImagePxGDMemoryLimit AND $srcWidth*$srcHeight>$this->maxImagePxGDMemoryLimit){
911                                $this->log("No resize allowed : image is " . $srcWidth*$srcHeight . "px, larger than ".$this->maxImagePxGDMemoryLimit."px");
912                                return $infos["fichier"];
913                        }
914                        $fonction_imagecreatefrom = $infos['fonction_imagecreatefrom'];
915
916                        if (!function_exists($fonction_imagecreatefrom))
917                                return $infos["fichier"];
918                        $im = @$fonction_imagecreatefrom($infos["fichier"]);
919
920                        if (!$im){
921                                throw new Exception("GD image creation fail for ".$infos["fichier"]);
922                        }
923
924                        $this->imagepalettetotruecolor($im);
925                        $im_ = imagecreatetruecolor($srcWidth, $srcHeight);
926                        if ($infos["format_source"] == "gif" AND function_exists('ImageCopyResampled')) {
927                                // if was a transparent GIF
928                                // make a tansparent PNG
929                                @imagealphablending($im_, false);
930                                @imagesavealpha($im_,true);
931                                if (function_exists("imageAntiAlias")) imageAntiAlias($im_,true);
932                                @ImageCopyResampled($im_, $im, 0, 0, 0, 0, $srcWidth, $srcHeight, $srcWidth, $srcHeight);
933                                imagedestroy($im);
934                                $im = $im_;
935                        }
936
937                        // allocate background Color
938                        $color_t = ImageColorAllocate( $im_, $dr, $dv, $db);
939
940                        imagefill ($im_, 0, 0, $color_t);
941
942                        for ($x = 0; $x < $srcWidth; $x++) {
943                                for ($y=0; $y < $srcHeight; $y++) {
944
945                                        $rgb = ImageColorAt($im, $x, $y);
946                                        $a = ($rgb >> 24) & 0xFF;
947                                        $r = ($rgb >> 16) & 0xFF;
948                                        $g = ($rgb >> 8) & 0xFF;
949                                        $b = $rgb & 0xFF;
950
951                                        $a = (127-$a) / 127;
952
953                                        // faster if no transparency
954                                        if ($a == 1) {
955                                                $r = $r;
956                                                $g = $g;
957                                                $b = $b;
958                                        }
959                                        // faster if full transparency
960                                        else if ($a == 0) {
961                                                $r = $dr;
962                                                $g = $dv;
963                                                $b = $db;
964
965                                        }
966                                        else {
967                                                $r = round($a * $r + $dr * (1-$a));
968                                                $g = round($a * $g + $dv * (1-$a));
969                                                $b = round($a * $b + $db * (1-$a));
970                                        }
971                                        $a = (1-$a) *127;
972                                        $color = ImageColorAllocateAlpha( $im_, $r, $g, $b, $a);
973                                        imagesetpixel ($im_, $x, $y, $color);
974                                }
975                        }
976                        $this->saveGDImage($im_, $infos, $quality);
977                        if ($im!==$im_)
978                                imagedestroy($im);
979                        imagedestroy($im_);
980                }
981                return $infos["fichier_dest"];
982        }
983
984        /**
985         * Resize without bluring, and save image with needed quality if JPG image
986         * @author : Arno* from http://zone.spip.org/trac/spip-zone/browser/_plugins_/image_responsive/action/image_responsive.php
987         *
988         * @param string $source
989         * @param string $destDir
990         * @param int $maxWidth
991         * @param int $maxHeight
992         * @param int|null $quality
993         * @return string
994         *   file name of the resized image (or source image if fail)
995         * @throws Exception
996         */
997        function imgSharpResize($source, $destDir, $maxWidth = 0, $maxHeight = 0, $quality=null){
998                $infos = $this->readSourceImage($source, $destDir);
999                if (!$infos) return $source;
1000
1001                if ($maxWidth==0 AND $maxHeight==0)
1002                        return $source;
1003
1004                if ($maxWidth==0) $maxWidth = 10000;
1005                elseif ($maxHeight==0) $maxHeight = 10000;
1006
1007                $srcFile = $infos['fichier'];
1008                $srcExt = $infos['format_source'];
1009
1010                $destination = dirname($infos['fichier_dest']) . "/" . basename($infos['fichier_dest'], ".".$infos["format_dest"]);
1011
1012                // compute width & height
1013                $srcWidth = $infos['largeur'];
1014                $srcHeight = $infos['hauteur'];
1015                list($destWidth,$destHeight) = $this->computeImageSize($srcWidth, $srcHeight, $maxWidth, $maxHeight);
1016
1017                if ($infos['creer']==false)
1018                        return $infos['fichier_dest'];
1019
1020                // If source image is smaller than desired size, keep source
1021                if ($srcWidth
1022                  AND $srcWidth<=$destWidth
1023                  AND $srcHeight<=$destHeight){
1024
1025                        $infos['format_dest'] = $srcExt;
1026                        $infos['fichier_dest'] = $destination.".".$srcExt;
1027                        @copy($srcFile, $infos['fichier_dest']);
1028
1029                }
1030                else {
1031                        if ($this->maxImagePxGDMemoryLimit AND $srcWidth*$srcHeight>$this->maxImagePxGDMemoryLimit){
1032                                $this->log("No resize allowed : image is " . $srcWidth*$srcHeight . "px, larger than ".$this->maxImagePxGDMemoryLimit."px");
1033                                return $srcFile;
1034                        }
1035                        $destExt = $infos['format_dest'];
1036                        if (!$destExt){
1037                                throw new Exception("No output extension for {$srcFile}");
1038                        }
1039
1040                        $fonction_imagecreatefrom = $infos['fonction_imagecreatefrom'];
1041
1042                        if (!function_exists($fonction_imagecreatefrom))
1043                                return $srcFile;
1044                        $srcImage = @$fonction_imagecreatefrom($srcFile);
1045                        if (!$srcImage){
1046                                throw new Exception("GD image creation fail for {$srcFile}");
1047                        }
1048
1049                        // Initialization of dest image
1050                        $destImage = ImageCreateTrueColor($destWidth, $destHeight);
1051
1052                        // Copy and resize source image
1053                        $ok = false;
1054                        if (function_exists('ImageCopyResampled')){
1055                                // if transparent GIF, keep the transparency
1056                                if ($srcExt=="gif"){
1057                                        $transparent_index = ImageColorTransparent($srcImage);
1058                                        if($transparent_index!=(-1)){
1059                                                $transparent_color = ImageColorsForIndex($srcImage,$transparent_index);
1060                                                if(!empty($transparent_color)) {
1061                                                        $transparent_new = ImageColorAllocate($destImage,$transparent_color['red'],$transparent_color['green'],$transparent_color['blue']);
1062                                                        $transparent_new_index = ImageColorTransparent($destImage,$transparent_new);
1063                                                        ImageFill($destImage, 0,0, $transparent_new_index);
1064                                                }
1065                                        }
1066                                }
1067                                if ($destExt=="png"){
1068                                        // keep transparency
1069                                        if (function_exists("imageAntiAlias")) imageAntiAlias($destImage, true);
1070                                        @imagealphablending($destImage, false);
1071                                        @imagesavealpha($destImage, true);
1072                                }
1073                                $ok = @ImageCopyResampled($destImage, $srcImage, 0, 0, 0, 0, $destWidth, $destHeight, $srcWidth, $srcHeight);
1074                        }
1075                        if (!$ok)
1076                                $ok = ImageCopyResized($destImage, $srcImage, 0, 0, 0, 0, $destWidth, $destHeight, $srcWidth, $srcHeight);
1077
1078                        if ($destExt=="jpg" && function_exists('imageconvolution')){
1079                                $intSharpness = $this->computeSharpCoeff($srcWidth, $destWidth);
1080                                $arrMatrix = array(
1081                                        array(-1, -2, -1),
1082                                        array(-2, $intSharpness+12, -2),
1083                                        array(-1, -2, -1)
1084                                );
1085                                imageconvolution($destImage, $arrMatrix, $intSharpness, 0);
1086                        }
1087                        // save destination image
1088                        $this->saveGDImage($destImage, $infos, $quality);
1089
1090                        if ($srcImage)
1091                                ImageDestroy($srcImage);
1092                        ImageDestroy($destImage);
1093                }
1094
1095                return $infos['fichier_dest'];
1096
1097        }
1098
1099        /**
1100         * @author : Arno* from http://zone.spip.org/trac/spip-zone/browser/_plugins_/image_responsive/action/image_responsive.php
1101         *
1102         * @param int $intOrig
1103         * @param int $intFinal
1104         * @return mixed
1105         */
1106        function computeSharpCoeff($intOrig, $intFinal) {
1107          $intFinal = $intFinal * (750.0 / $intOrig);
1108          $intA     = 52;
1109          $intB     = -0.27810650887573124;
1110          $intC     = .00047337278106508946;
1111          $intRes   = $intA + $intB * $intFinal + $intC * $intFinal * $intFinal;
1112          return max(round($intRes), 0);
1113        }
1114
1115        /**
1116         * Read and preprocess informations about source image
1117         *
1118         * @param string $img
1119         *              HTML img tag <img src=... /> OR source filename
1120         * @param string $destDir
1121         *              Destination dir of new image
1122         * @param null|string $outputFormat
1123         *              forced extension of output image file : jpg, png, gif
1124         * @param bool $hashfilename
1125         *    if true the final file name is a md5 of source file name
1126         *    otherwise final file name keeps the source path and file starting from $destDir
1127         * @return bool|array
1128         *              false in case of error
1129         *    array of image information otherwise
1130         * @throws Exception
1131         */
1132        protected function readSourceImage($img, $destDir, $outputFormat = null, $hashfilename = false) {
1133                if (strlen($img)==0) return false;
1134                $ret = array();
1135
1136                $source = trim($this->tagAttribute($img, 'src'));
1137                if (strlen($source) < 1){
1138                        $source = $img;
1139                        $img = "<img src='$source' />";
1140                }
1141                # gerer img src="data:....base64"
1142                # don't process base64
1143                else if (preg_match('@^data:image/(jpe?g|png|gif);base64,(.*)$@isS', $source)) {
1144                        return false;
1145                }
1146
1147                // don't process distant images
1148                if (preg_match(';^(\w{3,7}://);', $source)){
1149                        return false;
1150                }
1151
1152                // remove timestamp as query string
1153                $source=preg_replace(',[?][0-9]+$,','',$source);
1154
1155                $extension_dest = "";
1156                if (preg_match(",\.(gif|jpe?g|png)($|[?]),i", $source, $regs)) {
1157                        $extension = strtolower($regs[1]);
1158                        $extension_dest = $extension;
1159                }
1160                if (!is_null($outputFormat)) $extension_dest = $outputFormat;
1161
1162                if (!$extension_dest) return false;
1163
1164                if (@file_exists($source)){
1165                        list ($ret["largeur"],$ret["hauteur"]) = $this->imgSize($img);
1166                        $date_src = @filemtime($source);
1167                }
1168                else
1169                        return false;
1170
1171                // error if no known size
1172                if (!($ret["hauteur"] OR $ret["largeur"]))
1173                        return false;
1174
1175
1176                // dest filename : md5(source) or full source path name
1177                $nom_fichier = (!$hashfilename?(substr($source, 0, strlen($source) - (strlen($extension) + 1))):md5($source));
1178                $fichier_dest = rtrim($destDir,"/") . "/" . $nom_fichier . "." . $extension_dest;
1179
1180                $creer = true;
1181                if (@file_exists($f = $fichier_dest)){
1182                        if (filemtime($f)>=$date_src)
1183                                $creer = false;
1184                }
1185                // mkdir complete path if needed
1186                if ($creer
1187                  AND !is_dir($d=dirname($fichier_dest))){
1188                        mkdir($d,0777,true);
1189                        if (!is_dir($d)){
1190                                throw new Exception("Unable to mkdir {$d}");
1191                        }
1192                }
1193
1194                $ret["fonction_imagecreatefrom"] = "imagecreatefrom".($extension != 'jpg' ? $extension : 'jpeg');
1195                $ret["fichier"] = $source;
1196                $ret["fichier_dest"] = $fichier_dest;
1197                $ret["format_source"] = ($extension != 'jpeg' ? $extension : 'jpg');
1198                $ret["format_dest"] = $extension_dest;
1199                $ret["date_src"] = $date_src;
1200                $ret["creer"] = $creer;
1201                $ret["tag"] = $img;
1202
1203                if (!function_exists($ret["fonction_imagecreatefrom"])) return false;
1204                return $ret;
1205        }
1206
1207        /**
1208         * Compute new image size according to max Width and max Height and initial width/height ratio
1209         * @param int $srcWidth
1210         * @param int $srcHeight
1211         * @param int $maxWidth
1212         * @param int $maxHeight
1213         * @return array
1214         */
1215        function computeImageSize($srcWidth, $srcHeight, $maxWidth, $maxHeight) {
1216                $ratioWidth = $srcWidth/$maxWidth;
1217                $ratioHeight = $srcHeight/$maxHeight;
1218
1219                if ($ratioWidth <=1 AND $ratioHeight <=1) {
1220                        return array($srcWidth,$srcHeight);
1221                }
1222                else if ($ratioWidth < $ratioHeight) {
1223                        $destWidth = intval(ceil($srcWidth/$ratioHeight));
1224                        $destHeight = $maxHeight;
1225                }
1226                else {
1227                        $destWidth = $maxWidth;
1228                        $destHeight = intval(ceil($srcHeight/$ratioWidth));
1229                }
1230                return array ($destWidth, $destHeight);
1231        }
1232
1233        /**
1234         * SaveAffiche ou sauvegarde une image au format PNG
1235         * Utilise les fonctions spécifiques GD.
1236         *
1237         * @param resource $img
1238         *   GD image resource
1239         * @param array $infos
1240         *   image description
1241         * @param int|null $quality
1242         *   compression quality for JPG images
1243         * @return bool
1244         */
1245        protected function saveGDImage($img, $infos, $quality=null) {
1246                $fichier = $infos['fichier_dest'];
1247                $tmp = $fichier.".tmp";
1248                switch($infos['format_dest']){
1249                        case "gif":
1250                                $ret = imagegif($img,$tmp);
1251                                break;
1252                        case "png":
1253                                $ret = imagepng($img,$tmp);
1254                                break;
1255                        case "jpg":
1256                        case "jpeg":
1257                                $ret = imagejpeg($img,$tmp,$quality);
1258                                break;
1259                }
1260                if(file_exists($tmp)){
1261                        $taille_test = getimagesize($tmp);
1262                        if ($taille_test[0] < 1) return false;
1263
1264                        @unlink($fichier); // le fichier peut deja exister
1265                        @rename($tmp, $fichier);
1266                        return $ret;
1267                }
1268                return false;
1269        }
1270
1271
1272        /**
1273         * Convert indexed colors image to true color image
1274         * available in PHP 5.5+ http://www.php.net/manual/fr/function.imagepalettetotruecolor.php
1275         * @param resource $img
1276         * @return bool
1277         */
1278        protected function imagepalettetotruecolor(&$img) {
1279                if (function_exists("imagepalettetotruecolor"))
1280                        return imagepalettetotruecolor($img);
1281
1282                if ($img AND !imageistruecolor($img) AND function_exists('imagecreatetruecolor')) {
1283                        $w = imagesx($img);
1284                        $h = imagesy($img);
1285                        $img1 = imagecreatetruecolor($w,$h);
1286                        // keep alpha layer if possible
1287                        if(function_exists('ImageCopyResampled')) {
1288                                if (function_exists("imageAntiAlias")) imageAntiAlias($img1,true);
1289                                @imagealphablending($img1, false);
1290                                @imagesavealpha($img1,true);
1291                                @ImageCopyResampled($img1, $img, 0, 0, 0, 0, $w, $h, $w, $h);
1292                        } else {
1293                                imagecopy($img1,$img,0,0,0,0,$w,$h);
1294                        }
1295
1296                        $img = $img1;
1297                        return true;
1298                }
1299                return false;
1300        }
1301
1302
1303        /**
1304         * Translate HTML color to hexa color
1305         * @param string $color
1306         * @return string
1307         */
1308        protected function colorHTML2Hex($color){
1309                static $html_colors=array(
1310                        'aqua'=>'00FFFF','black'=>'000000','blue'=>'0000FF','fuchsia'=>'FF00FF','gray'=>'808080','green'=>'008000','lime'=>'00FF00','maroon'=>'800000',
1311                        'navy'=>'000080','olive'=>'808000','purple'=>'800080','red'=>'FF0000','silver'=>'C0C0C0','teal'=>'008080','white'=>'FFFFFF','yellow'=>'FFFF00');
1312                if (isset($html_colors[$lc=strtolower($color)]))
1313                        return $html_colors[$lc];
1314                return $color;
1315        }
1316
1317        /**
1318         * Translate hexa color to RGB
1319         * @param string $color
1320         *   hexa color (#000000 à #FFFFFF).
1321         * @return array
1322         */
1323        protected function colorHEX2RGB($color) {
1324                $color = $this->colorHTML2Hex($color);
1325                $color = ltrim($color,"#");
1326                $retour["red"] = hexdec(substr($color, 0, 2));
1327                $retour["green"] = hexdec(substr($color, 2, 2));
1328                $retour["blue"] = hexdec(substr($color, 4, 2));
1329
1330                return $retour;
1331        }
1332
1333}
Note: See TracBrowser for help on using the repository browser.