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