Contactez-nous

1

Créer automatiquement une publicité iPod

22 mars 2006
par ARNO*

[SPIP 1.9 et GD2] À moins de vivre dans une cave, vous ne pouvez pas avoir manqué la nouvelle campagne d’affichage pour l’iPod d’Apple : fond bleu « moucheté », ombres détourées et podPod blanc.

Le présent article va illustrer l’intérêt, notamment pour un graphiste pas trop manchot avec PHP, de créer son propre filtre graphique pour traiter automatiquement les images dans SPIP. Les rédacteurs se contentent d’installer des photographies, et les squelettes fabriquent automatiquement des images très typées, qui respecteront alors le graphisme défini pour l’ensemble du site. Nous verrons qu’avec un filtre adapté à ses propres besoins, on peut réellement réaliser des images très marquées graphiquement, mais sans demander que les rédacteurs du site maîtrisent Photoshop (ou Gimp, pardon) dans les moindres détails (et pas seulement des vignettes sépia...).

Nous allons réaliser l’effet suivant avec notre filtre maison :

résultat obtenu à partir d’une image du site, dans le cas présent, celle-ci :

L’image d’origine
Cliquez sur la vignette pour obtenir l’image d’origine.

Analyser l’image

La première tâche va consister à tenter de décomposer l’image finale pour en extraire les différentes éléments, afin de voir s’il est possible d’automatiser sa création.

- Tout d’abord, le fond bleu texturé. En voici un, différent des publicités originales, mais qui en rappelle l’esprit :

La texture du fond
Cliquez sur l’image pour obtenir la version originale.

Comme cette image est utilisée en fond, elle n’a pas besoin d’être transparente. J’utilise donc un JPEG pour limiter, dans une certaine mesure, son poids (bon, ici, 132 ko, c’est un peu beaucoup...).

- Sur ce fond tramé, la « couche » la plus visible est le détourage noir du personnage. Voici ce que nous cherchons à obtenir :

La masque noir

C’est ici que notre travail d’analyse commence réellement : nous devons tenter de définir comment on obtient cet aplat noir à partir des informations de l’image d’origine. La solution : nous voulons transformer en noir (complètement noir) tous les pixels suffisamment sombres (pas noirs, simplement foncés). Nous voulons, en quelque sorte, « brûler », ou « saturer » les zones les plus sombres, pour qu’elles deviennent complètement noires. Tous les autres pixels sont rendus complètement transparents.

- Enfin, nous voulons isoler, selon une logique similaire, les pixels les plus clairs et les transformer en pixels complètement blancs, pour obtenir cette « couche » :

Le masque blanc
Où l’on peut constater, dans la partie inférieure de l’image que j’ai un peu détouré comme un cochon (mais j’avais mon bookBook sur les genoux dans le métro).

- Par ailleurs, puisque nous travaillons dans certains cas avec des images PNG 24 transparentes (c’est le cas dans notre exemple), nous devons respecter la transparence d’origine, et ne pas « noircir » ou « blanchir » les zones transparentes (qui deviendraient alors visibles, ce qui serait désastreux).

- Traduisons cela en informations plus « informatiques » (suivez bien ce passage, c’est là que tout se joue) :
— commençons par récupérer, pour chaque pixels, la valeur de ses composantes rouge, vert, bleu (RVB) ; nous obtenons 3 valeurs (R, V et B) comprises entre 0 et 255 ;
— convertissons ces valeurs RVB en un niveau de gris G ; très simplement, c’est la moyenne des composantes RVB (le filtre image_nb fait mieux que cela, mais nous nous contenterons d’une moyenne pour nos besoins) ; nous avons maintenant, pour chaque pixel, une unique valeur G comprise entre 0 et 255 (0 pour le noir, 255 pour le blanc) ;
— prenons trois « plages » de niveau de gris :

  • pour G entre 0 et 80 (les zones sombres), on remplacera G par la valeur « noir », c’est-à-dire 0 ;
  • pour G entre 180 et 255 (les zones claires), on remplacera G par la valeur « blanc », c’est-à-dire 255 ;
  • pour G entre 80 et 180 (gris intermédiaires), on rend le pixel complètement transparent ;
  • enfin, dans les cas où l’on « colore » le pixel, on décide d’utiliser la transparence de l’image d’origine ; ainsi, les zones non transparentes apparaîtront bien, les zones transparentes resteront transparentes.

