Contactez-nous

1

Un filtre de dispersion graphique

29 mars 2006
par ARNO*

[SPIP 1.9 et GD2] La puissance des effets que l’on peut obtenir en quelques lignes de code avec GD2 ne cesse de m’épater. Si vous avez suivi la présentation de quelques uns des filtres précédents, vous aurez compris que l’essentiel du code des filtres graphiques de SPIP sert à gérer la création et le cache des fichiers graphiques ; les transformations induites par les fitres reposent en général sur une poignée de lignes PHP particulièrement simples.

Dans cette logique, voici un filtre particulièrement puissant, assez ardu à maîtriser graphiquement, mais dont le code est très simple. Il s’agit d’une adaptation, en filtre SPIP, de l’effet « Dispersion » (Displace dans la version anglaise) de Photoshop.

L’effet « Dispersion » consiste à déplacer chaque pixel d’une image en fonction des informations de niveau de gris de chaque pixel d’une autre image (que l’on nomme « image de référence »). Si le pixel de l’image de référence est un gris à 50%, le pixel de l’image d’origine n’est pas déplacé. Si le pixel de l’image de référence est noir ou blanc, alors on déplace le pixel de l’image d’origine vers la droite ou la gauche, le haut ou le bas.

Cet effet de Photoshop est très bien présenté sur ce site. On y trouvera les explications théoriques illustrées et différentes applications. Si vous ne connaissez pas cet effet, la lecture de ces pages me semble indispensable.

Le filtre

