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

Last change on this file since 104338 was 104338, checked in by spip.franck@…, 3 years ago

zone.spip est maintenant en https, donc j'ajoute le "s"

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