Le code du filtre

Selon la logique précédente, il est possible de décomposer le résultat en trois couches superposées. Pour nous laisser le maximum de souplesse, nous allons créer un filtre qui créer les images superposées séparément (et non une seule image rendue en une fois). En effet, réaliser la couche noire et la couche blanche relève de la même logique, mais avec des valeurs différentes :
— la couleur de l’aplat (noir ou blanc, mais on peut imaginer d’autres couleurs),
— la valeur inférieure de la plage de gris concernée,
— la valeur supérieure de la plage de gris.

De plus, bénéficier d’un filtre qui traite une seule couleur permettra des réglages plus fins directement depuis le squelette (changer les couleurs, changer les plages de sélection...).

Voici le code de notre filtre :

  1. function image_podpod($im, $coul='000000', $deb=0, $fin=70)
  2. {
  3. include_ecrire("filtres");
  4. $image = valeurs_image_trans($im, "podpod-$coul-$deb-$fin","png");
  5. if (!$image) return("");
  6.  
  7. $couleurs = couleur_hex_to_dec($coul);
  8. $dr= $couleurs["red"];
  9. $dv= $couleurs["green"];
  10. $db= $couleurs["blue"];
  11.  
  12. $x_i = $image["largeur"];
  13. $y_i = $image["hauteur"];
  14.  
  15. $im = $image["fichier"];
  16. $dest = $image["fichier_dest"];
  17.  
  18. $creer = $image["creer"];
  19.  
  20. if ($creer) {
  21. $im = $image["fonction_imagecreatefrom"]($im);
  22.  
  23. $im_ = imagecreatetruecolor($x_i, $y_i);
  24. @imagealphablending($im_, false);
  25. @imagesavealpha($im_,true);
  26. $color_t = ImageColorAllocateAlpha( $im_, 255, 255, 255 , 127 );
  27. imagefill ($im_, 0, 0, $color_t);
  28.  
  29. for ($x = 0; $x < $x_i; $x++) {
  30. for ($y=0; $y < $y_i; $y++) {
  31.  
  32. $rgb = ImageColorAt($im, $x, $y);
  33. $a = ($rgb >> 24) & 0xFF;
  34. $r = ($rgb >> 16) & 0xFF;
  35. $g = ($rgb >> 8) & 0xFF;
  36. $b = $rgb & 0xFF;
  37.  
  38. $g = round(($r+$g+$b) / 3);
  39.  
  40. if ($g >= $deb AND $g <= $fin) $color = ImageColorAllocateAlpha( $im_, $dr, $dv, $db , $a );
  41. else $color = ImageColorAllocateAlpha( $im_, 0, 0, 0 , 127 );
  42.  
  43. imagesetpixel ($im_, $x, $y, $color);
  44. }
  45. }
  46. $image["fonction_image"]($im_, "$dest");
  47. }
  48.  
  49. $class = $image["class"];
  50. if (strlen($class) > 1) $tags=" class='$class'";
  51. $tags = "$tags alt='".$image["alt"]."'";
  52. $style = $image["style"];
  53.  
  54. return "<img src='$dest'$tags />";
  55. }

Télécharger

