Certains développeurs/webmasters sont souvent confrontés à ce problème de compression/optimisation des images tout en gardant leur qualité afin de rendre toujours plus fluide la navigation sur son site web.
Dans cet article je vais vous proposer de :
- Trouver les images non-optimisées sur ses pages avec Page Speed et YSlow.
- Logiciels GUI pour compresser des images.
- Compresser des images en lignes de commande.
- Optimiser des images en PHP
Pour savoir si votre site contient des images pouvant être optimisées, je vous conseille d'utiliser l'addon Page Speed de Google.
Si vous êtes sous Firefox, il vous faudra au préalable télécharger Firebug puis vous rendre sur cette page pour télécharger l'addon Page Speed pour Firebug.
Si vous êtes sous Chrome il faudra suivre cette procédure afin d'activer le mode expérimental du navigateur pour utiliser Page Speed.
Maintenant que vous avez Page Speed, lançons un petit scan d'une page web.
Dans cet exemple, Page Speed nous montre que des images peuvent être optimisées et ainsi gagner jusqu'à 30.4 Ko ce qui n'est pas négligeable bien que minime.
Vous avez sans doute remarqué que Page Speed nous propose une version optimisée de chaque image à la volée. Cela concerne seulement les images de type JPEG et PNG mais c'est déjà pas mal! Et en complément l'extension vous renvoie vers une page pour en apprendre un peu plus sur l'optimisation des images.
Dans le même style vous avez YSlow de Yahoo qui est disponible sur Firefox toujours en coexistence avec Firebug et sur Chrome et qui va aussi vous permettre de faire une analyse complète de votre page. Un complément d'informations est aussi disponible sur la page développeur de Yahoo pour en apprendre d'avantage sur l'optimisation des images.
Par rapport à Page Speed qui propose un traitement à la volée des images présentes sur la page, Yahoo nous propose un service en ligne baptisé Smush.it qui vous permettra d'optimiser vos images.
En dehors de Page Speed qui vous propose une compression à la volée des images présentes sur une page, vous avez un sacré lot d'outils disponibles pour effectuer des compressions par lot.
PngGauntlet est une application tournant sous Windows et qui permet d'optimiser et de convertir des images en PNG. Les images acceptées en entrée sont les JPG, GIF, TIFF, BMP et bien sûr les PNG.
Vous pouvez choisir de compresser un lot d'images ce qui est plutôt sympa et il dispose des algorithmes les plus puissants et reconnus pour l'optimisation des PNG tels que PNGOUT, OptiPNG et DeflOp afin de ne pas perdre en qualité. C'est une application vraiment puissante que je recommande fortement!
Caesium est un autre logiciel open-source tournant aussi sous Windows, très apprécié des photographes, qui vous permettra de compresser des images jusqu'à 90%. Il dispose d'un grand nombre de fonctionnalités comme un traitement d'images par lot, le réglage du niveau de compression, la prévisualisation des images, des suffixes personnalisés lors de traitements multiples et une conservation du format d'entrée qui peut être JPG, JPEG, PNG ou BMP.
Il utilise son propre algorithme qui fonctionne très bien et donne des résultats assez bluffants comme vous pouvez le voir sur ces samples.
Vous vous attendiez sûrement à voir passer RIOT (Radical Image Optimization Tool) dans cette liste, un logiciel très connu sur le web et de la même trempe que ceux cités ci-dessus.
Le plus avec RIOT c'est qu'il accepte un grand nombre d'images : JPG, BMP, GIF, JP2, PNG, TGA, TIFF, PSD, PCD, PXC, PPM, PBM, RAS, ICO, MNG, WBMP, XBM, XPM, HDR, SGI. Il fonctionne également sur Windows et peut être utilisé en tant que plugin avec IfranView, The Gimp et XNView.
Des logiciels comme PngGauntlet que j'ai cité plus haut utilisent comme algorithmes des outils s'exécutant via l'invite de commandes. Il en existe une multitude mais je vais vous citer ceux qui me paraissent les plus intéressants.
Comme son nom l'indique, Pngcrush permet d'optimiser des fichiers PNG sans perte. Il va se charger de réduire la taille du flux de données PNG IDAT en essayant plusieurs niveaux de compression via l'algorithme LZ77.
Un exemple de ligne de commande simple serait :
pngcrush -reduce -brute "file1.png" "file2.png"
Si vous souhaitez supprimer les données de correction de couleur :
pngcrush -rem gAMA -rem cHRM -rem iCCP -rem sRGB "file1.png" "file2.png"
Et pour supprimer les blocs auxiliaires et morceaux de texte :
pngcrush -rem alla -rem text "file1.png" "file2.png"
OptiPNG est un outil très connu recommandé par Google pour optimiser/compresser ses images toujours sans perte. Il va tenter de réduire la taille du PNG en essayant divers filtres et méthodes de compression. Il réduira automatiquement le nombre de bits en encodage, la palette de couleur si possible et corrigera certaines erreurs d'intégrité sur le fichier entrant.
Un exemple de ligne de commande serait :
optipng -o7 "file1.png" -out "file2.png"
Pour plus d'infos sur les options disponibles il y a le manuel en ligne.
Il ne faut pas non plus oublier les fameux fichiers JPG. Avec jpegtran, toujours conseillé par Google, vous pourrez compresser/optimiser vos JPG rapidement et sans perte en se basant sur la bibliothèque libjpeg. Il permet le retrait de données spécifiques non-standards insérées par certains programmes de traitement d'images, le retrait des commentaires et d'autres données inutiles.
Un exemple de ligne de commande serait :
jpegtran -copy none -optimize -perfect "file1.png" "file2.png"
Vous vous dites que cet article n'est pas le fruit du hasard? Et vous avez raison! En effet, après avoir fait le tuto pour installer ImageMagick et phMagick sur Wamp, vous vous doutiez bien que j'allais faire une autre partie sur l'optimisation d'image
Alors comme pour les logiciels qui permettent de faire du traitement par lot on va effectuer ce traitement par lot via une fonction PHP qui permet de lister tous les fichiers d'un répertoire ainsi que de ses sous-répertoires. J'ai pris les devants en la postant hier
On va tout d'abord se baser sur l'excellent ImageMagick en utilisant la classe PHP phMagick pour commencer. On ne va pour l'instant s'intéresser qu'aux fichiers de type JPG et PNG et on va prendre pour exemple le répertoire uploads de Wordpress.
Définissons le répertoire de base, le répertoire de destination et les extensions de fichiers autorisées dans un tableau :
$main_dir = "/home/www/wp-content/uploads/"; $dest_dir = "/home/www/wp-content/uploads_opt/"; $exts = array('jpg', 'png');
Récupérons tous les fichiers en filtrant les extensions via ma fonction :
function get_extension($file){ if( preg_match('/^[^\x00]+\.([a-z0-9]+)$/i', $file, $matchResult) ){ return strtolower($matchResult[1]); } } $files = array(); $others = array(); $i = 0; $nb_dirs = 0; $all_folders[$nb_dirs] = $main_dir; while( $i <= $nb_dirs ) { $current_dir = $all_folders[$i]; $handle = @opendir($current_dir); while( $file = readdir($handle) ) { if( $file != '.' && $file != '..' ) { if ( is_dir($current_dir . '/' . $file) ) { $nb_dirs++; $all_folders[$nb_dirs] = $current_dir . $file . '/'; } else { $file_ext = get_extension($file); if( in_array($file_ext, $exts) ) { $files[] = $current_dir . $file; } } } } closedir($handle); $i++; }
Sur Wordpress en général, les fichiers dans le répertoire uploads sont triés par date. On va donc mettre de côté ces répertoires afin de les créer. Mais ce n'est pas tout! Lorsque vous envoyez une image sur votre Wordpress, celle-ci est en général redimensionnée jusqu'à 4 tailles différentes avec le même nom de fichier mais se terminant par la taille définie. On va regrouper tout ça
$files_dim = array(); $i = 0; foreach( $files as $file ) { $filesplit = explode('/', $file); $ext = get_extension($file); $filename_ext = $filesplit[count($filesplit) - 1]; $filename = str_replace('.' . $ext, '', $filename_ext); $month = $filesplit[count($filesplit) - 2]; $year = $filesplit[count($filesplit) - 3]; $img_nosize = false; $redim = explode('-', $filename); if( count($redim) > 1 ) { $dims = $redim[count($redim) - 1]; $dims = explode('x', $dims); if( count($dims) > 1 && is_numeric($dims[0]) && is_numeric($dims[1]) ) { $filename_or = str_replace('-' . $redim[count($redim) - 1], '', $filename); $ext = $ext == 'jpeg' ? 'jpg' : $ext; $key_found = false; foreach( $files_dim as $key => $cur_file ) { if( $cur_file['filename'] == $filename_or ) { $key_found = $key; } } if( $key_found === false ) { $files_dim[$i]['path'] = $main_dir . $year . '/' . $month . '/' . $filename_or . '.' . $ext; $files_dim[$i]['filename'] = $filename_or; $files_dim[$i]['ext'] = $ext; $files_dim[$i]['year'] = $year; $files_dim[$i]['month'] = $month; $files_dim[$i]['nb'] = 1; $files_dim[$i]['size'][] = array( 'path' => $main_dir . $year . '/' . $month . '/' . $filename . '.' . $ext, 'width' => intval($dims[0]), 'height' => intval($dims[1]) ); $i++; } else { $files_dim[$key_found]['nb']++; $files_dim[$key_found]['size'][] = array( 'path' => $main_dir . $year . '/' . $month . '/' . $filename . '.' . $ext, 'width' => intval($dims[0]), 'height' => intval($dims[1]) ); } } else { $img_nosize = true; } } if( count($redim) == 1 || $img_nosize ) { $key_found = false; foreach( $files_dim as $key => $cur_file ) { if( $cur_file['filename'] == $filename ) { $key_found = $key; } } if( $key_found === false ) { $files_dim[$i]['path'] = $main_dir . $year . '/' . $month . '/' . $filename . '.' . $ext; $files_dim[$i]['filename'] = $filename; $files_dim[$i]['ext'] = $ext; $files_dim[$i]['year'] = $year; $files_dim[$i]['month'] = $month; $files_dim[$i]['nb'] = 1; $i++; } else { $files_dim[$key_found]['nb']++; } } }
C'est un peu barbare mais ça fonctionne très bien
Au final vous aurez quelque chose qui ressemble à ceci pour un élément du tableau :
Plus clair? Non? Alors un petit peu d'explications :
- path : realpath vers le fichier original.
- filename : nom du fichier sans son extension.
- ext : l'extension du fichier.
- year : l'année (répertoire parent)
- month : le mois (répertoire parent)
- nb : le nombre d'image générées par Wordpress (original compris)
- size : le tableau contenant le fichiers avec leurs tailles générées via l'original.
- size -> path : le chemin vers une des images redimensionnées.
- size -> width/height : largeur et hauteur de l'image.
Créons le répertoire de destination et les sous-répertoires afin d'avoir la même structure :
if( !file_exists($dest_dir) ) mkdir($dest_dir); foreach( $files_dim as $file ) { $folder_year = $dest_dir . $file['year'] . '/'; if( !file_exists($folder_year) ) mkdir($folder_year); $folder_month = $folder_year . $file['month'] . '/'; if( !file_exists($folder_month) ) mkdir($folder_month); }
Maintenant que tout est prêt, c'est parti pour la conversion! (enfin)
Alors afin d'optimiser au mieux le processus de traitement des images, je ne passe que par l'image originale. En effet lors de la conversion je vais effectuer un redimensionnement si l'image originale a été travaillée par Wordpress.
Pour être sûr d'arriver à un résultat optimal, je ne mets les fichiers compressés dans le répertoire de destination seulement si celui-ci a un poids inférieur à l'original, sinon je copie simplement le fichier original. J'ai aussi créé deux variables pour avoir un aperçu avant et après optimisation en stockant le poids de chaque fichier.
$old_size = 0; $new_size = 0; foreach( $files_dim as $file ) { $current = $file['path']; $new = str_replace($main_dir, $dest_dir, $current); $found = true; if( file_exists($current) ) { clearstatcache(true, $current); $filesize = filesize($current); $old_size += $filesize; $dim = getimagesize($current); $phmagick = new phmagick($current, $new); $phmagick->debug = true; $phmagick->convert(); clearstatcache(true, $new); $new_filesize = filesize($new); if( $new_filesize > $filesize ) { copy($current, $new); clearstatcache(true, $new); $new_filesize = filesize($new); } $new_size += $new_filesize; } else { $found = false; } if( isset($file['size']) && is_array($file['size']) ) { foreach( $file['size'] as $size ) { $current2 = $size['path']; $new2 = str_replace($main_dir, $dest_dir, $current2); if( file_exists($current2) && $found ) { clearstatcache(true, $current2); $filesize = filesize($current2); $old_size += $filesize; $dim = getimagesize($current2); $phmagick = new phmagick($new, $new2); $phmagick->debug = true; $phmagick->resize($size['width'], $size['height']); clearstatcache(true, $new2); $new_filesize = filesize($new2); if( $new_filesize > $filesize ) { $copy = copy($current2, $new2); clearstatcache(true, $new2); $new_filesize = filesize($new2); } $new_size += $new_filesize; } } } }
Voilà pour ImageMagick avec la classe phMagick!
Grâce à ça j'ai pu gagner 50Mo sur un de mes serveurs.
Allez encore un petit plus avant de terminer ce tuto, si vous souhaitez par exemple utiliser OptiPNG ou une autre application qui s'exécute via l'invite de commandes ou le terminal, vous pouvez l'utiliser en PHP!
On va pour cela créer une fonction qui va utiliser OptiPNG :
function optipng($file1, $file2) { $infos = array(); $infos['command'] = 'optipng -o7 "' . $file1 . '" -out "' . $file2 . '"'; exec($infos['command'], $infos['debug']); return $infos; }
Dans le code qui permet la conversion de tous les fichiers du dossier, il faudra mettre une exception en fonction de l'extension et remplacer ceci :
$phmagick = new phmagick($current, $new); $phmagick->debug = true; $phmagick->convert();
Par ceci :
if( $file['ext'] == 'png' ) { $optipng = optipng($current, $new); } else { $phmagick = new phmagick($current, $new); $phmagick->debug = true; $phmagick->convert(); }
J'espère que ce tuto vous aura bien servi, j'avoue avoir mis un petit moment à vous le préparer
Si vous avez des questions n'hésitez pas!
MISES A JOUR DE L'ARTICLE |
Bonjour,
Grâce à votre article j'ai découvert Caesium et Riot.
Je les ai installés et testés et ils sont en effet remarquablement efficaces.
J'utilisais jusqu'à maintenant Photo-Filtre ou Irfanview pour les photos que je mets sur mon site, mais je vais maintenant utiliser Riot ou Caesium.
En effet, pour la photo test de 160 ko que j'ai utilisée, ils ont pu la compresser jusqu'à 35 ko sans perte de qualité ( pour un affichage sur un site internet, pas pour imprimer ) là où Photo-Filtre et irfanview ne pouvaient pas descendre en dessous de 50 ko sans perte de qualité.
Cordialement, Manu.
Alors ça c'est excellent, cela fait un moment que je dois me pencher sur la question sans forcément savoir par où commencer, merci mille fois !
Ravi que ça te serve Matt
Bonjour CrazyMax,
Déjà merci pour le partage, ça fait plaisir ! Moi j'aimerais que mon script php puisse retailler (réduire) l'image en gardant les proportions et qu'il la compresse sans perte et de manière optimale pour performer sur google pagespeed. Que me conseilles tu comme solution ? Merci d'avance pour ton aide : )
Tout d'abord désolé de répondre si tard à ton commentaire :s
Pour répondre à ta question, je te conseille de jeter un oeil à ZebraImage
Bonjour ,
je suis tombé sur cet article en cherchant sur google et ça répond vraiment bien au problème des tailles d'images,surtout avec plusieurs solutions performantes et avec des outils d'analyse qui permettent de faire un point précis avant d'intervenir .J'ai déjà amélioré le chargement et je vais affiner avec les diverses solutions.
Bonjour, merci pour cet article instructif. Je connaissais déjà Riot et toutes les images de mon site son optimisées par lui, mais ca ne suffit pas à google. J'ai donc choisis de tester Caesium et j'ai l'effet inverse, toutes les images ont pris en taille. Je comprend pas. Quand à jpegtran, c'est très compliqué ...
PngGaubtlet qui reste pour moi le meilleur. En revanche, compresser des images par console, je trouve ça vraiment limite