Current File : /home/igihango/public_html/ecrire/inc/filtres_images_lib_mini.php |
<?php
/* *************************************************************************\
* SPIP, Système de publication pour l'internet *
* *
* Copyright © avec tendresse depuis 2001 *
* Arnaud Martin, Antoine Pitrou, Philippe Rivière, Emmanuel Saint-James *
* *
* Ce programme est un logiciel libre distribué sous licence GNU/GPL. *
\***************************************************************************/
/**
* Ce fichier contient les fonctions utilisées
* par les fonctions-filtres de traitement d'image.
*
* @package SPIP\Core\Filtres\Images
*/
if (!defined('_ECRIRE_INC_VERSION')) {
return;
}
include_spip('inc/filtres'); // par precaution
include_spip('inc/filtres_images_mini'); // par precaution
define('_SVG_SUPPORTED', true); // ne sert qu'a la lisibilite du code
/**
* Transforme une couleur vectorielle R,G,B en hexa (par exemple pour usage css)
*
* @param int $red
* Valeur du rouge de 0 à 255.
* @param int $green
* Valeur du vert de 0 à 255.
* @param int $blue
* Valeur du bleu de 0 à 255.
* @return string
* Le code de la couleur en hexadécimal.
*/
function _couleur_dec_to_hex($red, $green, $blue) {
$red = dechex($red);
$green = dechex($green);
$blue = dechex($blue);
if (strlen($red) == 1) {
$red = '0' . $red;
}
if (strlen($green) == 1) {
$green = '0' . $green;
}
if (strlen($blue) == 1) {
$blue = '0' . $blue;
}
return "$red$green$blue";
}
/**
* Transforme une couleur hexa en vectorielle R,G,B
*
* @param string $couleur
* Code couleur en hexa (#000000 à #FFFFFF).
* @return array
* Un tableau des 3 éléments : rouge, vert, bleu.
*/
function _couleur_hex_to_dec($couleur) {
$couleur ??= '';
$couleur = couleur_html_to_hex($couleur);
$couleur = ltrim($couleur, '#');
if (strlen($couleur) === 3) {
$couleur = $couleur[0] . $couleur[0] . $couleur[1] . $couleur[1] . $couleur[2] . $couleur[2];
}
$retour = [];
$retour['red'] = hexdec(substr($couleur, 0, 2));
$retour['green'] = hexdec(substr($couleur, 2, 2));
$retour['blue'] = hexdec(substr($couleur, 4, 2));
return $retour;
}
/**
* Transforme une couleur vectorielle H,S,L en hexa (par exemple pour usage css)
*
* @param int $hue
* Valeur de teinte de 0 à 1.
* @param int $saturation
* Valeur de saturation de 0 à 1.
* @param int $lightness
* Valeur de luminosité de 0 à 1.
* @return string
* Le code de la couleur en hexadécimal.
*/
function _couleur_hsl_to_hex($hue, $saturation, $lightness) {
$rgb = _couleur_hsl_to_rgb($hue, $saturation, $lightness);
return _couleur_dec_to_hex($rgb['r'], $rgb['g'], $rgb['b']);
}
/**
* Transforme une couleur vectorielle H,S,L en hexa (par exemple pour usage css)
*
* @param string $couleur
* Code couleur en hexa (#000000 à #FFFFFF).
* @return array
* Un tableau des 3 éléments : teinte, saturation, luminosité.
*/
function _couleur_hex_to_hsl($couleur) {
$rgb = _couleur_hex_to_dec($couleur);
return _couleur_rgb_to_hsl($rgb['red'], $rgb['green'], $rgb['blue']);
}
/**
* Transformation d'une couleur RGB en HSL
*
* HSL float entre 0 et 1
* RGB entiers entre 0 et 255
*
* @param int $R
* @param int $G
* @param int $B
* @return array
*/
function _couleur_rgb_to_hsl($R, $G, $B) {
$H = null;
$var_R = ($R / 255); // Where RGB values = 0 ÷ 255
$var_G = ($G / 255);
$var_B = ($B / 255);
$var_Min = min($var_R, $var_G, $var_B); //Min. value of RGB
$var_Max = max($var_R, $var_G, $var_B); //Max. value of RGB
$del_Max = $var_Max - $var_Min; //Delta RGB value
$L = ($var_Max + $var_Min) / 2;
if ($del_Max == 0) {
//This is a gray, no chroma...
$H = 0; //HSL results = 0 ÷ 1
$S = 0;
} else {
// Chromatic data...
if ($L < 0.5) {
$S = $del_Max / ($var_Max + $var_Min);
} else {
$S = $del_Max / (2 - $var_Max - $var_Min);
}
$del_R = ((($var_Max - $var_R) / 6) + ($del_Max / 2)) / $del_Max;
$del_G = ((($var_Max - $var_G) / 6) + ($del_Max / 2)) / $del_Max;
$del_B = ((($var_Max - $var_B) / 6) + ($del_Max / 2)) / $del_Max;
if ($var_R == $var_Max) {
$H = $del_B - $del_G;
} else {
if ($var_G == $var_Max) {
$H = (1 / 3) + $del_R - $del_B;
} else {
if ($var_B == $var_Max) {
$H = (2 / 3) + $del_G - $del_R;
}
}
}
if ($H < 0) {
$H += 1;
}
if ($H > 1) {
$H -= 1;
}
}
$ret = [];
$ret['h'] = $H;
$ret['s'] = $S;
$ret['l'] = $L;
return $ret;
}
/**
* Transformation d'une couleur HSL en RGB
*
* HSL float entre 0 et 1
* RGB entiers entre 0 et 255
*
* @param float $H
* @param float $S
* @param float $L
* @return array
*/
function _couleur_hsl_to_rgb($H, $S, $L) {
// helper
$hue_2_rgb = function ($v1, $v2, $vH) {
if ($vH < 0) {
$vH += 1;
}
if ($vH > 1) {
$vH -= 1;
}
if ((6 * $vH) < 1) {
return ($v1 + ($v2 - $v1) * 6 * $vH);
}
if ((2 * $vH) < 1) {
return ($v2);
}
if ((3 * $vH) < 2) {
return ($v1 + ($v2 - $v1) * ((2 / 3) - $vH) * 6);
}
return ($v1);
};
if ($S == 0) {
// HSV values = 0 -> 1
$R = $L * 255;
$G = $L * 255;
$B = $L * 255;
} else {
if ($L < 0.5) {
$var_2 = $L * (1 + $S);
} else {
$var_2 = ($L + $S) - ($S * $L);
}
$var_1 = 2 * $L - $var_2;
$R = 255 * $hue_2_rgb($var_1, $var_2, $H + (1 / 3));
$G = 255 * $hue_2_rgb($var_1, $var_2, $H);
$B = 255 * $hue_2_rgb($var_1, $var_2, $H - (1 / 3));
}
$ret = [];
$ret['r'] = floor($R);
$ret['g'] = floor($G);
$ret['b'] = floor($B);
return $ret;
}
/**
* Donne un statut au fichier-image intermédiaire servant au traitement d'image
* selon qu'il doit être gravé (fichier .src) ou pas.
*
* Un appel PHP direct aux fonctions de filtre d'image produira ainsi une image
* permanente (gravée) ; un appel généré par le compilateur via
* `filtrer('image_xx, ...)` effacera automatiquement le fichier-image temporaire.
*
* @param bool|string $stat
* - true, false ou le statut déjà défini
* - 'get' pour obtenir le statut sauvé si traitements enchaînés.
* @return bool|void
* true si il faut supprimer le fichier temporaire ; false sinon.
*/
function statut_effacer_images_temporaires($stat) {
static $statut = false; // par defaut on grave toute les images
if ($stat === 'get') {
return $statut;
}
$statut = $stat ? true : false;
}
/**
* Fonctions de preparation aux filtres de traitement d'image
* les fonctions de lecture et d'ecriture renseignees traitent uniquement le cas GD2
* qui est le cas general des filtres images
*
* mais image_reduire utilise aussi cette fonction en adaptant le traitement
* en fonction de la librairie graphique choisie dans la configuration de SPIP
*
* @pipeline_appel image_preparer_filtre
* @uses extraire_attribut()
* @uses inserer_attribut()
* @uses tester_url_absolue()
* @uses copie_locale() Si l'image est distante
* @uses taille_image()
* @uses _image_ratio()
* @uses reconstruire_image_intermediaire()
*
* @param string $img
* Chemin de l'image ou balise html `<img src=... />`.
* @param string $effet
* Les nom et paramètres de l'effet à apporter sur l'image
* (par exemple : reduire-300-200).
* @param bool|string $forcer_format
* Un nom d'extension spécifique demandé (par exemple : jpg, png, txt...).
* Par défaut false : GD se débrouille seule).
* @param array $fonction_creation
* Un tableau à 2 éléments :
* 1) string : indique le nom du filtre de traitement demandé (par exemple : `image_reduire`) ;
* 2) array : tableau reprenant la valeur de `$img` et chacun des arguments passés au filtre utilisé.
* @param bool $find_in_path
* false (par défaut) indique que l'on travaille sur un fichier
* temporaire (.src) ; true, sur un fichier définitif déjà existant.
* @param bool $support_svg
* false (par defaut) indique que le filtre ne sait pas traiter le cas particulier du SVG
* on lui substitue un filtre generique qui ne fait rien pour ne pas briser la chaine des filtres images
* true si le filtre appelant sait traiter les SVG
* @return bool|string|array
*
* - false si pas de tag `<img`,
* - si l'extension n'existe pas,
* - si le fichier source n'existe pas,
* - si les dimensions de la source ne sont pas accessibles,
* - si le fichier temporaire n'existe pas,
* - si la fonction `_imagecreatefrom{extension}` n'existe pas ;
* - "" (chaîne vide) si le fichier source est distant et n'a pas
* réussi à être copié sur le serveur ;
* - array : tableau décrivant de l'image
*/
function _image_valeurs_trans($img, $effet, $forcer_format = false, $fonction_creation = null, $find_in_path = false, $support_svg = false) {
$ret = [];
$f = null;
static $images_recalcul = [];
if (strlen($img) == 0) {
return false;
}
$source = trim(extraire_attribut($img, 'src') ?? '');
if (strlen($source) < 1) {
if (strpos($img, '<img ') !== 0) {
$source = $img;
$img = "<img src='$source' />";
}
else {
// pas d'attribut src sur cette balise <img../>
return false;
}
} elseif (
preg_match('@^data:image/([^;]*);base64,(.*)$@isS', $source, $regs)
and $extension = _image_trouver_extension_depuis_mime('image/' . $regs[1])
and in_array($extension, _image_extensions_acceptees_en_entree())
) {
# gerer img src="data:....base64"
$local = sous_repertoire(_DIR_VAR, 'image-data') . md5($regs[2]) . '.' . _image_extension_normalisee($extension);
if (!file_exists($local)) {
ecrire_fichier($local, base64_decode($regs[2]));
}
if ($sanitizer = charger_fonction($extension, 'sanitizer', true)) {
$sanitizer($local);
}
$source = $local;
$img = inserer_attribut($img, 'src', $source);
# eviter les mauvaises surprises lors de conversions de format
$img = inserer_attribut($img, 'width', '');
$img = inserer_attribut($img, 'height', '');
}
// les protocoles web prennent au moins 3 lettres
if (tester_url_absolue($source)) {
include_spip('inc/distant');
$fichier = _DIR_RACINE . copie_locale($source);
if (!$fichier) {
return '';
}
if (
$extension = _image_trouver_extension($fichier)
and $sanitizer = charger_fonction($extension, 'sanitizer', true)
) {
$sanitizer($fichier);
}
} else {
// enlever le timestamp eventuel
if (strpos($source, '?') !== false) {
$source = preg_replace(',[?][0-9]+$,', '', $source);
}
if (
strpos($source, '?') !== false
and strncmp($source, _DIR_IMG, strlen(_DIR_IMG)) == 0
and file_exists($f = preg_replace(',[?].*$,', '', $source))
) {
$source = $f;
}
$fichier = $source;
}
$terminaison_dest = '';
if ($terminaison = _image_trouver_extension($fichier)) {
$terminaison_dest = ($terminaison == 'gif') ? 'png' : $terminaison;
}
if (
$forcer_format !== false
// ignorer forcer_format si on a une image svg, que le filtre appelant ne supporte pas SVG, et que le forcage est un autre format image
and ($terminaison_dest !== 'svg' or $support_svg or !in_array($forcer_format, _image_extensions_acceptees_en_sortie()))
) {
$terminaison_dest = $forcer_format;
}
if (!$terminaison_dest) {
return false;
}
$nom_fichier = substr($fichier, 0, strlen($fichier) - (strlen($terminaison) + 1));
$fichier_dest = $nom_fichier;
if (
($find_in_path and $f = find_in_path($fichier) and $fichier = $f)
or @file_exists($f = $fichier)
) {
// on ne passe jamais la balise img qui est peut-être en x2 et à laquelle on ne peut pas faire confiance
// on lit directement les infos du fichier
[$ret['hauteur'], $ret['largeur']] = taille_image($f);
$date_src = @filemtime($f);
} elseif (
@file_exists($f = "$fichier.src")
and lire_fichier($f, $valeurs)
and $valeurs = unserialize($valeurs)
and isset($valeurs['hauteur_dest'])
and isset($valeurs['largeur_dest'])
) {
$ret['hauteur'] = $valeurs['hauteur_dest'];
$ret['largeur'] = $valeurs['largeur_dest'];
$date_src = $valeurs['date'];
} // pas de fichier source par la
else {
return false;
}
// pas de taille mesurable ?
if (!$ret['hauteur']
or !($ret['hauteur'] = intval(round($ret['hauteur'])))
or !$ret['largeur']
or !($ret['largeur'] = intval(round($ret['largeur'])))
) {
return false;
}
// les images calculees dependent du chemin du fichier source
// 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
// ce n'est pas totalement optimal en terme de stockage, mais chaque image est associee a un fichier .src
// qui contient la methode de reconstrucion (le filtre + les arguments d'appel) et les arguments different entre prive et public
// la mise en commun du fichier image cree donc un bug et des problemes qui necessiteraient beaucoup de complexite de code
// alors que ca concerne peu de site au final
// la release de r23632+r23633+r23634 a provoque peu de remontee de bug attestant du peu de sites impactes
$identifiant = $fichier;
// cas general :
// on a un dossier cache commun et un nom de fichier qui varie avec l'effet
// cas particulier de reduire :
// un cache par dimension, et le nom de fichier est conserve, suffixe par la dimension aussi
$cache = 'cache-gd2';
if (substr($effet, 0, 7) == 'reduire') {
[, $maxWidth, $maxHeight] = explode('-', $effet);
[$destWidth, $destHeight] = _image_ratio($ret['largeur'], $ret['hauteur'], $maxWidth, $maxHeight);
$ret['largeur_dest'] = $destWidth;
$ret['hauteur_dest'] = $destHeight;
$effet = "L{$destWidth}xH$destHeight";
$cache = 'cache-vignettes';
$fichier_dest = basename($fichier_dest);
if (($ret['largeur'] <= $maxWidth) && ($ret['hauteur'] <= $maxHeight)) {
// on garde la terminaison initiale car image simplement copiee
// et on postfixe son nom avec un md5 du path
$terminaison_dest = $terminaison;
$fichier_dest .= '-' . substr(md5("$identifiant"), 0, 5);
} else {
$fichier_dest .= '-' . substr(md5("$identifiant-$effet"), 0, 5);
}
$cache = sous_repertoire(_DIR_VAR, $cache);
$cache = sous_repertoire($cache, $effet);
} else {
$fichier_dest = md5("$identifiant-$effet");
$cache = sous_repertoire(_DIR_VAR, $cache);
$cache = sous_repertoire($cache, substr($fichier_dest, 0, 2));
$fichier_dest = substr($fichier_dest, 2);
}
$fichier_dest = $cache . $fichier_dest . '.' . $terminaison_dest;
$GLOBALS['images_calculees'][] = $fichier_dest;
$creer = true;
// si recalcul des images demande, recalculer chaque image une fois
if (defined('_VAR_IMAGES') and _VAR_IMAGES and !isset($images_recalcul[$fichier_dest])) {
$images_recalcul[$fichier_dest] = true;
} else {
if (@file_exists($f = $fichier_dest)) {
if (filemtime($f) >= $date_src) {
$creer = false;
}
} else {
if (
@file_exists($f = "$fichier_dest.src")
and lire_fichier($f, $valeurs)
and $valeurs = unserialize($valeurs)
and $valeurs['date'] >= $date_src
) {
$creer = false;
}
}
}
if ($creer) {
if (!@file_exists($fichier)) {
if (!@file_exists("$fichier.src")) {
spip_log("Image absente : $fichier", 'images' . _LOG_ERREUR);
return false;
}
# on reconstruit l'image source absente a partir de la chaine des .src
reconstruire_image_intermediaire($fichier);
}
}
if ($creer) {
spip_log(
'filtre image ' . ($fonction_creation ? reset($fonction_creation) : '') . "[$effet] sur $fichier",
'images' . _LOG_DEBUG
);
}
$term_fonction = _image_trouver_extension_pertinente($fichier);
$ret['fonction_imagecreatefrom'] = '_imagecreatefrom' . $term_fonction;
$ret['fichier'] = $fichier;
$ret['fonction_image'] = '_image_image' . $terminaison_dest;
$ret['fichier_dest'] = $fichier_dest;
$ret['format_source'] = _image_extension_normalisee($terminaison);
$ret['format_dest'] = $terminaison_dest;
$ret['date_src'] = $date_src;
$ret['creer'] = $creer;
$ret['class'] = extraire_attribut($img, 'class');
$ret['alt'] = extraire_attribut($img, 'alt');
$ret['style'] = extraire_attribut($img, 'style');
$ret['tag'] = $img;
if ($fonction_creation) {
$ret['reconstruction'] = $fonction_creation;
# ecrire ici comment creer le fichier, car il est pas sur qu'on l'ecrira reelement
# cas de image_reduire qui finalement ne reduit pas l'image source
# ca evite d'essayer de le creer au prochain hit si il n'est pas la
#ecrire_fichier($ret['fichier_dest'].'.src',serialize($ret),true);
}
$ret = pipeline('image_preparer_filtre', [
'args' => [
'img' => $img,
'effet' => $effet,
'forcer_format' => $forcer_format,
'fonction_creation' => $fonction_creation,
'find_in_path' => $find_in_path,
],
'data' => $ret
]);
// une globale pour le debug en cas de crash memoire
$GLOBALS['derniere_image_calculee'] = $ret;
// traiter le cas particulier des SVG : si le filtre n'a pas annonce explicitement qu'il savait faire, on delegue
if ($term_fonction === 'svg') {
if ($creer and !$support_svg) {
process_image_svg_identite($ret);
$ret['creer'] = false;
}
}
else {
if (!function_exists($ret['fonction_imagecreatefrom'])) {
return false;
}
}
return $ret;
}
/**
* @param string $quoi
* @return array
*/
function _image_extensions_acceptees_en_entree() {
static $extensions = null;
if (empty($extensions)) {
$extensions = ['png', 'gif', 'jpg', 'jpeg'];
if (!empty($GLOBALS['meta']['gd_formats'])) {
// action=tester renseigne gd_formats et detecte le support de webp
$extensions = array_merge(explode(',', $GLOBALS['meta']['gd_formats']));
$extensions = array_map('trim', $extensions);
$extensions = array_filter($extensions);
if (in_array('jpg', $extensions)) {
$extensions[] = 'jpeg';
}
$extensions = array_unique($extensions);
}
$extensions[] = 'svg'; // on le supporte toujours avec des fonctions specifiques
}
return $extensions;
}
/**
* @return array|string[]|null
*/
function _image_extensions_acceptees_en_sortie() {
static $extensions = null;
if (empty($extensions)) {
$extensions = _image_extensions_acceptees_en_entree();
$extensions = array_diff($extensions, ['jpeg']);
if (in_array('gif', $extensions) and !function_exists('imagegif')) {
$extensions = array_diff($extensions, ['gif']);
}
if (in_array('webp', $extensions) and !function_exists('imagewebp')) {
$extensions = array_diff($extensions, ['webp']);
}
}
return $extensions;
}
function _image_extension_normalisee($extension) {
$extension = strtolower($extension);
if ($extension === 'jpeg') {
$extension = 'jpg';
}
return $extension;
}
function _image_extensions_conservent_transparence() {
return ['png', 'webp'];
}
/**
* Retourne la terminaison d’un fichier image
* @param string $path
* @return string
*/
function _image_trouver_extension($path) {
$preg_extensions = implode('|', _image_extensions_acceptees_en_entree());
if (preg_match(",\.($preg_extensions)($|[?]),i", $path, $regs)) {
$terminaison = strtolower($regs[1]);
return $terminaison;
}
return '';
}
/**
* Tente de trouver le véritable type d’une image,
* même si une image est d’extension .jpg alors que son contenu est autre chose (gif ou png)
*
* @param string $path
* @return string Extension, dans le format attendu par les fonctions 'gd' ('jpeg' pour les .jpg par exemple)
*/
function _image_trouver_extension_pertinente($path) {
$path = supprimer_timestamp($path);
$terminaison = _image_trouver_extension($path);
if ($terminaison == 'jpg') {
$terminaison = 'jpeg';
}
if (!file_exists($path)) {
return $terminaison;
}
if (!$info = @spip_getimagesize($path)) {
return $terminaison;
}
if (isset($info['mime'])) {
$mime = $info['mime'];
}
else {
$mime = image_type_to_mime_type($info[2]);
}
$_terminaison = _image_trouver_extension_depuis_mime($mime);
if ($_terminaison and $_terminaison !== $terminaison) {
spip_log("Mauvaise extension du fichier : $path . Son type mime est : $mime", 'images.' . _LOG_INFO_IMPORTANTE);
$terminaison = $_terminaison;
}
return $terminaison;
}
/**
* @param string $mime
* @return string
*/
function _image_trouver_extension_depuis_mime($mime) {
switch (strtolower($mime)) {
case 'image/png':
case 'image/x-png':
$terminaison = 'png';
break;
case 'image/jpg':
case 'image/jpeg':
case 'image/pjpeg':
$terminaison = 'jpeg';
break;
case 'image/gif':
$terminaison = 'gif';
break;
case 'image/webp':
case 'image/x-webp':
$terminaison = 'webp';
break;
case 'image/svg+xml':
$terminaison = 'svg';
break;
default:
$terminaison = '';
}
return $terminaison;
}
/**
* Crée une image depuis un fichier ou une URL (en indiquant la fonction GD à utiliser)
*
* Utilise les fonctions spécifiques GD.
* - Si la fonction GD n'existe pas (GD non actif?), génère une erreur, mais ne retourne rien
* - Si l'image est impossible à créer avec la fonction indiquée, génère une erreur, et une image vide
*
* @param string $func
* Fonction GD à utiliser tel que 'imagecreatefromjpeg'
* @param string $filename
* Le path vers l'image à traiter (par exemple : IMG/distant/jpg/image.jpg
* ou local/cache-vignettes/L180xH51/image.jpg).
* @return resource|null
* Une ressource de type Image GD.
*/
function _imagecreatefrom_func(string $func, string $filename) {
if (!function_exists($func)) {
spip_log("GD indisponible : $func inexistante. Traitement $filename impossible.", _LOG_CRITIQUE);
erreur_squelette("GD indisponible : $func inexistante. Traitement $filename impossible.");
return null;
}
$img = @$func($filename);
if (!$img) {
spip_log("Erreur lecture $func $filename", _LOG_CRITIQUE);
erreur_squelette("Erreur lecture $func $filename");
$img = imagecreate(10, 10);
}
return $img;
}
/**
* Crée une image depuis un fichier ou une URL (au format jpeg)
*
* Utilise les fonctions spécifiques GD.
*
* @uses _imagecreatefrom_func()
* @param string $filename
* Le path vers l'image à traiter (par exemple : IMG/distant/jpg/image.jpg
* ou local/cache-vignettes/L180xH51/image.jpg).
* @return resource|null
* Une ressource de type Image GD.
*/
function _imagecreatefromjpeg($filename) {
return _imagecreatefrom_func('imagecreatefromjpeg', $filename);
}
/**
* Crée une image depuis un fichier ou une URL (au format png)
*
* Utilise les fonctions spécifiques GD.
*
* @uses _imagecreatefrom_func()
* @param string $filename
* Le path vers l'image à traiter (par exemple : IMG/distant/png/image.png
* ou local/cache-vignettes/L180xH51/image.png).
* @return resource|null
* Une ressource de type Image GD.
*/
function _imagecreatefrompng($filename) {
return _imagecreatefrom_func('imagecreatefrompng', $filename);
}
/**
* Crée une image depuis un fichier ou une URL (au format gif)
*
* Utilise les fonctions spécifiques GD.
*
* @uses _imagecreatefrom_func()
* @param string $filename
* Le path vers l'image à traiter (par exemple : IMG/distant/gif/image.gif
* ou local/cache-vignettes/L180xH51/image.gif).
* @return resource|null
* Une ressource de type Image GD.
*/
function _imagecreatefromgif($filename) {
return _imagecreatefrom_func('imagecreatefromgif', $filename);
}
/**
* Crée une image depuis un fichier ou une URL (au format webp)
*
* Utilise les fonctions spécifiques GD.
*
* @uses _imagecreatefrom_func()
* @param string $filename
* Le path vers l'image à traiter (par exemple : IMG/distant/png/image.webp
* ou local/cache-vignettes/L180xH51/image.webp).
* @return resource|null
* Une ressource de type Image GD.
*/
function _imagecreatefromwebp($filename) {
return _imagecreatefrom_func('imagecreatefromwebp', $filename);
}
/**
* Affiche ou sauvegarde une image au format PNG
*
* Utilise les fonctions spécifiques GD.
*
* @param resource $img
* Une ressource de type Image GD.
* @param string $fichier
* Le path vers l'image (ex : local/cache-vignettes/L180xH51/image.png).
* @return bool
*
* - false si l'image créée a une largeur nulle ou n'existe pas ;
* - true si une image est bien retournée.
*/
function _image_imagepng($img, $fichier) {
if (!function_exists('imagepng')) {
return false;
}
$tmp = $fichier . '.tmp';
$ret = imagepng($img, $tmp);
if (file_exists($tmp)) {
$taille_test = @getimagesize($tmp);
if (empty($taille_test[0])) {
return false;
}
spip_unlink($fichier); // le fichier peut deja exister
@rename($tmp, $fichier);
return $ret;
}
return false;
}
/**
* Affiche ou sauvegarde une image au format GIF
*
* Utilise les fonctions spécifiques GD.
*
* @param resource $img
* Une ressource de type Image GD.
* @param string $fichier
* Le path vers l'image (ex : local/cache-vignettes/L180xH51/image.gif).
* @return bool
*
* - false si l'image créée a une largeur nulle ou n'existe pas ;
* - true si une image est bien retournée.
*/
function _image_imagegif($img, $fichier) {
if (!function_exists('imagegif')) {
return false;
}
$tmp = $fichier . '.tmp';
$ret = imagegif($img, $tmp);
if (file_exists($tmp)) {
$taille_test = @getimagesize($tmp);
if (empty($taille_test[0])) {
return false;
}
spip_unlink($fichier); // le fichier peut deja exister
@rename($tmp, $fichier);
return $ret;
}
return false;
}
/**
* Affiche ou sauvegarde une image au format JPG
*
* Utilise les fonctions spécifiques GD.
*
* @param resource $img
* Une ressource de type Image GD.
* @param string $fichier
* Le path vers l'image (ex : local/cache-vignettes/L180xH51/image.jpg).
* @param int $qualite
* Le niveau de qualité du fichier résultant : de 0 (pire qualité, petit
* fichier) à 100 (meilleure qualité, gros fichier). Par défaut, prend la
* valeur (85) de la constante _IMG_GD_QUALITE (modifiable depuis
* mes_options.php).
* @return bool
*
* - false si l'image créée a une largeur nulle ou n'existe pas ;
* - true si une image est bien retournée.
*/
function _image_imagejpg($img, $fichier, $qualite = _IMG_GD_QUALITE) {
if (!function_exists('imagejpeg')) {
return false;
}
$tmp = $fichier . '.tmp';
// Enable interlancing
imageinterlace($img, true);
$ret = imagejpeg($img, $tmp, $qualite);
if (file_exists($tmp)) {
$taille_test = @getimagesize($tmp);
if (empty($taille_test[0])) {
return false;
}
spip_unlink($fichier); // le fichier peut deja exister
@rename($tmp, $fichier);
return $ret;
}
return false;
}
/**
* Crée un fichier-image au format ICO
*
* Utilise les fonctions de la classe phpthumb_functions.
*
* @uses phpthumb_functions::GD2ICOstring()
*
* @param resource $img
* Une ressource de type Image GD.
* @param string $fichier
* Le path vers l'image (ex : local/cache-vignettes/L180xH51/image.jpg).
* @return bool
* true si le fichier a bien été créé ; false sinon.
*/
function _image_imageico($img, $fichier) {
$gd_image_array = [$img];
return ecrire_fichier($fichier, phpthumb_functions::GD2ICOstring($gd_image_array));
}
/**
* Affiche ou sauvegarde une image au format WEBP
*
* Utilise les fonctions spécifiques GD.
*
* @param resource $img
* Une ressource de type Image GD.
* @param string $fichier
* Le path vers l'image (ex : local/cache-vignettes/L180xH51/image.webp).
* @return bool
*
* - false si l'image créée a une largeur nulle ou n'existe pas ;
* - true si une image est bien retournée.
*/
function _image_imagewebp($img, $fichier, $qualite = _IMG_GD_QUALITE) {
if (!function_exists('imagewebp')) {
return false;
}
$tmp = $fichier . '.tmp';
$ret = imagewebp($img, $tmp, $qualite);
if (file_exists($tmp)) {
$taille_test = @getimagesize($tmp);
if (empty($taille_test[0])) {
return false;
}
spip_unlink($fichier); // le fichier peut deja exister
@rename($tmp, $fichier);
return $ret;
}
return false;
}
/**
* Sauvegarde une image au format SVG
*
* - N'UTILISE PAS GD -
* C'est une fonction derogatoire pour faciliter le traitement des SVG
*
* @param string $img
* contenu du SVG ou chemin vers le SVG source (et c'est alors une copie)
* @param string $fichier
* Le path vers l'image (ex : local/cache-vignettes/L180xH51/image.png).
* @return bool
*
* - false si l'image créée a une largeur nulle ou n'existe pas ;
* - true si une image est bien retournée.
*/
function _image_imagesvg($img, $fichier) {
$tmp = $fichier . '.tmp';
if (strpos($img, '<') === false) {
$img = supprimer_timestamp($img);
if (!file_exists($img)) {
return false;
}
@copy($img, $tmp);
if (filesize($tmp) == filesize($img)) {
spip_unlink($fichier); // le fichier peut deja exister
@rename($tmp, $fichier);
return true;
}
return false;
}
file_put_contents($tmp, $img);
if (file_exists($tmp)) {
$taille_test = spip_getimagesize($tmp);
if (empty($taille_test[0])) {
return false;
}
spip_unlink($fichier); // le fichier peut deja exister
@rename($tmp, $fichier);
return true;
}
return false;
}
/**
* Finalise le traitement GD
*
* Crée un fichier_image temporaire .src ou vérifie que le fichier_image
* définitif a bien été créé.
*
* @uses statut_effacer_images_temporaires()
*
* @param resource|string $img
* Une ressource de type Image GD (ou une string pour un SVG)
* @param array $valeurs
* Un tableau des informations (tailles, traitement, path...) accompagnant
* l'image.
* @param int $qualite
* N'est utilisé que pour les images jpg.
* Le niveau de qualité du fichier résultant : de 0 (pire qualité, petit
* fichier) à 100 (meilleure qualité, gros fichier). Par défaut, prend la
* valeur (85) de la constante _IMG_GD_QUALITE (modifiable depuis
* mes_options.php).
* @return bool
* - true si le traitement GD s'est bien finalisé ;
* - false sinon.
*/
function _image_gd_output($img, $valeurs, $qualite = _IMG_GD_QUALITE, $fonction = null) {
if (is_null($fonction)) {
$fonction = '_image_image' . $valeurs['format_dest'];
}
$ret = false;
#un flag pour reperer les images gravees
$lock = (
!statut_effacer_images_temporaires('get') // si la fonction n'a pas ete activee, on grave tout
or (@file_exists($valeurs['fichier_dest']) and !@file_exists($valeurs['fichier_dest'] . '.src'))
);
if (
function_exists($fonction)
&& ($ret = $fonction($img, $valeurs['fichier_dest'], $qualite)) # on a reussi a creer l'image
&& isset($valeurs['reconstruction']) # et on sait comment la resonctruire le cas echeant
&& !$lock
) {
if (@file_exists($valeurs['fichier_dest'])) {
// dans tous les cas mettre a jour la taille de l'image finale
[$valeurs['hauteur_dest'], $valeurs['largeur_dest']] = taille_image($valeurs['fichier_dest']);
$valeurs['date'] = @filemtime($valeurs['fichier_dest']); // pour la retrouver apres disparition
ecrire_fichier($valeurs['fichier_dest'] . '.src', serialize($valeurs), true);
}
}
return $ret;
}
/**
* Reconstruit une image à partir des sources de contrôle de son ancienne
* construction
*
* @uses ramasse_miettes()
*
* @param string $fichier_manquant
* Chemin vers le fichier manquant
**/
function reconstruire_image_intermediaire($fichier_manquant) {
$reconstruire = [];
$fichier = $fichier_manquant;
while (
strpos($fichier, '://') === false
and !@file_exists($fichier)
and lire_fichier($src = "$fichier.src", $source)
and $valeurs = unserialize($source)
and ($fichier = $valeurs['fichier']) # l'origine est connue (on ne verifie pas son existence, qu'importe ...)
) {
spip_unlink($src); // si jamais on a un timeout pendant la reconstruction, elle se fera naturellement au hit suivant
$reconstruire[] = $valeurs['reconstruction'];
}
while (count($reconstruire)) {
$r = array_pop($reconstruire);
$fonction = $r[0];
$args = $r[1];
$fonction(...$args);
}
// cette image intermediaire est commune a plusieurs series de filtre, il faut la conserver
// mais l'on peut nettoyer les miettes de sa creation
ramasse_miettes($fichier_manquant);
}
/**
* Indique qu'un fichier d'image calculé est à conserver
*
* Permet de rendre une image définitive et de supprimer les images
* intermédiaires à son calcul.
*
* Supprime le fichier de contrôle de l’image cible (le $fichier.src)
* ce qui indique que l'image est définitive.
*
* Remonte ensuite la chaîne des fichiers de contrôle pour supprimer
* les images temporaires (mais laisse les fichiers de contrôle permettant
* de les reconstruire).
*
* @param string $fichier
* Chemin du fichier d'image calculé
**/
function ramasse_miettes($fichier) {
if (
strpos($fichier, '://') !== false
or !lire_fichier($src = "$fichier.src", $source)
or !$valeurs = unserialize($source)
) {
return;
}
spip_unlink($src); # on supprime la reference a sa source pour marquer cette image comme non intermediaire
while (
($fichier = $valeurs['fichier']) # l'origine est connue (on ne verifie pas son existence, qu'importe ...)
and (substr($fichier, 0, strlen(_DIR_VAR)) == _DIR_VAR) # et est dans local
and (lire_fichier(
$src = "$fichier.src",
$source
)) # le fichier a une source connue (c'est donc une image calculee intermediaire)
and ($valeurs = unserialize($source)) # et valide
) {
# on efface le fichier
spip_unlink($fichier);
# mais laisse le .src qui permet de savoir comment reconstruire l'image si besoin
#spip_unlink($src);
}
}
/**
* Clôture une série de filtres d'images
*
* Ce filtre est automatiquement appelé à la fin d'une série de filtres
* d'images dans un squelette.
*
* @filtre
* @uses reconstruire_image_intermediaire()
* Si l'image finale a déjà été supprimée car considérée comme temporaire
* par une autre série de filtres images débutant pareil
* @uses ramasse_miettes()
* Pour déclarer l'image définitive et nettoyer les images intermédiaires.
*
* @pipeline_appel post_image_filtrer
*
* @param string $img
* Code HTML de l'image
* @return string
* Code HTML de l'image
**/
function image_graver($img) {
// appeler le filtre post_image_filtrer qui permet de faire
// des traitements auto a la fin d'une serie de filtres
$img = pipeline('post_image_filtrer', $img);
$fichier_ori = $fichier = (extraire_attribut($img, 'src') ?? '');
if (($p = strpos($fichier, '?')) !== false) {
$fichier = substr($fichier, 0, $p);
}
if (strlen($fichier) < 1) {
if (strpos($img, '<img ') !== 0) {
$fichier = $img;
}
}
if (strlen($fichier)) {
# si jamais le fichier final n'a pas ete calcule car suppose temporaire
# et qu'il ne s'agit pas d'une URL
if (strpos($fichier, '://') === false and !@file_exists($fichier)) {
reconstruire_image_intermediaire($fichier);
}
ramasse_miettes($fichier);
// ajouter le timestamp si besoin
if (strpos($fichier_ori, '?') === false) {
// on utilise str_replace pour attraper le onmouseover des logo si besoin
$img = str_replace($fichier_ori, timestamp($fichier_ori), $img);
}
}
return $img;
}
/**
* Applique des attributs de taille (width, height) à une balise HTML
*
* Utilisé avec des balises `<img>` tout particulièrement.
*
* Modifie l'attribut style s'il était renseigné, en enlevant les
* informations éventuelles width / height dedans.
*
* @uses extraire_attribut()
* @uses inserer_attribut()
*
* @param string $tag
* Code html de la balise
* @param int $width
* Hauteur
* @param int $height
* Largeur
* @param bool|string $style
* Attribut html style à appliquer.
* False extrait celui présent dans la balise
* @return string
* Code html modifié de la balise.
**/
function _image_tag_changer_taille($tag, $width, $height, $style = false) {
if ($style === false) {
$style = extraire_attribut($tag, 'style');
}
// enlever le width et height du style
if ($style) {
$style = preg_replace(',(^|;)\s*(width|height)\s*:\s*[^;]+,ims', '', $style);
}
if ($style and $style[0] === ';') {
$style = substr($style, 1);
}
// mettre des attributs de width et height sur les images,
// ca accelere le rendu du navigateur
// ca permet aux navigateurs de reserver la bonne taille
// quand on a desactive l'affichage des images.
$tag = inserer_attribut($tag, 'width', round($width));
$tag = inserer_attribut($tag, 'height', round($height));
// attributs deprecies. Transformer en CSS
if ($espace = extraire_attribut($tag, 'hspace')) {
$style = "margin:{$espace}px;" . $style;
$tag = inserer_attribut($tag, 'hspace', '');
}
$tag = inserer_attribut($tag, 'style', (string) $style, true, $style ? false : true);
return $tag;
}
/**
* Écriture de la balise img en sortie de filtre image
*
* Reprend le tag initial et surcharge les attributs modifiés
*
* @pipeline_appel image_ecrire_tag_preparer
* @pipeline_appel image_ecrire_tag_finir
*
* @uses _image_tag_changer_taille()
* @uses extraire_attribut()
* @uses inserer_attribut()
* @see _image_valeurs_trans()
*
* @param array $valeurs
* Description de l'image tel que retourné par `_image_valeurs_trans()`
* @param array $surcharge
* Permet de surcharger certaines descriptions présentes dans `$valeurs`
* tel que 'style', 'width', 'height'
* @return string
* Retourne le code HTML de l'image
**/
function _image_ecrire_tag($valeurs, $surcharge = []) {
$valeurs = pipeline('image_ecrire_tag_preparer', $valeurs);
// fermer les tags img pas bien fermes;
$tag = str_replace('>', '/>', str_replace('/>', '>', $valeurs['tag']));
// le style
$style = $valeurs['style'];
if (isset($surcharge['style'])) {
$style = $surcharge['style'];
unset($surcharge['style']);
}
// traiter specifiquement la largeur et la hauteur
$width = $valeurs['largeur'];
if (isset($surcharge['width'])) {
$width = $surcharge['width'];
unset($surcharge['width']);
}
$height = $valeurs['hauteur'];
if (isset($surcharge['height'])) {
$height = $surcharge['height'];
unset($surcharge['height']);
}
$tag = _image_tag_changer_taille($tag, $width, $height, $style);
// traiter specifiquement le src qui peut etre repris dans un onmouseout
// on remplace toute les ref a src dans le tag
$src = extraire_attribut($tag, 'src');
if (isset($surcharge['src'])) {
$tag = str_replace($src, $surcharge['src'], $tag);
// si il y a des & dans src, alors ils peuvent provenir d'un &
// pas garanti comme methode, mais mieux que rien
if (strpos($src, '&') !== false) {
$tag = str_replace(str_replace('&', '&', $src), $surcharge['src'], $tag);
}
$src = $surcharge['src'];
unset($surcharge['src']);
}
$class = $valeurs['class'];
if (isset($surcharge['class'])) {
$class = $surcharge['class'];
unset($surcharge['class']);
}
if (is_scalar($class) && strlen($class)) {
$tag = inserer_attribut($tag, 'class', $class);
}
if (count($surcharge)) {
foreach ($surcharge as $attribut => $valeur) {
$tag = inserer_attribut($tag, $attribut, $valeur);
}
}
$tag = pipeline(
'image_ecrire_tag_finir',
[
'args' => [
'valeurs' => $valeurs,
'surcharge' => $surcharge,
],
'data' => $tag
]
);
return $tag;
}
/**
* Crée si possible une miniature d'une image
*
* @see _image_valeurs_trans()
* @uses _image_ratio()
*
* @param array $valeurs
* Description de l'image, telle que retournée par `_image_valeurs_trans()`
* @param int $maxWidth
* Largeur maximum en px de la miniature à réaliser
* @param int $maxHeight
* Hauteur maximum en px de la miniateure à réaliser
* @param string $process
* Librairie graphique à utiliser (gd1, gd2, netpbm, convert, imagick).
* AUTO utilise la librairie sélectionnée dans la configuration.
* @param bool $force
* @return array|null
* Description de l'image, sinon null.
**/
function _image_creer_vignette($valeurs, $maxWidth, $maxHeight, $process = 'AUTO', $force = false) {
$srcHeight = null;
$retour = [];
// ordre de preference des formats graphiques pour creer les vignettes
// le premier format disponible, selon la methode demandee, est utilise
$image = $valeurs['fichier'];
$format = $valeurs['format_source'];
$destdir = dirname($valeurs['fichier_dest']);
$destfile = basename($valeurs['fichier_dest'], '.' . $valeurs['format_dest']);
$format_sortie = $valeurs['format_dest'];
if (($process == 'AUTO') and isset($GLOBALS['meta']['image_process'])) {
$process = $GLOBALS['meta']['image_process'];
}
// si le doc n'est pas une image dans un format accetpable, refuser
if (!$force and !in_array($format, formats_image_acceptables(in_array($process, ['gd1', 'gd2'])))) {
return;
}
$destination = "$destdir/$destfile";
// calculer la taille
if (($srcWidth = $valeurs['largeur']) && ($srcHeight = $valeurs['hauteur'])) {
if (!($destWidth = $valeurs['largeur_dest']) || !($destHeight = $valeurs['hauteur_dest'])) {
[$destWidth, $destHeight] = _image_ratio($srcWidth, $srcHeight, $maxWidth, $maxHeight);
}
} elseif ($process == 'convert' or $process == 'imagick') {
$destWidth = $maxWidth;
$destHeight = $maxHeight;
} else {
spip_log("echec $process sur $image");
return;
}
$vignette = '';
// Si l'image est de la taille demandee (ou plus petite), simplement la retourner
if ($srcWidth and $srcWidth <= $maxWidth and $srcHeight <= $maxHeight) {
$vignette = $destination . '.' . $format;
@copy($image, $vignette);
}
elseif ($valeurs['format_source'] === 'svg') {
include_spip('inc/svg');
if ($svg = svg_redimensionner($valeurs['fichier'], $destWidth, $destHeight)) {
$format_sortie = 'svg';
$vignette = $destination . '.' . $format_sortie;
$valeurs['fichier_dest'] = $vignette;
_image_gd_output($svg, $valeurs);
}
}
// imagemagick en ligne de commande
elseif ($process == 'convert') {
if (!defined('_CONVERT_COMMAND')) {
define('_CONVERT_COMMAND', 'convert');
} // Securite : mes_options.php peut preciser le chemin absolu
if (!defined('_RESIZE_COMMAND')) {
define('_RESIZE_COMMAND', _CONVERT_COMMAND . ' -quality ' . _IMG_CONVERT_QUALITE . ' -orient Undefined -resize %xx%y! %src %dest');
}
$vignette = $destination . '.' . $format_sortie;
$commande = str_replace(
['%x', '%y', '%src', '%dest'],
[
$destWidth,
$destHeight,
escapeshellcmd($image),
escapeshellcmd($vignette)
],
_RESIZE_COMMAND
);
spip_log($commande);
exec($commande);
if (!@file_exists($vignette)) {
spip_log("echec convert sur $vignette");
return; // echec commande
}
}
// php5 imagemagick
elseif ($process == 'imagick') {
if (!class_exists(\Imagick::class)) {
spip_log('Classe Imagick absente !', _LOG_ERREUR);
return;
}
// chemin compatible Windows
$output = realpath(dirname($destination));
if (!$output) {
return;
}
$vignette = $output . DIRECTORY_SEPARATOR . basename($destination) . '.' . $format_sortie;
$imagick = new Imagick();
$imagick->readImage(realpath($image));
$imagick->resizeImage(
$destWidth,
$destHeight,
Imagick::FILTER_LANCZOS,
1
);//, IMAGICK_FILTER_LANCZOS, _IMG_IMAGICK_QUALITE / 100);
$imagick->writeImage($vignette);
if (!@file_exists($vignette)) {
spip_log("echec imagick sur $vignette");
return;
}
// remettre le chemin relatif car c'est ce qu'attend SPIP pour la suite (en particlier action/tester)
$vignette = $destination . '.' . $format_sortie;
}
// netpbm
elseif ($process == 'netpbm') {
if (!defined('_PNMSCALE_COMMAND')) {
define('_PNMSCALE_COMMAND', 'pnmscale');
} // Securite : mes_options.php peut preciser le chemin absolu
if (_PNMSCALE_COMMAND == '') {
return;
}
$vignette = $destination . '.' . $format_sortie;
$pnmtojpeg_command = str_replace('pnmscale', 'pnmtojpeg', _PNMSCALE_COMMAND);
if ($format == 'jpg') {
$jpegtopnm_command = str_replace('pnmscale', 'jpegtopnm', _PNMSCALE_COMMAND);
exec("$jpegtopnm_command $image | " . _PNMSCALE_COMMAND . " -width $destWidth | $pnmtojpeg_command > $vignette");
if (!($s = @filesize($vignette))) {
spip_unlink($vignette);
}
if (!@file_exists($vignette)) {
spip_log("echec netpbm-jpg sur $vignette");
return;
}
} else {
if ($format == 'gif') {
$giftopnm_command = str_replace('pnmscale', 'giftopnm', _PNMSCALE_COMMAND);
exec("$giftopnm_command $image | " . _PNMSCALE_COMMAND . " -width $destWidth | $pnmtojpeg_command > $vignette");
if (!($s = @filesize($vignette))) {
spip_unlink($vignette);
}
if (!@file_exists($vignette)) {
spip_log("echec netpbm-gif sur $vignette");
return;
}
} else {
if ($format == 'png') {
$pngtopnm_command = str_replace('pnmscale', 'pngtopnm', _PNMSCALE_COMMAND);
exec("$pngtopnm_command $image | " . _PNMSCALE_COMMAND . " -width $destWidth | $pnmtojpeg_command > $vignette");
if (!($s = @filesize($vignette))) {
spip_unlink($vignette);
}
if (!@file_exists($vignette)) {
spip_log("echec netpbm-png sur $vignette");
return;
}
}
}
}
}
// gd ou gd2
elseif ($process == 'gd1' or $process == 'gd2') {
if (!function_exists('gd_info')) {
spip_log('Librairie GD absente !', _LOG_ERREUR);
return;
}
if (_IMG_GD_MAX_PIXELS && $srcWidth * $srcHeight > _IMG_GD_MAX_PIXELS) {
spip_log('vignette gd1/gd2 impossible : ' . $srcWidth * $srcHeight . 'pixels');
return;
}
$destFormat = $format_sortie;
if (!$destFormat) {
spip_log("pas de format pour $image");
return;
}
$fonction_imagecreatefrom = $valeurs['fonction_imagecreatefrom'];
if (!function_exists($fonction_imagecreatefrom)) {
return;
}
$srcImage = @$fonction_imagecreatefrom($image);
if (!$srcImage) {
spip_log('echec gd1/gd2');
return;
}
// Initialisation de l'image destination
$destImage = null;
if ($process == 'gd2' and $destFormat != 'gif') {
$destImage = ImageCreateTrueColor($destWidth, $destHeight);
}
if (!$destImage) {
$destImage = ImageCreate($destWidth, $destHeight);
}
// Recopie de l'image d'origine avec adaptation de la taille
$ok = false;
if (($process == 'gd2') and function_exists('ImageCopyResampled')) {
if ($format == 'gif') {
// Si un GIF est transparent,
// fabriquer un PNG transparent
$transp = imagecolortransparent($srcImage);
if ($transp > 0) {
$destFormat = 'png';
}
}
if (in_array($destFormat, _image_extensions_conservent_transparence())) {
// Conserver la transparence
if (function_exists('imageAntiAlias')) {
imageAntiAlias($destImage, true);
}
@imagealphablending($destImage, false);
@imagesavealpha($destImage, true);
}
$ok = @ImageCopyResampled($destImage, $srcImage, 0, 0, 0, 0, $destWidth, $destHeight, $srcWidth, $srcHeight);
}
if (!$ok) {
$ok = ImageCopyResized($destImage, $srcImage, 0, 0, 0, 0, $destWidth, $destHeight, $srcWidth, $srcHeight);
}
// Sauvegarde de l'image destination
$valeurs['fichier_dest'] = $vignette = "$destination.$destFormat";
$valeurs['format_dest'] = $format = $destFormat;
_image_gd_output($destImage, $valeurs);
if ($srcImage) {
ImageDestroy($srcImage);
}
ImageDestroy($destImage);
}
if (!$vignette or !$size = @spip_getimagesize($vignette)) {
$size = [$destWidth, $destHeight];
}
// Gaffe: en safe mode, pas d'acces a la vignette,
// donc risque de balancer "width='0'", ce qui masque l'image sous MSIE
if ($size[0] < 1) {
$size[0] = $destWidth;
}
if ($size[1] < 1) {
$size[1] = $destHeight;
}
$retour['width'] = $largeur = $size[0];
$retour['height'] = $hauteur = $size[1];
$retour['fichier'] = $vignette;
$retour['format'] = $format;
$retour['date'] = @filemtime($vignette);
// renvoyer l'image
return $retour;
}
/**
* Réduire des dimensions en respectant un ratio
*
* Réduit des dimensions (hauteur, largeur) pour qu'elles
* soient incluses dans une hauteur et largeur maximum fournies
* en respectant la proportion d'origine
*
* @example `image_ratio(1000, 1000, 100, 10)` donne `array(10, 10, 100)`
* @see ratio_passe_partout() Assez proche.
*
* @param int $srcWidth Largeur de l'image source
* @param int $srcHeight Hauteur de l'image source
* @param int $maxWidth Largeur maximum souhaitée
* @param int $maxHeight Hauteur maximum souhaitée
* @return array Liste [ largeur, hauteur, ratio de réduction ]
**/
function _image_ratio(int $srcWidth, int $srcHeight, int $maxWidth, int $maxHeight): array {
$ratioWidth = $srcWidth / $maxWidth;
$ratioHeight = $srcHeight / $maxHeight;
if ($srcWidth <= $maxWidth and $srcHeight <= $maxHeight) {
$destWidth = $srcWidth;
$destHeight = $srcHeight;
} elseif ($ratioWidth < $ratioHeight) {
$destWidth = $srcWidth / $ratioHeight;
$destHeight = $maxHeight;
} else {
$destWidth = $maxWidth;
$destHeight = $srcHeight / $ratioWidth;
}
return [
intval(round($destWidth)),
intval(round($destHeight)),
max($ratioWidth, $ratioHeight)
];
}
/**
* Réduire des dimensions en respectant un ratio sur la plus petite dimension
*
* Réduit des dimensions (hauteur, largeur) pour qu'elles
* soient incluses dans la plus grande hauteur ou largeur maximum fournie
* en respectant la proportion d'origine
*
* @example `ratio_passe_partout(1000, 1000, 100, 10)` donne `array(100, 100, 10)`
* @see _image_ratio() Assez proche.
*
* @param int $srcWidth Largeur de l'image source
* @param int $srcHeight Hauteur de l'image source
* @param int $maxWidth Largeur maximum souhaitée
* @param int $maxHeight Hauteur maximum souhaitée
* @return array Liste [ largeur, hauteur, ratio de réduction ]
**/
function ratio_passe_partout(int $srcWidth, int $srcHeight, int $maxWidth, int $maxHeight): array {
$ratioWidth = $srcWidth / $maxWidth;
$ratioHeight = $srcHeight / $maxHeight;
if ($srcWidth <= $maxWidth and $srcHeight <= $maxHeight) {
$destWidth = $srcWidth;
$destHeight = $srcHeight;
} elseif ($ratioWidth > $ratioHeight) {
$destWidth = $srcWidth / $ratioHeight;
$destHeight = $maxHeight;
} else {
$destWidth = $maxWidth;
$destHeight = $srcHeight / $ratioWidth;
}
return [
intval(round($destWidth)),
intval(round($destHeight)),
min($ratioWidth, $ratioHeight)
];
}
/**
* Fonction identite de traitement par defaut des images SVG
* (quand un filtre n'annonce pas qu'il sait traiter un SVG on applique cette fonction a la place)
*
* @param array $image
* tableau des valeurs crees par _image_valeurs_trans
* @return string
*/
function process_image_svg_identite($image) {
if ($image['creer']) {
$source = $image['fichier'];
_image_gd_output($source, $image);
}
return _image_ecrire_tag($image, ['src' => $image['fichier_dest']]);
}
/**
* Réduit une image
*
* @uses extraire_attribut()
* @uses inserer_attribut()
* @uses _image_valeurs_trans()
* @uses _image_ratio()
* @uses _image_tag_changer_taille()
* @uses _image_ecrire_tag()
* @uses _image_creer_vignette()
*
* @param array $fonction
* Un tableau à 2 éléments :
* 1) string : indique le nom du filtre de traitement demandé (par exemple : `image_reduire`) ;
* 2) array : tableau reprenant la valeur de `$img` et chacun des arguments passés au filtre utilisé.
* @param string $img
* Chemin de l'image ou texte contenant une balise img
* @param int $taille
* Largeur désirée
* @param int $taille_y
* Hauteur désirée
* @param bool $force
* @param string $process
* Librairie graphique à utiliser (gd1, gd2, netpbm, convert, imagick).
* AUTO utilise la librairie sélectionnée dans la configuration.
* @return string
* Code HTML de la balise img produite
**/
function process_image_reduire($fonction, $img, $taille, $taille_y, $force, $process = 'AUTO') {
$image = false;
if (($process == 'AUTO') and isset($GLOBALS['meta']['image_process'])) {
$process = $GLOBALS['meta']['image_process'];
}
# determiner le format de sortie
$format_sortie = false; // le choix par defaut sera bon
if ($process == 'netpbm') {
$format_sortie = 'jpg';
} elseif ($process == 'gd1' or $process == 'gd2') {
$image = _image_valeurs_trans($img, "reduire-{$taille}-{$taille_y}", $format_sortie, $fonction, false, _SVG_SUPPORTED);
// on verifie que l'extension choisie est bonne (en principe oui)
$gd_formats = formats_image_acceptables(true);
if (
is_array($image)
and (!in_array($image['format_dest'], $gd_formats)
or (!in_array($image['format_dest'], _image_extensions_acceptees_en_sortie()))
)
) {
if ($image['format_source'] == 'jpg') {
$formats_sortie = ['jpg', 'png', 'gif'];
} else // les gif sont passes en png preferentiellement pour etre homogene aux autres filtres images
{
$formats_sortie = ['png', 'jpg', 'gif'];
}
// Choisir le format destination
// - on sauve de preference en JPEG (meilleure compression)
// - pour le GIF : les GD recentes peuvent le lire mais pas l'ecrire
# bug : gd_formats contient la liste des fichiers qu'on sait *lire*,
# pas *ecrire*
$format_sortie = '';
foreach ($formats_sortie as $fmt) {
if (in_array($fmt, $gd_formats) and in_array($fmt, _image_extensions_acceptees_en_sortie())) {
$format_sortie = $fmt;
break;
}
}
$image = false;
}
}
if (!is_array($image)) {
$image = _image_valeurs_trans($img, "reduire-{$taille}-{$taille_y}", $format_sortie, $fonction, false, _SVG_SUPPORTED);
}
if (!is_array($image) or !$image['largeur'] or !$image['hauteur']) {
spip_log("image_reduire_src:pas de version locale de $img ou extension non prise en charge");
// on peut resizer en mode html si on dispose des elements
[$srcw, $srch] = taille_image($img);
if ($srcw and $srch) {
[$w, $h] = _image_ratio($srcw, $srch, $taille, $taille_y);
return _image_tag_changer_taille($img, $w, $h);
}
// la on n'a pas d'infos sur l'image source... on refile le truc a css
// sous la forme style='max-width: NNpx;'
return inserer_attribut(
$img,
'style',
"max-width: {$taille}px;max-width: min(100%,{$taille}px); max-height: {$taille_y}px"
);
}
// si l'image est plus petite que la cible retourner une copie cachee de l'image
if (($image['largeur'] <= $taille) && ($image['hauteur'] <= $taille_y)) {
if ($image['creer']) {
@copy($image['fichier'], $image['fichier_dest']);
}
return _image_ecrire_tag($image, ['src' => $image['fichier_dest']]);
}
if ($image['creer'] == false && !$force) {
return _image_ecrire_tag(
$image,
['src' => $image['fichier_dest'], 'width' => $image['largeur_dest'], 'height' => $image['hauteur_dest']]
);
}
if (in_array($image['format_source'], _image_extensions_acceptees_en_entree())) {
$destWidth = $image['largeur_dest'];
$destHeight = $image['hauteur_dest'];
$logo = $image['fichier'];
$date = $image['date_src'];
$preview = _image_creer_vignette($image, $taille, $taille_y, $process, $force);
if ($preview && $preview['fichier']) {
$logo = $preview['fichier'];
$destWidth = $preview['width'];
$destHeight = $preview['height'];
$date = $preview['date'];
}
// dans l'espace prive mettre un timestamp sur l'adresse
// de l'image, de facon a tromper le cache du navigateur
// quand on fait supprimer/reuploader un logo
// (pas de filemtime si SAFE MODE)
$date = test_espace_prive() ? ('?' . $date) : '';
return _image_ecrire_tag($image, ['src' => "$logo$date", 'width' => $destWidth, 'height' => $destHeight]);
}
else {
# BMP, tiff ... les redacteurs osent tout!
return $img;
}
}
/**
* Produire des fichiers au format .ico
*
* Avec du code récupéré de phpThumb()
*
* @author James Heinrich <[email protected]>
* @link http://phpthumb.sourceforge.net
*
* Class phpthumb_functions
*/
class phpthumb_functions {
/**
* Retourne la couleur d'un pixel dans une image
*
* @param resource|GdImage $img
* @param int $x
* @param int $y
* @return array|bool
*/
public static function GetPixelColor(&$img, $x, $y) {
if (is_resource($img) || (is_object($img) && $img instanceof \GdImage)) {
return @ImageColorsForIndex($img, @ImageColorAt($img, $x, $y));
}
return false;
}
/**
* Retourne un nombre dans une représentation en Little Endian
*
* @param int $number
* @param int $minbytes
* @return string
*/
public static function LittleEndian2String($number, $minbytes = 1) {
$intstring = '';
while ($number > 0) {
$intstring = $intstring . chr($number & 255);
$number >>= 8;
}
return str_pad($intstring, $minbytes, "\x00", STR_PAD_RIGHT);
}
/**
* Transforme une ressource GD en image au format ICO
*
* @param array $gd_image_array
* Tableau de ressources d'images GD
* @return string
* Image au format ICO
*/
public static function GD2ICOstring(&$gd_image_array) {
foreach ($gd_image_array as $key => $gd_image) {
$ImageWidths[$key] = ImageSX($gd_image);
$ImageHeights[$key] = ImageSY($gd_image);
$bpp[$key] = ImageIsTrueColor($gd_image) ? 32 : 24;
$totalcolors[$key] = ImageColorsTotal($gd_image);
$icXOR[$key] = '';
for ($y = $ImageHeights[$key] - 1; $y >= 0; $y--) {
for ($x = 0; $x < $ImageWidths[$key]; $x++) {
$argb = phpthumb_functions::GetPixelColor($gd_image, $x, $y);
$a = round(255 * ((127 - $argb['alpha']) / 127));
$r = $argb['red'];
$g = $argb['green'];
$b = $argb['blue'];
if ($bpp[$key] == 32) {
$icXOR[$key] .= chr($b) . chr($g) . chr($r) . chr($a);
} elseif ($bpp[$key] == 24) {
$icXOR[$key] .= chr($b) . chr($g) . chr($r);
}
if ($a < 128) {
@$icANDmask[$key][$y] .= '1';
} else {
@$icANDmask[$key][$y] .= '0';
}
}
// mask bits are 32-bit aligned per scanline
while (strlen($icANDmask[$key][$y]) % 32) {
$icANDmask[$key][$y] .= '0';
}
}
$icAND[$key] = '';
foreach ($icANDmask[$key] as $y => $scanlinemaskbits) {
for ($i = 0; $i < strlen($scanlinemaskbits); $i += 8) {
$icAND[$key] .= chr(bindec(str_pad(substr($scanlinemaskbits, $i, 8), 8, '0', STR_PAD_LEFT)));
}
}
}
foreach ($gd_image_array as $key => $gd_image) {
$biSizeImage = $ImageWidths[$key] * $ImageHeights[$key] * ($bpp[$key] / 8);
// BITMAPINFOHEADER - 40 bytes
$BitmapInfoHeader[$key] = '';
$BitmapInfoHeader[$key] .= "\x28\x00\x00\x00"; // DWORD biSize;
$BitmapInfoHeader[$key] .= phpthumb_functions::LittleEndian2String($ImageWidths[$key], 4); // LONG biWidth;
// The biHeight member specifies the combined
// height of the XOR and AND masks.
$BitmapInfoHeader[$key] .= phpthumb_functions::LittleEndian2String($ImageHeights[$key] * 2, 4); // LONG biHeight;
$BitmapInfoHeader[$key] .= "\x01\x00"; // WORD biPlanes;
$BitmapInfoHeader[$key] .= chr($bpp[$key]) . "\x00"; // wBitCount;
$BitmapInfoHeader[$key] .= "\x00\x00\x00\x00"; // DWORD biCompression;
$BitmapInfoHeader[$key] .= phpthumb_functions::LittleEndian2String($biSizeImage, 4); // DWORD biSizeImage;
$BitmapInfoHeader[$key] .= "\x00\x00\x00\x00"; // LONG biXPelsPerMeter;
$BitmapInfoHeader[$key] .= "\x00\x00\x00\x00"; // LONG biYPelsPerMeter;
$BitmapInfoHeader[$key] .= "\x00\x00\x00\x00"; // DWORD biClrUsed;
$BitmapInfoHeader[$key] .= "\x00\x00\x00\x00"; // DWORD biClrImportant;
}
$icondata = "\x00\x00"; // idReserved; // Reserved (must be 0)
$icondata .= "\x01\x00"; // idType; // Resource Type (1 for icons)
$icondata .= phpthumb_functions::LittleEndian2String(count($gd_image_array), 2); // idCount; // How many images?
$dwImageOffset = 6 + (count($gd_image_array) * 16);
foreach ($gd_image_array as $key => $gd_image) {
// ICONDIRENTRY idEntries[1]; // An entry for each image (idCount of 'em)
$icondata .= chr($ImageWidths[$key]); // bWidth; // Width, in pixels, of the image
$icondata .= chr($ImageHeights[$key]); // bHeight; // Height, in pixels, of the image
$icondata .= chr($totalcolors[$key]); // bColorCount; // Number of colors in image (0 if >=8bpp)
$icondata .= "\x00"; // bReserved; // Reserved ( must be 0)
$icondata .= "\x01\x00"; // wPlanes; // Color Planes
$icondata .= chr($bpp[$key]) . "\x00"; // wBitCount; // Bits per pixel
$dwBytesInRes = 40 + strlen($icXOR[$key]) + strlen($icAND[$key]);
$icondata .= phpthumb_functions::LittleEndian2String(
$dwBytesInRes,
4
); // dwBytesInRes; // How many bytes in this resource?
$icondata .= phpthumb_functions::LittleEndian2String(
$dwImageOffset,
4
); // dwImageOffset; // Where in the file is this image?
$dwImageOffset += strlen($BitmapInfoHeader[$key]);
$dwImageOffset += strlen($icXOR[$key]);
$dwImageOffset += strlen($icAND[$key]);
}
foreach ($gd_image_array as $key => $gd_image) {
$icondata .= $BitmapInfoHeader[$key];
$icondata .= $icXOR[$key];
$icondata .= $icAND[$key];
}
return $icondata;
}
}