Pour créer la couche noire, on l’utilisera ainsi :

  1. [(#FICHIER|image_podpod{000000,0,80})]

La première variable est la couleur de l’aplat (#000000, c’est-à-dire noir). Les deux suivantes délimitent la plage de niveau de gris à remplir (entre 0 et 80, sachant que le maximum possible est 255).

Le code expliqué

  1. include_ecrire("filtres");
  2. $image = valeurs_image_trans($im, "podpod-$coul-$deb-$fin","png");
  3. if (!$image) return("");
  4.  
  5. $couleurs = couleur_hex_to_dec($coul);
  6. $dr= $couleurs["red"];
  7. $dv= $couleurs["green"];
  8. $db= $couleurs["blue"];
  9.  
  10. $x_i = $image["largeur"];
  11. $y_i = $image["hauteur"];
  12.  
  13. $im = $image["fichier"];
  14. $dest = $image["fichier_dest"];
  15.  
  16. $creer = $image["creer"];

Télécharger

Cette première partie du code a deux objectifs :
— transformer la couleur passée en RVB hexadécimal (entre 000000 et FFFFFF) en trois variables décimales (entre 0 et 255) ;
— définir le fichier cache dans lequel l’image finale sera stockée, et voir s’il est nécessaire de créer (ou recréer) ce fichier.

La ligne intéressante est :

  1. $image = valeurs_image_trans($im, "podpod-$coul-$deb-$fin","png");

Cette ligne stocke différentes informations dans $image (dimensions, image de départ, image finale, nécessité de créer le fichier ou bien d’utiliser directement une version déjà stockée en cache). Elle se base pour cela sur le nom du fichier d’origine (par exemple : gitane.png), le nom de l’image finale (de la forme : gitane-podpod-000000-0-80...) et, éventuellement, le format de l’image finale (ici on veut forcer au format PNG pour conserver la transparence).

  1. $class = $image["class"];
  2. if (strlen($class) > 1) $tags=" class='$class'";
  3. $tags = "$tags alt='".$image["alt"]."'";
  4. $style = $image["style"];
  5.  
  6. return "<img src='$dest'$tags />";

Télécharger

La partie finale du filtre fabrique le code HTML pour afficher l’image finale.

Ces deux étapes sont relativement systématiques, on les retrouve quasiment à l’identique dans tous les filtres graphiques de SPIP.

La partie du code qui est réellement intéressante est celle du centre :

  1. if ($creer) {
  2. $im = $image["fonction_imagecreatefrom"]($im);
  3.  
  4. $im_ = imagecreatetruecolor($x_i, $y_i);
  5. @imagealphablending($im_, false);
  6. @imagesavealpha($im_,true);
  7. $color_t = ImageColorAllocateAlpha( $im_, 255, 255, 255 , 127 );
  8. imagefill ($im_, 0, 0, $color_t);
  9.  
  10. for ($x = 0; $x < $x_i; $x++) {
  11. for ($y=0; $y < $y_i; $y++) {
  12.  
  13. $rgb = ImageColorAt($im, $x, $y);
  14. $a = ($rgb >> 24) & 0xFF;
  15. $r = ($rgb >> 16) & 0xFF;
  16. $g = ($rgb >> 8) & 0xFF;
  17. $b = $rgb & 0xFF;
  18.  
  19. $g = round(($r+$g+$b) / 3);
  20.  
  21. if ($g >= $deb AND $g <= $fin) $color = ImageColorAllocateAlpha( $im_, $dr, $dv, $db , $a );
  22. else $color = ImageColorAllocateAlpha( $im_, 0, 0, 0 , 127 );
  23.  
  24. imagesetpixel ($im_, $x, $y, $color);
  25. }
  26. }
  27. $image["fonction_image"]($im_, "$dest"); }

Télécharger

On fabrique une image en mémoire ($im_). On parcourt cette image pixel par pixel (coordonnées $x et $y), et on récupère les informations de ce pixel (ImageColorAt) : les valeurs de transparence ($a) et les composantes RVB ($r, $g et $b).

Jusqu’ici, il s’agit d’un code qu’on retrouve quasiment dans tous les filtres graphiques.

Le travail original du filtre se limite en réalité à la partie suivante :

  1. $g = round(($r+$g+$b) / 3);
  2.  
  3. if ($g >= $deb AND $g <= $fin) $color = ImageColorAllocateAlpha( $im_, $dr, $dv, $db , $a );
  4. else $color = ImageColorAllocateAlpha( $im_, 0, 0, 0 , 127 );

Télécharger

— On récupère le niveau de gris $g en faisant la moyenne des composantes RVB ;
— si ce niveau de gris est compris entre les valeurs passées en paramètre, alors on définit la couleur du point ($color) comme étant celle des couleurs passées dans la fonction ; la transparence de ce pixel est celle du pixel d’origine ;
— sinon, la « couleur » du pixel est tout simplement un pixel complètement transparent (la valeur alpha est 127).

Encore une fois : c’est la seule partie réellement propre à ce filtre, le reste est présent dans quasiment tous les autres filtres graphiques livrés avec SPIP. Il suffit ici d’exactement trois lignes pour réaliser l’effet recherché !

La fin redevient classique :
— on applique la « couleur » définie plus haut au pixel de l’image destination ;
— une fois tous les pixels de l’image analysés, on sauvegarde le fichier.

Le code dans le squelette

Il suffit ensuite de superposer les différentes « couches » pour obtenir le résultat voulu :

  1. <BOUCLE_doc(DOCUMENTS){id_article}{par hasard}{0,1}>
  2. <div style="position: relative;">
  3. <div style="position: absolute; background: url(#DOSSIER_SQUELETTE/fondpub.jpg);">[(#FICHIER|image_podpod{000000,0,80})]</div>
  4. <div style="position: absolute;">[(#FICHIER|image_podpod{ffffff,180,255})]</div>
  5. </div>
  6. </BOUCLE_doc>

Télécharger

(Notre BOUCLE_doc sélectionne un document joint au hasard dans l’article en cours ; évidemment, vous adaptez à vos besoins...)

Cliquez sur la vignette pour afficher l’image de grande taille.

En pratique, il y a deux difficultés :
— appliquer ce genre de filtre à une grande image peut être lourd pour le serveur ; je préfère donc commencer par réduire l’image avant d’appliquer le filtre image_podpod (en réalité, le filtre n’est pas excessivement lourd ; il est équivalent, en ce domaine, à image_nb, qui n’est pas lui-même bien méchant) ;
— notre filtre produit forcément des images où les transitions entre pixels noirs et pixels transparents sont trop nets ; comme le fond est lui-même relativement texturé, ça n’est pas forcément trop vilain (voir l’exemple ci-dessus d’une image non réduite) ; mais pour un résultat de bien meilleure qualité, on réduit encore les dimensions de l’image, la réduction provoquant le lissage des zones de contraste.

Le code devient un peu plus long :

  1. <BOUCLE_doc(DOCUMENTS){id_article}{par hasard}{0,1}>
  2. <div style="position: relative;">
  3. <div style="position: absolute; background: url(#DOSSIER_SQUELETTE/fondpub.jpg);">[(#FICHIER|reduire_image{600}|image_podpod{000000,0,80}|reduire_image{300})]</div>
  4. <div style="position: absolute;">[(#FICHIER|reduire_image{600}|image_podpod{ffffff,180,255}|reduire_image{300})]</div>
  5. </div>
  6. </BOUCLE_doc>

Télécharger

Tester ces valeurs

Si vous avez bien compris, il ne s’agit pas de traiter une image très spécifique et d’appliquer le filtre avec des réglages choisis aux petits oignons : ce que nous cherchons à obtenir, c’est un traitement automatique. Il faut donc que, appliqué à n’importe quelle image du site, il donne des résultats probants, et cela sans que les rédacteurs soient des experts ès graphisme.

Il faut donc tester le filtre sur différentes images...

On obtient par exemple :

D’accord, tout n’est pas toujours démentiel, certaines images donnent même des résultats assez laids, mais dans l’ensemble, ça reste cohérent. L’idée étant, avec ce genre de filtres, de créer des éléments de navigation dans un site Web (et non des grands posters à afficher dans le métro), il me semble que c’est suffisamment satisfaisant.

En revanche, pour des éléments graphiques plus importants du site, on peut très bien imaginer que le graphiste réalise quelques images-clés, mieux préparées à un tel filtre. Ici, il suffit généralement de réaliser des images PNG 24 déjà détourées pour que les résultats soient épatants.

Et si vous préférez le rap, c’est pas plus cher (0,99 € par morceau, 9,99 € l’album) :

Qui êtes-vous ?
Votre message

Ce formulaire accepte les raccourcis SPIP [->url] {{gras}} {italique} <quote> <code> et le code HTML <q> <del> <ins>. Pour créer des paragraphes, laissez simplement des lignes vides.