Voici le code de notre filtre. Oui, il est un peu long... Mais vous pouvez très bien le copier-coller dans votre fichier mes_options et l’expérimenter sans chercher à en comprendre les détails (la partie réellement intéressante est expliquée plus loin).

  1. function image_dispersion($im, $masque, $h=5, $v=5, $pos="") {
  2.  
  3. $nom = ereg_replace("\.(png|jpg|gif)$", "", $masque);
  4. $nom = ereg_replace("/","-",$nom);
  5.  
  6. $numargs = func_num_args();
  7. $arg_list = func_get_args();
  8. $texte = $arg_list[0];
  9. for ($i = 1; $i < $numargs; $i++) {
  10. if (ereg("\=", $arg_list[$i])) {
  11. $nom_variable = substr($arg_list[$i], 0, strpos($arg_list[$i], "="));
  12. $val_variable = substr($arg_list[$i], strpos($arg_list[$i], "=")+1, strlen($arg_list[$i]));
  13. $variable["$nom_variable"] = $val_variable;
  14. $defini["$nom_variable"] = 1;
  15. }
  16. }
  17.  
  18. $image = valeurs_image_trans($im, "disp$nom-$h-$v$pos", "png");
  19. if (!$image) return("");
  20.  
  21. $x_i = $image["largeur"];
  22. $y_i = $image["hauteur"];
  23. $im = $image["fichier"];
  24. $dest = $image["fichier_dest"];
  25. $creer = $image["creer"];
  26.  
  27. if (strlen($pos) > 0) {
  28. $placer = true;
  29. }
  30. else $placer = false;
  31.  
  32. if ($creer) {
  33. include_spip('inc/logos'); // bicoz presence reduire_image
  34.  
  35. $masque = find_in_path($masque);
  36. $mask = valeurs_image_trans($masque,"");
  37. $im_m = $mask["fichier"];
  38. $x_m = $mask["largeur"];
  39. $y_m = $mask["hauteur"];
  40.  
  41. $im2 = $mask["fonction_imagecreatefrom"]($masque);
  42.  
  43. if ($placer) {
  44. // On fabriquer une version "agrandie" du masque,
  45. // aux dimensions de l'image source
  46. // et on "installe" le masque dans cette image
  47. // ainsi: aucun redimensionnement
  48.  
  49. $dx = 0;
  50. $dy = 0;
  51.  
  52. if ($defini["right"]) {
  53. $right = $variable["right"];
  54. $dx = ($x_i - $x_m) - $right;
  55. }
  56. if ($defini["bottom"]) {
  57. $bottom = $variable["bottom"];
  58. $dy = ($y_i - $y_m) - $bottom;
  59. }
  60. if ($defini["top"]) {
  61. $top = $variable["top"];
  62. $dy = $top;
  63. }
  64. if ($defini["left"]) {
  65. $left = $variable["left"];
  66. $dx = $left;
  67. }
  68.  
  69. $im3 = imagecreatetruecolor($x_i, $y_i);
  70. @imagealphablending($im3, false);
  71. @imagesavealpha($im3,true);
  72. $color_t = ImageColorAllocateAlpha( $im3, 128, 128, 128 , 0 );
  73. imagefill ($im3, 0, 0, $color_t);
  74.  
  75. imagecopy ( $im3, $im2, $dx, $dy, 0, 0, $x_m, $y_m);
  76.  
  77. imagedestroy($im2);
  78. $im2 = imagecreatetruecolor($x_i, $y_i);
  79. @imagealphablending($im2, false);
  80. @imagesavealpha($im2,true);
  81.  
  82. imagecopy ( $im2, $im3, 0, 0, 0, 0, $x_i, $y_i);
  83. imagedestroy($im3);
  84. $x_m = $x_i;
  85. $y_m = $y_i;
  86. }
  87.  
  88. $rapport = $x_i / $x_m;
  89. if (($y_i / $y_m) < $rapport ) {
  90. $rapport = $y_i / $y_m;
  91. }
  92.  
  93. $x_d = ceil($x_i / $rapport);
  94. $y_d = ceil($y_i / $rapport);
  95.  
  96. if ($x_i < $x_m OR $y_i < $y_m) {
  97. $x_dest = $x_i;
  98. $y_dest = $y_i;
  99. $x_dec = 0;
  100. $y_dec = 0;
  101. } else {
  102. $x_dest = $x_m;
  103. $y_dest = $y_m;
  104. $x_dec = round(($x_d - $x_m) /2);
  105. $y_dec = round(($y_d - $y_m) /2);
  106. }
  107.  
  108. $nouveau = valeurs_image_trans(reduire_image($im, $x_d, $y_d),"");
  109. $im_n = $nouveau["fichier"];
  110. $im = $nouveau["fonction_imagecreatefrom"]($im_n);
  111. $im_ = imagecreatetruecolor($x_dest, $y_dest);
  112. @imagealphablending($im_, false);
  113. @imagesavealpha($im_,true);
  114. $color_t = ImageColorAllocateAlpha( $im_, 255, 255, 255 , 127 );
  115. imagefill ($im_, 0, 0, $color_t);
  116.  
  117. for ($x = 0; $x < $x_dest; $x++) {
  118. for ($y=0; $y < $y_dest; $y++) {
  119.  
  120. $rgb2 = ImageColorAt($im2, $x+$x_dec, $y+$y_dec);
  121. $a2 = ($rgb2 >> 24) & 0xFF;
  122. $r2 = ($rgb2 >> 16) & 0xFF;
  123. $g2 = ($rgb2 >> 8) & 0xFF;
  124. $b2 = $rgb2 & 0xFF;
  125.  
  126. $g2 = ($r2+$g2+$b2)/3;
  127. $val = ($g2-127)/127;
  128. $xd = $x - ($val*$h);
  129. $yd = $y + ($val*$v);
  130.  
  131. $xd = max(0,$xd);
  132. $yd = max(0,$yd);
  133. if ($xd > $x_dest - $x_dec - 1) $xd = $x_dest - $x_dec - 1;
  134. if ($yd > $y_dest - $y_dec - 1) $yd = $y_dest - $y_dec - 1;
  135.  
  136. $rgb = ImageColorAt($im, $xd+$x_dec, $yd+$y_dec);
  137. $a = ($rgb >> 24) & 0xFF;
  138. $r = ($rgb >> 16) & 0xFF;
  139. $g = ($rgb >> 8) & 0xFF;
  140. $b = $rgb & 0xFF;
  141.  
  142. $color = ImageColorAllocateAlpha( $im_, $r, $g, $b, $a );
  143. imagesetpixel ($im_, $x, $y, $color); }
  144. }
  145.  
  146. $image["fonction_image"]($im_, "$dest");
  147. imagedestroy($im_);
  148. imagedestroy($im2);
  149.  
  150. }
  151.  
  152. $class = $image["class"];
  153. if (strlen($class) > 1) $tags=" class='$class'";
  154. $tags = "$tags alt='".$image["alt"]."'";
  155. $style = $image["style"];
  156. if (strlen($style) > 1) $tags="$tags style='$style'";
  157. return "<img src='$dest'$tags />";
  158. }

