[script] miniature et cache d'image à la volée

WRInaute accro
Pour les sites qui ont beaucoup d'images à gérer et ou qui sont amenés a les afficher dans des tailles variables, il est évident que la gestion de miniature et de cache deviens un point important.

Je me suis penché sur ce problème car j'ai quelques 20 000 images a afficher dans des tailles variables (4 au moins en plus de l'original) et le serveur qui est affecté a se travail commençait a montrer des signes de faiblesse.
Ma première démarche (qui date déjà de quelques années) a été de produire des images a la taille voulue a la demande. Ne souhaitant pas pour des raisons de volume conserver ces images sur le serveur le parti pris fut de la produire a la volée avec la bibliothèque GD.

Centralisation des requêtes

Le premier point était donc de centraliser ces images sur un script capable de générer cela voici la technique utilisée qui servira par la suite au diverses solutions proposées :

Dans un premier temps donc il conviens d'intercepter toutes les demande d'image retaillées via une règle de réécriture :
Dans le htaccess :

Code:
	# traitement des miniatures
	RewriteCond %{REQUEST_URI}  ^(.*)thumb.*
	RewriteRule ^(.*)thumb(.*)$ /images/images.php?data=$2 [L]

Le principe est simple toute URL contenant "thumb" est intercepté par la règle, ce qui suis "thumb" est envoyé comme paramètre (en GET) au script "images.php"

Si votre URL de miniature se présente sous la forme :
-http://www.example.com/images/thumb/250/dossier/photo.jpg
le script images.php recevra comme paramètre "data" la valeur "/250/dossier/photo.jpg".
Si "/dossier/photo.jpg" correspond a un chemin physique vers votre photo le script sera en plus en mesure de manipuler la photo physique d'origine pour y appliquer différents traitements que nous allons voir ci dessous.

Première approche basique

Le script de miniature recevant toutes les requêtes http il conviens donc de les satisfaire, pour cela la première démarche du script "images.php" sera de comprendre ce qu'on lui demande a savoir d'être en mesure de connaitre la photo demandée et la taille désirée.

Nous avons vu que le paramètre reçu se présentait sous la forme "/250/dossier/photo.jpg". 250 représente la largeur de l'image, "/dossier/photo.jpg" le chemin du fichier. J'ai été tenté d'utiliser des expressions régulières pour extraire les deux données utiles, je l'ai même fait longtemps mais c'est très gourmand en ressource j'ai donc changé pour un simple parcours de la variable dans une boucle ce qui fait très bien l'affaire a moindre frais au final voici le code :

