spip_nursit/ecrire/inc/filtres_images_lib_mini.php
2023-06-01 17:30:12 +02:00

1581 lines
49 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
/* *************************************************************************\
* SPIP, Systeme de publication pour l'internet *
* *
* Copyright (c) 2001-2019 *
* Arnaud Martin, Antoine Pitrou, Philippe Riviere, Emmanuel Saint-James *
* *
* Ce programme est un logiciel libre distribue sous licence GNU/GPL. *
* Pour plus de details voir le fichier COPYING.txt ou l'aide en ligne. *
\***************************************************************************/
/**
* 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
/**
* 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_html_to_hex($couleur);
$couleur = preg_replace(",^#,", "", $couleur);
$retour["red"] = hexdec(substr($couleur, 0, 2));
$retour["green"] = hexdec(substr($couleur, 2, 2));
$retour["blue"] = hexdec(substr($couleur, 4, 2));
return $retour;
}
/**
* 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 si traitements enchaînés.
* @return bool
* 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 traitement d'image
*
* Uniquement pour GD2.
*
* @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.
* @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) {
static $images_recalcul = array();
if (strlen($img) == 0) {
return false;
}
$source = trim(extraire_attribut($img, 'src'));
if (strlen($source) < 1) {
$source = $img;
$img = "<img src='$source' />";
} # gerer img src="data:....base64"
elseif (preg_match('@^data:image/(jpe?g|png|gif);base64,(.*)$@isS', $source, $regs)) {
$local = sous_repertoire(_DIR_VAR, 'image-data') . md5($regs[2]) . '.' . str_replace('jpeg', 'jpg', $regs[1]);
if (!file_exists($local)) {
ecrire_fichier($local, base64_decode($regs[2]));
}
$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 "";
}
} 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) {
$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 passe la balise img a taille image qui exraira les attributs si possible
// au lieu de faire un acces disque sur le fichier
list($ret["hauteur"], $ret["largeur"]) = taille_image($find_in_path ? $f : $img);
$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["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') {
list(, $maxWidth, $maxHeight) = explode('-', $effet);
list($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);
# cherche un cache existant
/*foreach (array('gif','jpg','png') as $fmt)
if (@file_exists($cache . $fichier_dest . '.' . $fmt)) {
$terminaison_dest = $fmt;
}*/
} 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");
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"] = ($terminaison != 'jpeg' ? $terminaison : 'jpg');
$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', array(
'args' => array(
'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;
if (!function_exists($ret["fonction_imagecreatefrom"])) {
return false;
}
return $ret;
}
/**
* Retourne la terminaison dun fichier image
* @param string $path
* @return string
*/
function _image_trouver_extension($path) {
if (preg_match(",\.(gif|jpe?g|png)($|[?]),i", $path, $regs)) {
$terminaison = strtolower($regs[1]);
return $terminaison;
}
return '';
}
/**
* Tente de trouver le véritable type dune image,
* même si une image est dextension .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 = @getimagesize($path)) {
return $terminaison;
}
$mime = image_type_to_mime_type($info[2]);
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;
default:
$_terminaison = '';
}
if ($_terminaison !== $terminaison) {
spip_log("Mauvaise extension du fichier : $path . Son type mime est : $mime", "images." . _LOG_INFO_IMPORTANTE);
$terminaison = $_terminaison;
}
return $terminaison;
}
/**
* Crée une image depuis un fichier ou une URL
*
* Utilise les fonctions spécifiques GD.
*
* @param string $filename
* Le path vers l'image à traiter (par exemple : IMG/distant/jpg/image.jpg
* ou local/cache-vignettes/L180xH51/image.jpg).
* @return ressource
* Une ressource de type Image GD.
*/
function _imagecreatefromjpeg($filename) {
$img = @imagecreatefromjpeg($filename);
if (!$img) {
spip_log("Erreur lecture imagecreatefromjpeg $filename", _LOG_CRITIQUE);
erreur_squelette("Erreur lecture imagecreatefromjpeg $filename");
$img = imagecreate(10, 10);
}
return $img;
}
/**
* Crée une image depuis un fichier ou une URL (au format png)
*
* Utilise les fonctions spécifiques GD.
*
* @param string $filename
* Le path vers l'image à traiter (par exemple : IMG/distant/png/image.png
* ou local/cache-vignettes/L180xH51/image.png).
* @return ressource
* Une ressource de type Image GD.
*/
function _imagecreatefrompng($filename) {
$img = @imagecreatefrompng($filename);
if (!$img) {
spip_log("Erreur lecture imagecreatefrompng $filename", _LOG_CRITIQUE);
erreur_squelette("Erreur lecture imagecreatefrompng $filename");
$img = imagecreate(10, 10);
}
return $img;
}
/**
* Crée une image depuis un fichier ou une URL (au format gif)
*
* Utilise les fonctions spécifiques GD.
*
* @param string $filename
* Le path vers l'image à traiter (par exemple : IMG/distant/gif/image.gif
* ou local/cache-vignettes/L180xH51/image.gif).
* @return ressource
* Une ressource de type Image GD.
*/
function _imagecreatefromgif($filename) {
$img = @imagecreatefromgif($filename);
if (!$img) {
spip_log("Erreur lecture imagecreatefromgif $filename", _LOG_CRITIQUE);
erreur_squelette("Erreur lecture imagecreatefromgif $filename");
$img = imagecreate(10, 10);
}
return $img;
}
/**
* Affiche ou sauvegarde une image au format PNG
*
* Utilise les fonctions spécifiques GD.
*
* @param ressource $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 ($taille_test[0] < 1) {
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 ressource $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 ($taille_test[0] < 1) {
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 ressource $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 ($taille_test[0] < 1) {
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 ressource $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 = array($img);
return ecrire_fichier($fichier, phpthumb_functions::GD2ICOstring($gd_image_array));
}
/**
* 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 ressource $img
* Une ressource de type Image GD.
* @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 = "_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
list($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 = array();
$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];
call_user_func_array($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 limage 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) {
$fichier = $img;
}
# 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;
}
if (!function_exists("imagepalettetotruecolor")) {
/**
* Transforme une image à palette indexée (256 couleurs max) en "vraies" couleurs RGB
*
* @note Pour compatibilité avec PHP < 5.5
*
* @link http://php.net/manual/fr/function.imagepalettetotruecolor.php
*
* @param ressource $img
* @return bool
* - true si l'image est déjà en vrai RGB ou peut être transformée
* - false si la transformation ne peut être faite.
**/
function imagepalettetotruecolor(&$img) {
if (!$img or !function_exists('imagecreatetruecolor')) {
return false;
} elseif (!imageistruecolor($img)) {
$w = imagesx($img);
$h = imagesy($img);
$img1 = imagecreatetruecolor($w, $h);
//Conserver la transparence si possible
if (function_exists('ImageCopyResampled')) {
if (function_exists("imageAntiAlias")) {
imageAntiAlias($img1, true);
}
@imagealphablending($img1, false);
@imagesavealpha($img1, true);
@ImageCopyResampled($img1, $img, 0, 0, 0, 0, $w, $h, $w, $h);
} else {
imagecopy($img1, $img, 0, 0, 0, 0, $w, $h);
}
$img = $img1;
}
return true;
}
}
/**
* 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
$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', $width);
$tag = inserer_attribut($tag, 'height', $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', $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 = array()) {
$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 &amp
// pas garanti comme methode, mais mieux que rien
if (strpos($src, '&') !== false) {
$tag = str_replace(str_replace("&", "&amp;", $src), $surcharge['src'], $tag);
}
$src = $surcharge['src'];
unset($surcharge['src']);
}
$class = $valeurs['class'];
if (isset($surcharge['class'])) {
$class = $surcharge['class'];
unset($surcharge['class']);
}
if (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',
array(
'args' => array(
'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) {
// 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'];
}
// liste des formats qu'on sait lire
$img = isset($GLOBALS['meta']['formats_graphiques'])
? (strpos($GLOBALS['meta']['formats_graphiques'], $format) !== false)
: false;
// si le doc n'est pas une image, refuser
if (!$force and !$img) {
return;
}
$destination = "$destdir/$destfile";
// calculer la taille
if (($srcWidth = $valeurs['largeur']) && ($srcHeight = $valeurs['hauteur'])) {
if (!($destWidth = $valeurs['largeur_dest']) || !($destHeight = $valeurs['hauteur_dest'])) {
list($destWidth, $destHeight) = _image_ratio($valeurs['largeur'], $valeurs['hauteur'], $maxWidth, $maxHeight);
}
} elseif ($process == 'convert' or $process == 'imagick') {
$destWidth = $maxWidth;
$destHeight = $maxHeight;
} else {
spip_log("echec $process sur $image");
return;
}
// 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);
} // 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 . ' -resize %xx%y! %src %dest');
}
$vignette = $destination . "." . $format_sortie;
$commande = str_replace(
array('%x', '%y', '%src', '%dest'),
array(
$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') {
$vignette = "$destination." . $format_sortie;
if (!class_exists('Imagick')) {
spip_log("Classe Imagick absente !", _LOG_ERREUR);
return;
}
$imagick = new Imagick();
$imagick->readImage($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;
}
} // 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 ($destFormat == "png") {
// 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);
}
$size = @getimagesize($vignette);
// 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($srcWidth, $srcHeight, $maxWidth, $maxHeight) {
$ratioWidth = $srcWidth / $maxWidth;
$ratioHeight = $srcHeight / $maxHeight;
if ($ratioWidth <= 1 and $ratioHeight <= 1) {
$destWidth = $srcWidth;
$destHeight = $srcHeight;
} elseif ($ratioWidth < $ratioHeight) {
$destWidth = $srcWidth / $ratioHeight;
$destHeight = $maxHeight;
} else {
$destWidth = $maxWidth;
$destHeight = $srcHeight / $ratioWidth;
}
return array(
ceil($destWidth),
ceil($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($srcWidth, $srcHeight, $maxWidth, $maxHeight) {
$ratioWidth = $srcWidth / $maxWidth;
$ratioHeight = $srcHeight / $maxHeight;
if ($ratioWidth <= 1 and $ratioHeight <= 1) {
$destWidth = $srcWidth;
$destHeight = $srcHeight;
} elseif ($ratioWidth > $ratioHeight) {
$destWidth = $srcWidth / $ratioHeight;
$destHeight = $maxHeight;
} else {
$destWidth = $maxWidth;
$destHeight = $srcHeight / $ratioWidth;
}
return array(
ceil($destWidth),
ceil($destHeight),
min($ratioWidth, $ratioHeight)
);
}
/**
* 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);
// on verifie que l'extension choisie est bonne (en principe oui)
$gd_formats = explode(',', $GLOBALS['meta']["gd_formats"]);
if (is_array($image)
and (!in_array($image['format_dest'], $gd_formats)
or ($image['format_dest'] == 'gif' and !function_exists('ImageGif'))
)
) {
if ($image['format_source'] == 'jpg') {
$formats_sortie = array('jpg', 'png', 'gif');
} else // les gif sont passes en png preferentiellement pour etre homogene aux autres filtres images
{
$formats_sortie = array('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)) {
if ($fmt <> "gif" or function_exists('ImageGif')) {
$format_sortie = $fmt;
}
break;
}
}
$image = false;
}
}
if (!is_array($image)) {
$image = _image_valeurs_trans($img, "reduire-{$taille}-{$taille_y}", $format_sortie, $fonction);
}
if (!is_array($image) or !$image['largeur'] or !$image['hauteur']) {
spip_log("image_reduire_src:pas de version locale de $img");
// on peut resizer en mode html si on dispose des elements
if ($srcw = extraire_attribut($img, 'width')
and $srch = extraire_attribut($img, 'height')
) {
list($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-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, array('src' => $image['fichier_dest']));
}
if ($image['creer'] == false && !$force) {
return _image_ecrire_tag($image,
array('src' => $image['fichier_dest'], 'width' => $image['largeur_dest'], 'height' => $image['hauteur_dest']));
}
if (in_array($image["format_source"], array('jpg', 'gif', 'png'))) {
$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, array('src' => "$logo$date", 'width' => $destWidth, 'height' => $destHeight));
} else # SVG par exemple ? 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 <info@silisoftware.com>
* @link http://phpthumb.sourceforge.net
*
* Class phpthumb_functions
*/
class phpthumb_functions {
/**
* Retourne la couleur d'un pixel dans une image
*
* @param ressource $img
* @param int $x
* @param int $y
* @return array|bool
*/
public static function GetPixelColor(&$img, $x, $y) {
if (!is_resource($img)) {
return false;
}
return @ImageColorsForIndex($img, @ImageColorAt($img, $x, $y));
}
/**
* 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;
}
}