Télécharger

Il s’agit en réalité de code dérivé du filtre image_masque présent dans ecrire/inc/filtres.php de la distribution de SPIP. Sa longueur vient du fait que image_masque permet de positionner un masque plus petit que l’image à l’intérieur d’une image plus grande, ce qui complique le code1.

En réalité, la partie originale est, comme toujours, la double boucle qui parcourt l’image pixel par pixel :

  1. for ($x = 0; $x < $x_dest; $x++) {
  2. for ($y=0; $y < $y_dest; $y++) {
  3. $rgb2 = ImageColorAt($im2, $x+$x_dec, $y+$y_dec);
  4. $a2 = ($rgb2 >> 24) & 0xFF;
  5. $r2 = ($rgb2 >> 16) & 0xFF;
  6. $g2 = ($rgb2 >> 8) & 0xFF;
  7. $b2 = $rgb2 & 0xFF;
  8.  
  9. $g2 = ($r2+$g2+$b2)/3;
  10. $val = ($g2-127)/127;
  11. $xd = $x - ($val*$h);
  12. $yd = $y + ($val*$v);
  13.  
  14. $xd = max(0,$xd);
  15. $yd = max(0,$yd);
  16. if ($xd > $x_dest - $x_dec - 1) $xd = $x_dest - $x_dec - 1;
  17. if ($yd > $y_dest - $y_dec - 1) $yd = $y_dest - $y_dec - 1;
  18.  
  19. $rgb = ImageColorAt($im, $xd+$x_dec, $yd+$y_dec);
  20. $a = ($rgb >> 24) & 0xFF;
  21. $r = ($rgb >> 16) & 0xFF;
  22. $g = ($rgb >> 8) & 0xFF;
  23. $b = $rgb & 0xFF;
  24.  
  25. $color = ImageColorAllocateAlpha( $im_, $r, $g, $b, $a );
  26. imagesetpixel ($im_, $x, $y, $color);
  27. }
  28. }

Télécharger

Voici le principe :

- on récupère le pixel de l’image de référence $im2 ; on calcule un niveau de gris ($g2) à partir des composantes RVB. On « décale » cette valeur de niveau de gris (initialement entre 0 et 255) de 127, de façon à obtenir une valeur comprise entre -127 et 127 (0 étant le gris 50%). On divise cette valeur par 127, et ainsi notre valeur de référence est, selon le niveau de gris du pixel, comprise entre -1 et 1 ;

- on décale les valeurs de $x et $y d’une certaine valeur, proportionnelle à notre valeur de niveau de gris ; quatre lignes limitent ces valeurs, pour rester à l’intérieur de l’image ;

- on récupère les informations de couleur de ce pixel décalé ;

- on applique cette valeur au pixel en cours.

Ainsi, pour chaque pixel, on est allé chercher dans l’image d’origine un pixel légèrement déplacé.

Utilisation du filtre

Comme d’habitude, on applique le filtre à une image, que ce soit un logo (d’article, de rubrique...) ou un document joint à un article.

