[SPIP][PLUGINS] v3.0-->v3.2
[lhc/web/www.git] / www / ecrire / inc / filtres_images_lib_mini.php
1 <?php
2
3 /* *************************************************************************\
4 * SPIP, Systeme de publication pour l'internet *
5 * *
6 * Copyright (c) 2001-2017 *
7 * Arnaud Martin, Antoine Pitrou, Philippe Riviere, Emmanuel Saint-James *
8 * *
9 * Ce programme est un logiciel libre distribue sous licence GNU/GPL. *
10 * Pour plus de details voir le fichier COPYING.txt ou l'aide en ligne. *
11 \***************************************************************************/
12
13 /**
14 * Ce fichier contient les fonctions utilisées
15 * par les fonctions-filtres de traitement d'image.
16 *
17 * @package SPIP\Core\Filtres\Images
18 */
19
20
21 if (!defined('_ECRIRE_INC_VERSION')) {
22 return;
23 }
24 include_spip('inc/filtres'); // par precaution
25 include_spip('inc/filtres_images_mini'); // par precaution
26
27 /**
28 * Transforme une couleur vectorielle R,G,B en hexa (par exemple pour usage css)
29 *
30 * @param int $red
31 * Valeur du rouge de 0 à 255.
32 * @param int $green
33 * Valeur du vert de 0 à 255.
34 * @param int $blue
35 * Valeur du bleu de 0 à 255.
36 * @return string
37 * Le code de la couleur en hexadécimal.
38 */
39 function _couleur_dec_to_hex($red, $green, $blue) {
40 $red = dechex($red);
41 $green = dechex($green);
42 $blue = dechex($blue);
43
44 if (strlen($red) == 1) {
45 $red = "0" . $red;
46 }
47 if (strlen($green) == 1) {
48 $green = "0" . $green;
49 }
50 if (strlen($blue) == 1) {
51 $blue = "0" . $blue;
52 }
53
54 return "$red$green$blue";
55 }
56
57 /**
58 * Transforme une couleur hexa en vectorielle R,G,B
59 *
60 * @param string $couleur
61 * Code couleur en hexa (#000000 à #FFFFFF).
62 * @return array
63 * Un tableau des 3 éléments : rouge, vert, bleu.
64 */
65 function _couleur_hex_to_dec($couleur) {
66 $couleur = couleur_html_to_hex($couleur);
67 $couleur = preg_replace(",^#,", "", $couleur);
68 $retour["red"] = hexdec(substr($couleur, 0, 2));
69 $retour["green"] = hexdec(substr($couleur, 2, 2));
70 $retour["blue"] = hexdec(substr($couleur, 4, 2));
71
72 return $retour;
73 }
74
75
76 /**
77 * Donne un statut au fichier-image intermédiaire servant au traitement d'image
78 * selon qu'il doit être gravé (fichier .src) ou pas.
79 *
80 * Un appel PHP direct aux fonctions de filtre d'image produira ainsi une image
81 * permanente (gravée) ; un appel généré par le compilateur via
82 * `filtrer('image_xx, ...)` effacera automatiquement le fichier-image temporaire.
83 *
84 * @param bool|string $stat
85 * true, false ou le statut déjà défini si traitements enchaînés.
86 * @return bool
87 * true si il faut supprimer le fichier temporaire ; false sinon.
88 */
89 function statut_effacer_images_temporaires($stat) {
90 static $statut = false; // par defaut on grave toute les images
91 if ($stat === 'get') {
92 return $statut;
93 }
94 $statut = $stat ? true : false;
95 }
96
97
98 /**
99 * Fonctions de traitement d'image
100 *
101 * Uniquement pour GD2.
102 *
103 * @pipeline_appel image_preparer_filtre
104 * @uses extraire_attribut()
105 * @uses inserer_attribut()
106 * @uses tester_url_absolue()
107 * @uses copie_locale() Si l'image est distante
108 * @uses taille_image()
109 * @uses _image_ratio()
110 * @uses reconstruire_image_intermediaire()
111 *
112 * @param string $img
113 * Chemin de l'image ou balise html `<img src=... />`.
114 * @param string $effet
115 * Les nom et paramètres de l'effet à apporter sur l'image
116 * (par exemple : reduire-300-200).
117 * @param bool|string $forcer_format
118 * Un nom d'extension spécifique demandé (par exemple : jpg, png, txt...).
119 * Par défaut false : GD se débrouille seule).
120 * @param array $fonction_creation
121 * Un tableau à 2 éléments :
122 * 1) string : indique le nom du filtre de traitement demandé (par exemple : `image_reduire`) ;
123 * 2) array : tableau reprenant la valeur de `$img` et chacun des arguments passés au filtre utilisé.
124 * @param bool $find_in_path
125 * false (par défaut) indique que l'on travaille sur un fichier
126 * temporaire (.src) ; true, sur un fichier définitif déjà existant.
127 * @return bool|string|array
128 *
129 * - false si pas de tag `<img`,
130 * - si l'extension n'existe pas,
131 * - si le fichier source n'existe pas,
132 * - si les dimensions de la source ne sont pas accessibles,
133 * - si le fichier temporaire n'existe pas,
134 * - si la fonction `_imagecreatefrom{extension}` n'existe pas ;
135 * - "" (chaîne vide) si le fichier source est distant et n'a pas
136 * réussi à être copié sur le serveur ;
137 * - array : tableau décrivant de l'image
138 */
139 function _image_valeurs_trans($img, $effet, $forcer_format = false, $fonction_creation = null, $find_in_path = false) {
140 static $images_recalcul = array();
141 if (strlen($img) == 0) {
142 return false;
143 }
144
145 $source = trim(extraire_attribut($img, 'src'));
146 if (strlen($source) < 1) {
147 $source = $img;
148 $img = "<img src='$source' />";
149 } # gerer img src="data:....base64"
150 elseif (preg_match('@^data:image/(jpe?g|png|gif);base64,(.*)$@isS', $source, $regs)) {
151 $local = sous_repertoire(_DIR_VAR, 'image-data') . md5($regs[2]) . '.' . str_replace('jpeg', 'jpg', $regs[1]);
152 if (!file_exists($local)) {
153 ecrire_fichier($local, base64_decode($regs[2]));
154 }
155 $source = $local;
156 $img = inserer_attribut($img, 'src', $source);
157 # eviter les mauvaises surprises lors de conversions de format
158 $img = inserer_attribut($img, 'width', '');
159 $img = inserer_attribut($img, 'height', '');
160 }
161
162 // les protocoles web prennent au moins 3 lettres
163 if (tester_url_absolue($source)) {
164 include_spip('inc/distant');
165 $fichier = _DIR_RACINE . copie_locale($source);
166 if (!$fichier) {
167 return "";
168 }
169 } else {
170 // enlever le timestamp eventuel
171 if (strpos($source, "?") !== false) {
172 $source = preg_replace(',[?][0-9]+$,', '', $source);
173 }
174 if (strpos($source, "?") !== false
175 and strncmp($source, _DIR_IMG, strlen(_DIR_IMG)) == 0
176 and file_exists($f = preg_replace(',[?].*$,', '', $source))
177 ) {
178 $source = $f;
179 }
180 $fichier = $source;
181 }
182
183 $terminaison = $terminaison_dest = "";
184 if (preg_match(",\.(gif|jpe?g|png)($|[?]),i", $fichier, $regs)) {
185 $terminaison = strtolower($regs[1]);
186 $terminaison_dest = $terminaison;
187
188 if ($terminaison == "gif") {
189 $terminaison_dest = "png";
190 }
191 }
192 if ($forcer_format !== false) {
193 $terminaison_dest = $forcer_format;
194 }
195
196 if (!$terminaison_dest) {
197 return false;
198 }
199
200 $term_fonction = $terminaison;
201 if ($term_fonction == "jpg") {
202 $term_fonction = "jpeg";
203 }
204
205 $nom_fichier = substr($fichier, 0, strlen($fichier) - (strlen($terminaison) + 1));
206 $fichier_dest = $nom_fichier;
207 if (($find_in_path and $f = find_in_path($fichier) and $fichier = $f)
208 or @file_exists($f = $fichier)
209 ) {
210 // on passe la balise img a taille image qui exraira les attributs si possible
211 // au lieu de faire un acces disque sur le fichier
212 list($ret["hauteur"], $ret["largeur"]) = taille_image($find_in_path ? $f : $img);
213 $date_src = @filemtime($f);
214 } elseif (@file_exists($f = "$fichier.src")
215 and lire_fichier($f, $valeurs)
216 and $valeurs = unserialize($valeurs)
217 and isset($valeurs["hauteur_dest"])
218 and isset($valeurs["largeur_dest"])
219 ) {
220 $ret["hauteur"] = $valeurs["hauteur_dest"];
221 $ret["largeur"] = $valeurs["largeur_dest"];
222 $date_src = $valeurs["date"];
223 } // pas de fichier source par la
224 else {
225 return false;
226 }
227
228 // pas de taille mesurable
229 if (!($ret["hauteur"] or $ret["largeur"])) {
230 return false;
231 }
232
233 // les images calculees dependent du chemin du fichier source
234 // pour une meme image source et un meme filtre on aboutira a 2 fichiers selon si l'appel est dans le public ou dans le prive
235 // ce n'est pas totalement optimal en terme de stockage, mais chaque image est associee a un fichier .src
236 // qui contient la methode de reconstrucion (le filtre + les arguments d'appel) et les arguments different entre prive et public
237 // la mise en commun du fichier image cree donc un bug et des problemes qui necessiteraient beaucoup de complexite de code
238 // alors que ca concerne peu de site au final
239 // la release de r23632+r23633+r23634 a provoque peu de remontee de bug attestant du peu de sites impactes
240 $identifiant = $fichier;
241
242 // cas general :
243 // on a un dossier cache commun et un nom de fichier qui varie avec l'effet
244 // cas particulier de reduire :
245 // un cache par dimension, et le nom de fichier est conserve, suffixe par la dimension aussi
246 $cache = "cache-gd2";
247 if (substr($effet, 0, 7) == 'reduire') {
248 list(, $maxWidth, $maxHeight) = explode('-', $effet);
249 list($destWidth, $destHeight) = _image_ratio($ret['largeur'], $ret['hauteur'], $maxWidth, $maxHeight);
250 $ret['largeur_dest'] = $destWidth;
251 $ret['hauteur_dest'] = $destHeight;
252 $effet = "L{$destWidth}xH$destHeight";
253 $cache = "cache-vignettes";
254 $fichier_dest = basename($fichier_dest);
255 if (($ret['largeur'] <= $maxWidth) && ($ret['hauteur'] <= $maxHeight)) {
256 // on garde la terminaison initiale car image simplement copiee
257 // et on postfixe son nom avec un md5 du path
258 $terminaison_dest = $terminaison;
259 $fichier_dest .= '-' . substr(md5("$identifiant"), 0, 5);
260 } else {
261 $fichier_dest .= '-' . substr(md5("$identifiant-$effet"), 0, 5);
262 }
263 $cache = sous_repertoire(_DIR_VAR, $cache);
264 $cache = sous_repertoire($cache, $effet);
265 # cherche un cache existant
266 /*foreach (array('gif','jpg','png') as $fmt)
267 if (@file_exists($cache . $fichier_dest . '.' . $fmt)) {
268 $terminaison_dest = $fmt;
269 }*/
270 } else {
271 $fichier_dest = md5("$identifiant-$effet");
272 $cache = sous_repertoire(_DIR_VAR, $cache);
273 $cache = sous_repertoire($cache, substr($fichier_dest, 0, 2));
274 $fichier_dest = substr($fichier_dest, 2);
275 }
276
277 $fichier_dest = $cache . $fichier_dest . "." . $terminaison_dest;
278
279 $GLOBALS["images_calculees"][] = $fichier_dest;
280
281 $creer = true;
282 // si recalcul des images demande, recalculer chaque image une fois
283 if (defined('_VAR_IMAGES') and _VAR_IMAGES and !isset($images_recalcul[$fichier_dest])) {
284 $images_recalcul[$fichier_dest] = true;
285 } else {
286 if (@file_exists($f = $fichier_dest)) {
287 if (filemtime($f) >= $date_src) {
288 $creer = false;
289 }
290 } else {
291 if (@file_exists($f = "$fichier_dest.src")
292 and lire_fichier($f, $valeurs)
293 and $valeurs = unserialize($valeurs)
294 and $valeurs["date"] >= $date_src
295 ) {
296 $creer = false;
297 }
298 }
299 }
300 if ($creer) {
301 if (!@file_exists($fichier)) {
302 if (!@file_exists("$fichier.src")) {
303 spip_log("Image absente : $fichier");
304
305 return false;
306 }
307 # on reconstruit l'image source absente a partir de la chaine des .src
308 reconstruire_image_intermediaire($fichier);
309 }
310 }
311
312 if ($creer) {
313 spip_log("filtre image " . ($fonction_creation ? reset($fonction_creation) : '') . "[$effet] sur $fichier",
314 "images" . _LOG_DEBUG);
315 }
316
317 // TODO: si une image png est nommee .jpg, le reconnaitre avec le bon $f
318 $ret["fonction_imagecreatefrom"] = "_imagecreatefrom" . $term_fonction;
319 $ret["fichier"] = $fichier;
320 $ret["fonction_image"] = "_image_image" . $terminaison_dest;
321 $ret["fichier_dest"] = $fichier_dest;
322 $ret["format_source"] = ($terminaison != 'jpeg' ? $terminaison : 'jpg');
323 $ret["format_dest"] = $terminaison_dest;
324 $ret["date_src"] = $date_src;
325 $ret["creer"] = $creer;
326 $ret["class"] = extraire_attribut($img, 'class');
327 $ret["alt"] = extraire_attribut($img, 'alt');
328 $ret["style"] = extraire_attribut($img, 'style');
329 $ret["tag"] = $img;
330 if ($fonction_creation) {
331 $ret["reconstruction"] = $fonction_creation;
332 # ecrire ici comment creer le fichier, car il est pas sur qu'on l'ecrira reelement
333 # cas de image_reduire qui finalement ne reduit pas l'image source
334 # ca evite d'essayer de le creer au prochain hit si il n'est pas la
335 #ecrire_fichier($ret['fichier_dest'].'.src',serialize($ret),true);
336 }
337
338 $ret = pipeline('image_preparer_filtre', array(
339 'args' => array(
340 'img' => $img,
341 'effet' => $effet,
342 'forcer_format' => $forcer_format,
343 'fonction_creation' => $fonction_creation,
344 'find_in_path' => $find_in_path,
345 ),
346 'data' => $ret
347 )
348 );
349
350 // une globale pour le debug en cas de crash memoire
351 $GLOBALS["derniere_image_calculee"] = $ret;
352
353 if (!function_exists($ret["fonction_imagecreatefrom"])) {
354 return false;
355 }
356
357 return $ret;
358 }
359
360 /**
361 * Crée une image depuis un fichier ou une URL
362 *
363 * Utilise les fonctions spécifiques GD.
364 *
365 * @param string $filename
366 * Le path vers l'image à traiter (par exemple : IMG/distant/jpg/image.jpg
367 * ou local/cache-vignettes/L180xH51/image.jpg).
368 * @return ressource
369 * Une ressource de type Image GD.
370 */
371 function _imagecreatefromjpeg($filename) {
372 $img = @imagecreatefromjpeg($filename);
373 if (!$img) {
374 spip_log("Erreur lecture imagecreatefromjpeg $filename", _LOG_CRITIQUE);
375 erreur_squelette("Erreur lecture imagecreatefromjpeg $filename");
376 $img = imagecreate(10, 10);
377 }
378
379 return $img;
380 }
381
382 /**
383 * Crée une image depuis un fichier ou une URL (au format png)
384 *
385 * Utilise les fonctions spécifiques GD.
386 *
387 * @param string $filename
388 * Le path vers l'image à traiter (par exemple : IMG/distant/png/image.png
389 * ou local/cache-vignettes/L180xH51/image.png).
390 * @return ressource
391 * Une ressource de type Image GD.
392 */
393 function _imagecreatefrompng($filename) {
394 $img = @imagecreatefrompng($filename);
395 if (!$img) {
396 spip_log("Erreur lecture imagecreatefrompng $filename", _LOG_CRITIQUE);
397 erreur_squelette("Erreur lecture imagecreatefrompng $filename");
398 $img = imagecreate(10, 10);
399 }
400
401 return $img;
402 }
403
404 /**
405 * Crée une image depuis un fichier ou une URL (au format gif)
406 *
407 * Utilise les fonctions spécifiques GD.
408 *
409 * @param string $filename
410 * Le path vers l'image à traiter (par exemple : IMG/distant/gif/image.gif
411 * ou local/cache-vignettes/L180xH51/image.gif).
412 * @return ressource
413 * Une ressource de type Image GD.
414 */
415 function _imagecreatefromgif($filename) {
416 $img = @imagecreatefromgif($filename);
417 if (!$img) {
418 spip_log("Erreur lecture imagecreatefromgif $filename", _LOG_CRITIQUE);
419 erreur_squelette("Erreur lecture imagecreatefromgif $filename");
420 $img = imagecreate(10, 10);
421 }
422
423 return $img;
424 }
425
426 /**
427 * Affiche ou sauvegarde une image au format PNG
428 *
429 * Utilise les fonctions spécifiques GD.
430 *
431 * @param ressource $img
432 * Une ressource de type Image GD.
433 * @param string $fichier
434 * Le path vers l'image (ex : local/cache-vignettes/L180xH51/image.png).
435 * @return bool
436 *
437 * - false si l'image créée a une largeur nulle ou n'existe pas ;
438 * - true si une image est bien retournée.
439 */
440 function _image_imagepng($img, $fichier) {
441 if (!function_exists('imagepng')) {
442 return false;
443 }
444 $tmp = $fichier . ".tmp";
445 $ret = imagepng($img, $tmp);
446 if (file_exists($tmp)) {
447 $taille_test = getimagesize($tmp);
448 if ($taille_test[0] < 1) {
449 return false;
450 }
451
452 spip_unlink($fichier); // le fichier peut deja exister
453 @rename($tmp, $fichier);
454
455 return $ret;
456 }
457
458 return false;
459 }
460
461 /**
462 * Affiche ou sauvegarde une image au format GIF
463 *
464 * Utilise les fonctions spécifiques GD.
465 *
466 * @param ressource $img
467 * Une ressource de type Image GD.
468 * @param string $fichier
469 * Le path vers l'image (ex : local/cache-vignettes/L180xH51/image.gif).
470 * @return bool
471 *
472 * - false si l'image créée a une largeur nulle ou n'existe pas ;
473 * - true si une image est bien retournée.
474 */
475 function _image_imagegif($img, $fichier) {
476 if (!function_exists('imagegif')) {
477 return false;
478 }
479 $tmp = $fichier . ".tmp";
480 $ret = imagegif($img, $tmp);
481 if (file_exists($tmp)) {
482 $taille_test = getimagesize($tmp);
483 if ($taille_test[0] < 1) {
484 return false;
485 }
486
487 spip_unlink($fichier); // le fichier peut deja exister
488 @rename($tmp, $fichier);
489
490 return $ret;
491 }
492
493 return false;
494 }
495
496 /**
497 * Affiche ou sauvegarde une image au format JPG
498 *
499 * Utilise les fonctions spécifiques GD.
500 *
501 * @param ressource $img
502 * Une ressource de type Image GD.
503 * @param string $fichier
504 * Le path vers l'image (ex : local/cache-vignettes/L180xH51/image.jpg).
505 * @param int $qualite
506 * Le niveau de qualité du fichier résultant : de 0 (pire qualité, petit
507 * fichier) à 100 (meilleure qualité, gros fichier). Par défaut, prend la
508 * valeur (85) de la constante _IMG_GD_QUALITE (modifiable depuis
509 * mes_options.php).
510 * @return bool
511 *
512 * - false si l'image créée a une largeur nulle ou n'existe pas ;
513 * - true si une image est bien retournée.
514 */
515 function _image_imagejpg($img, $fichier, $qualite = _IMG_GD_QUALITE) {
516 if (!function_exists('imagejpeg')) {
517 return false;
518 }
519 $tmp = $fichier . ".tmp";
520 $ret = imagejpeg($img, $tmp, $qualite);
521
522 if (file_exists($tmp)) {
523 $taille_test = getimagesize($tmp);
524 if ($taille_test[0] < 1) {
525 return false;
526 }
527
528 spip_unlink($fichier); // le fichier peut deja exister
529 @rename($tmp, $fichier);
530
531 return $ret;
532 }
533
534 return false;
535 }
536
537 /**
538 * Crée un fichier-image au format ICO
539 *
540 * Utilise les fonctions de la classe phpthumb_functions.
541 *
542 * @uses phpthumb_functions::GD2ICOstring()
543 *
544 * @param ressource $img
545 * Une ressource de type Image GD.
546 * @param string $fichier
547 * Le path vers l'image (ex : local/cache-vignettes/L180xH51/image.jpg).
548 * @return bool
549 * true si le fichier a bien été créé ; false sinon.
550 */
551 function _image_imageico($img, $fichier) {
552 $gd_image_array = array($img);
553
554 return ecrire_fichier($fichier, phpthumb_functions::GD2ICOstring($gd_image_array));
555 }
556
557 /**
558 * Finalise le traitement GD
559 *
560 * Crée un fichier_image temporaire .src ou vérifie que le fichier_image
561 * définitif a bien été créé.
562 *
563 * @uses statut_effacer_images_temporaires()
564 *
565 * @param ressource $img
566 * Une ressource de type Image GD.
567 * @param array $valeurs
568 * Un tableau des informations (tailles, traitement, path...) accompagnant
569 * l'image.
570 * @param int $qualite
571 * N'est utilisé que pour les images jpg.
572 * Le niveau de qualité du fichier résultant : de 0 (pire qualité, petit
573 * fichier) à 100 (meilleure qualité, gros fichier). Par défaut, prend la
574 * valeur (85) de la constante _IMG_GD_QUALITE (modifiable depuis
575 * mes_options.php).
576 * @return bool
577 * - true si le traitement GD s'est bien finalisé ;
578 * - false sinon.
579 */
580 function _image_gd_output($img, $valeurs, $qualite = _IMG_GD_QUALITE) {
581 $fonction = "_image_image" . $valeurs['format_dest'];
582 $ret = false;
583 #un flag pour reperer les images gravees
584 $lock =
585 !statut_effacer_images_temporaires('get') // si la fonction n'a pas ete activee, on grave tout
586 or (@file_exists($valeurs['fichier_dest']) and !@file_exists($valeurs['fichier_dest'] . '.src'));
587 if (
588 function_exists($fonction)
589 && ($ret = $fonction($img, $valeurs['fichier_dest'], $qualite)) # on a reussi a creer l'image
590 && isset($valeurs['reconstruction']) # et on sait comment la resonctruire le cas echeant
591 && !$lock
592 ) {
593 if (@file_exists($valeurs['fichier_dest'])) {
594 // dans tous les cas mettre a jour la taille de l'image finale
595 list($valeurs["hauteur_dest"], $valeurs["largeur_dest"]) = taille_image($valeurs['fichier_dest']);
596 $valeurs['date'] = @filemtime($valeurs['fichier_dest']); // pour la retrouver apres disparition
597 ecrire_fichier($valeurs['fichier_dest'] . '.src', serialize($valeurs), true);
598 }
599 }
600
601 return $ret;
602 }
603
604 /**
605 * Reconstruit une image à partir des sources de contrôle de son ancienne
606 * construction
607 *
608 * @uses ramasse_miettes()
609 *
610 * @param string $fichier_manquant
611 * Chemin vers le fichier manquant
612 **/
613 function reconstruire_image_intermediaire($fichier_manquant) {
614 $reconstruire = array();
615 $fichier = $fichier_manquant;
616 while (strpos($fichier,"://")===false
617 and !@file_exists($fichier)
618 and lire_fichier($src = "$fichier.src", $source)
619 and $valeurs = unserialize($source)
620 and ($fichier = $valeurs['fichier']) # l'origine est connue (on ne verifie pas son existence, qu'importe ...)
621 ) {
622 spip_unlink($src); // si jamais on a un timeout pendant la reconstruction, elle se fera naturellement au hit suivant
623 $reconstruire[] = $valeurs['reconstruction'];
624 }
625 while (count($reconstruire)) {
626 $r = array_pop($reconstruire);
627 $fonction = $r[0];
628 $args = $r[1];
629 call_user_func_array($fonction, $args);
630 }
631 // cette image intermediaire est commune a plusieurs series de filtre, il faut la conserver
632 // mais l'on peut nettoyer les miettes de sa creation
633 ramasse_miettes($fichier_manquant);
634 }
635
636 /**
637 * Indique qu'un fichier d'image calculé est à conserver
638 *
639 * Permet de rendre une image définitive et de supprimer les images
640 * intermédiaires à son calcul.
641 *
642 * Supprime le fichier de contrôle de l’image cible (le $fichier.src)
643 * ce qui indique que l'image est définitive.
644 *
645 * Remonte ensuite la chaîne des fichiers de contrôle pour supprimer
646 * les images temporaires (mais laisse les fichiers de contrôle permettant
647 * de les reconstruire).
648 *
649 * @param string $fichier
650 * Chemin du fichier d'image calculé
651 **/
652 function ramasse_miettes($fichier) {
653 if (strpos($fichier,"://")!==false
654 or !lire_fichier($src = "$fichier.src", $source)
655 or !$valeurs = unserialize($source)
656 ) {
657 return;
658 }
659 spip_unlink($src); # on supprime la reference a sa source pour marquer cette image comme non intermediaire
660 while (
661 ($fichier = $valeurs['fichier']) # l'origine est connue (on ne verifie pas son existence, qu'importe ...)
662 and (substr($fichier, 0, strlen(_DIR_VAR)) == _DIR_VAR) # et est dans local
663 and (lire_fichier($src = "$fichier.src",
664 $source)) # le fichier a une source connue (c'est donc une image calculee intermediaire)
665 and ($valeurs = unserialize($source)) # et valide
666 ) {
667 # on efface le fichier
668 spip_unlink($fichier);
669 # mais laisse le .src qui permet de savoir comment reconstruire l'image si besoin
670 #spip_unlink($src);
671 }
672 }
673
674
675 /**
676 * Clôture une série de filtres d'images
677 *
678 * Ce filtre est automatiquement appelé à la fin d'une série de filtres
679 * d'images dans un squelette.
680 *
681 * @filtre
682 * @uses reconstruire_image_intermediaire()
683 * Si l'image finale a déjà été supprimée car considérée comme temporaire
684 * par une autre série de filtres images débutant pareil
685 * @uses ramasse_miettes()
686 * Pour déclarer l'image définitive et nettoyer les images intermédiaires.
687 *
688 * @pipeline_appel post_image_filtrer
689 *
690 * @param string $img
691 * Code HTML de l'image
692 * @return string
693 * Code HTML de l'image
694 **/
695 function image_graver($img) {
696 // appeler le filtre post_image_filtrer qui permet de faire
697 // des traitements auto a la fin d'une serie de filtres
698 $img = pipeline('post_image_filtrer', $img);
699
700 $fichier_ori = $fichier = extraire_attribut($img, 'src');
701 if (($p = strpos($fichier, '?')) !== false) {
702 $fichier = substr($fichier, 0, $p);
703 }
704 if (strlen($fichier) < 1) {
705 $fichier = $img;
706 }
707 # si jamais le fichier final n'a pas ete calcule car suppose temporaire
708 # et qu'il ne s'agit pas d'une URL
709 if (strpos($fichier,"://")===false and !@file_exists($fichier)) {
710 reconstruire_image_intermediaire($fichier);
711 }
712 ramasse_miettes($fichier);
713
714 // ajouter le timestamp si besoin
715 if (strpos($fichier_ori, "?") === false) {
716 // on utilise str_replace pour attraper le onmouseover des logo si besoin
717 $img = str_replace($fichier_ori, timestamp($fichier_ori), $img);
718 }
719
720 return $img;
721 }
722
723
724 if (!function_exists("imagepalettetotruecolor")) {
725 /**
726 * Transforme une image à palette indexée (256 couleurs max) en "vraies" couleurs RGB
727 *
728 * @note Pour compatibilité avec PHP < 5.5
729 *
730 * @link http://php.net/manual/fr/function.imagepalettetotruecolor.php
731 *
732 * @param ressource $img
733 * @return bool
734 * - true si l'image est déjà en vrai RGB ou peut être transformée
735 * - false si la transformation ne peut être faite.
736 **/
737 function imagepalettetotruecolor(&$img) {
738 if (!$img or !function_exists('imagecreatetruecolor')) {
739 return false;
740 } elseif (!imageistruecolor($img)) {
741 $w = imagesx($img);
742 $h = imagesy($img);
743 $img1 = imagecreatetruecolor($w, $h);
744 //Conserver la transparence si possible
745 if (function_exists('ImageCopyResampled')) {
746 if (function_exists("imageAntiAlias")) {
747 imageAntiAlias($img1, true);
748 }
749 @imagealphablending($img1, false);
750 @imagesavealpha($img1, true);
751 @ImageCopyResampled($img1, $img, 0, 0, 0, 0, $w, $h, $w, $h);
752 } else {
753 imagecopy($img1, $img, 0, 0, 0, 0, $w, $h);
754 }
755
756 $img = $img1;
757 }
758
759 return true;
760 }
761 }
762
763 /**
764 * Applique des attributs de taille (width, height) à une balise HTML
765 *
766 * Utilisé avec des balises `<img>` tout particulièrement.
767 *
768 * Modifie l'attribut style s'il était renseigné, en enlevant les
769 * informations éventuelles width / height dedans.
770 *
771 * @uses extraire_attribut()
772 * @uses inserer_attribut()
773 *
774 * @param string $tag
775 * Code html de la balise
776 * @param int $width
777 * Hauteur
778 * @param int $height
779 * Largeur
780 * @param bool|string $style
781 * Attribut html style à appliquer.
782 * False extrait celui présent dans la balise
783 * @return string
784 * Code html modifié de la balise.
785 **/
786 function _image_tag_changer_taille($tag, $width, $height, $style = false) {
787 if ($style === false) {
788 $style = extraire_attribut($tag, 'style');
789 }
790
791 // enlever le width et height du style
792 $style = preg_replace(",(^|;)\s*(width|height)\s*:\s*[^;]+,ims", "", $style);
793 if ($style and $style{0} == ';') {
794 $style = substr($style, 1);
795 }
796
797 // mettre des attributs de width et height sur les images,
798 // ca accelere le rendu du navigateur
799 // ca permet aux navigateurs de reserver la bonne taille
800 // quand on a desactive l'affichage des images.
801 $tag = inserer_attribut($tag, 'width', $width);
802 $tag = inserer_attribut($tag, 'height', $height);
803
804 // attributs deprecies. Transformer en CSS
805 if ($espace = extraire_attribut($tag, 'hspace')) {
806 $style = "margin:${espace}px;" . $style;
807 $tag = inserer_attribut($tag, 'hspace', '');
808 }
809
810 $tag = inserer_attribut($tag, 'style', $style, true, $style ? false : true);
811
812 return $tag;
813 }
814
815
816 /**
817 * Écriture de la balise img en sortie de filtre image
818 *
819 * Reprend le tag initial et surcharge les attributs modifiés
820 *
821 * @pipeline_appel image_ecrire_tag_preparer
822 * @pipeline_appel image_ecrire_tag_finir
823 *
824 * @uses _image_tag_changer_taille()
825 * @uses extraire_attribut()
826 * @uses inserer_attribut()
827 * @see _image_valeurs_trans()
828 *
829 * @param array $valeurs
830 * Description de l'image tel que retourné par `_image_valeurs_trans()`
831 * @param array $surcharge
832 * Permet de surcharger certaines descriptions présentes dans `$valeurs`
833 * tel que 'style', 'width', 'height'
834 * @return string
835 * Retourne le code HTML de l'image
836 **/
837 function _image_ecrire_tag($valeurs, $surcharge = array()) {
838 $valeurs = pipeline('image_ecrire_tag_preparer', $valeurs);
839
840 // fermer les tags img pas bien fermes;
841 $tag = str_replace(">", "/>", str_replace("/>", ">", $valeurs['tag']));
842
843 // le style
844 $style = $valeurs['style'];
845 if (isset($surcharge['style'])) {
846 $style = $surcharge['style'];
847 unset($surcharge['style']);
848 }
849
850 // traiter specifiquement la largeur et la hauteur
851 $width = $valeurs['largeur'];
852 if (isset($surcharge['width'])) {
853 $width = $surcharge['width'];
854 unset($surcharge['width']);
855 }
856 $height = $valeurs['hauteur'];
857 if (isset($surcharge['height'])) {
858 $height = $surcharge['height'];
859 unset($surcharge['height']);
860 }
861
862 $tag = _image_tag_changer_taille($tag, $width, $height, $style);
863 // traiter specifiquement le src qui peut etre repris dans un onmouseout
864 // on remplace toute les ref a src dans le tag
865 $src = extraire_attribut($tag, 'src');
866 if (isset($surcharge['src'])) {
867 $tag = str_replace($src, $surcharge['src'], $tag);
868 // si il y a des & dans src, alors ils peuvent provenir d'un &amp
869 // pas garanti comme methode, mais mieux que rien
870 if (strpos($src, '&') !== false) {
871 $tag = str_replace(str_replace("&", "&amp;", $src), $surcharge['src'], $tag);
872 }
873 $src = $surcharge['src'];
874 unset($surcharge['src']);
875 }
876
877 $class = $valeurs['class'];
878 if (isset($surcharge['class'])) {
879 $class = $surcharge['class'];
880 unset($surcharge['class']);
881 }
882 if (strlen($class)) {
883 $tag = inserer_attribut($tag, 'class', $class);
884 }
885
886 if (count($surcharge)) {
887 foreach ($surcharge as $attribut => $valeur) {
888 $tag = inserer_attribut($tag, $attribut, $valeur);
889 }
890 }
891
892 $tag = pipeline('image_ecrire_tag_finir',
893 array(
894 'args' => array(
895 'valeurs' => $valeurs,
896 'surcharge' => $surcharge,
897 ),
898 'data' => $tag
899 )
900 );
901
902 return $tag;
903 }
904
905 /**
906 * Crée si possible une miniature d'une image
907 *
908 * @see _image_valeurs_trans()
909 * @uses _image_ratio()
910 *
911 * @param array $valeurs
912 * Description de l'image, telle que retournée par `_image_valeurs_trans()`
913 * @param int $maxWidth
914 * Largeur maximum en px de la miniature à réaliser
915 * @param int $maxHeight
916 * Hauteur maximum en px de la miniateure à réaliser
917 * @param string $process
918 * Librairie graphique à utiliser (gd1, gd2, netpbm, convert, imagick).
919 * AUTO utilise la librairie sélectionnée dans la configuration.
920 * @param bool $force
921 * @return array|null
922 * Description de l'image, sinon null.
923 **/
924 function _image_creer_vignette($valeurs, $maxWidth, $maxHeight, $process = 'AUTO', $force = false) {
925 // ordre de preference des formats graphiques pour creer les vignettes
926 // le premier format disponible, selon la methode demandee, est utilise
927 $image = $valeurs['fichier'];
928 $format = $valeurs['format_source'];
929 $destdir = dirname($valeurs['fichier_dest']);
930 $destfile = basename($valeurs['fichier_dest'], "." . $valeurs["format_dest"]);
931
932 $format_sortie = $valeurs['format_dest'];
933
934 if (($process == 'AUTO') and isset($GLOBALS['meta']['image_process'])) {
935 $process = $GLOBALS['meta']['image_process'];
936 }
937
938 // liste des formats qu'on sait lire
939 $img = isset($GLOBALS['meta']['formats_graphiques'])
940 ? (strpos($GLOBALS['meta']['formats_graphiques'], $format) !== false)
941 : false;
942
943 // si le doc n'est pas une image, refuser
944 if (!$force and !$img) {
945 return;
946 }
947 $destination = "$destdir/$destfile";
948
949 // calculer la taille
950 if (($srcWidth = $valeurs['largeur']) && ($srcHeight = $valeurs['hauteur'])) {
951 if (!($destWidth = $valeurs['largeur_dest']) || !($destHeight = $valeurs['hauteur_dest'])) {
952 list($destWidth, $destHeight) = _image_ratio($valeurs['largeur'], $valeurs['hauteur'], $maxWidth, $maxHeight);
953 }
954 } elseif ($process == 'convert' or $process == 'imagick') {
955 $destWidth = $maxWidth;
956 $destHeight = $maxHeight;
957 } else {
958 spip_log("echec $process sur $image");
959
960 return;
961 }
962
963 // Si l'image est de la taille demandee (ou plus petite), simplement la retourner
964 if ($srcWidth and $srcWidth <= $maxWidth and $srcHeight <= $maxHeight) {
965 $vignette = $destination . '.' . $format;
966 @copy($image, $vignette);
967 } // imagemagick en ligne de commande
968 elseif ($process == 'convert') {
969 if (!defined('_CONVERT_COMMAND')) {
970 define('_CONVERT_COMMAND', 'convert');
971 } // Securite : mes_options.php peut preciser le chemin absolu
972 if (!defined('_RESIZE_COMMAND')) {
973 define('_RESIZE_COMMAND', _CONVERT_COMMAND . ' -quality ' . _IMG_CONVERT_QUALITE . ' -resize %xx%y! %src %dest');
974 }
975 $vignette = $destination . "." . $format_sortie;
976 $commande = str_replace(
977 array('%x', '%y', '%src', '%dest'),
978 array(
979 $destWidth,
980 $destHeight,
981 escapeshellcmd($image),
982 escapeshellcmd($vignette)
983 ),
984 _RESIZE_COMMAND);
985 spip_log($commande);
986 exec($commande);
987 if (!@file_exists($vignette)) {
988 spip_log("echec convert sur $vignette");
989
990 return; // echec commande
991 }
992 } // php5 imagemagick
993 elseif ($process == 'imagick') {
994 $vignette = "$destination." . $format_sortie;
995
996 if (!class_exists('Imagick')) {
997 spip_log("Classe Imagick absente !", _LOG_ERREUR);
998
999 return;
1000 }
1001 $imagick = new Imagick();
1002 $imagick->readImage($image);
1003 $imagick->resizeImage($destWidth, $destHeight, Imagick::FILTER_LANCZOS,
1004 1);//, IMAGICK_FILTER_LANCZOS, _IMG_IMAGICK_QUALITE / 100);
1005 $imagick->writeImage($vignette);
1006
1007 if (!@file_exists($vignette)) {
1008 spip_log("echec imagick sur $vignette");
1009
1010 return;
1011 }
1012 } // netpbm
1013 elseif ($process == "netpbm") {
1014 if (!defined('_PNMSCALE_COMMAND')) {
1015 define('_PNMSCALE_COMMAND', 'pnmscale');
1016 } // Securite : mes_options.php peut preciser le chemin absolu
1017 if (_PNMSCALE_COMMAND == '') {
1018 return;
1019 }
1020 $vignette = $destination . "." . $format_sortie;
1021 $pnmtojpeg_command = str_replace("pnmscale", "pnmtojpeg", _PNMSCALE_COMMAND);
1022 if ($format == "jpg") {
1023
1024 $jpegtopnm_command = str_replace("pnmscale", "jpegtopnm", _PNMSCALE_COMMAND);
1025 exec("$jpegtopnm_command $image | " . _PNMSCALE_COMMAND . " -width $destWidth | $pnmtojpeg_command > $vignette");
1026 if (!($s = @filesize($vignette))) {
1027 spip_unlink($vignette);
1028 }
1029 if (!@file_exists($vignette)) {
1030 spip_log("echec netpbm-jpg sur $vignette");
1031
1032 return;
1033 }
1034 } else {
1035 if ($format == "gif") {
1036 $giftopnm_command = str_replace("pnmscale", "giftopnm", _PNMSCALE_COMMAND);
1037 exec("$giftopnm_command $image | " . _PNMSCALE_COMMAND . " -width $destWidth | $pnmtojpeg_command > $vignette");
1038 if (!($s = @filesize($vignette))) {
1039 spip_unlink($vignette);
1040 }
1041 if (!@file_exists($vignette)) {
1042 spip_log("echec netpbm-gif sur $vignette");
1043
1044 return;
1045 }
1046 } else {
1047 if ($format == "png") {
1048 $pngtopnm_command = str_replace("pnmscale", "pngtopnm", _PNMSCALE_COMMAND);
1049 exec("$pngtopnm_command $image | " . _PNMSCALE_COMMAND . " -width $destWidth | $pnmtojpeg_command > $vignette");
1050 if (!($s = @filesize($vignette))) {
1051 spip_unlink($vignette);
1052 }
1053 if (!@file_exists($vignette)) {
1054 spip_log("echec netpbm-png sur $vignette");
1055
1056 return;
1057 }
1058 }
1059 }
1060 }
1061 } // gd ou gd2
1062 elseif ($process == 'gd1' or $process == 'gd2') {
1063 if (!function_exists('gd_info')) {
1064 spip_log("Librairie GD absente !", _LOG_ERREUR);
1065
1066 return;
1067 }
1068 if (_IMG_GD_MAX_PIXELS && $srcWidth * $srcHeight > _IMG_GD_MAX_PIXELS) {
1069 spip_log("vignette gd1/gd2 impossible : " . $srcWidth * $srcHeight . "pixels");
1070
1071 return;
1072 }
1073 $destFormat = $format_sortie;
1074 if (!$destFormat) {
1075 spip_log("pas de format pour $image");
1076
1077 return;
1078 }
1079
1080 $fonction_imagecreatefrom = $valeurs['fonction_imagecreatefrom'];
1081 if (!function_exists($fonction_imagecreatefrom)) {
1082 return '';
1083 }
1084 $srcImage = @$fonction_imagecreatefrom($image);
1085 if (!$srcImage) {
1086 spip_log("echec gd1/gd2");
1087
1088 return;
1089 }
1090
1091 // Initialisation de l'image destination
1092 $destImage = null;
1093 if ($process == 'gd2' and $destFormat != "gif") {
1094 $destImage = ImageCreateTrueColor($destWidth, $destHeight);
1095 }
1096 if (!$destImage) {
1097 $destImage = ImageCreate($destWidth, $destHeight);
1098 }
1099
1100 // Recopie de l'image d'origine avec adaptation de la taille
1101 $ok = false;
1102 if (($process == 'gd2') and function_exists('ImageCopyResampled')) {
1103 if ($format == "gif") {
1104 // Si un GIF est transparent,
1105 // fabriquer un PNG transparent
1106 $transp = imagecolortransparent($srcImage);
1107 if ($transp > 0) {
1108 $destFormat = "png";
1109 }
1110 }
1111 if ($destFormat == "png") {
1112 // Conserver la transparence
1113 if (function_exists("imageAntiAlias")) {
1114 imageAntiAlias($destImage, true);
1115 }
1116 @imagealphablending($destImage, false);
1117 @imagesavealpha($destImage, true);
1118 }
1119 $ok = @ImageCopyResampled($destImage, $srcImage, 0, 0, 0, 0, $destWidth, $destHeight, $srcWidth, $srcHeight);
1120 }
1121 if (!$ok) {
1122 $ok = ImageCopyResized($destImage, $srcImage, 0, 0, 0, 0, $destWidth, $destHeight, $srcWidth, $srcHeight);
1123 }
1124
1125 // Sauvegarde de l'image destination
1126 $valeurs['fichier_dest'] = $vignette = "$destination.$destFormat";
1127 $valeurs['format_dest'] = $format = $destFormat;
1128 _image_gd_output($destImage, $valeurs);
1129
1130 if ($srcImage) {
1131 ImageDestroy($srcImage);
1132 }
1133 ImageDestroy($destImage);
1134 }
1135
1136 $size = @getimagesize($vignette);
1137 // Gaffe: en safe mode, pas d'acces a la vignette,
1138 // donc risque de balancer "width='0'", ce qui masque l'image sous MSIE
1139 if ($size[0] < 1) {
1140 $size[0] = $destWidth;
1141 }
1142 if ($size[1] < 1) {
1143 $size[1] = $destHeight;
1144 }
1145
1146 $retour['width'] = $largeur = $size[0];
1147 $retour['height'] = $hauteur = $size[1];
1148
1149 $retour['fichier'] = $vignette;
1150 $retour['format'] = $format;
1151 $retour['date'] = @filemtime($vignette);
1152
1153 // renvoyer l'image
1154 return $retour;
1155 }
1156
1157 /**
1158 * Réduire des dimensions en respectant un ratio
1159 *
1160 * Réduit des dimensions (hauteur, largeur) pour qu'elles
1161 * soient incluses dans une hauteur et largeur maximum fournies
1162 * en respectant la proportion d'origine
1163 *
1164 * @example `image_ratio(1000, 1000, 100, 10)` donne `array(10, 10, 100)`
1165 * @see ratio_passe_partout() Assez proche.
1166 *
1167 * @param int $srcWidth Largeur de l'image source
1168 * @param int $srcHeight Hauteur de l'image source
1169 * @param int $maxWidth Largeur maximum souhaitée
1170 * @param int $maxHeight Hauteur maximum souhaitée
1171 * @return array Liste [ largeur, hauteur, ratio de réduction ]
1172 **/
1173 function _image_ratio($srcWidth, $srcHeight, $maxWidth, $maxHeight) {
1174 $ratioWidth = $srcWidth / $maxWidth;
1175 $ratioHeight = $srcHeight / $maxHeight;
1176
1177 if ($ratioWidth <= 1 and $ratioHeight <= 1) {
1178 $destWidth = $srcWidth;
1179 $destHeight = $srcHeight;
1180 } elseif ($ratioWidth < $ratioHeight) {
1181 $destWidth = $srcWidth / $ratioHeight;
1182 $destHeight = $maxHeight;
1183 } else {
1184 $destWidth = $maxWidth;
1185 $destHeight = $srcHeight / $ratioWidth;
1186 }
1187
1188 return array(
1189 ceil($destWidth),
1190 ceil($destHeight),
1191 max($ratioWidth, $ratioHeight)
1192 );
1193 }
1194
1195 /**
1196 * Réduire des dimensions en respectant un ratio sur la plus petite dimension
1197 *
1198 * Réduit des dimensions (hauteur, largeur) pour qu'elles
1199 * soient incluses dans la plus grande hauteur ou largeur maximum fournie
1200 * en respectant la proportion d'origine
1201 *
1202 * @example `ratio_passe_partout(1000, 1000, 100, 10)` donne `array(100, 100, 10)`
1203 * @see _image_ratio() Assez proche.
1204 *
1205 * @param int $srcWidth Largeur de l'image source
1206 * @param int $srcHeight Hauteur de l'image source
1207 * @param int $maxWidth Largeur maximum souhaitée
1208 * @param int $maxHeight Hauteur maximum souhaitée
1209 * @return array Liste [ largeur, hauteur, ratio de réduction ]
1210 **/
1211 function ratio_passe_partout($srcWidth, $srcHeight, $maxWidth, $maxHeight) {
1212 $ratioWidth = $srcWidth / $maxWidth;
1213 $ratioHeight = $srcHeight / $maxHeight;
1214
1215 if ($ratioWidth <= 1 and $ratioHeight <= 1) {
1216 $destWidth = $srcWidth;
1217 $destHeight = $srcHeight;
1218 } elseif ($ratioWidth > $ratioHeight) {
1219 $destWidth = $srcWidth / $ratioHeight;
1220 $destHeight = $maxHeight;
1221 } else {
1222 $destWidth = $maxWidth;
1223 $destHeight = $srcHeight / $ratioWidth;
1224 }
1225
1226 return array(
1227 ceil($destWidth),
1228 ceil($destHeight),
1229 min($ratioWidth, $ratioHeight)
1230 );
1231 }
1232
1233
1234 /**
1235 * Réduit une image
1236 *
1237 * @uses extraire_attribut()
1238 * @uses inserer_attribut()
1239 * @uses _image_valeurs_trans()
1240 * @uses _image_ratio()
1241 * @uses _image_tag_changer_taille()
1242 * @uses _image_ecrire_tag()
1243 * @uses _image_creer_vignette()
1244 *
1245 * @param array $fonction
1246 * Un tableau à 2 éléments :
1247 * 1) string : indique le nom du filtre de traitement demandé (par exemple : `image_reduire`) ;
1248 * 2) array : tableau reprenant la valeur de `$img` et chacun des arguments passés au filtre utilisé.
1249 * @param string $img
1250 * Chemin de l'image ou texte contenant une balise img
1251 * @param int $taille
1252 * Largeur désirée
1253 * @param int $taille_y
1254 * Hauteur désirée
1255 * @param bool $force
1256 * @param string $process
1257 * Librairie graphique à utiliser (gd1, gd2, netpbm, convert, imagick).
1258 * AUTO utilise la librairie sélectionnée dans la configuration.
1259 * @return string
1260 * Code HTML de la balise img produite
1261 **/
1262 function process_image_reduire($fonction, $img, $taille, $taille_y, $force, $process = 'AUTO') {
1263 $image = false;
1264 if (($process == 'AUTO') and isset($GLOBALS['meta']['image_process'])) {
1265 $process = $GLOBALS['meta']['image_process'];
1266 }
1267 # determiner le format de sortie
1268 $format_sortie = false; // le choix par defaut sera bon
1269 if ($process == "netpbm") {
1270 $format_sortie = "jpg";
1271 } elseif ($process == 'gd1' or $process == 'gd2') {
1272 $image = _image_valeurs_trans($img, "reduire-{$taille}-{$taille_y}", $format_sortie, $fonction);
1273
1274 // on verifie que l'extension choisie est bonne (en principe oui)
1275 $gd_formats = explode(',', $GLOBALS['meta']["gd_formats"]);
1276 if (is_array($image)
1277 and (!in_array($image['format_dest'], $gd_formats)
1278 or ($image['format_dest'] == 'gif' and !function_exists('ImageGif'))
1279 )
1280 ) {
1281 if ($image['format_source'] == 'jpg') {
1282 $formats_sortie = array('jpg', 'png', 'gif');
1283 } else // les gif sont passes en png preferentiellement pour etre homogene aux autres filtres images
1284 {
1285 $formats_sortie = array('png', 'jpg', 'gif');
1286 }
1287 // Choisir le format destination
1288 // - on sauve de preference en JPEG (meilleure compression)
1289 // - pour le GIF : les GD recentes peuvent le lire mais pas l'ecrire
1290 # bug : gd_formats contient la liste des fichiers qu'on sait *lire*,
1291 # pas *ecrire*
1292 $format_sortie = "";
1293 foreach ($formats_sortie as $fmt) {
1294 if (in_array($fmt, $gd_formats)) {
1295 if ($fmt <> "gif" or function_exists('ImageGif')) {
1296 $format_sortie = $fmt;
1297 }
1298 break;
1299 }
1300 }
1301 $image = false;
1302 }
1303 }
1304
1305 if (!is_array($image)) {
1306 $image = _image_valeurs_trans($img, "reduire-{$taille}-{$taille_y}", $format_sortie, $fonction);
1307 }
1308
1309 if (!is_array($image) or !$image['largeur'] or !$image['hauteur']) {
1310 spip_log("image_reduire_src:pas de version locale de $img");
1311 // on peut resizer en mode html si on dispose des elements
1312 if ($srcw = extraire_attribut($img, 'width')
1313 and $srch = extraire_attribut($img, 'height')
1314 ) {
1315 list($w, $h) = _image_ratio($srcw, $srch, $taille, $taille_y);
1316
1317 return _image_tag_changer_taille($img, $w, $h);
1318 }
1319 // la on n'a pas d'infos sur l'image source... on refile le truc a css
1320 // sous la forme style='max-width: NNpx;'
1321 return inserer_attribut($img, 'style',
1322 "max-width: ${taille}px; max-height: ${taille_y}px");
1323 }
1324
1325 // si l'image est plus petite que la cible retourner une copie cachee de l'image
1326 if (($image['largeur'] <= $taille) && ($image['hauteur'] <= $taille_y)) {
1327 if ($image['creer']) {
1328 @copy($image['fichier'], $image['fichier_dest']);
1329 }
1330
1331 return _image_ecrire_tag($image, array('src' => $image['fichier_dest']));
1332 }
1333
1334 if ($image['creer'] == false && !$force) {
1335 return _image_ecrire_tag($image,
1336 array('src' => $image['fichier_dest'], 'width' => $image['largeur_dest'], 'height' => $image['hauteur_dest']));
1337 }
1338
1339 if (in_array($image["format_source"], array('jpg', 'gif', 'png'))) {
1340 $destWidth = $image['largeur_dest'];
1341 $destHeight = $image['hauteur_dest'];
1342 $logo = $image['fichier'];
1343 $date = $image["date_src"];
1344 $preview = _image_creer_vignette($image, $taille, $taille_y, $process, $force);
1345
1346 if ($preview && $preview['fichier']) {
1347 $logo = $preview['fichier'];
1348 $destWidth = $preview['width'];
1349 $destHeight = $preview['height'];
1350 $date = $preview['date'];
1351 }
1352 // dans l'espace prive mettre un timestamp sur l'adresse
1353 // de l'image, de facon a tromper le cache du navigateur
1354 // quand on fait supprimer/reuploader un logo
1355 // (pas de filemtime si SAFE MODE)
1356 $date = test_espace_prive() ? ('?' . $date) : '';
1357
1358 return _image_ecrire_tag($image, array('src' => "$logo$date", 'width' => $destWidth, 'height' => $destHeight));
1359 } else # SVG par exemple ? BMP, tiff ... les redacteurs osent tout!
1360 {
1361 return $img;
1362 }
1363 }
1364
1365 /**
1366 * Produire des fichiers au format .ico
1367 *
1368 * Avec du code récupéré de phpThumb()
1369 *
1370 * @author James Heinrich <info@silisoftware.com>
1371 * @link http://phpthumb.sourceforge.net
1372 *
1373 * Class phpthumb_functions
1374 */
1375 class phpthumb_functions {
1376
1377 /**
1378 * Retourne la couleur d'un pixel dans une image
1379 *
1380 * @param ressource $img
1381 * @param int $x
1382 * @param int $y
1383 * @return array|bool
1384 */
1385 public static function GetPixelColor(&$img, $x, $y) {
1386 if (!is_resource($img)) {
1387 return false;
1388 }
1389
1390 return @ImageColorsForIndex($img, @ImageColorAt($img, $x, $y));
1391 }
1392
1393 /**
1394 * Retourne un nombre dans une représentation en Little Endian
1395 *
1396 * @param int $number
1397 * @param int $minbytes
1398 * @return string
1399 */
1400 public static function LittleEndian2String($number, $minbytes = 1) {
1401 $intstring = '';
1402 while ($number > 0) {
1403 $intstring = $intstring . chr($number & 255);
1404 $number >>= 8;
1405 }
1406
1407 return str_pad($intstring, $minbytes, "\x00", STR_PAD_RIGHT);
1408 }
1409
1410 /**
1411 * Transforme une ressource GD en image au format ICO
1412 *
1413 * @param array $gd_image_array
1414 * Tableau de ressources d'images GD
1415 * @return string
1416 * Image au format ICO
1417 */
1418 public static function GD2ICOstring(&$gd_image_array) {
1419 foreach ($gd_image_array as $key => $gd_image) {
1420
1421 $ImageWidths[$key] = ImageSX($gd_image);
1422 $ImageHeights[$key] = ImageSY($gd_image);
1423 $bpp[$key] = ImageIsTrueColor($gd_image) ? 32 : 24;
1424 $totalcolors[$key] = ImageColorsTotal($gd_image);
1425
1426 $icXOR[$key] = '';
1427 for ($y = $ImageHeights[$key] - 1; $y >= 0; $y--) {
1428 for ($x = 0; $x < $ImageWidths[$key]; $x++) {
1429 $argb = phpthumb_functions::GetPixelColor($gd_image, $x, $y);
1430 $a = round(255 * ((127 - $argb['alpha']) / 127));
1431 $r = $argb['red'];
1432 $g = $argb['green'];
1433 $b = $argb['blue'];
1434
1435 if ($bpp[$key] == 32) {
1436 $icXOR[$key] .= chr($b) . chr($g) . chr($r) . chr($a);
1437 } elseif ($bpp[$key] == 24) {
1438 $icXOR[$key] .= chr($b) . chr($g) . chr($r);
1439 }
1440
1441 if ($a < 128) {
1442 @$icANDmask[$key][$y] .= '1';
1443 } else {
1444 @$icANDmask[$key][$y] .= '0';
1445 }
1446 }
1447 // mask bits are 32-bit aligned per scanline
1448 while (strlen($icANDmask[$key][$y]) % 32) {
1449 $icANDmask[$key][$y] .= '0';
1450 }
1451 }
1452 $icAND[$key] = '';
1453 foreach ($icANDmask[$key] as $y => $scanlinemaskbits) {
1454 for ($i = 0; $i < strlen($scanlinemaskbits); $i += 8) {
1455 $icAND[$key] .= chr(bindec(str_pad(substr($scanlinemaskbits, $i, 8), 8, '0', STR_PAD_LEFT)));
1456 }
1457 }
1458
1459 }
1460
1461 foreach ($gd_image_array as $key => $gd_image) {
1462 $biSizeImage = $ImageWidths[$key] * $ImageHeights[$key] * ($bpp[$key] / 8);
1463
1464 // BITMAPINFOHEADER - 40 bytes
1465 $BitmapInfoHeader[$key] = '';
1466 $BitmapInfoHeader[$key] .= "\x28\x00\x00\x00"; // DWORD biSize;
1467 $BitmapInfoHeader[$key] .= phpthumb_functions::LittleEndian2String($ImageWidths[$key], 4); // LONG biWidth;
1468 // The biHeight member specifies the combined
1469 // height of the XOR and AND masks.
1470 $BitmapInfoHeader[$key] .= phpthumb_functions::LittleEndian2String($ImageHeights[$key] * 2, 4); // LONG biHeight;
1471 $BitmapInfoHeader[$key] .= "\x01\x00"; // WORD biPlanes;
1472 $BitmapInfoHeader[$key] .= chr($bpp[$key]) . "\x00"; // wBitCount;
1473 $BitmapInfoHeader[$key] .= "\x00\x00\x00\x00"; // DWORD biCompression;
1474 $BitmapInfoHeader[$key] .= phpthumb_functions::LittleEndian2String($biSizeImage, 4); // DWORD biSizeImage;
1475 $BitmapInfoHeader[$key] .= "\x00\x00\x00\x00"; // LONG biXPelsPerMeter;
1476 $BitmapInfoHeader[$key] .= "\x00\x00\x00\x00"; // LONG biYPelsPerMeter;
1477 $BitmapInfoHeader[$key] .= "\x00\x00\x00\x00"; // DWORD biClrUsed;
1478 $BitmapInfoHeader[$key] .= "\x00\x00\x00\x00"; // DWORD biClrImportant;
1479 }
1480
1481
1482 $icondata = "\x00\x00"; // idReserved; // Reserved (must be 0)
1483 $icondata .= "\x01\x00"; // idType; // Resource Type (1 for icons)
1484 $icondata .= phpthumb_functions::LittleEndian2String(count($gd_image_array), 2); // idCount; // How many images?
1485
1486 $dwImageOffset = 6 + (count($gd_image_array) * 16);
1487 foreach ($gd_image_array as $key => $gd_image) {
1488 // ICONDIRENTRY idEntries[1]; // An entry for each image (idCount of 'em)
1489
1490 $icondata .= chr($ImageWidths[$key]); // bWidth; // Width, in pixels, of the image
1491 $icondata .= chr($ImageHeights[$key]); // bHeight; // Height, in pixels, of the image
1492 $icondata .= chr($totalcolors[$key]); // bColorCount; // Number of colors in image (0 if >=8bpp)
1493 $icondata .= "\x00"; // bReserved; // Reserved ( must be 0)
1494
1495 $icondata .= "\x01\x00"; // wPlanes; // Color Planes
1496 $icondata .= chr($bpp[$key]) . "\x00"; // wBitCount; // Bits per pixel
1497
1498 $dwBytesInRes = 40 + strlen($icXOR[$key]) + strlen($icAND[$key]);
1499 $icondata .= phpthumb_functions::LittleEndian2String($dwBytesInRes,
1500 4); // dwBytesInRes; // How many bytes in this resource?
1501
1502 $icondata .= phpthumb_functions::LittleEndian2String($dwImageOffset,
1503 4); // dwImageOffset; // Where in the file is this image?
1504 $dwImageOffset += strlen($BitmapInfoHeader[$key]);
1505 $dwImageOffset += strlen($icXOR[$key]);
1506 $dwImageOffset += strlen($icAND[$key]);
1507 }
1508
1509 foreach ($gd_image_array as $key => $gd_image) {
1510 $icondata .= $BitmapInfoHeader[$key];
1511 $icondata .= $icXOR[$key];
1512 $icondata .= $icAND[$key];
1513 }
1514
1515 return $icondata;
1516 }
1517
1518 }