Redimensionner une image au vol

1 01 2009

Comme vous le savez, je développe en ce moment une application Facebook. Dans cette application, je devrai afficher des images un peu partout, issues d’une sorte de base de données. Pour ne pas révéler le contenu de mon application, supposons qu’il s’agisse de pochettes de CDs. Je dispose donc d’un répertoire dans lequel se trouvent des milliers d’images de pochettes, généralement en bonne qualité, assez grandes. Mais un peu comme sur last.fm, je devrais pouvoir afficher une série de pochettes de taille plus petite, disons 64 pixels de large.

Pour redimesionner ces images, j’ai deux possibilités:

  • Faire un traitement en batch avec un logiciel conçu pour des conversions en séries.
  • Convertir au vol ces images via GD ou ImageMagick

La première solution ne me convient pas, car de nouvelles pochettes seront ajoutées au fil du temps, et je ne veux pas être obligé de refaire une conversion de temps à autres. La seconde est gourmande en ressources, la conversion d’images prend un grand nombre de cycles processeur, et s’il y a ne serait-ce que 50 personnes qui veulent afficher une page contenant 20 de ces images, le serveur risque de planter.

L’idéal est un mélange entre ces deux solutions. Le redimensionnement de l’image par ImageMagick (ou GD) suivi de son stockage dans un autre répertoire. Mon script va donc faire cela. Cependant cela donne lieu à un problème technique. Comment savoir si l’image a déjà été convertie auparavant?

Ma solution à ce problème est tellement logique qu’elle ne saute pas aux yeux : ne lancer le script de redimensionnement que si l’image stockée n’existe pas.

En pseudo-code, cela donnerait ceci:

1
2
3
4
5
if(is_file($fichier_demandé)) {
  envoyer_le_fichier();
} else {
  generer_le_fichier_redimensionne();
}

Mais cela voudrait dire qu’on lance un script php et fait un accès au disque pour vérifier si le fichier existe ou pas. Il y a mieux à faire…

Au lieu de faire ce script qui vérifie si le fichier existe ou pas, ne lancer ce script qu’a condition que le fichier n’existe pas à l’endroit attendu. Et comment faire en sorte que cela soit automatisé, ou géré par le serveur lui-même?

ErrorDocument 404 404.php

Toutes mes images sont stockées dans le répertoire /i/ de ma racine de site web. Donc j’ai mis cette directive dans /i/.htaccess.

Mon script n’est ainsi appelé que si l’image n’existe pas. Et au lieu d’afficher le sempiternel message d’erreur, je corrige cette erreur en créant le fichier redimensionné. D’autant que vu qu’on appelle une image, afficher un “message d’erreur” ne sert à rien…

Example:

1
<img src="/i/w=64/pochette/Aphex-Twin/Hangable-Auto-Bulb.jpg" />

Code source de 404.php:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
<?php
/* Initialisation des variables */
$requestedWidth = 0;    // Largeur demandée
$requestedHeight= 0;   // Hauteur demandée
$filename='';            // Nom du fichier original (avec chemin)
$requestedFilename = ''; // Nom de fichier redimensionné (avec chemin)
$basePath = '/var/foo/mon_site/';

/* Parcours de l'url demandée */
if(ereg("/w=([0-9]+)/", $_SERVER['REQUEST_URI'], $regs)) {
    $requestedWidth = (int)$regs[1];
}
unset($regs);
if(ereg('/h=([0-9]+)/', $_SERVER['REQUEST_URI'], $regs)) {
    $requestedHeight = (int)$regs[1];
}
unset($regs);

/* Détermine le fichier à lire */
ereg('i/[wh]{1}=[0-9]*/([a-zA-Z0-9/-\.]*)', $_SERVER['REQUEST_URI'], $regs);
if(!is_file($basePath.'i/'.$regs[1])) {
    error_log("The path doesn't lead to a file: ".$basePath.$regs[1]);
}
if(!is_readable($basePath.'i/'.$regs[1])) {
    error_log('The file is not readable:'.$basePath.$regs[1]);
}
$filename = $basePath.'i/'.$regs[1];
unset($regs);

/* Détermine le fichier à créer */
ereg('(i/[wh]{1}=[0-9]*/[a-zA-Z0-9/-\.]*)', $_SERVER['REQUEST_URI'], $regs);
$requestedFilename = $basePath.$regs[1];
unset($regs);

$image = new Imagick();
$image->readImage($filename);
$image->resizeImage($requestedWidth, $requestedHeight, imagick::FILTER_LANCZOS, 1);
if(!is_dir(dirname($requestedFilename))) {
    mkdir(dirname($requestedFilename), 0777, true);
}
$image->writeImage($requestedFilename);

header('Content-Type: image/'.$image->getImageFormat());
echo $image;

?>

N’oubliez pas, si vous utilisez ce script, que sur mon serveur, toutes les images sont dans /var/foo/mon_site/i/. Aussi, vu que ce répertoire ne contient QUE des images, je ne vérifie pas si le visiteur demande une image ou une page web normale… Pensez donc à faire cette vérification si vous utilisez ce script en tant que page 404 sur tout le serveur, au lieu de limiter l’emploi de celle-ci à un répertoire donné via un .htaccess ou une directive dans votre .conf Apache…

Enfin, une demande vers l’image donnée en exemple créera un répertoire /var/foo/mon_site/i/w=64/pochette/……

Dernière considération: ce script ne peut gérer le redimensionnement que selon la largeur OU la hauteur, et redimensionne en conservant le rapport largeur/hauteur original.



Optimization de mon LAMP

20 07 2008

Une chose est d’avoir une machine (plus ou moins) puissante pour gérer un LAMP1, une autre est d’optimizer son fonctionnement. En effet, si un LAMP basique peut traiter un certain nombre de conexions simultanées, optimiser la configuration de ce même LAMP permet de servir ces requètes plus rapidement, ou d’en servir un plus grand nombre.

Je compte ajouter quelques améliorations à mon LAMP dans les jours et les mois qui suivent, et les indiquerai dans ce post. Lire la suite »

  1. LAMP est l’acronyme de Linux, Apache, MySQL, P(HP|erl|ython), qui sont les éléments classiques présents sur un serveur web. []


Pas encore au point

10 07 2008

J’ai donc réinstallé mon Lamp ce week-end, et j’ai eu du mal avec l’UTF-8 dans Apache/PHP/MySql/Wordpress, enfin en fin de compte, j’ai réparé ce bug sans trop savoir comment… Mais là, en deux jours, j’ai eu droit à deux plantages de MySql, qui connement, ne redémarre pas tout seul.

J’ai chipoté à la config et l’ai relancé. Croisons les doigts!

Mise à jour: Bah le système a complètement planté cette fois. J’ai supprimé un max de modules dont je n’ai pas besoin dans mon make.conf et j’ai recompilé puis relancé. On verra bien. J’hésite à aller m’acheter un nouveau serveur, basé sur un Athlon X2 64 avec 2Go de ram DDR2-800, et y flanquer mon disque dur SATA comme maître. Le seul truc qui me gonfle, c’est de devoir racheter une alim juste pour le temps de configurer la nouvelle machine…