Une texture de petits disques « floutés »
Cliquez sur la vignette pour obtenir l’image d’origine.
  1. [(#FICHIER|image_dispersion{disp-points.png,4,4})]

Le premier paramètre est, comme pour image_masque, le nom du fichier qui sera utilisé pour calculer l’effet (l’image de référence). On peut se contenter d’un fichier JPEG (donc non transparent, la transparence de l’image de référence n’étant pas exploitée) en niveaux de gris.

Les deux paramètres suivants indiquent la valeur maximale de décalage des pixels, horizontalement et verticalement. Notez bien : ces valeurs peuvent être nulles ou négatives. Ainsi, avec les valeurs 4 et 4, un pixel complètement blanc ou noir sera décalé de 4 pixels vers la gauche ou la droite, vers le bas ou le haut. Pour les pixels intermédiaires, on effectuera des décalages proportionnels, entre 4 et -4.

Nous nous contenterons aujourd’hui de l’utilisation la plus simple de ce filtre : déformer une photographie grâce à une image de référence. Dans d’autres articles à suivre, nous pourrons déformer du texte (effet assez hallucinant...), voire carrément déformer une grille en fonction de l’image (c’est, en gros, l’utilisation inverse de celle présentée aujourd’hui).

Un ensemble de « tourbillons »
Cliquez sur la vignette pour obtenir l’image d’origine.

Appliquée à notre image ainsi :

  1. [(#FICHIER|image_dispersion{disp-twirl.png,4,4})]

Nous obtenons ce résultat :

On obtient ainsi assez facilement des effets de verre transparent mais bosselé. Il est très facile de moduler l’effet en jouant sur les valeurs de déplacement horizontal et vertical (note : beaucoup de tutoriaux en ligne concernant cet effet demandent de renforcer le contraste de l’image de référence ; en réalité, il suffit d’augmenter les valeurs de décalage horizontal et vertical, sans retoucher le fichier, pour simuler une image de référence plus contrastée).

La présentation de l’effet « Dispersion » de Photoshop fourni quelques exemples avec plusieurs images de référence. Notez bien que, dans le cas de notre filtre, il n’est pas possible (pas encore ?) d’utiliser une image de référence comme un motif répété ; si vous utilisez un tel motif, vous devez fabriquer une grande image de référence qui contient ce motif déjà répété (comme dans les fichiers d’exemple fournis ici).

On utilise souvent des images de référence « floutées », c’est-à-dire présentant toujours une transition douce entre les pixels noirs et les pixels blancs. C’est ce qui permet d’obtenir l’effet de verre déformant. Cependant, on peut aussi utiliser des images de textures un peu plus « extrêmes », qui donneront des effets plus originaux (parce que les photographies façon verre de douche, on s’en lasse vite)...

Une texture de petits carrés noirs
Cliquez sur la vignette pour obtenir l’image d’origine.

L’effet se combine, évidemment, avec les autres filtres graphiques de SPIP. Voici un essai avec notre filtre image_podpod :

  1. <div style="position: relative;">
  2. <div style="position: absolute;">[(#FICHIER|reduire_image{400,480}|image_podpod{ff0000,50,140}|image_dispersion{disp-points.png,4,4})]</div>
  3. <div style="position: absolute;">[(#FICHIER|reduire_image{400,480}|image_podpod{000000,0,50}|image_dispersion{disp-points.png,4,4})]</div>
  4. </div>

Télécharger

1L’utilisation de ce filtre me semble suffisamment générique pour être intégré dans la distribution standard de SPIP. Cependant, plutôt que de l’intégrer tel quel, je préfère travailler à rendre le filtre image_masque plus générique.

  • Sylvain
    Mars 2006

    Salut Arno,

    bon allez arrête maintenant ... sinon je vais l’dire à Adobe que tu copie tous leurs effets Photoshop :-p

    - > je sort

    ... blagues à part, depuis que je suis tes articles, je me dis chaque fois ... mais où s’arrête les possibilités de ce truc ?

    allez, juste pour rire, un défit sympa, maintenant sur ta photo :
    - tu t’enlève la casquette ;
    - et te mets une crète rose (façon punk).

    alors, alors ... ?

  • Juin 2007

    Bonjour, j’ai téléchargé SPIP et ca ne marche pas . J’aimerai essayer tes exemples à condition que j’arrive à utiliser SPIP et préférablement en local. Merci de ton aide.

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.