PHP:
<span class="syntaxdefault">    $comut </span><span class="syntaxkeyword">=</span><span class="syntaxdefault"> true</span><span class="syntaxkeyword">;<br /></span><span class="syntaxdefault">    for</span><span class="syntaxkeyword">(</span><span class="syntaxdefault">$i</span><span class="syntaxkeyword">=</span><span class="syntaxdefault">1</span><span class="syntaxkeyword">;</span><span class="syntaxdefault">$i</span><span class="syntaxkeyword"><</span><span class="syntaxdefault">strlen</span><span class="syntaxkeyword">(</span><span class="syntaxdefault">$_GET</span><span class="syntaxkeyword">[</span><span class="syntaxstring">'data'</span><span class="syntaxkeyword">]);</span><span class="syntaxdefault">$i</span><span class="syntaxkeyword">++){<br /></span><span class="syntaxdefault">        if</span><span class="syntaxkeyword">(</span><span class="syntaxdefault">$comut</span><span class="syntaxkeyword">){<br /></span><span class="syntaxdefault">            </span><span class="syntaxcomment">// on a pas trouvé le slash<br /></span><span class="syntaxdefault">            if</span><span class="syntaxkeyword">(</span><span class="syntaxdefault">$_GET</span><span class="syntaxkeyword">[</span><span class="syntaxstring">'data'</span><span class="syntaxkeyword">][</span><span class="syntaxdefault">$i</span><span class="syntaxkeyword">]==</span><span class="syntaxstring">'/'</span><span class="syntaxkeyword">){<br /></span><span class="syntaxdefault">                $comut </span><span class="syntaxkeyword">=</span><span class="syntaxdefault"> false</span><span class="syntaxkeyword">;<br /></span><span class="syntaxdefault">            </span><span class="syntaxkeyword">}else{<br /></span><span class="syntaxdefault">                $format </span><span class="syntaxkeyword">=</span><span class="syntaxdefault"> $format</span><span class="syntaxkeyword">.</span><span class="syntaxdefault">$_GET</span><span class="syntaxkeyword">[</span><span class="syntaxstring">'data'</span><span class="syntaxkeyword">][</span><span class="syntaxdefault">$i</span><span class="syntaxkeyword">];<br /></span><span class="syntaxdefault">            </span><span class="syntaxkeyword">}<br /></span><span class="syntaxdefault">        </span><span class="syntaxkeyword">}else{<br /></span><span class="syntaxdefault">            $fichier </span><span class="syntaxkeyword">=</span><span class="syntaxdefault"> $fichier</span><span class="syntaxkeyword">.</span><span class="syntaxdefault">$_GET</span><span class="syntaxkeyword">[</span><span class="syntaxstring">'data'</span><span class="syntaxkeyword">][</span><span class="syntaxdefault">$i</span><span class="syntaxkeyword">];<br /></span><span class="syntaxdefault">        </span><span class="syntaxkeyword">}<br /></span><span class="syntaxdefault">    </span><span class="syntaxkeyword">}</span><span class="syntaxdefault"> </span>
La variable GET est lue en intégralité depuis la position 1 (on évite ainsi le slash de démarrage) son contenu lettre a lettre est stocké dans $format tant qu'on ne rencontre pas un autre slash indiquant que c'est le chemin qui est en train d'être lu, on change alors de variable de stockage ($fichier) en utilisant une bascule ($comut)

A partir de là le script sais ce qu'il doit faire :

1/ envoyer les entêtes
2/ évaluer les dimensions de l'image a générer
3/ créer l'image avec GD
4/ livrer l'image a l'usager

PHP:
<span class="syntaxdefault">    $fichier </span><span class="syntaxkeyword">=</span><span class="syntaxdefault"> $_SERVER</span><span class="syntaxkeyword">[</span><span class="syntaxstring">'DOCUMENT_ROOT'</span><span class="syntaxkeyword">].</span><span class="syntaxstring">'/images/'</span><span class="syntaxkeyword">.</span><span class="syntaxdefault">$fichier</span><span class="syntaxkeyword">;<br /></span><span class="syntaxdefault">    <br />    </span><span class="syntaxcomment">// header php<br /></span><span class="syntaxdefault">    $offset </span><span class="syntaxkeyword">=</span><span class="syntaxdefault"> 290304000</span><span class="syntaxkeyword">;<br /></span><span class="syntaxdefault">    $expire </span><span class="syntaxkeyword">=</span><span class="syntaxdefault"> gmdate </span><span class="syntaxkeyword">(</span><span class="syntaxstring">"D, d M Y H:i:s"</span><span class="syntaxkeyword">,</span><span class="syntaxdefault"> time</span><span class="syntaxkeyword">()</span><span class="syntaxdefault"> </span><span class="syntaxkeyword">+</span><span class="syntaxdefault"> $offset</span><span class="syntaxkeyword">)</span><span class="syntaxdefault"> </span><span class="syntaxkeyword">.</span><span class="syntaxdefault"> </span><span class="syntaxstring">" GMT"</span><span class="syntaxkeyword">;<br /></span><span class="syntaxdefault">    header</span><span class="syntaxkeyword">(</span><span class="syntaxstring">"Last-Modified: "</span><span class="syntaxdefault"> </span><span class="syntaxkeyword">.</span><span class="syntaxdefault"> gmdate</span><span class="syntaxkeyword">(</span><span class="syntaxstring">"D, d M Y H:i:s"</span><span class="syntaxkeyword">)</span><span class="syntaxdefault"> </span><span class="syntaxkeyword">.</span><span class="syntaxdefault"> </span><span class="syntaxstring">" GMT"</span><span class="syntaxkeyword">);<br /></span><span class="syntaxdefault">    header</span><span class="syntaxkeyword">(</span><span class="syntaxstring">"Cache-Control: max-age=$offset, public"</span><span class="syntaxkeyword">);<br /></span><span class="syntaxdefault">    header</span><span class="syntaxkeyword">(</span><span class="syntaxstring">"expires: "</span><span class="syntaxkeyword">.</span><span class="syntaxdefault">$expire</span><span class="syntaxkeyword">);<br /></span><span class="syntaxdefault">    header</span><span class="syntaxkeyword">(</span><span class="syntaxstring">'Content-Type: image/jpeg'</span><span class="syntaxkeyword">);<br /><br /></span><span class="syntaxdefault">    </span><span class="syntaxcomment">// Calcul des nouvelles dimensions<br /></span><span class="syntaxdefault">    list</span><span class="syntaxkeyword">(</span><span class="syntaxdefault">$width</span><span class="syntaxkeyword">,</span><span class="syntaxdefault"> $height</span><span class="syntaxkeyword">)</span><span class="syntaxdefault"> </span><span class="syntaxkeyword">=</span><span class="syntaxdefault"> getimagesize</span><span class="syntaxkeyword">(</span><span class="syntaxdefault">$fichier</span><span class="syntaxkeyword">);<br /></span><span class="syntaxdefault">    $facteur </span><span class="syntaxkeyword">=</span><span class="syntaxdefault"> $width </span><span class="syntaxkeyword">/</span><span class="syntaxdefault"> $format</span><span class="syntaxkeyword">;<br /></span><span class="syntaxdefault">    $newwidth </span><span class="syntaxkeyword">=</span><span class="syntaxdefault"> $width </span><span class="syntaxkeyword">/</span><span class="syntaxdefault"> $facteur</span><span class="syntaxkeyword">;<br /></span><span class="syntaxdefault">    $newheight </span><span class="syntaxkeyword">=</span><span class="syntaxdefault"> $height </span><span class="syntaxkeyword">/</span><span class="syntaxdefault"> $facteur</span><span class="syntaxkeyword">;<br /></span><span class="syntaxdefault">    <br />    </span><span class="syntaxcomment">// Chargement<br /></span><span class="syntaxdefault">    $thumb </span><span class="syntaxkeyword">=</span><span class="syntaxdefault"> imagecreatetruecolor</span><span class="syntaxkeyword">(</span><span class="syntaxdefault">$newwidth</span><span class="syntaxkeyword">,</span><span class="syntaxdefault"> $newheight</span><span class="syntaxkeyword">);<br /></span><span class="syntaxdefault">    $source </span><span class="syntaxkeyword">=</span><span class="syntaxdefault"> imagecreatefromjpeg</span><span class="syntaxkeyword">(</span><span class="syntaxdefault">$fichier</span><span class="syntaxkeyword">);<br /></span><span class="syntaxdefault">    <br />    </span><span class="syntaxcomment">// Redimensionnement<br /></span><span class="syntaxdefault">    imagecopyresized</span><span class="syntaxkeyword">(</span><span class="syntaxdefault">$thumb</span><span class="syntaxkeyword">,</span><span class="syntaxdefault"> $source</span><span class="syntaxkeyword">,</span><span class="syntaxdefault"> 0</span><span class="syntaxkeyword">,</span><span class="syntaxdefault"> 0</span><span class="syntaxkeyword">,</span><span class="syntaxdefault"> 0</span><span class="syntaxkeyword">,</span><span class="syntaxdefault"> 0</span><span class="syntaxkeyword">,</span><span class="syntaxdefault"> $newwidth</span><span class="syntaxkeyword">,</span><span class="syntaxdefault"> $newheight</span><span class="syntaxkeyword">,</span><span class="syntaxdefault"> $width</span><span class="syntaxkeyword">,</span><span class="syntaxdefault"> $height</span><span class="syntaxkeyword">);<br /></span><span class="syntaxdefault">    <br />    </span><span class="syntaxcomment">// Affichage<br /></span><span class="syntaxdefault">    imagejpeg</span><span class="syntaxkeyword">(</span><span class="syntaxdefault">$thumb</span><span class="syntaxkeyword">);</span><span class="syntaxdefault"> </span>

Notez au passage la gestion des entête qui définissent un temps d'expiration important pour cette ressource qui est censée être statique.

Les principaux soucis que j'ai rencontré en deux ans avec cette solution sont :

1/ la relative faible qualité des images produites, en effet Si GD est une très bonne bibliothèque très bien intégrée à php et largement diffusée, la sortie n'est pas toujours de très bonne qualité surtout pour des tailles assez faibles.

2/ GD est aussi gourmand en ressources ...

Voici un résultat comparatif avec GD a droite et l'original traité avec gimp à gauche pour 250px de large :
177962141.jpg

GD produit une image acceptable mais avec des marches d'escalier et des tons très froids "taillés à la serpe".

Seconde approche optimisée

Dans une seconde approche je me suis penché sur la bibliothèque ImageMagik. ImageMagick n'est malheureusement pas aussi répandue que GD sur les serveurs web, si vous avez un dédié ce n'est pas un souci en revanche si vous travaillez sur un mutu c'est parfois plus délicat quoi qu'il en
soit, si les mutu ne donne pas accès a ImageMagick via php c'est bien rare que la bibliothèque n'existe pas sur le serveur et c'est pourquoi nous utiliserons la commande exec() pour y accéder.

A partir de là, plusieurs facteurs ont retenus mon attention :

1/ la qualité de l'image produite.
2/ la "vélocité du script" (donc la charge php infligée au serveur)
3/ la résultante des deux précédents donc le temps de chargement de l'image.

Voici la partie script lié purement a la fabrication de l'image :

PHP:
<span class="syntaxdefault">    </span><span class="syntaxcomment">// Calcul des nouvelles dimensions<br /></span><span class="syntaxdefault">    list</span><span class="syntaxkeyword">(</span><span class="syntaxdefault">$width</span><span class="syntaxkeyword">,</span><span class="syntaxdefault"> $height</span><span class="syntaxkeyword">)</span><span class="syntaxdefault"> </span><span class="syntaxkeyword">=</span><span class="syntaxdefault"> getimagesize</span><span class="syntaxkeyword">(</span><span class="syntaxdefault">$fichier</span><span class="syntaxkeyword">);<br /></span><span class="syntaxdefault">    $facteur </span><span class="syntaxkeyword">=</span><span class="syntaxdefault"> $width </span><span class="syntaxkeyword">/</span><span class="syntaxdefault"> $format</span><span class="syntaxkeyword">;<br /></span><span class="syntaxdefault">    $newwidth </span><span class="syntaxkeyword">=</span><span class="syntaxdefault"> $width </span><span class="syntaxkeyword">/</span><span class="syntaxdefault"> $facteur</span><span class="syntaxkeyword">;<br /></span><span class="syntaxdefault">    $newheight </span><span class="syntaxkeyword">=</span><span class="syntaxdefault"> $height </span><span class="syntaxkeyword">/</span><span class="syntaxdefault"> $facteur</span><span class="syntaxkeyword">;<br /><br /></span><span class="syntaxdefault">    </span><span class="syntaxcomment">// Création de la mniature<br /></span><span class="syntaxdefault">    $cmd </span><span class="syntaxkeyword">=</span><span class="syntaxdefault"> </span><span class="syntaxstring">"convert ${fichier} -thumbnail ${newwidth}x${newheight} -quality 50 -strip miniature.jpg"</span><span class="syntaxkeyword">;<br /></span><span class="syntaxdefault">    exec</span><span class="syntaxkeyword">(</span><span class="syntaxdefault">$cmd</span><span class="syntaxkeyword">);<br /></span><span class="syntaxdefault">    $contents </span><span class="syntaxkeyword">=</span><span class="syntaxdefault"> file_get_contents</span><span class="syntaxkeyword">(</span><span class="syntaxstring">'miniature.jpg'</span><span class="syntaxkeyword">);<br /></span><span class="syntaxdefault">    echo $contents</span><span class="syntaxkeyword">;</span><span class="syntaxdefault"> </span>

Je ne reviens pas sur l'extraction des données ni sur l'envoie du header, c'est pareil mais je donne quelques explications sur la commande exécutée via exec() :

convert ${fichier} -thumbnail ${newwidth}x${newheight} -quality 50 -strip miniature.jpg

Le script fait donc appel a la commande "convert" pour produire une image de taille ${newwidth}x${newheight} en qualité 50 (plus le chiffre est bas plus la qualité baisse), "strip" quand a lui vire les données exif diverses (de mémoire) afin d'alléger le poids de l'image le résultat est envoyé dans le fichier "miniature.jpg" sur le disque.

file_get_contents() lira le fichier pour ne faire un écho et comme les entêtes on été envoyé, le client recevra son image. J'utilise ici file_get_contents() car je n'ai pas trouvé le moyen d'envoyer directement sur la sortie standard de php le contenu via la ligne de commande. Nous verrons par la suite que cet inconvénient va se transformer en avantage.

Le résultat est, malgré la faible qualité demandée (afin d'économiser de la ressource), déjà bien supérieur a ce que produisais GD même référence de comparaison que précédemment :

513489991.jpg


la comparaison des deux bibliothèques en vis a vis de passe de tous commentaire :

303065371.jpg


Pour que la comparaison soit a peut prêt valable, il faut noter le poids physique de chaques images. GD fourni une image de 30,50Ko, avec les réglages choisis, ImageMagick produit une image don le poids est de 28,44Ko. A ce stade nous avons gagné sur deux points importants :

1/ la taille du fichier donc (temps de chargement, économie de Bande passante, ....)
2/ qualité picturale de l'image.

Que pouvons nous encore gagner ?

Mise en cache

Le gros souci des scripts de génération de miniature a la volée est en fait tout entier dans la ressource processeur utilisée pour la créations des images. D'une part ça mobilise php pour des taches peut enviable, ensuite ça peut vite mettre a genoux la mémoire si on brasse de grosse tailles. Que dire sinon que cette consommation se répète a chaque chargement d'une photo ...

Malgré les headers adaptés et la gestion du cache des navigateurs vous ne gagnerez pas grand chose niveau serveur si un internaute décide de visualiser beaucoup de vos photos ...

Principal inconvénient tout a l'heure, le fichier généré qu'il fallait relire pour l'envoyer vers la sortie va nous servir pour un cache intelligent ...

Un cache intelligent est pour moi un système qu'on va pouvoir manipuler pour en faire autre chose (on verra plus loin), qui est économe si possible en ressource (je part du principe que la taille des disques n'est pas un souci) et qui (normal) nous fait gagner du temps et du calcul.

Voici donc la modification du script précédent qui introduit la notion de mise en cache des miniature :

PHP:
<span class="syntaxdefault">    if</span><span class="syntaxkeyword">(</span><span class="syntaxdefault">file_exists</span><span class="syntaxkeyword">(</span><span class="syntaxstring">'cacheFile.php'</span><span class="syntaxkeyword">)){<br /></span><span class="syntaxdefault">        include</span><span class="syntaxkeyword">(</span><span class="syntaxstring">'cacheFile.php'</span><span class="syntaxkeyword">);<br /></span><span class="syntaxdefault">        if</span><span class="syntaxkeyword">(isset(</span><span class="syntaxdefault">$photo</span><span class="syntaxkeyword">[</span><span class="syntaxdefault">$ident</span><span class="syntaxkeyword">][</span><span class="syntaxdefault">$format</span><span class="syntaxkeyword">])){<br /></span><span class="syntaxdefault">            </span><span class="syntaxcomment">// le fichier cache existe et la photo est dedans<br /></span><span class="syntaxdefault">            echo base64_decode</span><span class="syntaxkeyword">(</span><span class="syntaxdefault">$photo</span><span class="syntaxkeyword">[</span><span class="syntaxdefault">$ident</span><span class="syntaxkeyword">][</span><span class="syntaxdefault">$format</span><span class="syntaxkeyword">]);<br /></span><span class="syntaxdefault">            exit</span><span class="syntaxkeyword">;<br /></span><span class="syntaxdefault">        </span><span class="syntaxkeyword">}else{<br /></span><span class="syntaxdefault">            </span><span class="syntaxcomment">// le fichier cache existe mais la photo n'y est pas<br /></span><span class="syntaxdefault">            $cmd </span><span class="syntaxkeyword">=</span><span class="syntaxdefault"> </span><span class="syntaxstring">"convert ${fichier} -thumbnail ${newwidth}x${newheight} -quality 50 -strip ${fichierDisque}"</span><span class="syntaxkeyword">;<br /></span><span class="syntaxdefault">            exec</span><span class="syntaxkeyword">(</span><span class="syntaxdefault">$cmd</span><span class="syntaxkeyword">);<br /></span><span class="syntaxdefault">            $contents </span><span class="syntaxkeyword">=</span><span class="syntaxdefault"> file_get_contents</span><span class="syntaxkeyword">(</span><span class="syntaxdefault">$fichierDisque</span><span class="syntaxkeyword">);<br /></span><span class="syntaxdefault">            echo $contents</span><span class="syntaxkeyword">;<br /></span><span class="syntaxdefault">            </span><span class="syntaxcomment">// le fichier cache n'existe pas on le crée<br /></span><span class="syntaxdefault">            $fd </span><span class="syntaxkeyword">=</span><span class="syntaxdefault"> fopen</span><span class="syntaxkeyword">(</span><span class="syntaxstring">'cacheFile.php'</span><span class="syntaxkeyword">,</span><span class="syntaxdefault"> </span><span class="syntaxstring">"a"</span><span class="syntaxkeyword">);</span><span class="syntaxdefault"> <br />            if</span><span class="syntaxkeyword">(</span><span class="syntaxdefault">$fd</span><span class="syntaxkeyword">){<br /></span><span class="syntaxdefault">                fwrite</span><span class="syntaxkeyword">(</span><span class="syntaxdefault">$fd</span><span class="syntaxkeyword">,</span><span class="syntaxstring">"\t\$photo['$ident']['$format'] = \""</span><span class="syntaxkeyword">.</span><span class="syntaxdefault">base64_encode</span><span class="syntaxkeyword">(</span><span class="syntaxdefault">$contents</span><span class="syntaxkeyword">).</span><span class="syntaxstring">"\";\n\n"</span><span class="syntaxkeyword">);</span><span class="syntaxdefault"> <br />                fclose</span><span class="syntaxkeyword">(</span><span class="syntaxdefault">$fd</span><span class="syntaxkeyword">);<br /></span><span class="syntaxdefault">            </span><span class="syntaxkeyword">}<br /></span><span class="syntaxdefault">        </span><span class="syntaxkeyword">}<br /></span><span class="syntaxdefault">    </span><span class="syntaxkeyword">}else{<br /></span><span class="syntaxdefault">        $cmd </span><span class="syntaxkeyword">=</span><span class="syntaxdefault"> </span><span class="syntaxstring">"convert ${fichier} -thumbnail ${newwidth}x${newheight} -quality 80 -strip ${fichierDisque}"</span><span class="syntaxkeyword">;<br /></span><span class="syntaxdefault">        exec</span><span class="syntaxkeyword">(</span><span class="syntaxdefault">$cmd</span><span class="syntaxkeyword">);<br /></span><span class="syntaxdefault">        $contents </span><span class="syntaxkeyword">=</span><span class="syntaxdefault"> file_get_contents</span><span class="syntaxkeyword">(</span><span class="syntaxdefault">$fichierDisque</span><span class="syntaxkeyword">);<br /></span><span class="syntaxdefault">        echo $contents</span><span class="syntaxkeyword">;<br /></span><span class="syntaxdefault">        </span><span class="syntaxcomment">// le fichier cache n'existe pas on le crée<br /></span><span class="syntaxdefault">        $fd </span><span class="syntaxkeyword">=</span><span class="syntaxdefault"> fopen</span><span class="syntaxkeyword">(</span><span class="syntaxstring">'cacheFile.php'</span><span class="syntaxkeyword">,</span><span class="syntaxdefault"> </span><span class="syntaxstring">"w"</span><span class="syntaxkeyword">);</span><span class="syntaxdefault"> <br />        if</span><span class="syntaxkeyword">(</span><span class="syntaxdefault">$fd</span><span class="syntaxkeyword">){<br /></span><span class="syntaxdefault">            fwrite</span><span class="syntaxkeyword">(</span><span class="syntaxdefault">$fd</span><span class="syntaxkeyword">,</span><span class="syntaxstring">"<?php \n\t\$photo['$ident']['$format'] = \""</span><span class="syntaxkeyword">.</span><span class="syntaxdefault">base64_encode</span><span class="syntaxkeyword">(</span><span class="syntaxdefault">$contents</span><span class="syntaxkeyword">).</span><span class="syntaxstring">"\";\n\n"</span><span class="syntaxkeyword">);</span><span class="syntaxdefault"> <br />            fclose</span><span class="syntaxkeyword">(</span><span class="syntaxdefault">$fd</span><span class="syntaxkeyword">);<br /></span><span class="syntaxdefault">        </span><span class="syntaxkeyword">}<br /></span><span class="syntaxdefault">    </span><span class="syntaxkeyword">}</span><span class="syntaxdefault"> </span>

Le système s'articule autour d'une première condition évidente, le fichier de cache existe t il ?

1/ si non (cas else) on va générer l'image et créer e fichier de cache en la rangeant dedans
2/ si oui il faudra recourir a un second test pour savoir si l'image est dedans. En effet le cache est commun a toutes les tailles d'une photo donc pour chaque photo on aura un fichier de cache qui contiendra toutes les tailles voulues.

Le code qui gère la création du cache avec un fopen crée un fichier php (qu'on peut donc inclure très important !, mais aussi downloader, ....) qui contiens un simple ligne :

PHP:
<span class="syntaxdefault"></span><span class="syntaxkeyword"><?</span><span class="syntaxdefault">php<br />     $photo</span><span class="syntaxkeyword">[</span><span class="syntaxstring">'dossier/photo.jpg'</span><span class="syntaxkeyword">][</span><span class="syntaxstring">'250'</span><span class="syntaxkeyword">]</span><span class="syntaxdefault"> </span><span class="syntaxkeyword">=</span><span class="syntaxdefault"> </span><span class="syntaxstring">"/9j/4AAQSkZJRgABAQEBL .... WkOEITI//Z"</span><span class="syntaxkeyword">;</span><span class="syntaxdefault"> </span>

Vous le voyez, le cache est un "script" qui assigne le contenu de l'image encodé base 64 a un variable de tableau portant le nom de l'image et sa taille en coordonnée (notez le base 64 on va y revenir :wink: ).

Dans le cas ou une photo serait demandée ailleurs avec une autre dimension une nouvelle variable de tableau serait générée pour contenir l'autre dimension dans le même fichier de cache :

PHP:
<span class="syntaxdefault"></span><span class="syntaxkeyword"><?</span><span class="syntaxdefault">php <br />     $photo</span><span class="syntaxkeyword">[</span><span class="syntaxstring">'dossier/photo.jpg'</span><span class="syntaxkeyword">][</span><span class="syntaxstring">'250'</span><span class="syntaxkeyword">]</span><span class="syntaxdefault"> </span><span class="syntaxkeyword">=</span><span class="syntaxdefault"> </span><span class="syntaxstring">"/9j/4AAQSkZJRgABAQEBL .... WkOEITI//Z"</span><span class="syntaxkeyword">;</span><span class="syntaxdefault"> <br />     $photo</span><span class="syntaxkeyword">[</span><span class="syntaxstring">'dossier/photo.jpg'</span><span class="syntaxkeyword">][</span><span class="syntaxstring">'900'</span><span class="syntaxkeyword">]</span><span class="syntaxdefault"> </span><span class="syntaxkeyword">=</span><span class="syntaxdefault"> </span><span class="syntaxstring">"/9j/4AAQSkZJRgABAQEBL ............... WfOGITIT"</span><span class="syntaxkeyword">;</span><span class="syntaxdefault">  </span>

Un unique fichier de cache peux donc contenir toutes les photos en différentes tailles.

Nous avions vu qu'un écho envoyait la photo demandé sur la sortie standard, là avec le dispositif de cache un include nous permet de savoir si la variable correspondant a l'image existe si oui on en fait un echo après avoir décodé son contenu ( echo base64_decode($photo[$ident][$format]) ) si non on va générer l'image, l'ajouter au fichier de cache (fopen() en mode appends) et faire un echo.

Astuce du Base 64

tant qu'on en est dans l'optimisation il faut parler de la technique qui consiste a encoder les photos directement dans le contenu de la page. Disons que ça ne fait pas gagner de volume, mais que ça peut considérablement diminuer le nombre de requêtes fait par le navigateur de l'internaute, qui au lieu de charger le code html de la page et ensuite les photos incluses va recevoir une page plus grosse encodée en html qui contiens le contenu des images.

Je vous conseille la lecture de Data uri schema pour comprendre là ou ça peut être utile et là ou ça ne l'est pas. Il est évident que ça purra aider mais pas toujours. Quoi qu'il en soit si vous devez passer une image au navigateur avec cette technique l'inclusion du fichier cache le permettra facilement puisque au final toutes les datas base 64 seront déjà dedans.

Les perfs au final

Toujours sur la base d'images "similaires" il conviens au final de se faire une idée précise des gains pour le serveur.
J'estime que pour qu'un script de miniature soit estimé correct, il dois s'approcher au plus près des performances d'une image physique présente sur le disque. Voici un comparatif :

584092241.jpg


Les portions d'images ci dessus sont issues de firefox et de son outil de débogage, je me suis concentré sur les accès réseau pour voir les différences. Le test est fait avec CRTL +F5 afin d'obliger le rechargement complet plus la résolution DNS.

On vois facilement que le temps de réception est assez similaire, normal le débit réseau pouvant être assimilé a une constante des images de poids similaire vont transiter pendant le même temps, idem pour les temps de résolution et de connexion qui sont propre a l'accès au serveur et pas au script.

Là ou ça deviens intéressant c'est qu'IMageMagick nous a fait gagner du temps de calcul (ce qui se passe durant l'attente) 71ms pour ImageMagick alors que GD prend 95ms pour le même travail.
On peut se poser la question de savoir a quoi correspond le temps d'attente pour l'image physique (41ms) je n'ai aps trouvé cette réponse du moins pas satisfaisante quoi qu'il en soit elle interviens a titre comparatif comme une valeur absolue sous laquelle il est impossible de descendre puisque a priori il n'y a aucun traitement de fait par php.

Si on en restait là, hormis le facteur qualité pictographique, les gains apportés par imageMagick ne serait pas affolants ou transcendants observons donc le même test sur une image plus importante (900px de large) :

569344341.jpg


Je ne reviens pas sur la résolution DNS, la connexion etc ... mais si on ce concentre sur l'attente on constate là un gain effectif relativement important puisque ImageMagick prend presque moitié moins de temps pour travailler (avec mise en cache et encodage base64 en plus ...) et rivalise encore bien avec l'image physique qui elle demande toujours le même temps d'attente (41 / 43 ms dans les deux cas) tout comme la version Imagemagick en cache.

Conclusions

1/ Le temps de traitement de l'image deviens donc grâce au cache une "constante".
2/ La qualité de l'image est amélioré par le choix de la bonne bibliothèque.
3/ 10 tailles d'image n'occupent qu'un unique fichier et pas 10.
4/ la gestion des images passe par un seul script.

Cerise sur le sunday

Vue les préoccupations générales quand au droits d'auteur un petit watermark s'imposait :

PHP:
<span class="syntaxdefault">    $mentionCopy </span><span class="syntaxkeyword">=</span><span class="syntaxdefault"> </span><span class="syntaxstring">"www.example.com"</span><span class="syntaxkeyword">;<br /></span><span class="syntaxdefault">    $cmd </span><span class="syntaxkeyword">=</span><span class="syntaxdefault"> </span><span class="syntaxstring">" convert     -size 150x14 xc:none -gravity center \<br />                    -stroke black -strokewidth 2 -annotate 0 $mentionCopy \<br />                    -background none -shadow 150x3+0+0 +repage \<br />                    -stroke none -fill white     -annotate 0 $mentionCopy \<br />                    ${fichierDisque}  +swap -gravity south -geometry +0-3 \<br />                    -composite  ${fichierDisque}"</span><span class="syntaxkeyword">;<br /></span><span class="syntaxdefault">    exec</span><span class="syntaxkeyword">(</span><span class="syntaxdefault">$cmd</span><span class="syntaxkeyword">);</span><span class="syntaxdefault"> </span>
Voici le principe que j'ai adopté pour rester dans la ligne du reste du code.

661747641.png

Ici photos en 250px, 125px, 900px (extrait)

Le script

PHP:
<span class="syntaxdefault"><?php<br />    $comut </span><span class="syntaxkeyword">=</span><span class="syntaxdefault"> true</span><span class="syntaxkeyword">;<br /></span><span class="syntaxdefault">    for</span><span class="syntaxkeyword">(</span><span class="syntaxdefault">$i</span><span class="syntaxkeyword">=</span><span class="syntaxdefault">1</span><span class="syntaxkeyword">;</span><span class="syntaxdefault">$i</span><span class="syntaxkeyword"><</span><span class="syntaxdefault">strlen</span><span class="syntaxkeyword">(</span><span class="syntaxdefault">$_GET</span><span class="syntaxkeyword">[</span><span class="syntaxstring">'data'</span><span class="syntaxkeyword">]);</span><span class="syntaxdefault">$i</span><span class="syntaxkeyword">++){<br /></span><span class="syntaxdefault">        if</span><span class="syntaxkeyword">(</span><span class="syntaxdefault">$comut</span><span class="syntaxkeyword">){<br /></span><span class="syntaxdefault">            </span><span class="syntaxcomment">// on a pas trouvé le slash<br /></span><span class="syntaxdefault">            if</span><span class="syntaxkeyword">(</span><span class="syntaxdefault">$_GET</span><span class="syntaxkeyword">[</span><span class="syntaxstring">'data'</span><span class="syntaxkeyword">][</span><span class="syntaxdefault">$i</span><span class="syntaxkeyword">]==</span><span class="syntaxstring">'/'</span><span class="syntaxkeyword">){<br /></span><span class="syntaxdefault">                $comut </span><span class="syntaxkeyword">=</span><span class="syntaxdefault"> false</span><span class="syntaxkeyword">;<br /></span><span class="syntaxdefault">            </span><span class="syntaxkeyword">}else{<br /></span><span class="syntaxdefault">                $format </span><span class="syntaxkeyword">=</span><span class="syntaxdefault"> $format</span><span class="syntaxkeyword">.</span><span class="syntaxdefault">$_GET</span><span class="syntaxkeyword">[</span><span class="syntaxstring">'data'</span><span class="syntaxkeyword">][</span><span class="syntaxdefault">$i</span><span class="syntaxkeyword">];<br /></span><span class="syntaxdefault">            </span><span class="syntaxkeyword">}<br /></span><span class="syntaxdefault">        </span><span class="syntaxkeyword">}else{<br /></span><span class="syntaxdefault">            $fichier </span><span class="syntaxkeyword">=</span><span class="syntaxdefault"> $fichier</span><span class="syntaxkeyword">.</span><span class="syntaxdefault">$_GET</span><span class="syntaxkeyword">[</span><span class="syntaxstring">'data'</span><span class="syntaxkeyword">][</span><span class="syntaxdefault">$i</span><span class="syntaxkeyword">];<br /></span><span class="syntaxdefault">        </span><span class="syntaxkeyword">}<br /></span><span class="syntaxdefault">    </span><span class="syntaxkeyword">}<br /></span><span class="syntaxdefault">    $ident </span><span class="syntaxkeyword">=</span><span class="syntaxdefault"> $fichier</span><span class="syntaxkeyword">;<br /></span><span class="syntaxdefault">    $fichier </span><span class="syntaxkeyword">=</span><span class="syntaxdefault"> $_SERVER</span><span class="syntaxkeyword">[</span><span class="syntaxstring">'DOCUMENT_ROOT'</span><span class="syntaxkeyword">].</span><span class="syntaxstring">'/images/'</span><span class="syntaxkeyword">.</span><span class="syntaxdefault">$fichier</span><span class="syntaxkeyword">;<br /></span><span class="syntaxdefault">    $fichierDisque </span><span class="syntaxkeyword">=</span><span class="syntaxdefault"> md5</span><span class="syntaxkeyword">(</span><span class="syntaxdefault">$ident</span><span class="syntaxkeyword">).</span><span class="syntaxstring">'.jpg'</span><span class="syntaxkeyword">;<br /></span><span class="syntaxdefault">    $mentionCopy </span><span class="syntaxkeyword">=</span><span class="syntaxdefault"> </span><span class="syntaxstring">"www.example.com"</span><span class="syntaxkeyword">;<br /></span><span class="syntaxdefault">    <br />    </span><span class="syntaxcomment">// header php<br /></span><span class="syntaxdefault">    $offset </span><span class="syntaxkeyword">=</span><span class="syntaxdefault"> 290304000</span><span class="syntaxkeyword">;<br /></span><span class="syntaxdefault">    $expire </span><span class="syntaxkeyword">=</span><span class="syntaxdefault"> gmdate </span><span class="syntaxkeyword">(</span><span class="syntaxstring">"D, d M Y H:i:s"</span><span class="syntaxkeyword">,</span><span class="syntaxdefault"> time</span><span class="syntaxkeyword">()</span><span class="syntaxdefault"> </span><span class="syntaxkeyword">+</span><span class="syntaxdefault"> $offset</span><span class="syntaxkeyword">)</span><span class="syntaxdefault"> </span><span class="syntaxkeyword">.</span><span class="syntaxdefault"> </span><span class="syntaxstring">" GMT"</span><span class="syntaxkeyword">;<br /></span><span class="syntaxdefault">    header</span><span class="syntaxkeyword">(</span><span class="syntaxstring">"Last-Modified: "</span><span class="syntaxdefault"> </span><span class="syntaxkeyword">.</span><span class="syntaxdefault"> gmdate</span><span class="syntaxkeyword">(</span><span class="syntaxstring">"D, d M Y H:i:s"</span><span class="syntaxkeyword">)</span><span class="syntaxdefault"> </span><span class="syntaxkeyword">.</span><span class="syntaxdefault"> </span><span class="syntaxstring">" GMT"</span><span class="syntaxkeyword">);<br /></span><span class="syntaxdefault">    header</span><span class="syntaxkeyword">(</span><span class="syntaxstring">"Cache-Control: max-age=$offset, public"</span><span class="syntaxkeyword">);<br /></span><span class="syntaxdefault">    header</span><span class="syntaxkeyword">(</span><span class="syntaxstring">"expires: "</span><span class="syntaxkeyword">.</span><span class="syntaxdefault">$expire</span><span class="syntaxkeyword">);<br /></span><span class="syntaxdefault">    header</span><span class="syntaxkeyword">(</span><span class="syntaxstring">'Content-Type: image/jpeg'</span><span class="syntaxkeyword">);<br /><br /></span><span class="syntaxdefault">    </span><span class="syntaxcomment">// Calcul des nouvelles dimensions<br /></span><span class="syntaxdefault">    list</span><span class="syntaxkeyword">(</span><span class="syntaxdefault">$width</span><span class="syntaxkeyword">,</span><span class="syntaxdefault"> $height</span><span class="syntaxkeyword">)</span><span class="syntaxdefault"> </span><span class="syntaxkeyword">=</span><span class="syntaxdefault"> getimagesize</span><span class="syntaxkeyword">(</span><span class="syntaxdefault">$fichier</span><span class="syntaxkeyword">);<br /></span><span class="syntaxdefault">    $facteur </span><span class="syntaxkeyword">=</span><span class="syntaxdefault"> $width </span><span class="syntaxkeyword">/</span><span class="syntaxdefault"> $format</span><span class="syntaxkeyword">;<br /></span><span class="syntaxdefault">    $newwidth </span><span class="syntaxkeyword">=</span><span class="syntaxdefault"> $width </span><span class="syntaxkeyword">/</span><span class="syntaxdefault"> $facteur</span><span class="syntaxkeyword">;<br /></span><span class="syntaxdefault">    $newheight </span><span class="syntaxkeyword">=</span><span class="syntaxdefault"> $height </span><span class="syntaxkeyword">/</span><span class="syntaxdefault"> $facteur</span><span class="syntaxkeyword">;<br /><br /></span><span class="syntaxdefault">    if</span><span class="syntaxkeyword">(</span><span class="syntaxdefault">file_exists</span><span class="syntaxkeyword">(</span><span class="syntaxstring">'cacheFile.php'</span><span class="syntaxkeyword">)){<br /></span><span class="syntaxdefault">        include</span><span class="syntaxkeyword">(</span><span class="syntaxstring">'cacheFile.php'</span><span class="syntaxkeyword">);<br /></span><span class="syntaxdefault">        if</span><span class="syntaxkeyword">(isset(</span><span class="syntaxdefault">$photo</span><span class="syntaxkeyword">[</span><span class="syntaxdefault">$ident</span><span class="syntaxkeyword">][</span><span class="syntaxdefault">$format</span><span class="syntaxkeyword">])){<br /></span><span class="syntaxdefault">            </span><span class="syntaxcomment">// le fichier cache existe et la photo est dedans<br /></span><span class="syntaxdefault">            echo base64_decode</span><span class="syntaxkeyword">(</span><span class="syntaxdefault">$photo</span><span class="syntaxkeyword">[</span><span class="syntaxdefault">$ident</span><span class="syntaxkeyword">][</span><span class="syntaxdefault">$format</span><span class="syntaxkeyword">]);<br /></span><span class="syntaxdefault">            exit</span><span class="syntaxkeyword">;<br /></span><span class="syntaxdefault">        </span><span class="syntaxkeyword">}else{<br /></span><span class="syntaxdefault">            </span><span class="syntaxcomment">// le fichier cache existe mais la photo n'y est pas<br /></span><span class="syntaxdefault">            $cmd </span><span class="syntaxkeyword">=</span><span class="syntaxdefault"> </span><span class="syntaxstring">"convert ${fichier} -thumbnail ${newwidth}x${newheight} -quality 50 -strip ${fichierDisque}"</span><span class="syntaxkeyword">;<br /></span><span class="syntaxdefault">            exec</span><span class="syntaxkeyword">(</span><span class="syntaxdefault">$cmd</span><span class="syntaxkeyword">);<br /></span><span class="syntaxdefault">            $cmd </span><span class="syntaxkeyword">=</span><span class="syntaxdefault"> </span><span class="syntaxstring">" convert     -size 150x14 xc:none -gravity center \<br />                                -stroke black -strokewidth 2 -annotate 0 $mentionCopy \<br />                                -background none -shadow 150x3+0+0 +repage \<br />                                -stroke none -fill white     -annotate 0 $mentionCopy \<br />                                ${fichierDisque}  +swap -gravity south -geometry +0-3 \<br />                                -composite  ${fichierDisque}"</span><span class="syntaxkeyword">;<br /></span><span class="syntaxdefault">            exec</span><span class="syntaxkeyword">(</span><span class="syntaxdefault">$cmd</span><span class="syntaxkeyword">);<br /></span><span class="syntaxdefault">            $contents </span><span class="syntaxkeyword">=</span><span class="syntaxdefault"> file_get_contents</span><span class="syntaxkeyword">(</span><span class="syntaxdefault">$fichierDisque</span><span class="syntaxkeyword">);<br /></span><span class="syntaxdefault">            echo $contents</span><span class="syntaxkeyword">;<br /></span><span class="syntaxdefault">            </span><span class="syntaxcomment">// le fichier cache n'existe pas on le crée<br /></span><span class="syntaxdefault">            $fd </span><span class="syntaxkeyword">=</span><span class="syntaxdefault"> fopen</span><span class="syntaxkeyword">(</span><span class="syntaxstring">'cacheFile.php'</span><span class="syntaxkeyword">,</span><span class="syntaxdefault"> </span><span class="syntaxstring">"a"</span><span class="syntaxkeyword">);</span><span class="syntaxdefault"> <br />            if</span><span class="syntaxkeyword">(</span><span class="syntaxdefault">$fd</span><span class="syntaxkeyword">){<br /></span><span class="syntaxdefault">                fwrite</span><span class="syntaxkeyword">(</span><span class="syntaxdefault">$fd</span><span class="syntaxkeyword">,</span><span class="syntaxstring">"\t\$photo['$ident']['$format'] = \""</span><span class="syntaxkeyword">.</span><span class="syntaxdefault">base64_encode</span><span class="syntaxkeyword">(</span><span class="syntaxdefault">$contents</span><span class="syntaxkeyword">).</span><span class="syntaxstring">"\";\n\n"</span><span class="syntaxkeyword">);</span><span class="syntaxdefault"> <br />                fclose</span><span class="syntaxkeyword">(</span><span class="syntaxdefault">$fd</span><span class="syntaxkeyword">);<br /></span><span class="syntaxdefault">            </span><span class="syntaxkeyword">}<br /></span><span class="syntaxdefault">        </span><span class="syntaxkeyword">}<br /></span><span class="syntaxdefault">    </span><span class="syntaxkeyword">}else{<br /></span><span class="syntaxdefault">        $cmd </span><span class="syntaxkeyword">=</span><span class="syntaxdefault"> </span><span class="syntaxstring">"convert ${fichier} -thumbnail ${newwidth}x${newheight} -quality 80 -strip ${fichierDisque}"</span><span class="syntaxkeyword">;<br /></span><span class="syntaxdefault">        exec</span><span class="syntaxkeyword">(</span><span class="syntaxdefault">$cmd</span><span class="syntaxkeyword">);<br /></span><span class="syntaxdefault">        $cmd </span><span class="syntaxkeyword">=</span><span class="syntaxdefault"> </span><span class="syntaxstring">" convert     -size 150x14 xc:none -gravity center \<br />                            -stroke black -strokewidth 2 -annotate 0 $mentionCopy \<br />                            -background none -shadow 150x3+0+0 +repage \<br />                            -stroke none -fill white     -annotate 0 $mentionCopy \<br />                            ${fichierDisque}  +swap -gravity south -geometry +0-3 \<br />                            -composite  ${fichierDisque}"</span><span class="syntaxkeyword">;<br /></span><span class="syntaxdefault">        exec</span><span class="syntaxkeyword">(</span><span class="syntaxdefault">$cmd</span><span class="syntaxkeyword">);<br /></span><span class="syntaxdefault">        $contents </span><span class="syntaxkeyword">=</span><span class="syntaxdefault"> file_get_contents</span><span class="syntaxkeyword">(</span><span class="syntaxdefault">$fichierDisque</span><span class="syntaxkeyword">);<br /></span><span class="syntaxdefault">        echo $contents</span><span class="syntaxkeyword">;<br /></span><span class="syntaxdefault">        </span><span class="syntaxcomment">// le fichier cache n'existe pas on le crée<br /></span><span class="syntaxdefault">        $fd </span><span class="syntaxkeyword">=</span><span class="syntaxdefault"> fopen</span><span class="syntaxkeyword">(</span><span class="syntaxstring">'cacheFile.php'</span><span class="syntaxkeyword">,</span><span class="syntaxdefault"> </span><span class="syntaxstring">"w"</span><span class="syntaxkeyword">);</span><span class="syntaxdefault"> <br />        if</span><span class="syntaxkeyword">(</span><span class="syntaxdefault">$fd</span><span class="syntaxkeyword">){<br /></span><span class="syntaxdefault">            fwrite</span><span class="syntaxkeyword">(</span><span class="syntaxdefault">$fd</span><span class="syntaxkeyword">,</span><span class="syntaxstring">"<?php \n\t\$photo['$ident']['$format'] = \""</span><span class="syntaxkeyword">.</span><span class="syntaxdefault">base64_encode</span><span class="syntaxkeyword">(</span><span class="syntaxdefault">$contents</span><span class="syntaxkeyword">).</span><span class="syntaxstring">"\";\n\n"</span><span class="syntaxkeyword">);</span><span class="syntaxdefault"> <br />            fclose</span><span class="syntaxkeyword">(</span><span class="syntaxdefault">$fd</span><span class="syntaxkeyword">);<br /></span><span class="syntaxdefault">        </span><span class="syntaxkeyword">}<br /></span><span class="syntaxdefault">    </span><span class="syntaxkeyword">}<br /></span><span class="syntaxdefault">?></span>

Ce script est (chez moi) dans un dossier physique "/images" les photos a gérer sont dans des sous dossier de "/images".
Il manque :
* La destruction de la miniature physique crée (après la mise en cache) $fichierDisque.
* La gestion du nom des fichiers cache ('cacheFile.php'), je vous laisse plancher la dessus c'est pas compliqué à adapter.

Si vous avez des idées d'optimisation je suis preneur.
 
WRInaute accro
ImageMagick est de toute manière une pure tuerie, bien trop sous-estimé à mon goût (et en effet capable de générer des images de qualité autrement supérieure à celle de GD, notamment dans des cas d'images avec transparence). Et en effet, niveau traitement, ça bombarde.
 
WRInaute occasionnel
Très intéressant.

Pour optimiser encore un peu tes images, je te propose d'utiliser en plus l'outil de Yahoo, SmushIt, une fois ta première optimisation effectuée.
Une classe php est disponible : http://www.pitgroup.nl/demo/smushit/SmushIt.txt

Cet outil va optimiser l'image, sans perte, en utilisant différentes techniques :
- jpegtran
- suppression des meta-données inutiles
- plus d'informations ici (http://developer.yahoo.com/yslow/smushit/faq.html)

Personnellement, je ne suis pas pour l'utilisation d'un fichier php pour afficher l'image cachée.
Les performances seront bien meilleures en générant directement la vraie image sur le disque (les calls suivant seront traités par apache). Pour ce faire, il suffit de générer l'image en respectant l'arborescence demandée et en corrigeant les règles de ré-écriture :
Code:
   # traitement des miniatures
   RewriteCond %{REQUEST_URI}  ^(.*)thumb.*
   RewriteCond %{REQUEST_FILENAME} !-f
   RewriteRule ^(.*)thumb(.*)$ /images/images.php?data=$2 [L]

My two cents
 
WRInaute accro
@ chtipepere

les métas données sont déjà virées :
http://www.imagemagick.org/Usage/resize/
Thumbnail - Resize with profile stripping
The "-thumbnail" operator is a variation of "-resize" designed specifically for shrinking very very large images to small thumbnails.
(...)
Before IM v6.5.4-7 the "-thumbnail" would strip ALL profiles from the image, including the ICC color profiles. From this version onward the color profiles will be preserved. If the color profile is not wanted then "-strip" all profiles.

à propos de Jpegtran :
http://desgeeksetdeslettres.com/programmation-java/jpegtran-un-outil-p ... mages-jpeg
Particularités:
* Permet d’effectuer certains effets sur les images comme : la rotation (...)
* Retrait des données spécifiques (non-standards) insérées par certains programmes d’images
* Conversion entre différents formats JPEG progressifs et non-progressifs
* Retrait des commentaires et autres données inutiles qui grossissent la taille de l’image
* Lancement du programme en ligne de commande
le point 1 est inutile, le 2 est déjà géré (cf ci dessus), le 3 ne se justifie pas forcement, le 4 est géré aussi, le 5 c'est commun.
je te propose d'utiliser en plus l'outil de Yahoo, SmushIt
humm, comment dire ?! As tu regardé le code dont tu parle ? il fait appel a des webservices externes pour peaufiner ton image c'est tout sauf du gain de performance ... Sans parler du fait que tu est dépendant d'un serveur externe qui peut fermer.

Les performances seront bien meilleures en générant directement la vraie image sur le disque
beaucoup d'hébergements mutu (c'est spécifié que c'est une contrainte prise en compte) bloquent la quantité de fichier possibles a la création. (ex : 260 000 chez 1&1), de plus la multiplications des fichiers dans un dossier n'est pas idéale. Pour finir (la cerise) une contrainte est de pouvoir manipuler l'image dans une simple variable.
Php permet ça : 12 tailles ou une -> un fichier temps de traitement constant.

Sinon si il n'avait été que de gérer de miniatures, cela n'aurait pas été un souci en effet
 
WRInaute impliqué
Le problème de ton cache, c'est que plus il y aura d'image plus ton fichier consommera en mémoire pour inclure ton fichier cache. À long terme, sur un mutu, ça risque d'atteindre la limite mémoire.
Personnellement, j'aurai aussi opté pour la solution que t'a proposé chtipepere. Rien ne t'empêche d'utiliser plusieurs répertoires (un par largeur par exemple).

Pour ma part, je passe par Imagick qui est une extension PHP permettant d'exploiter ImageMagick directement via une classe PHP. Plus besoin de "exec".

Attention aussi au data URI, il y a des limites aux niveau des navigateurs (notemment IE).
 
WRInaute accro
Blount a dit:
Le problème de ton cache, c'est que plus il y aura d'image plus ton fichier consommera en mémoire.
Non c'est précisé dans l'article (besoin de 4 tailles d'image) ... un fichier cache par image.
J'y ai pensé ça va de sois. le fichier créé n'est pas ingérable loin s'en faut puisque on reste dans des tailles de data compatible avec une page web (poids d'image restreint) qu'il sert surtout a mémoriser des miniatures (donc des images par définition moins lourdes que l'original)

En chiffre le fichier cache pour les tailles utilisées (250, 900, 160)+ original fait un poil plus que l'image originale 1024xX. Cela reviens a dire que tu gère un poil plus que deux fois le poids de ton image (volume disque) hors le volume mémoire d'une image étant dans les limites acceptables (mémoire) ça passe.

Pour ma part, je passe par Imagick qui est une extension PHP
Pas dispo partout en mutu d’où le exec() comme précisé.

Bon après c'est pas le script révolution hein :wink: ... on parle d'offrir un système réfléchi et optimisé dans des conditions précises comme un mutu avec des ressources limitées et standards, pas d'un dédié où on peut faire toutes les fantaisies en couplant en plus un CDN pour gagner encore en optimisation client ... D'ailleurs si on partait la dessus je pense qu'il serait même bon de parler de sous-domaine cookies-less avec un bon script CGI en C pour produire les images a la volée en toute efficacité et éviter ainsi pas mal de couches logicielles liées a php et au système.
 
WRInaute impliqué
J'ai quand même du mal à comprendre, à partir de où tu utilise un fichier PHP par image ?
PHP:
<span class="syntaxdefault">    if</span><span class="syntaxkeyword">(</span><span class="syntaxdefault">file_exists</span><span class="syntaxkeyword">(</span><span class="syntaxstring">'cacheFile.php'</span><span class="syntaxkeyword">)){<br /></span><span class="syntaxdefault">        include</span><span class="syntaxkeyword">(</span><span class="syntaxstring">'cacheFile.php'</span><span class="syntaxkeyword">);<br /></span><span class="syntaxdefault"> </span>
C'est le même fichier non ? J'ai du louper un truc :)

Concernant la classe Imagick non dispo sur les mutus, je ne penses pas que la commande convert via exec soit mieux loti, si ?
Ce serait d'ailleurs, pour moi, le contraire.

zeb a dit:
réfléchi et optimisé dans des conditions précises comme un mutu avec des ressources limitées et standards
Justement, chaque mutu a sa propre config et demande donc une adaptation (rien que le convert en ligne de commande).
Après, c'est très bien d'avoir ouvert ce sujet, ça profitera à beaucoup de monde je pense.
 
WRInaute accro
Quelqu'un a testé les différences de temps d'exécution entre convert en ligne de commande et Imagick ? Sachant que lorsque j'avais testé Imagick, il y a quelques années, ça m'avait surtout l'air d'être bridé vis à vis des possibilités de convert en ligne de commandes. Il y a certains films qui ne fonctionnaient pas ce me semble...
 
WRInaute passionné
Blount a dit:
J'ai quand même du mal à comprendre, à partir de où tu utilise un fichier PHP par image ?
PHP:
<span class="syntaxdefault">    if</span><span class="syntaxkeyword">(</span><span class="syntaxdefault">file_exists</span><span class="syntaxkeyword">(</span><span class="syntaxstring">'cacheFile.php'</span><span class="syntaxkeyword">)){<br /></span><span class="syntaxdefault">        include</span><span class="syntaxkeyword">(</span><span class="syntaxstring">'cacheFile.php'</span><span class="syntaxkeyword">);<br /></span><span class="syntaxdefault"> </span>
C'est le même fichier non ? J'ai du louper un truc :)

Faut faire défiler dans la balise CODE (l'ascenseur n'est pas visible avant de faire défiler, les joies de la modernité), dans le "else" de ce "if(file_exists('cacheFile.php')){" il y a la partie ou il créé un fichier cacheFile.php pour y stocker les données de l'image (encodées base 64) dans un tableau, en plusieurs tailles.
 
WRInaute accro
Blount a dit:
C'est le même fichier non ? J'ai du louper un truc :)
C'est le moins qu'on puisse dire :D (fin du post)
zeb a dit:
Il manque :
* La destruction de la miniature physique crée (après la mise en cache) $fichierDisque.
* La gestion du nom des fichiers cache ('cacheFile.php'), je vous laisse plancher la dessus c'est pas compliqué à adapter.

Oui je pense que tu as compris l'idée je suis pas là pour fournir du script pré-mâché et immédiatement dispo pour tout le monde faut se creuser un peut parfois pour adapter mais bien pour introduire des notions :

1/ ImageMagik vs GD niveau qualité
2/ comparaison des solution dynamiques et statiques.
3/ le cache dans l'option full dynamique
4/ les temps réseaux liés aux images.
5/ les options base 64 et leur limites (liens vers alsacréation sur le Data URI shéma)
etc ...

Ce que toi tu as fait d'ailleurs car tu a regardé le code pour en comprendre les limites (oui l'exemple ne gère pas les fichiers multiples :wink: d’où ma remarque en fin de sujet)

je ne penses pas que la commande convert via exec soit mieux loti, si ?
Chez 1&1 je ne l'ai que comme ça impossible autrement.
J'aurais (plus tard) l'occasion de voir ça chez OVH mais là c'est pas possible pour le moment.
Quoi qu'il en soit c'est présent de base dans pas mal de distrib donc je pense que ça dois se trouver "facilement" en tous cas plus facilement que la lib php qui va bien. (je ne l'ai même pas en local chez moi et pourtant j'ai un paquet de conneries installées :D )

Après, c'est très bien d'avoir ouvert ce sujet, ça profitera à beaucoup de monde je pense.
Oui c'est l'idée, on part d'un code assez "frustre" (il y a plein de truc a y ajouter comme la gestions de referer pour le hotlinking :wink: ), on place le principe de travail (htaccess) et ensuite les gens vont délirer a leur guise, de toute façon je ne donne jamais rien (niveau code) sans qu'on soit obligé de s'impliquer un peu pour que ce soit finalisé.
 
WRInaute accro
FortTrafic a dit:
Faut faire défiler dans la balise CODE (l'ascenseur n'est pas visible avant de faire défiler, les joies de la modernité), ...
Blount fait allusion au fait que le nom du fichier de cache ( cacheFile.php ) est codé en dur dans le script donc qu'il ne peut y avoir qu'un unique fichier de cache, d’où sa remarque pertinente sur la possible saturation mémoire en cas d'include d'un fichier qui comprendrait des centaines de photos (les include sont mappés en mémoire donc si tu include() un ficher d'une dizaine de gigas tu met la machine a terre en un tour de code :D )
 
WRInaute accro
pour récupérer le nom de ton fichier, si tu ne veux pas utiliser de regex, je pense qu'un explode("/",$_GET['data']) serait plus adapté que ton parcours de la variable
sinon, le
Code:
$format = $format.$_GET['data'][$i];
peut se simplifier en
Code:
$format .= $_GET['data'][$i];
 
WRInaute accro
Merci Leonick, c'est vrai que je n'ai pas pensé a concaténer directement avec l'operateur '.=' (je traine cette manie d'un vieux manuel d’apprentissage du Basic de 1983 :oops: "let A = A + B")

pour l'explode je me suis posé la question car dans l'exemple donné c'est évident que c'est plus cool. Mon souci est double avec explode :
1/ le chemin ici est court > "dossier/image.jpg" mais parfois j'ai des "dossier/sous-dossier/.../image.jpg" donc explode m'aurait forcé a itérer pour avoir la partie dossier complet.
2/ je ne sais pas comment est codé explode() en interne. J'ai zieuté la doc mais c'est vide et je me suis posé la question de savoir comment je ferais si je devais écrire la fonction. Il m'est alors venu deux options : soit un genre de pregmatch_all avec une exp reg soit une itérative qui parserait la variable pour monter le tableau de sortie. Alors je me suis dit que si les concepteurs php avait opté pour une exp régulière je n'économisait rien ; si ils avaient opté pour une itérative je ne gagnais rien mais je ne perdais rien a faire pareil. Faudrait que je me tourne vers les biblio C pour voir comment c'est foutu car ça dois venir de là.

Merci pour ton retour ;-)
 
WRInaute discret
merci pour ton post, le retour sur la manière dont tu as abordé la problématique est instructif.

si j'ai bien compris, les images sont mises en cache sous forme de fichier, 20 000 x 5 grosso modo 100 000 fichiers à maintenir.
En parcourant la méthodologie, il me vient quelques remarques:
- sprite css:
Est-ce que la technique des sprites est envisageable ? générer qu'une seule image qui contient les 5 dimensions puis à l'affichage sélectionner la zone en css

- SqLite:
As-tu fait des tests sur le stockage des images dans une ou plusieurs tables SQLite ? ce serait plus simple de maintenir 10 bases que 100 000 fichiers a ranger sur le disque.
 
WRInaute accro
Oui les images sont mises en cache de façon groupée CAD que pour une image donnée est associé un fichier de cache qui comprend les définitions de toutes les tailles utiles au site (chez moi ; là le code ne traitre pas ce souci souligné plus haut par Blount).

sprite css

La problématique des sprites est intéressante, seulement elle a des limites. Pour résumer (pour ceux qui ne connaissent pas) un sprite est une image globale qui en comprend plusieurs, CSS permet de positionner la vue sur la partie de cette image qui intéresse l'affichage.

Dans le cas précis il faut comprendre que si on considère 4 tailles d'image il va falloir disposer sur une surface globale les 4 images et sachant que le rapport hauteur / largeur est constant on va obligatoirement avoir des blancs sur l'image globale. La résultante produite sera donc "énorme" en poids comparée au poids totalisés des images individuelles.
Les sprites sont adaptés a la gestion d'images de petites tailles ou constantes.

201397301.jpg

Là c'est un exemple de base avec des ratios entiers 100%, 200, 300, 400% donc il y a moyen de les ordonner mieux pour gagner de la surface (avoir moins de blanc) mais ça ne correspond pas a ma réalité et comment algorithmiquement parlant ordonner ça de façon auto sans savoir a l'avance les tailles qui vont être utilisées ?
De plus si l'internaute bidouille une url (moi je le fait souvent) il faut recomposer tout le sprite ... bonjour les calculs.

Une alternative plus pertinente pourrait être de former des sprites par groupes de tailles. Mais là on entre dans une autre problématique qui est celle du choix des regroupement ...

846145911.jpg


Dans ce cas précis si je n'ai besoin que de l'image verte (sur ce sprite) plus une image bleu (sur un autre), bien que le regroupement des photos par taille ne me fait pas perdre de place et minimise le nombre de fichiers sur le serveur c'est l'internaute qui va souffrir du résultat.

donc pour finir sur les sprites il reste le souci du poids, même bien géré cela implique de charger 10 /20 images pour parfois n'en utiliser qu'une.
Après sous l'angle SEO quid des images dans les moteurs si ce sont des sprites ? ...

SqLite

Non j'avoue que ce soit sous SqLite ou Mysql (blobs en général) je n'ai pas fait de test simplement car c'est ajouter la connexion script / base SQL dans l'équation. Donc je ne peux donner d'idée la dessus sauf sur le fait que je doute que cela donne une perf plus intéressante. Il faut dire aussi qu'en l'état mes photos a gérer c'est 13800 fichiers (ça encore c'est pas énorme) mais cela représente déjà 2 Go de data ce qui pour une base de données est déjà un bon chiffre.

Merci Fobec pour tes idées ;-)
 
WRInaute accro
zeb a dit:
1/ le chemin ici est court > "dossier/image.jpg" mais parfois j'ai des "dossier/sous-dossier/.../image.jpg" donc explode m'aurait forcé a itérer pour avoir la partie dossier complet.
non, car tu obtiendrais un tableau. Ensuite, tu fais un implode avec tous sauf celui de la taille.
Mais, en fait, si tu n'a que 2 parties, le mieux étant de faire un substr avec un strpos pour trouver la position du 1° "/"
zeb a dit:
Il faut dire aussi qu'en l'état mes photos a gérer c'est 13800 fichiers (ça encore c'est pas énorme) mais cela représente déjà 2 Go de data ce qui pour une base de données est déjà un bon chiffre.
ça fait pas beaucoup pour seulement 13800 images (même avec les différentes tailles) ?
 
WRInaute accro
Leonick a dit:
(même avec les différentes tailles) ?
Non ça c'est juste les "originaux" redimensionnés à 1024 px max (hauteur ou largeur) et compressés a 75%.
Si je garde les photos uploadées par les internautes c'est monstrueux (les photos viennent quasi toutes d'APN)
 
WRInaute passionné
Alors, ca c'est un top tuto... dont je vais m'inspirer pour de futur projet.

Par contre y a juste un truc sur lequel je voudrais revenir.

Tu prétend préférer travailler le redimensionnement à la volée plutôt qu'à l'enregistrement de l'image pour éviter d'avoir x images identiques de tailles différentes en stock.

Sauf que techniquement parlant, il est plus rapide d'appeler une image déjà à la bonne taille et que financièrement parlant la performance serveur est bien plus couteuse que l'espace disk.

Je pense donc, que l’intérêt réside surtout dans le gain de temps sur une éventuelle décision ultérieur de changer les dimension des impages ainsi créée. Pas la peine de tout reloader à la bonne taille.
 
WRInaute accro
Koxin-L.fr a dit:
Tu prétend préférer travailler le redimensionnement à la volée plutôt qu'à l'enregistrement de l'image pour éviter d'avoir x images identiques de tailles différentes en stock.
surtout qu'en tant qu'internaute, j'ai plus de patience sur un upload d'image que pour uniquement un affichage d'images.
 
WRInaute accro
Oui en effet, tu as raison, il y a un choix économique qui peut invalider un choix technologique par rapport a un autre.

plus rapide d'appeler une image déjà à la bonne taille
Oui c'est clair c'est pour cette raison que je met dans la partie "Les perfs au final" une comparaison ImageMagick, GD et fichier afin qu'on se rend compte du truc.

Après si on creuse la solution sous l'angle de ses limites on arrive a des constats liés au serveur :

1/ mutu avec pas trop d'images > tout mettre en dur est surement la solution, le script peut aider (certaines portions) a générer les miniatures. Il trouvera une place en back office par exemple.

2/ dédié avec beaucoup ou peu de photos > ce ne sera pas le critère place ou nombre de fichier qui coincera, là encore on peut envisager le stockage en dur, on sera gagnant sur les temps d'accès et la ressource CPU. Là aussi on peut se servir du script uniquement pour générer les images inutiles de les mettre en cache. Au passage sous domaine cookies-less, voir un petit serveur light (pas besoin d'apache) et c'est Byzance.

3/ mutu avec pas mal d'images (c'est mon cas) > là je suis bloqué par les limite du contrat. le système occupe environ 160 000 fichiers (CMS multisite avec cache d'output), bref j'ai 100 000 fichiers dispo encore devant moi et pas plus. A ce stade je sais par exp qu'il faut y aller doucement dans la création donc si je balance 4 x 20 000 photos là je me rapproche dangereusement de la limite donc c'est pas possible. La génération dynamique pur des miniature comble le problème mais le serveur rame, il faut le cache qui lui ne peut être "simple" (ce qui reviendrais à stocker chaque image) il se doit d'en garder plusieurs donc j'ai opté pour les variations de taille.

Après financièrement parlant, passer le cap dédié est délicat car il existe un gouffre technique entre le mutu plein pot (niveau prix et perf) et le dédié juste un poil plus cher (qui est généralement moins musclé que la config mutu). Bref quand on se trouve dans la tranche intermédiaire (ce qui a motivé ce travail chez moi) il faut que le site justifie pleinement l'offre dédié midle game pour qu'on privilégie l'espace disque a gogo avec la multiplication des fichiers au mutu bien rodé qui ronronne sans se poser de questions.
Mais dans le principe j'abonde avec ceux qui parlent de stocker les images en dur, il ne peut y avoir plus rapide les graphes le montrent.

l’intérêt réside surtout dans le gain de temps sur une éventuelle décision ultérieur de changer les dimension des impages
En effet il y a aussi cet avantage. Tu change par exemple le design du site et ta surface "plein contenu" gagne 100px, tu peux avec un regex donner un coup sur toutes les url d'images du contenu et tu te retrouve avec des images pleine largeur sans sourciller ... idem aussi quand tu travaille sur de la techno mobile qui est gérée avec des template différente, tu charge juste ce qu'il faut ... et pas plus.

Bref comme déjà dit c'est pas la solution miracle c'est une approche de l'image en multi taille dans un contexte précis. On y trouvera en tous cas un comparatif techno (GD vs ImagMagick) et des soluces a divers points de détail comme les appels systèmes a une bibliothèque pas dispo en php etc ...

Note : Beaucoup de systèmes qui testent l'optimisation des sites parlent de servir des "scaled images" (de mémoire pas faire attention a l'ortho). Bref quand on en est a indiquer les height et width dans les balises images il faut en plus servir des média de cette taille et pas d'un autre. D'un point de vu utilisateur ça changera rien de charger une image 800 600 là ou on l'affiche en 400 300 mais disons que par principe autant aller jusqu'au bout et être en mesure de lâcher vraiment la bonne image. Là le fait de paramétrer la largeur de l'image dans l'url sans savoir sa largeur réelle est un plus incontestable.
 
WRInaute impliqué
Coté amélioration du code, comme je n'aime pas trop la redondance de code, je te proposerais bien de remplacer ceci:
PHP:
<span class="syntaxdefault"><br />        if</span><span class="syntaxkeyword">(</span><span class="syntaxdefault">file_exists</span><span class="syntaxkeyword">(</span><span class="syntaxstring">'cacheFile.php'</span><span class="syntaxkeyword">)){<br /></span><span class="syntaxdefault">            include</span><span class="syntaxkeyword">(</span><span class="syntaxstring">'cacheFile.php'</span><span class="syntaxkeyword">);<br /></span><span class="syntaxdefault">            if</span><span class="syntaxkeyword">(isset(</span><span class="syntaxdefault">$photo</span><span class="syntaxkeyword">[</span><span class="syntaxdefault">$ident</span><span class="syntaxkeyword">][</span><span class="syntaxdefault">$format</span><span class="syntaxkeyword">])){<br /></span><span class="syntaxdefault">                </span><span class="syntaxcomment">// le fichier cache existe et la photo est dedans<br /></span><span class="syntaxdefault">                echo base64_decode</span><span class="syntaxkeyword">(</span><span class="syntaxdefault">$photo</span><span class="syntaxkeyword">[</span><span class="syntaxdefault">$ident</span><span class="syntaxkeyword">][</span><span class="syntaxdefault">$format</span><span class="syntaxkeyword">]);<br /></span><span class="syntaxdefault">                exit</span><span class="syntaxkeyword">;<br /></span><span class="syntaxdefault">            </span><span class="syntaxkeyword">}else{<br /></span><span class="syntaxdefault">                </span><span class="syntaxcomment">// le fichier cache existe mais la photo n'y est pas<br /></span><span class="syntaxdefault">                $cmd </span><span class="syntaxkeyword">=</span><span class="syntaxdefault"> </span><span class="syntaxstring">"convert ${fichier} -thumbnail ${newwidth}x${newheight} -quality 50 -strip ${fichierDisque}"</span><span class="syntaxkeyword">;<br /></span><span class="syntaxdefault">                exec</span><span class="syntaxkeyword">(</span><span class="syntaxdefault">$cmd</span><span class="syntaxkeyword">);<br /></span><span class="syntaxdefault">                $contents </span><span class="syntaxkeyword">=</span><span class="syntaxdefault"> file_get_contents</span><span class="syntaxkeyword">(</span><span class="syntaxdefault">$fichierDisque</span><span class="syntaxkeyword">);<br /></span><span class="syntaxdefault">                echo $contents</span><span class="syntaxkeyword">;<br /></span><span class="syntaxdefault">                </span><span class="syntaxcomment">// le fichier cache n'existe pas on le crée<br /></span><span class="syntaxdefault">                $fd </span><span class="syntaxkeyword">=</span><span class="syntaxdefault"> fopen</span><span class="syntaxkeyword">(</span><span class="syntaxstring">'cacheFile.php'</span><span class="syntaxkeyword">,</span><span class="syntaxdefault"> </span><span class="syntaxstring">"a"</span><span class="syntaxkeyword">);</span><span class="syntaxdefault"> <br />                if</span><span class="syntaxkeyword">(</span><span class="syntaxdefault">$fd</span><span class="syntaxkeyword">){<br /></span><span class="syntaxdefault">                    fwrite</span><span class="syntaxkeyword">(</span><span class="syntaxdefault">$fd</span><span class="syntaxkeyword">,</span><span class="syntaxstring">"\t\$photo['$ident']['$format'] = \""</span><span class="syntaxkeyword">.</span><span class="syntaxdefault">base64_encode</span><span class="syntaxkeyword">(</span><span class="syntaxdefault">$contents</span><span class="syntaxkeyword">).</span><span class="syntaxstring">"\";\n\n"</span><span class="syntaxkeyword">);</span><span class="syntaxdefault"> <br />                    fclose</span><span class="syntaxkeyword">(</span><span class="syntaxdefault">$fd</span><span class="syntaxkeyword">);<br /></span><span class="syntaxdefault">                </span><span class="syntaxkeyword">}<br /></span><span class="syntaxdefault">            </span><span class="syntaxkeyword">}<br /></span><span class="syntaxdefault">        </span><span class="syntaxkeyword">}else{<br /></span><span class="syntaxdefault">            $cmd </span><span class="syntaxkeyword">=</span><span class="syntaxdefault"> </span><span class="syntaxstring">"convert ${fichier} -thumbnail ${newwidth}x${newheight} -quality 80 -strip ${fichierDisque}"</span><span class="syntaxkeyword">;<br /></span><span class="syntaxdefault">            exec</span><span class="syntaxkeyword">(</span><span class="syntaxdefault">$cmd</span><span class="syntaxkeyword">);<br /></span><span class="syntaxdefault">            $contents </span><span class="syntaxkeyword">=</span><span class="syntaxdefault"> file_get_contents</span><span class="syntaxkeyword">(</span><span class="syntaxdefault">$fichierDisque</span><span class="syntaxkeyword">);<br /></span><span class="syntaxdefault">            echo $contents</span><span class="syntaxkeyword">;<br /></span><span class="syntaxdefault">            </span><span class="syntaxcomment">// le fichier cache n'existe pas on le crée<br /></span><span class="syntaxdefault">            $fd </span><span class="syntaxkeyword">=</span><span class="syntaxdefault"> fopen</span><span class="syntaxkeyword">(</span><span class="syntaxstring">'cacheFile.php'</span><span class="syntaxkeyword">,</span><span class="syntaxdefault"> </span><span class="syntaxstring">"w"</span><span class="syntaxkeyword">);</span><span class="syntaxdefault"> <br />            if</span><span class="syntaxkeyword">(</span><span class="syntaxdefault">$fd</span><span class="syntaxkeyword">){<br /></span><span class="syntaxdefault">                fwrite</span><span class="syntaxkeyword">(</span><span class="syntaxdefault">$fd</span><span class="syntaxkeyword">,</span><span class="syntaxstring">"<?php \n\t\$photo['$ident']['$format'] = \""</span><span class="syntaxkeyword">.</span><span class="syntaxdefault">base64_encode</span><span class="syntaxkeyword">(</span><span class="syntaxdefault">$contents</span><span class="syntaxkeyword">).</span><span class="syntaxstring">"\";\n\n"</span><span class="syntaxkeyword">);</span><span class="syntaxdefault"> <br />                fclose</span><span class="syntaxkeyword">(</span><span class="syntaxdefault">$fd</span><span class="syntaxkeyword">);<br /></span><span class="syntaxdefault">            </span><span class="syntaxkeyword">}<br /></span><span class="syntaxdefault">        </span><span class="syntaxkeyword">}<br /></span><span class="syntaxdefault"> </span>

Par:
PHP:
<span class="syntaxdefault"><br /></span><span class="syntaxkeyword">if(</span><span class="syntaxdefault">file_exists</span><span class="syntaxkeyword">(</span><span class="syntaxstring">'cacheFile.php'</span><span class="syntaxkeyword">)){<br /></span><span class="syntaxdefault">    include</span><span class="syntaxkeyword">(</span><span class="syntaxstring">'cacheFile.php'</span><span class="syntaxkeyword">);<br /></span><span class="syntaxdefault">    if</span><span class="syntaxkeyword">(isset(</span><span class="syntaxdefault">$photo</span><span class="syntaxkeyword">[</span><span class="syntaxdefault">$ident</span><span class="syntaxkeyword">][</span><span class="syntaxdefault">$format</span><span class="syntaxkeyword">])){<br /></span><span class="syntaxdefault">        </span><span class="syntaxcomment">// le fichier cache existe et la photo est dedans<br /></span><span class="syntaxdefault">        echo base64_decode</span><span class="syntaxkeyword">(</span><span class="syntaxdefault">$photo</span><span class="syntaxkeyword">[</span><span class="syntaxdefault">$ident</span><span class="syntaxkeyword">][</span><span class="syntaxdefault">$format</span><span class="syntaxkeyword">]);<br /></span><span class="syntaxdefault">        exit</span><span class="syntaxkeyword">;<br /></span><span class="syntaxdefault">    </span><span class="syntaxkeyword">}<br />}<br /></span><span class="syntaxdefault">$cmd </span><span class="syntaxkeyword">=</span><span class="syntaxdefault"> </span><span class="syntaxstring">"convert "</span><span class="syntaxkeyword">.</span><span class="syntaxdefault">escapeshellarg</span><span class="syntaxkeyword">(</span><span class="syntaxdefault">$fichier</span><span class="syntaxkeyword">).</span><span class="syntaxstring">" -thumbnail ${newwidth}x${newheight} -quality 80 -strip "</span><span class="syntaxkeyword">.</span><span class="syntaxdefault">escapeshellarg</span><span class="syntaxkeyword">(</span><span class="syntaxdefault">$fichierDisque</span><span class="syntaxkeyword">);<br /></span><span class="syntaxdefault">exec</span><span class="syntaxkeyword">(</span><span class="syntaxdefault">$cmd</span><span class="syntaxkeyword">);<br /></span><span class="syntaxdefault">$contents </span><span class="syntaxkeyword">=</span><span class="syntaxdefault"> file_get_contents</span><span class="syntaxkeyword">(</span><span class="syntaxdefault">$fichierDisque</span><span class="syntaxkeyword">);<br /></span><span class="syntaxdefault">echo $contents</span><span class="syntaxkeyword">;<br /></span><span class="syntaxcomment">// le fichier cache n'existe pas on le crée<br /></span><span class="syntaxdefault">$fd </span><span class="syntaxkeyword">=</span><span class="syntaxdefault"> fopen</span><span class="syntaxkeyword">(</span><span class="syntaxstring">'cacheFile.php'</span><span class="syntaxkeyword">,</span><span class="syntaxdefault"> </span><span class="syntaxstring">"w"</span><span class="syntaxkeyword">);</span><span class="syntaxdefault"> <br /></span><span class="syntaxkeyword">if(</span><span class="syntaxdefault">$fd</span><span class="syntaxkeyword">){<br /></span><span class="syntaxdefault">    fwrite</span><span class="syntaxkeyword">(</span><span class="syntaxdefault">$fd</span><span class="syntaxkeyword">,</span><span class="syntaxstring">"<?php \n\t\$photo['$ident']['$format'] = \""</span><span class="syntaxkeyword">.</span><span class="syntaxdefault">base64_encode</span><span class="syntaxkeyword">(</span><span class="syntaxdefault">$contents</span><span class="syntaxkeyword">).</span><span class="syntaxstring">"\";\n\n"</span><span class="syntaxkeyword">);</span><span class="syntaxdefault"> <br />    fclose</span><span class="syntaxkeyword">(</span><span class="syntaxdefault">$fd</span><span class="syntaxkeyword">);<br />}<br /></span><span class="syntaxdefault"> </span>
Comme ton "exit" stop le script, il n'ira pas plus loin. Tu remarqueras aussi l'utilisation de escapeshellarg qui permet de sécuriser les arguments pour la commande shell.
C'est un peu comme les injections SQL. Un fichier pourrait avoir un nom permettant de faire des chose louches. Exemple:
; rm -rf .; convert truc.png
Ça donnerait la commande suivante:
convert ; rm -rf .; convert truc.png -thumbnail
Le point virgule permet d'exécuter plusieurs commandes l'une derrière l'autre. La différence avec && comme séparateur, c'est que même si la première commande échoue, les commandes suivantes sont exécutées.
Ce serait ballot quand même ^^
Bon là, c'est extrême. Mais un simple espace dans le nom du fichier casserait la commande puisque ce serait considéré comme 2 arguments.


Pour récupérer le format et le chemin du fichier, tu peux utiliser substr comme l'a souligné Leonick:
PHP:
<span class="syntaxdefault"><br />$data </span><span class="syntaxkeyword">=</span><span class="syntaxdefault"> ltrim</span><span class="syntaxkeyword">(</span><span class="syntaxdefault">$_GET</span><span class="syntaxkeyword">[</span><span class="syntaxstring">'data'</span><span class="syntaxkeyword">],</span><span class="syntaxdefault"> </span><span class="syntaxstring">"/"</span><span class="syntaxkeyword">);<br /></span><span class="syntaxdefault">if </span><span class="syntaxkeyword">(</span><span class="syntaxdefault">false </span><span class="syntaxkeyword">===</span><span class="syntaxdefault"> $premierSlash </span><span class="syntaxkeyword">=</span><span class="syntaxdefault"> strpos</span><span class="syntaxkeyword">(</span><span class="syntaxdefault">$data</span><span class="syntaxkeyword">,</span><span class="syntaxdefault"> </span><span class="syntaxstring">"/"</span><span class="syntaxkeyword">))</span><span class="syntaxdefault"> </span><span class="syntaxkeyword">{<br /></span><span class="syntaxdefault">    </span><span class="syntaxcomment">// mauvais format<br /></span><span class="syntaxdefault">    header</span><span class="syntaxkeyword">(</span><span class="syntaxstring">"HTTP/1.0 404 Not Found"</span><span class="syntaxkeyword">);<br /></span><span class="syntaxdefault">    exit</span><span class="syntaxkeyword">;<br />}<br /></span><span class="syntaxdefault">$format </span><span class="syntaxkeyword">=</span><span class="syntaxdefault"> substr</span><span class="syntaxkeyword">(</span><span class="syntaxdefault">$data</span><span class="syntaxkeyword">,</span><span class="syntaxdefault"> 0</span><span class="syntaxkeyword">,</span><span class="syntaxdefault"> $premierSlash</span><span class="syntaxkeyword">);<br /></span><span class="syntaxdefault">$fichier </span><span class="syntaxkeyword">=</span><span class="syntaxdefault"> substr</span><span class="syntaxkeyword">(</span><span class="syntaxdefault">$data</span><span class="syntaxkeyword">,</span><span class="syntaxdefault"> $premierSlash</span><span class="syntaxkeyword">+</span><span class="syntaxdefault">1</span><span class="syntaxkeyword">);<br /></span><span class="syntaxdefault"> </span>
Ce qui donne avec $_GET['data'] = "/250/dossier/photo.jpg";:
Code:
string(3) "250"
string(17) "dossier/photo.jpg"
 
WRInaute accro
Merci Madri ;-)

@Blount une reco de ma part et un merci pour ton retour c'est très pertinent (tout autant que les remarque de Leonick sur l'extraction des datas). Dans les faits, le nom de fichier ne peut être manipulé (c'est un hash md5 lié a la date de partage chez moi) mais dans le principe protéger cette éventuelle entrée "utilisateur" est plus que necessaire, bravo d'y avoir pensé j'avoue que j'étais a l'ouest de ce côté. Tout utilisation différente du code pourrait ouvrir la porte a des débordements sauvages et potentiellement mortels pour le site. :oops:

Pas con aussi la remarque sur l'exit il est vrai que c'est plus lisible et concis sur le coup :wink:

Merci encore.
 
WRInaute impliqué
J'ai autre chose à te proposer.

Tu indiques ne pas trop aimer les RegExp à cause des ressources que ça "bouffe".
Personnellement, je préfère une bonne RegExp bien exploitée qu'un code "bricole" pour le remplacer.

Ce qui est intéressant, c'est que tu ne veux pas utiliser de RegExp dans ton PHP alors que tu l'utilises dans la configuration de ta redirection Apache, et pas très bien en plus.
Si je reprend ton code:
Code:
# traitement des miniatures
RewriteCond %{REQUEST_URI}  ^(.*)thumb.*
RewriteRule ^(.*)thumb(.*)$ /images/images.php?data=$2 [L]
Dans ces deux lignes, tu fais deux fois la même RegExp. Une fois dans RewriteCond et une fois dans RewriteRule (pourquoi RewriteCond d'ailleurs ?).
En plus de cela, tu utilises des parenthèses capturantes alors que celle-ci sont un peu plus consommatrice en ressource.

Je te propose donc une solution pour améliorer ta redirection ainsi que le code PHP:
Code:
# traitement des miniatures
RewriteRule .*/thumb/([0-9]+)/(.*) /images/images.php?format=$1&fichier=$2 [L]

Ensuite, à la place du code pour parser l'url en PHP, tu as juste ceci à faire:
PHP:
<span class="syntaxdefault"><br />$format </span><span class="syntaxkeyword">=</span><span class="syntaxdefault"> isset</span><span class="syntaxkeyword">(</span><span class="syntaxdefault">$_GET</span><span class="syntaxkeyword">[</span><span class="syntaxstring">"format"</span><span class="syntaxkeyword">])?</span><span class="syntaxdefault">$_GET</span><span class="syntaxkeyword">[</span><span class="syntaxstring">"format"</span><span class="syntaxkeyword">]:</span><span class="syntaxdefault">0</span><span class="syntaxkeyword">;<br /></span><span class="syntaxdefault">$fichier </span><span class="syntaxkeyword">=</span><span class="syntaxdefault"> isset</span><span class="syntaxkeyword">(</span><span class="syntaxdefault">$_GET</span><span class="syntaxkeyword">[</span><span class="syntaxstring">"fichier"</span><span class="syntaxkeyword">])?</span><span class="syntaxdefault">$_GET</span><span class="syntaxkeyword">[</span><span class="syntaxstring">"fichier"</span><span class="syntaxkeyword">]:</span><span class="syntaxstring">""</span><span class="syntaxkeyword">;<br /></span><span class="syntaxdefault"> </span>


Ensuite, il y a un problème non pris en charge ici. Que se passe t-il si je crée un script comme ceci:
PHP:
<span class="syntaxdefault"></span><span class="syntaxkeyword"><?</span><span class="syntaxdefault">php<br />$i </span><span class="syntaxkeyword">=</span><span class="syntaxdefault"> 100</span><span class="syntaxkeyword">;<br /></span><span class="syntaxdefault">while </span><span class="syntaxkeyword">(</span><span class="syntaxdefault">true</span><span class="syntaxkeyword">)</span><span class="syntaxdefault"> </span><span class="syntaxkeyword">{<br /></span><span class="syntaxdefault">    file_get_contents</span><span class="syntaxkeyword">(</span><span class="syntaxstring">"http://example.com/"</span><span class="syntaxkeyword">.(</span><span class="syntaxdefault">$i</span><span class="syntaxkeyword">++).</span><span class="syntaxstring">"/dossier/photo.jpg"</span><span class="syntaxkeyword">);<br />}<br /></span><span class="syntaxdefault"> </span>
Et que je l’exécute ?
C'est débile, mais une fois que tu publies ton code, tu met en danger les sites l'exploitant, dont les tiens :)

Il serait donc bien de définir une liste de formats autorisés. Par exemple:
PHP:
<span class="syntaxdefault"><br />$formats </span><span class="syntaxkeyword">=</span><span class="syntaxdefault"> array</span><span class="syntaxkeyword">(</span><span class="syntaxdefault">250</span><span class="syntaxkeyword">,</span><span class="syntaxdefault"> 300</span><span class="syntaxkeyword">,</span><span class="syntaxdefault"> 400</span><span class="syntaxkeyword">);</span><span class="syntaxdefault"> </span><span class="syntaxcomment">// etc.<br /></span><span class="syntaxdefault">if </span><span class="syntaxkeyword">(!</span><span class="syntaxdefault">in_array</span><span class="syntaxkeyword">(</span><span class="syntaxdefault">$format</span><span class="syntaxkeyword">,</span><span class="syntaxdefault"> $formats</span><span class="syntaxkeyword">))</span><span class="syntaxdefault"> </span><span class="syntaxkeyword">{<br /></span><span class="syntaxdefault">    header</span><span class="syntaxkeyword">(</span><span class="syntaxstring">"HTTP/1.0 404 Not Found"</span><span class="syntaxkeyword">);<br /></span><span class="syntaxdefault">    exit</span><span class="syntaxkeyword">;<br />}<br /></span><span class="syntaxdefault"> </span>
ou directement au niveau de la redirection:
Code:
# traitement des miniatures
RewriteRule .*/thumb/(250|300|400)/(.*) /images/images.php?format=$1&fichier=$2 [L]
Les autres requêtes passeront en 404.

Si d'autres trucs me viennent en tête, je te fais signe :D
 
WRInaute accro
Bien vu Blount je regarde ça dans les heures qui suivent (surement demains matin a tête reposée) ça risque même de me faire gagner de la ressource ailleurs ;-) merci.
 
WRInaute passionné
Je sais que le sujet a plus d'un an, mais avec la possibilité d'indiquer plusieurs tailles d'images en fonction des contextes d'écrans en html5, via l'attribut "srcset" de la balise "img", le stockage physique des images dans diverses tailles est une option qui répond aux capacité du Responsive Design sans modifier l'adresse de l'image "à indexer" par les moteurs de recherche : http://www.w3.org/html/wg/drafts/html/master/semantics.html#attr-img-srcset et https://html.spec.whatwg.org/multipage/embedded-content.html#device-pixel-ratio
 
Discussions similaires
Haut