1

Un habillage irrégulier (2)

20 mars 2006
par ARNO*

[SPIP 1.9 et GD2] L’article suivant complète l’article sur l’habillage irrégulier des images. Celui-ci étant déjà relativement lourd, je préfère le compléter par un nouvel article, ce qui me permettra d’insister sur les détails qui ont évolué. Dans tous les cas, avant de lire le présent article, consultez le précédent qui, lui, contient les grands principes de cette méthode (ou bien copiez-collez le code du filtre sans vous demander comme ça fonctionne).

Dans la version précédente, le filtre image_float réalisait le détourage en fonction de la transparence réelle de l’image ; pour réaliser un détourage non rectangulaire, il fallait donc utiliser un fichier GIF ou PNG dans lequel l’image était détourée (pixels transparents du GIF ou couche alpha du PNG 24).

Or, pour le commun des mortels, détourer une image consiste plutôt à placer l’objet détouré sur un fond blanc ou noir, et à afficher cela sur une page Web de même couleur. Dans notre exemple, un gnou sur fond blanc dans un fichier JPEG (donc absolument pas transparent) affiché sur une page à fond blanc.

Voici donc une évolution de image_float qui va permettre de détourer non seulement selon la transparence de l’image mais aussi par rapport à une couleur de fond.

  1. function image_float ($img, $align, $margin=10, $coul=-1) {
  2.  
  3.         if (strlen($coul) == 6) {
  4.                 $couleurs = couleur_hex_to_dec($coul);
  5.                 $dr= $couleurs["red"];
  6.                 $dg= $couleurs["green"];
  7.                 $db= $couleurs["blue"];
  8.                 $placer_fond = true;
  9.         }
  10.         else $placer_fond = false;
  11.  
  12.         $image = valeurs_image_trans($img, "float-$align$coul", "php");
  13.         if (!$image) return("");
  14.  
  15.         $w = $image["largeur"];
  16.         $h = $image["hauteur"];
  17.         $precision = round($h / 5);
  18.        
  19.         $im = $image["fichier"];
  20.         $dest = $image["fichier_dest"];
  21.         $creer = $image["creer"];
  22.  
  23.         if (!$placer_fond) $ret = "<div style='position: relative; float: $align; width: 0px; height: 0px;'><img src='$im' class='format_png' alt='' style='position: absolute; $align: 0px;' /></div>";
  24.  
  25.         if ($creer) {
  26.                 include_spip('inc/logos'); // bicoz presence reduire_image
  27.                 $im_n = extraire_attribut(image_reduire($im, 0, $precision), "src");
  28.                 $nouveau = valeurs_image_trans($im_n, "reduction-$precision");
  29.                 $im_n = $nouveau["fichier"];
  30.                
  31.                 $x_i = $nouveau["largeur"];
  32.                 $y_i = $nouveau["hauteur"];
  33.                 $rapport = ($w / $x_i);
  34.                
  35.                 $im_n = $image["fonction_imagecreatefrom"]($im_n);
  36.  
  37.                 // une premiere passe
  38.                 // pour recuperer les valeurs
  39.                 for ($j = 0; $j < $y_i; $j++) {
  40.                         $transp = true;
  41.                
  42.                         for ($i = 0; $i < $x_i && $transp; $i++) {
  43.  
  44.                                 if ($align == "right") $rgb = ImageColorAt($im_n, $i+1, $j);
  45.                                 else $rgb = ImageColorAt($im_n, ($x_i - $i)-1, $j);
  46.                                 $a = ($rgb >> 24) & 0xFF;
  47.                                 $r = ($rgb >> 16) & 0xFF;
  48.                                 $g = ($rgb >> 8) & 0xFF;
  49.                                 $b = $rgb & 0xFF;
  50.  
  51.                                 if ($a > 125) $larg[$j] ++;
  52.                                 else if ($placer_fond && abs($r-$dr)+abs($g-$dg)+abs($b-$db) < 40) $larg[$j] ++;
  53.                                 else $transp = false;
  54.                         }                      
  55.                 }
  56.                
  57.                 $larg[-1] = $w;
  58.                 $larg[$y_i] = $w;
  59.  
  60.                 if ($align == "left") $mrg = "margin-right";
  61.                 else $mrg = "margin-left";
  62.  
  63.                 // une deuxieme passe
  64.                 // pour appliquer les valeurs
  65.                 // en utilisant les valeurs precedente et suivante
  66.                 for ($j = 0; $j < $y_i; $j++) {
  67.                         $reste = ($precision - $j);
  68.                         $haut_rest = $h - $haut_tot;
  69.                         $hauteur = round(($haut_rest) / $reste);
  70.                         $haut_tot = $haut_tot + $hauteur;
  71.                         $resultat = min($larg[$j-1],$larg[$j],$larg[$j+1]);
  72.  
  73.                         // Placer l'image en fond de differentes tranches
  74.                         // uniquement si detourage par la couleur de fond
  75.                         if ($placer_fond && $haut_tot <= $h) $backg = " background: url($im) $align -".($haut_tot-$hauteur)."px no-repeat;";
  76.                         else $backg = "";
  77.                        
  78.                         $forme .= "\n<div style='float: $align; clear: $align; $mrg: ".$margin."px; width: ".round(($w - ($resultat)*$rapport))."px ; height: ".round($hauteur)."px; overflow: hidden;$backg'></div>";
  79.                 }
  80.                 // Ajouter un div de plus en dessous
  81.                 $forme .= "\n<div style='float: $align; clear: $align; width: ".($margin+round(($w - ($resultat)*$rapport)))."px ; height: ".round($hauteur)."px; overflow: hidden;'></div>";
  82.  
  83.                 // Sauvegarder le fichier              
  84.                 $handle = fopen($dest, 'w');
  85.                 fwrite($handle, $forme);
  86.                 fclose($handle);
  87.  
  88.                 $ret .= $forme;
  89.         }
  90.         else {
  91.                 $ret .= join(file($dest),"");
  92.         }
  93.  
  94.         return $ret;
  95. }

Télécharger

Le filtre s’utilise exactement comme auparavant, à cela près qu’il accepte un paramètre supplémentaire optionnel : la couleur de fond sur laquelle on va baser le détourage.

  1. <BOUCLE_doc(DOCUMENTS){titre=GNU2}>
  2.  [(#FICHIER|image_float{left,10,ffffff})]
  3. </BOUCLE_doc>
  4.  
  5. <p>Lorem ipsum dolor sit amet ...</p>

Télécharger

Puisque c’est le sujet ici, l’image est désormais un fichier graphique non transparent :

Dans cet exemple, image_float{left,10,ffffff} signifie :
— l’image sera alignée à gauche,
— l’espacement entre l’image et le texte sera de 10 pixels,
— la couleur utilisée pour réaliser le détourage est ffffff (c’est-à-dire blanc).

Évidemment, on place tout cela, dans notre page, sur un fond blanc...

Bidouiller un filtre graphique

Un petit truc... Lorsque je développe mes filtres d’images, leur structure est toujours plus où moins la même :
— une première partie initialise certaines valeurs et, surtout, détermine les paramètres du fichier image à créer ;
— à l’intérieur du test « if($creer) », l’étape principale qui manipule l’image (si celle-ci n’a pas été détectée comme déjà existante dans la première partie) ;
— enfin, la sauvegarde éventuelle du fichier d’image et la création du code HTML pour l’afficher.

Tout le travail se déroulant à l’intérieur du test « if($creer) », pendant que je code la fonction, j’ai l’habitude tout simplement de remplacer cette ligne par :

  1.         if ($creer OR 1==1) {...

Ainsi, le cache des images créées par le filtre n’est jamais actif. Lorsque le code est définitif, je supprime simplement le « OR 1==1 ».

Détecter selon la couleur

En début de notre fonction, nous avons ajouté les lignes suivantes :

  1. if (strlen($coul) == 6) {
  2.         include_ecrire("filtres");
  3.         $couleurs = couleur_hex_to_dec($coul);
  4.         $dr= $couleurs["red"];
  5.         $dg= $couleurs["green"];
  6.         $db= $couleurs["blue"];
  7.         $placer_fond = true;
  8. }
  9. else $placer_fond = false;

Télécharger

On utilise ici les fonctions utilisées pour manipuler les couleurs dans SPIP 1.9. couleur_hex_to_dec est utilisé par ces fonctions pour convertir une couleur RVB hexadécimale (ffffff) en un tableau de trois couleurs décimales (255, 255, 255).

Au passage, nous initialisons une variable $placer_fond pour déclencher ou non le détourage selon la couleur de fond.

La modification essentielle se trouve lors de l’analyse de l’image. Jusque là on se contentait de détecter le niveau de transparence $a de chaque pixel, maintenant on va récupérer les valeurs RVB et les comparer à la couleur passée en argument :

  1. $a = ($rgb >> 24) & 0xFF;
  2. $r = ($rgb >> 16) & 0xFF;
  3. $g = ($rgb >> 8) & 0xFF;
  4. $b = $rgb & 0xFF;
  5.  
  6. if ($a > 125) $larg[$j] ++;
  7. else if ($placer_fond && abs($r-$dr)+abs($g-$dg)+abs($b-$db) < 40) $larg[$j] ++;
  8. else $transp = false;

Télécharger

Simplement :
— on conserve dans tous les cas la détection sur la transparence,
— ensuite on calcule la somme des écarts entre la couleur du point et la couleur de référence, pour chaque canal R, V et B ; somme que l’on veut inférieure à 40,
— sinon on arrête le détourage.

Cette valeur de 40 ne correspond à rien en théorie. Idéalement, on pourrait considérer que la somme devrait être égale à 0 (le fond du détourage est, en théorie, un bel aplat uniforme). Cependant, les détourages JPEG réalisés par des amateurs ne sont jamais parfaits et, surtout, la compression JPEG produit des artefacts (points parasites), particulièrement présents dans les zones de fort contraste. Dans mes propres essais, cette valeur empirique de 40 semble permettre de détourer selon la couleur de fond, tout en ne buttant pas sur les artefacts présents, en JPEG, dans ce qui devrait n’être qu’un bel aplat uniforme.

Afficher l’image

Les modifications précédentes sont, en réalité, celles qui posent le moins de difficulté. Comme souvent, c’est moins avec PHP qu’avec le HTML qu’on s’enquiquine. On se retrouve, ici, à ne pas pouvoir afficher l’image simplement !

La méthode d’affichage de l’image du premier article, à l’intérieur d’un <div> précédant le texte, n’est plus satisfaisante dans le cas d’une image non transparente. En effet, notamment sous MSIE, l’image se trouvait ainsi au-dessus du texte qui suit et, l’image n’étant pas transparente, le texte détouré passait sous les zones « blanches » (par exemple) de l’image et donc disparaissait.

La méthode de FlumpCakes qui consiste à afficher l’image en tant qu’image de fond n’est pas non plus satisfaisante. Certes, l’image n’étant plus un PNG 24 transparent, on peut l’utiliser en background-image sans problème d’affichage sous MSIE. Mais le problème n’est pas là ; en réalité :
— cette méthode nécessite d’encadrer l’intégralité du texte qui suit (« Lorem Ipsum ») dans un <div> (#container) ; ce qui est à la fois impossible dans un automatisme ponctuel, et scandaleux dans le fond...
— de toute façon, si le texte à l’intérieur de ce #container est trop court, l’image sera coupée verticalement.

Reste la méthode initiale d’Eric Meyer : utiliser des « tranches » de l’image à la place de nos <div> successifs. Sauf que vraiment, non, on ne va pas découper une image en 36 tranches pour l’afficher !

Je vous propose donc une combinaison de tout cela...
— Dans le cas où l’on travaille uniquement avec la transparence de l’image (pas de couleur passée en argument), on insère l’image dans le HTML selon la méthode de la première version de image_float, parce que c’est la méthode qui nous permet d’afficher du PNG 24 transparent dans MSIE.
— Sinon...

  • on va bien placer l’image en tant que background-image : non en tant que fond d’un #container totalement artificiel, mais en tant que fond de nos <div> empilés ;
  • nous avons donc à découper l’image en tranches, puisque chaque <div> affichera une tranche de l’image ; mais pour cela nous nous contenterons d’utiliser à chaque fois l’image complète, en décalant simplement le positionnement de l’image par rapport à chacun des <div>.

Ceci est réalisé ainsi :

  1. // Placer l'image en fond de differentes tranches
  2. // uniquement si detourage par la couleur de fond
  3. if ($placer_fond && $haut_tot <= $h) $backg = " background: url($im) $align -".($haut_tot-$hauteur)."px no-repeat;";
  4. else $backg = "";
  5.                        
  6. $forme .= "\n<div style='float: $align; clear: $align; $mrg: ".$margin."px; width: ".round(($w - ($resultat)*$rapport))."px ; height: ".round($hauteur)."px; overflow: hidden;$backg'></div>";

Télécharger

Si on réalise notre effet sur un fond de couleur différente, on voit ainsi ressortir les « tranches » (il est donc vital que la couleur de référence soit exactement celle du fond sur lequel on affiche d’image) :

Je ne sais pas pourquoi, je sens que certains n’ont pas tout suivi... Voici une version dans laquelle on passe le fond dans un bleu soutenu, et où l’on espace les différents <div> de deux pixels. On voit bien que chaque <div> affiche une « tranche » horizontale de l’image. Mais, dans notre méthode, cette tranche n’est pas un fichier différent pour chaque tranche, ça n’est qu’une partie d’un même grand fichier dont on n’affiche qu’une partie à l’écran.

Bugs d’affichage

Arrivé à ce stade, il faut encore signaler que tous les butineurs présentent des bugs d’affichage lorsqu’ils sont confrontés à notre empilement de <div>. Pas seulement MSIE, mais aussi Safari et... Firefox.

Encore une fois : faites du compliant, mais ne croyez pas que ça s’affichera correctement à coup sûr, pas même dans Firefox.

Ici, le texte « entre » parfois de quelques pixels à l’intérieur des <div> qui sont pourtant des flottants. L’image n’étant plus transparente, désormais cela se voit.

Pour limiter les dégâts, j’ai donc modifié le code de chaque <div>. Dans la version précédente, la marge ($margin) était ajoutée directement à la largeur du rectangle. Désormais c’est une margin-left (ou margin-right) qui est ajoutée, de façon à laisser un espace réellement transparent à côté du rectangle.

Ce qui réduit le nombre de cas problématiques. Augmenter la valeur de $margin dans ses squelettes réduit encore la difficulté (grâce à la modification précédente).

En tout cas, il est toujours pénible de tomber systématiquement sur des bugs d’affichage avec un code HTML simple, dans des navigateurs de nouvelle génération, surtout ceux qui prétendent avaler les dernières technologies et recommandations sans moufter... (Mais non je m’énerve pas, j’explique !)

Et voilà le travail

Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Vivamus ut nunc eget ante ornare nonummy. Ut arcu. Duis tincidunt tincidunt quam. In elementum blandit odio. Nullam ultrices. Nulla sem augue, mollis id, vulputate eget, ullamcorper ultrices, purus. Aenean porttitor odio at mauris. Mauris quis enim vitae purus dictum ultricies. Proin pharetra lectus auctor lacus. Quisque at sem ac lectus ornare vehicula. Nunc pulvinar, leo ut tristique auctor, felis diam gravida neque, consectetuer cursus sem nisl ut enim. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Donec justo. Aliquam erat volutpat. Sed vel enim nec tellus suscipit imperdiet. Maecenas sagittis, dolor id tincidunt suscipit, orci tortor fermentum mi, id varius dolor nisi quis lectus. Quisque ante sem, molestie a, euismod sed, tempus sit amet, mauris. Integer vel ante eget urna sagittis consectetuer. Quisque ullamcorper convallis velit.

Ut porta. Quisque euismod. Cras adipiscing, tellus id vestibulum ultrices, leo erat auctor diam, vitae aliquam libero orci ac augue. Fusce vel dui. Sed eleifend est ac mauris. Nam in leo. In hac habitasse platea dictumst. Suspendisse hendrerit nisl at orci. Curabitur condimentum. Proin scelerisque. Nunc eros.

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.