SVGround : cours SVG

Les filtres

Malgré toutes ses qualités, le dessin vectoriel semble souvent trop géométrique, trop mathématique. Les filtres, qui viennent du monde bitmap, peuvent aider à résoudre ce problème.

Fonctionnement général des filtres

Les filtres s’utilisent comme les masques, les motifs ou encore les dégradés. On commence par les définir au début du document puis on les utilise sur autant de tracés qu’on veut.

Les filtres sont définis grâce à l’élément filter qu’on placera par convention dans les définitions (defs). On n’oublie pas de lui attribuer un identifiant grâce auquel on l’appellera. Les fils de filter seront les différentes primitives du filtres, c’est à dire les différentes opérations réalisées sur l’image de départ qui aboutiront à une image modifiée.

Enfin, on appelle le filtre grâce à la propriété CSS filter qui prend en parmêtre le chemin vers le filtre, qui peut être dans un autre fichier. Voici le squelette de l’utilisation de filtres :

<]]> < Les filtres ]]>

Les primitives de base

Avant de rentrer dans le vif du sujet, il est important de connaître plusieurs primitives de base qui ont des fonctions basiques mais qui sont néanmoins indispensables dans quasiment tous les filtres. Nous en profiterons pour appréhender la manière dont les différentes primitives sont chaînées.

La primitive feFlood

La primitive feFlood permet de remplir uniformément une région d’une couleur… La couleur et son opacité sont indiquées grâce aux propriétés CSS flood-color et flood-opacity.

<]]> < La primitive feFlood ]]>
La primitive feFlood

Jusque là, rien de transcendant je vous l’accorde. En plus, l’image source (le cercle de droite) n’apparaît même pas.

La primitive feImage

La primitive feImage permet d’importer une image dans un filtre. En effet, l’image d’entré par défaut est la région sur laquelle on applique le filtre. Dans l’exemple précédent, l’image d’entrée, où image source, est le cercle de droite (qui n’apparaît pas au final). feImage permet d’appeler une autre image, quelquesoit son format (JPG, PNG ou SVG). En effet, nous verrons plus loin qu’on peut combiner différentes images.

Cette primitive se comporte exactement comme l’élément image que nous avons déjà vu, et on peut donc utiliser les attributs x, y, width, height, preserveAspectRatio et bien entendu xlink:href. Reprenons l’exemple précédent :

<]]> < La primitive feImage ]]>
La primitive feImage

La primitive feTile

Grâce à la primitive feImage et à feTile, on va pouvoir créer des motifs très facilement. En effet, feTile se contente de répéter horizontalement et verticalement l’image qu’elle a en entrée.

La plupart des primitives ont une entrée et une sortie. Jusqu’ici, nous n’avons pas nommé ces entrées et ces sorties ce qui fait que c’est le comportement par défaut qui s’est appliqué. Mais il est possible de préciser pour chaque primitive l’image d’entrée (grâce à l’attribut in) et le résultat de sortie (grâce à result) de telle sorte qu’on puisse l’appeler par la suite.

Si l’attribut in n’est pas précisé, alors c’est le résultat de la primitive précédente qui est utilisé. C’est un peu particulier pour la première primitive. En effet, à tout moment, on peut appeler des valeurs spéciales pour l’attribut in : SourceGraphic, SourceAlpha, BackgroundImage, BackgroundAlpha, FillPaint ou StrokePaint. Nous n’utiliserons pour le moment que les deux premières. Pour la valeur SourceGraphic, on prend comme image d’entrée le dessin SVG sur lequel on appelle le filtre (c’est la valeur par défaut). Pour la valeur SourceAlpha, on utilise le canal alpha seulement, c’est à dire la transparence. Retenez bien ça pour la suite.

Pour faire un motif, on va utiliser feImage comme image d’entrée de feTile :

<]]> < La primitive feTile ]]>
La primitive feTile

La primitive feOffset

La primitive feOffset décale tout simplement l’image d’entrée selon les attributs dx et dy. Cette primitive est très utile pour les effets d’ombrage, que nous verrons plus loin.

<]]> < La primitive feOffset ]]>
La primitive feOffset

On remarque que le bord en bas à droite est tronqué. C’est du au fonctionnement intrisèque des filtres et il est possible d’y remédier. Nous verrons plus tard comment. Avec Firefox, les autres bords sont tronqués ce qui n’est pas le comportement normal.

La primitive feMerge

La dernière primitive de base, feMerge sert à empiler les résultats de différents filtres. On l’utilise la plupart du temps comme dernière primitive d’un filtre pour assembler les différents résultats précédents. On voit donc l’intérêt de pouvoir nommer les résultats de chaque primitive.

feMerge contient plusieurs feMergeNode dont l’attribut in indique l’image à prendre en entrée.

<]]> < La primitive feMerge ]]>
La primitive feMerge

Notons que l’ordre dans lequel on appelle les différents résultats est très important puisque feMerge empile les différents résultats. Dans cet exemple, on a d’abord le pavage sur lequel on empile le résultat de feFlood (avec une opacité différente de 1 pour que ce qu’il y a derrière soit visible). On empile ensuite le cercle décalé par feOffset puis enfin le graphique d’origine, grâce au mot-clé SourceGraphic.

Maintenant que nous avons vu les primitives de base, passons aux primitives un peu plus complexes mais bien plus puissantes.

Le flou gaussien

La première primitive intéressante que nous allons voir est feGaussianBlur. Elle permet d’obtenir une image plus ou moins floue. Elle est très simple d’utilisation puisqu’elle n’utilise qu’un seul attribut : stdDeviation. stdDeviation prend en paramêtre la déviation standard qui est un réel positif. Plus ce nombre est grand, plus l’effet de flou est important. Il est possible d’indiquer deux valeurs séparées pour le flou horizontal et le flou vertical. Dans ce cas on écrira deux valeurs dans l’attribut stdDeviation : la première concerne l’axe x et la seconde l’axe y.

<]]> < La primitive feGaussianBlur Push me ]]>
La primitive feGaussianBlur

On voit bien que sur le dernier bouton, le flou ne se fait que horizontalement, selon l’axe x.

Cette primitive est très intéressante pour obtenir des effets d’ombrage. La technique est simple : on crée l’ombre grâce à feGaussianBlur, on la décale puis on empile l’ombre puis l’image d’origine. L’astuce est d’utiliser comme source d’entrée la composante alpha (c’est à dire la transparence) avec le mot-clé SourceAlpha et non l’image entière. Ainsi l’ombre est en niveaux de gris et non en couleur. Essayons les deux cas (SourceGraphic et SourceAlpha) sur notre bouton :

<]]> < Effet d’ombrage avec feGaussianBlur Push me ]]>
Effet d’ombrage avec feGaussianBlur

Le résultat est beaucoup plus convaincant avec SourceAlpha. Vous pouvez donc écrire un filtre générique pour avoir un effet d’ombrage et ceci très simplement.

Morphologie

La primitive feMorphology permet de modifier la morphologie de l’image d’entrée du filtre. Deux opérations sont possibles : la dilatation et la contraction des formes SVG. On le précisera grâce à l’attribut operator qui peut prendre la valeur erode ou dilate. On peut contrôler le niveau de contraction ou de dilatation grâce à l’attribut radius qui doit être une valeur positive (on peut en mettre deux valeur et dans ce cas la première concerne l’axe des x et la seconde l’axe des y).

Prenons par exemple cette image de départ :

<]]> < La primitive feMorphology - Image de départ Primitive feMorphology ]]>
La primitive feMorphology - Image de départ

Appliquons-y maintenant un filtre de dilatation :

<]]> < La primitive feMorphology - Dilatation Primitive feMorphology ]]>
La primitive feMorphology - Dilatation

Essayons pour finir la contraction :

<]]> < La primitive feMorphology - Contraction Primitive feMorphology ]]>
La primitive feMorphology - Contraction

Dans les deux cas, l’effet sur l’image bitmap a un côté artistique intéressant. À vous de tester différentes valeurs pour voir ce qu’on peut faire avec ce filtre.

Matrices de convolution

Les filtres de convolution permettent d’appliquer sur des images divers effets très utiles comme le floutage, l’embossage, la détection des contours ou le rehaussement des détails.

Un peu de théorie

Mais tout d’abord, comment fonctionne les filtres de convolution ? Le principe est le suivant : pour chaque pixel de l’image de départ, la nouvelle valeur du pixel est calculée en fonction de sa valeur et de celles des pixels voisins.

Pour le traitement d’image, on utilise des matrices. Par défaut, on utilise une matrice 3 × 3 où le pixel que l’on traite est le pixel du centre de la matrice. Les 8 autres valeurs de la matrice correspondent aux valeurs des pixels voisins du pixel traité.

Imaginons que nous souhaitons traiter le pixel p 0 d’une image que voici : . . . . . . p 1 p 2 p 3 . . p 4 p 0 p 5 . . p 6 p 7 p 8 . . . . . .

Les points sont les pixels voisins des pixels voisin de p 0 .

Voici notre matrice de convolution (qui est de taille 3 × 3 ) : m 1 m 2 m 3 m 4 m 0 m 5 m 6 m 7 m 8 . Cette matrice est appelée noyau.

Pour obtenir la nouvelle valeur de p 0 , on effectue le produit de convolution (différent du produit vectoriel) des pixels concernés dans l’image (qui dépend de la taille de la matrice de convolution mais pour le moment on ne travaille que sur des matrices 3 × 3 ) et de la matrice de convolution renversée. Ainsi on a : p 0 = p 1 p 2 p 3 p 4 p 0 p 5 p 6 p 7 p 8 × m 8 m 7 m 6 m 5 m 0 m 4 m 3 m 2 m 1 = p 1 × m 1 + p 2 × m 2 + p 3 × m 3 + p 4 × m 4 + p 0 × m 0 + p 5 × m 5 + p 6 × m 6 + p 7 × m 7 + p 8 × m 8

Cette opération est effectuée sur tous les pixels de l’image. Pour finir, ajoutons que le résultat est normalisé, c’est à dire qu’il est divisé par la somme des coefficients de la matrice de convolution.

Pour appliquer un tel filtre dans SVG, on se sert de la primitive feConvolveMatrix et de son attribut kernelMatrix pour les valeurs de la matrice de convolution.

Le filtre moyen

Le filtre moyen est un filtre qui permet le floutage de l’image d’entrée. Son noyau est le suivant : 1 1 1 1 1 1 1 1 1 . Le filtre étant automatiquement normalisé, le résultat de produit de convolution est divisé par 9 qui est la somme des coefficients. En réalité, la valeur finale de p 0 est calculée grâce à la matrice suivante : 1 9 1 9 1 9 1 9 1 9 1 9 1 9 1 9 1 9 . Il s’agit donc d’un filtre qui fait la moyenne des pixels voisins et du pixel traité, d’où son nom. Ce genre de filtre est utilisé quand une image à des défauts qu’il faut effacer.

<]]> < Filtre moyen avec une matrice de convolution ]]>
Filtre moyen avec une matrice de convolution

Le filtre gaussien

Il existe un filtre de floutage réputé pour donner un meilleur résultat pour éliminer le bruit d’une image : le filtre gaussien. Le principe est de donner un poids plus fort au pixel traité et aux pixels qui lui sont le plus proche. Voici son noyau : 1 2 1 2 4 2 1 2 1 . Reprenons l’exemple précédent :

<]]> < Filtre gaussien avec une matrice de convolution ]]>
Filtre gaussien avec une matrice de convolution

Détection des contours

Il est possible de détecter les contours avec des filtres de convolution, appelés filtres laplaciens. Il en existe plusieurs mais le principe reste le même : la somme de tous les coefficients doit être nulle. Voici quelques un des noyaux possibles : -1 0 -1 0 4 0 -1 0 -1 , 0 -1 0 -1 4 -1 0 -1 0 , -1 -1 -1 -1 8 -1 -1 -1 -1 .

Voici l’image dont on cherche à déterminer le contour :

<]]> < Filtre laplacien avec une matrice de convolution - Image d’origine Lotus Berthelotii ]]>
Filtre laplacien avec une matrice de convolution - Image d’origine

Appliquons y un filtre laplacien :

<]]> < Filtre laplacien avec une matrice de convolution Lotus Berthelotii ]]>
Filtre laplacien avec une matrice de convolution

Vous avez sans doute remarqué la présence de l’attribut preserveAlpha à la valeur true. Cela indique qu’on n’applique pas le filtre de convolution sur la couche alpha. Dans ce cas précis, vu le noyau, la couche alpha se retrouve forcément à zéro ! D’où l’utilisation de cet attribut.

On peut adapter ces filtres pour qu’ils ne détectent les contours que dans une direction. Par exemple, si on ne veut que les contours sur l’axe y, on aura pour noyau : -1 -1 -1 0 6 0 -1 -1 -1 .

Le rehaussement des contours

Pour rehausser les contours, il suffit de superposer l’image avec le résultat de la détection des contours. Il faut donc additionner le noyau du filtre laplacien au filtre unité. Comment coder le filtre unité ? C’est très simple: 0 0 0 0 1 0 0 0 0 . Ainsi, on doit écrire : -1 -1 -1 -1 8 -1 -1 -1 -1 + 0 0 0 0 1 0 0 0 0 = -1 -1 -1 -1 9 -1 -1 -1 -1 . Et voici notre image avec ses contours réhaussés :

<]]> < Rehaussement des contours avec une matrice de convolution Lotus Berthelotii ]]>
Rehaussement des contours avec une matrice de convolution

En changeant le coefficient du pixel traité (le coefficient central) on décide de montrer plus ou moins les contours. Si ce coefficient est plus élevé, alors l’image aura plus d’importance que les contours. Ça peut être utile pour réhausser légèrement les contours. Au contraire, si ce coefficient est faible, les contours auront plus d’importance. Voici ce qu’on obtient avec un noyau -1 -1 -1 -1 6.5 -1 -1 -1 -1  :

Rehaussement des contours avec une matrice de convolution

Embossage

L’embossage est un effet permettant d’obtenir un pseudo effet 3D. Les contours sont rehaussés selon une direction. Par exemple, avec ce noyau, -2 -1 0 -1 1 1 0 1 2 , on a un embossage sur la diagonale de haut en bas et de gauche à droite.

<]]> < Embossage avec une matrice de convolution Lotus Berthelotii ]]>
Embossage avec une matrice de convolution

Comme toujours, on peut jouer sur le coefficient central pour atténuer ou accentuer l’effet de l’embossage. De plus, on peut changer la direction de l’embossage (bien visible sur le texte) en modifiant le noyau.

Aller plus loin

L’élément feConvolveMatrix peut avoir plusieurs attributs qui permettent de modifier différents paramêtres du filtre de convolution.

Le premier est l’attribut order. Jusqu’ici, la matrice de base était de taille 3 × 3 mais grâce à order on peut donner la taille que l’on souhaite à notre matrice de convolution. Il prend en paramêtre un ou deux entiers strictement positifs (forcément !). Avec un nombre n, on obtient une matrice carré n × n et avec deux nombres n et m on obtient une matrice de n colonnes et m lignes. Attention, de grandes matrices impliquent beaucoup de calculs en plus, alors limitez vous !

On peut aussi choisir la position du pixel cible dans la matrice. En effet, jusqu’à maintenant il s’agissait du pixel central (2e colonne 2e ligne) et c’est le comportement par défaut quelquesoit la taille de la matrice. Avec les attributs targetX et targetY on peut choisir une position arbitraire pour le pixel cible.

Je vous ai déjà dit que le résultat était normalisé en étant divisé par la somme des coefficients de la matrice appelé diviseur. On peut modifier ce diviseur avec l’attribut divisor. Enfin, l’attribut bias permet d’ajouter, après normalisation, un nombre au résultat final de chaque pixel traité.

Avec ces attributs, on peut réaliser un flou unidirectionnel avec ce noyau : 1 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 1 .

<]]> < Flou unidirectionnel avec une matrice de convolution Lotus Berthelotii ]]>
Flou unidirectionnel avec une matrice de convolution

Ici, on utilise le biais pour assombrir l’image (en simplifiant, 0 étant le noir et 1 le blanc en enlevant 0.2 on assombrit bien l’ensemble de l’image).

Avec un peu d’imagination, on peut trouver bien d’autres filtres. Il faut procéder par tâtonnement. Voici un dernier filtre j’ai nommé le flou multi-directionnel, dont le noyau est : 1 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 1 0 0 0 0 1 1 0 0 0 1 0 1 1 0 0 0 0 0 0 1 0 0 0 0 0 0 1 1 0 1 0 0 0 1 1 0 0 0 0 1 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 1 .

<]]> < Flou multidirectionnel avec une matrice de convolution Lotus Berthelotii ]]>
Flou multidirectionnel avec une matrice de convolution

Les deux directions du flou sont bien visibles. On aurait pu rajouter une détection de contour ou de l’embossage.

Matrice de couleur

Tout comme le filtre de convolution, la primitive matrice de couleur permet de recalculer la valeur de chaque pixel de l’image. Mais ce n’est pas par rapport aux pixels voisins qu’on va calculer la nouvelle valeur du pixel (comme c’est le cas pour la convolution) mais c’est à partir des quatre composantes : rouge, vert, bleu, canal alpha (opacité). La méthode de base consiste à définir une matrice. De plus la primitive feColorMatrix met à disposition trois raccourcis. On le détermine grâce à l’attribut type qui peut prendre les valeurs matrix, saturate, hueRotate, luminanceToAlpha.

Matrice

La spécification donne la définition suivant pour le calcul des composantes du pixel : R final V final B final A final 1 = a 00 a 01 a 02 a 03 a 04 a 10 a 11 a 12 a 13 a 14 a 20 a 21 a 22 a 23 a 24 a 30 a 31 a 32 a 33 a 34 0 0 0 0 1 × R V B A 1 . Ce sont les a indicés que l’on devra renseigner. Les 0 et 1 supplémentaires sont là pour conserver la compatibilité de la multiplication matricielle.

En faisant le produit matriciel, on obtient la formule pour chaque composante : { R final = a 00 × R + a 01 × V + a 02 × B + a 03 × A + a 04 V final = a 10 × R + a 11 × V + a 12 × B + a 13 × A + a 14 B final = a 20 × R + a 21 × V + a 22 × B + a 23 × A + a 24 A final = a 30 × R + a 31 × V + a 32 × B + a 33 × A + a 34 .

Ça peut paraître compliqué au premier abord mais en fait c’est très simple.

Première remarque : il y a pour chaque composante un biais (les a n 4 en dernier) qui permettent d’ajouter sans prendre en compte les canaux de couleur une quantité. Si la quantité est positive, on éclaircit le dessin. Sinon, on l’assombrit. Ce qui est intéressant c’est qu’il y a un biais pour chaque composante. On peut donc décider de relever le biais pour une seule composante.

War memorial at Daylesford, VictoriaPrenons la photo suivante. Il fait trop beau, ça ne vas pas. Qu’à cela ne tienne, assombrissons le ciel ! Pour cela, enlevons une petite quantité à la composante bleue de cette image. On utilise cette matrice : 1 0 0 0 0 0 1 0 0 0 0 0 1 0 0.3 0 0 0 1 0 0 0 0 0 1 . La diagonale de 1 est là pour conserver la valeur d’origine du pixel (c’est la matrice unité). En redécomposant les différantes composantes on voit très vite pourquoi : { R final = 1 × R + 0 × V + 0 × B + 0 × A V final = 0 × R + 1 × V + 0 × B + 0 × A B final = 0 × R + 0 × V + 1 × B + 0 × A + ( 0.2 ) A final = 0 × R + 0 × V + 0 × B + 1 × A { R final = R V final = V B final = B 0.2 A final = A .

Je vous bichonne là B-). C’est l’attribut values qui reçoit les valeurs de la matrice. Comme la dernière ligne ne sert à rien, on ne donne que les 20 premiers coefficients (ceux en gras ci-dessus). Regardons cet exemple :

<]]> < Assombrissement de la composante bleue avec feColorMatrix ]]>
Assombrissement de la composante bleue avec feColorMatrix

Le ciel est plus sombre, la composante bleue a bien été diminuée. Mais dans le reste de l’image les couleurs ont aussi changé. C’est normal car les couleurs sont en fait des mélanges de plusieurs couleurs. Ainsi le blanc est la somme des trois couleurs (rouge, vert, bleu) et diminuer le bleu agit aussi sur le blanc, qui voit sa composante bleue diminuer.

Il y a un autre moyen pour renforcer ou diminuer une composante. Il suffit de modifier les coefficients de la matrice unitaire. Prenons cette matrice : 1.3 0 0 0 0 0 1.1 0 0 0 0 0 0.7 0 0 0 0 0 1 0 0 0 0 0 1 . La composante rouge (couleur chaude) est renforcé (+30%) tandis que la bleue (couleur froide) est attenuée (-30%). On peut donc s’attendre à ce que l’image se réchauffe. Testons ce filtre :

<]]> < Balance des couleurs avec feColorMatrix ]]>
Balance des couleurs avec feColorMatrix

L’effet obtenue est bien celui souhaité. On vient de faire la balance des couleurs. En associant le biais et cette méthode, on peut contrôler finement chaque composante.

Mais les possibilités de la matrice de couleur ne s’arrête pas là, bien heureusement ! Nous n’avons pas encore utilisé les coefficients hors diagonale (hormis les biais). Grâce à ces coefficients, on peut prendre en compte toutes les composantes pour calculer la nouvelle !

Dans le prochain exemple, on effectue une rotation sur les composantes. La composante bleue reçoit la rouge, la rouge reçoit la verte et la vert reçoit la bleue. La matrice est la suivante : 0 1 0 0 0 0 0 1 0 0 1 0 0 0 0 0 0 0 1 0 0 0 0 0 1 . En reprenant, une dernière fois, le développement du produit matriciel, on obtient : { R final = V V final = B B final = R A = A . Ce qui est vert va donc devenir rouge, ce qui est bleu va devenir vert et ce qui est rouge va devenir bleu.

<]]> < Rotation des composantes avec feColorMatrix ]]>
Rotation des composantes avec feColorMatrix

On le voit bien, la végétation verte donne après application du filtre dans les tons rouges et surtout les fruits rouges donnent un beau bleu.

On n’est pas obligé de conserver toutes les composantes. Dans l’exemple suivant, on ne conserve que la composante verte, en assombrissant le tout un petit peu (le -0.2).

<]]> < Tout en vert avec feColorMatrix ]]>
Tout en vert avec feColorMatrix

Étudions maintenant la manipulation canal alpha, ce que nous n’avons pas encore fait. Avec la matrice 1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 1 1 0 0 0.2 0 0 0 0 1 , on décide de conserver les couleurs (les trois 1 en diagonale) mais on modifie la transparence. On a A final = R + V 0.2 , c’est à dire que l’opacité sera maximale (A = 1) là ou il y a du rouge et du vert. Par contre, s’il n’y a que du bleu, la valeur de la composante alpha sera nulle et il y aura donc transparence. De plus, rappelez-vous qu’une couleur a le plus souvent un peu des trois composantes. Ainsi, la couleur CSS3 turquoise a pour notation rgb(64,224,208) : beaucoup de vert (224), beaucoup de bleu (208) et un peu de rouge (64) ! C’est pourquoi j’ai mis un -0.2 comme biais pour la transparence : même si un bleu a un peu des composantes rouge et verte, la somme de ces deux composantes sera annulée et on aura bien de la transparence. Afin de bien voir l’effet de la transparence, j’ai ajouté un quadrillage en fond.

<]]> < Manipulation du canal alpha avec feColorMatrix ]]>
Manipulation du canal alpha avec feColorMatrix

Enfin, et nous finirons par là, on peut convertir une image en niveaux de gris très facilement. La caractéristique d’un gris (quelle que soit son intensité) c’est que ses trois composantes rouge, verte et bleue sont égales. Ainsi, rgb(133,133,133), rgb(95,95,95) et rgb(212,212,212) sont des gris. Pour convertir une image en niveaux de gris, il suffit donc que les trois composantes R, V et B soient égales.

Le plus intuitif serait de mettre 1 3 de chaque composante dans le nouveau pixel, par exemple avec cette matrice : 0.33 0.33 0.33 0 0 0.33 0.33 0.33 0 0 0.33 0.33 0.33 0 0 0 0 0 1 0 0 0 0 0 1 . En développant le produit matriciel, on obtient : { R final = 1 3 × R + 1 3 × V + 1 3 × B V final = 1 3 × R + 1 3 × V + 1 3 × B B final = 1 3 × R + 1 3 × V + 1 3 × B A final = A . Les trois composantes de couleur ont bien la même valeur et il s’agit donc d’un niveau de gris. Appliqué à tous les pixels de l’image, ce filtre nous donnera bien une image en niveaux de gris.

Néanmoins, il y a une meilleur manière de faire. L’œil ne perçoit pas la luminosité de la même façon selon la couleur. On peut donc utiliser la luminance des couleurs pour convertir une image en niveaux de gris. Dans ce cas, on prendra 30 % de rouge, 59 % de vert et 11 % de bleu. Consultez l’article de wikipédia sur la luminance pour en savoir plus. Nous allons donc plutôt prendre cette matrice : 0.299 0.587 0.114 0 0 0.299 0.587 0.114 0 0 0.299 0.587 0.114 0 0 0 0 0 1 0 0 0 0 0 1 . Testons la sur la méduse :

<]]> < Niveaux de gris avec feColorMatrix ]]>
Niveaux de gris avec feColorMatrix

Nous avons a peu près fait le tour de ce qu’on peut faire avec les matrices de couleur. Presque, car on n’a modifié que quelques coefficients à la fois alors qu’il est possible de tous les modifier en une fois (il y en a 20).

Rotation

Quand l’attribut type prend la valeur hueRotate, la primitive effectue une rotation sur les couleurs. Que donne une rotation sur des couleurs ? Il s’agit en fait d’une rotation sur le cercle chromatique. Tout ce que vous devez savoir, c’est que l’attribut values prend la rotation en degrées à effectuer et qu’une rotation de 360° équivaut a une rotation de 0°. Testons 3 rotations, d’un quart de tour, d’un demi tour et de trois quarts de tour.

<]]> < Rotation avec feColorMatrix ]]>
Rotation avec feColorMatrix

On voit bien que la rotation d’un demi tour fait apparaître les couleurs complémentaires (à l’image du bleu qui devient orange).

Saturation

La saturation permet d’abaisser l’intensité des couleurs d’une image. On fixe type à la valeur saturate et values prend une valeur entre 0 et 1. Pour la valeur 1, il n’y a aucun changement. Plus on va vers 0, l’image se ternit, devient fade et à 0, on a une image en niveaux de gris.

<]]> < Saturation avec feColorMatrix ]]>
Saturation avec feColorMatrix

Luminance vers alpha

Le dernier mot-clé disponible est luminanceToAlpha. Chaque pixel reçoit en composante alpha la somme de ses différentes composantes. Les composantes sont nulles. Il reste donc un squelette opaque là où les couleurs étaient claires et transparent là où les couleurs étaient foncées, proches du noir.

<]]> < luminanceToAlpha avec feColorMatrix ]]>
luminanceToAlpha avec feColorMatrix

Nous avons fait le tour de cette primitive mais nous allons le voir, ce n’est pas la seule à pouvoir travailler sur les couleurs.

Transfert de composante

Il existe une autre primitive, feComponentTransfer, qui permet de jouer sur chaque composante (rouge, verte, bleue et alpha) d’une image. On manipule ces composantes via les quatre éléments fils feFuncR, feFuncG, feFuncB, feFuncA respectivement pour le rouge, le vert, le bleu et l’opacité. On peut traiter un ou plusieurs canaux.

Ces quatre éléments portent l’attribut type qui définit le type de transfert à effectuer : identity, linear, gamma, table et discrete. Vous aurez deviné qu’avec identity, rien ne se passe.

Avec le mot-clé linear

Nous avons déjà vu l’utilisation de ce mot-clé… avec feColorMatrix ! En effet, il sert à faire une balance des couleurs avec coefficient (attribut slope) et un biais (attribut intercept). L’opération réalisée est pour un pixel p : p final = slope × p + intercept .

Profitons de cette fonction très simple pour voir comment utiliser les différents éléments. Nous allons rendre une image plus chaude en augmentant la valeur du rouge et en diminuant légèrement les autres.

<]]> < Balance des couleurs avec feComponentTransfer ]]>
Balance des couleurs avec feComponentTransfer

Avec le mot-clé gamma

Le mot-clé gamma permet d’effectuer lui aussi des ajustement de luminosité mais d’une autre manière, non linéaire. C’est un opérateur bien connu (correction gamma pour les écrans par exemple).

L’ajustement se base sur l’exponentiation. Le changement de couleur d’un pixel se traite de a façon suivante :
p final = amplitude × p exponent + offset amplitude, exponent (le plus important) et offset sont les attributs utilisables. Vous l’aurez compris, offset peut être assimilé à un biais.

Attention : les valeurs de couleurs vont de 0 à 1. Par conséquent, un exposant supérieur à 1 va assombrir le canal. Au contraire, un exposant inférieur à 1 va éclaircir le canal. On le voit clairement dans la figure suivante :
Différents exposants entre 0 et 1

La photo utilisée précédemment est trop claire. Utilisons cet opérateur pour assombrir l’image.

<]]> < Balance des couleurs avec feComponentTransfer (gamma) ]]>
Balance des couleurs avec feComponentTransfer (gamma)

Bien sûr, vu que l’on a baissé la valeur du bleu un peu moins que les autres, cette couleur ressort plus.

Avec le mot-clé discrete

L’opération permise par le mot-clé discrete n’est absolument pas possible à effectuer avec une matrice de couleur, et c’est pourquoi elle est intéressante. Il s’agit ici de découper la plage d’une couleur en plusieurs portions (de taille égale) et de lui assigner une valeur discrete (fixe).

Pour mieux comprendre, prenons un exemple. Les valeurs discrètes sont à indiquer dans l’attribut tableValues. Le nombre de portions sera égale au nombres de valeurs. En prenant tableValues="1 0 0.5" pour le canal du vert, le filtre va traiter chaque pixel de la façon suivante :

On peut diviser l’intervalle de valeur autant que l’on veut. Afin de bien visualiser le fonctionnement de ce filtre, appliquons le sur des dégradés pur. J’entends par là des dégradés qui vont du noir (0 de couleur partout) à une des trois composantes de couleur (rouge, vert, bleu).

Un petit détail cependant. Vous remarquerez que dans la feuille de style CSS, j’ai utilisé une propriété jusque là inconnue : color-interpolation. La raison est la suivante : il existe différents espaces de couleurs. Les dégradés utilisent l’espace de couleur qui se nomme sRGB parcequ’il donne des résultats plus naturels qu’avec la valeur linearRGB. Au milieu d’un dégradé de vert (par exemple), on peut s’attendre à avoir un valeur de vert de 0.5, mais ce n’est justement pas le cas avec sRGB. C’est pourquoi on utilise dans notre cas linearRGB.

<]]> < Discrete avec feComponentTransfer ]]>
Discrete avec feComponentTransfer

Pour le dégradé rouge, on a les valeurs tableValues="0.3 0.7 1" et on observe bien :

Pour le vert, on a divisé le spectre en 11 plages (tableValues="0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1") que l’on observe bien.

Avec le bleu, on voit que la liste n’a pas l’obligation d’être croissante. En fait on peut mettre les valeurs qu’on souhaite. Avec les 5 valeurs tableValues="1 0.24 0.5 0.9 0.1" le transfert se fait de la façon suivante :

Que faire si on veut des plages pas forcément de même taille, par exemple si on veut traiter le premier tiers et le dernier cinquième ? La solution est mathématique et je vous donne un indice : le ppcm.

Ce mot-clé permet d’obtenir des effets sympas sur les images bitmaps en combinant les trois canaux de couleur.

Bien sûr, on peut utiliser des mot-clé différents sur feFuncR, feFuncG, feFuncB et feFuncA.

Voici un exemple sur cette image : Photo d’un oiseau

<]]> < Discrete avec feComponentTransfer sur une image raster ]]>
Discrete avec feComponentTransfer sur une image raster

Nous avons utilisé des valeurs discrètes pour toutes les composantes ce qui conduit à avoir un nombre de couleur fixe. Pour ce filtre ci, il y a une palette de 120 couleurs possibles. Je vous laisse chercher pourquoi !

Avec le mot-clé table

L’utilisation de cette primitive avec le mot-clé table ressemble à son utilisation avec le mot-clé discrete. La couleur traitée est séparée en plages et chaque plage va subir une transformation. Dans le cas de discrete, la transformation est simple : c’est le remplacement avec une valeur discrète. Avec table, il s’agit d’une transformation linéaire. Une plage est étirée linéairement entre deux valeurs. Prenons un exemple simple (on réutilise le même attribut tableValues) : tableValues="0 0.3 0.9".

La première différence est qu’ici, il y a une plage en moins puisque les flottants spécifiés sont des bornes. On a donc 2 plages pour trois valeurs : première moitié et seconde moitié. Voici comment la transformation s’effectue si on prend par exemple le canal de bleu :

Comme il n’y a rien de mieux que l’expérimentation lorsqu’on travaille avec les filtres, reprenons nos dégradés de couleur pure :

<]]> < Table avec feComponentTransfer ]]>
Table avec feComponentTransfer

Le canal rouge est traité par tiers :

En ce qui concerne le dégradé vert, le traitement est divisé en dixième (puisqu’il y a 11 valeurs). Cet exemple montre qu’on n’est pas obligé d’avoir un ordre croissant. Mais mieux encore, la transformation tient compte de la pente puisque c’est une transformation linéaire.

Pour le bleu, c’est un enchaînement de dégradés vert des bleus de plus en plus clairs.

Reprenons la photo précédente et changeons toutes les couleurs avec le mot-clé table :

<]]> < table avec feComponentTransfer sur une image raster ]]>
table avec feComponentTransfer sur une image raster

Vous savez même inverser les couleurs d’une image. Si, je vous jure ! Il suffit d’utiliser tableValues="1 0" sur les trois composantes de couleur !

<]]> < Inversion des couleurs avec feComponentTransfer sur une image raster ]]>
Inversion des couleurs avec feComponentTransfer sur une image raster

Nous avons fait le tour de cette primitive. Les deux derniers mot-clés sont sans aucun doute les plus puissants alors modifier les valeurs de ces exemples pour voir ce que l’on peut faire avec.

Turbulences

La primitive feTurbulence est très utile pour créer des motifs que l’on trouve dans la nature : eau, ciel, bois, marbre, etc. Il s’agit donc d’une primitive qui ne prend rien en entrée, comme feFlood. C’est une primitive à la fois très simple, il y a peu d’attributs, et complexe : on met souvent beaucoup de temps avant de trouver le résultat souhaité.

Il y a deux algorithmes pour générer du bruit. Le premier est accessible via le mot-clé fractalNoise et le second via le mot-clé turbulence avec l’attribut type.

L’attribut baseFrequency sert à déterminer la vitesse de variations des couleurs. Il s’agit d’un décimal supérieur ou égal à 0. On peut aussi renseigner deux nombres et dans ce cas le premier est pour l’axe x et le second pour l’axe y. Plus la fréquence est grande, plus les couleurs changent vite.

L’attribut numOctaves qui vaut 1 par défaut permet de régler la finesse des détails. Plus cet entier est grand, plus les détails seront fin.

Enfin, l’attribut seed est la valeur d’initialisation du générateur de nombre aléatoire. Changer ce nombre change totalement le dessin mais on garde la même texture.

Dans l’exemple suivant on peut visualiser la différence entre les deux types de turbulences (en haut fractalNoise, en bas turbulence).

<]]> < feTurbulence : les deux types de turbulence ]]>
feTurbulence : les deux types de turbulence

Comme vous le voyez, il vaut mieux utiliser de petites valeurs. Sinon, le résultat est décevant.

On peut aussi remarquer que si numOctaves est proche de 1, le résultat peut paraître flou (comme pour le premier carré).

Maintenant, une remarque importante pour la suite : le blanc que vous voyez n’est pas du blanc mais de la transparence et c’est donc le blanc du fond de la page que vous voyez entre les couleurs. Vous aller voir que ça a très vite son importance, mais avant voici un exemple pour vous en convaincre :

feTurbulence : les deux types de turbulence

C’est important car il va bien sûr falloir transformer le résultat de feTurbulence pour obtenir les textures qu’on veut. Les primitives qui nous aiderons le plus sont feColorMatrix et feComponentTransfer. Normal puisqu’on va juste faire des conversions de couleur.

Voici un premier exemple qui donne un résultat ressemblant à un liquide :

<]]> < Texture d’eau avec feTurbulence ]]>
Texture d’eau avec feTurbulence

Le principe est simple : on ne garde que le bleu que l’on relève (biais de 0.5). Pour avoir du cyan, on rajoute un peu de vert mais là où il y a du bleu (0.3).

On peut aussi obtenir des textures végétales, par exemple du bois. En essayant de trouver comment faire pour avoir du bois, j’ai trouvé une texture ressemblant à des végétaux tressés. Je vous laisse juge.

< Texture végétale avec feTurbulence ]]>
Texture végétale avec feTurbulence

Enfin, par hasard, j’ai trouvé une manière de faire des nuages dans un ciel bleu. Il y a peut être d’autres méthodes plus simples mais je vous livre celle-ci :

<]]> < Texture de nuage avec feTurbulence ]]>
Texture de nuage avec feTurbulence

Ici, le principe est de répandre du bleu partout et de supprimer les autres couleurs (transfert de composante) et ensuite de jouer sur le canal alpĥa (auquel on n’a pas touché avant).

Mélange

Fonctionnement

Grâce à la primitive feBlend, on peut mélanger deux images selon quatre modes différents : multiply, screen, darken et lighten (en utilisant l’attribut mode).

Cette primitive est très simple. En effet, il suffit de renseigner les attributs in et in2 qui désignent les deux images à mélanger (peu importe l’ordre ici).

Dans les exemples suivants, on utilise la texture d’eau vue précédemment et le graphique sur lequel on applique le filtre grâce â SourceGraphic. Voici l’image de test, avec mode="normal" qui signifie qu’on ne fait rien :

<]]> < feBlend normal SVGround SVGround ]]>
feBlend normal

Avec le mot-clé multiply

Le mot-clé multiply va provoquer la multiplication des couleurs de chaque pixel, en prenant compte de l’opacité.

<]]> < feBlend multiply SVGround SVGround ]]>
feBlend multiply

Avec le mot-clé screen

Ici, le pixel résultant sera la somme des deux pixels d’entrée moins leur produit. L’opacité n’est pas prise en compte.

<]]> < feBlend screen SVGround SVGround ]]>
feBlend screen

Avec le mot-clé darken

Avec darken, c’est le pixel le plus sombre des deux images d’entrée qui est sélectionne. Ce mot-clé a donc tendance à assombrir les images.

<]]> < feBlend darken SVGround SVGround ]]>
feBlend darken

Avec le mot-clé lighten

Enfin, le mot-clé lighten réalise l’inverse de darken : il sélectionne le pixel le plus clair des deux.

<]]> < feBlend lighten SVGround SVGround ]]>
feBlend lighten

Composition

Introduction, coordonnées du filtre

Il est possible d’appliquer différents opérateur arithmétiques avec la primitive feComposite. On utilisera l’attribut operator qui prendra une des valeurs suivantes : over (valeur par défaut), in, out, atop, xor, arithmetic. Comme pour le mélange, il y a deux image d’entrée qu’on indique grâce à in et in2 à la différence qu’ici, l’ordre à son importance. Pour faire simple, l’image renseigné par in est au-dessus de celle renseignée par in2.

Dans la suite nous prendrons cet exemple. L’attribut over ne fait rien que dessiner la première image sur la seconde.

<]]> < feComposite avec l’opérateur over SVGround ]]>
feComposite avec l’opérateur over

La première image est le texte (entrée du filtre) et la seconde un rectangle obtenu avec feFlood.

Cet exemple est le prétexte pour vous introduire de nouveaux attributs pour l’élément filter.

Jusqu’ici nous avons utilisé la valeur objectBoundingBox de l’attribut filterUnits. De ce fait, le filtre était appliqué sur la boîte englobant l’élément filtré, ou plus exactement une boîte 10% plus grande que la boîte englobante. Ainsi, on peut faire un petit décalage avec feOffset sans dépasser du filtre. Par défaut, les valeurs sont x="-10%" y="-10%" width="120%" height="120%". Vous pouvez bien sûr modifier ces attributs, et c’est parfois nécessaire (par exemple si on veut faire un décalage de plus de 10%).

Dans l’exemple précédent, j’ai utilisé userSpaceOnUse à la place d’objectBoundingBox. Dans ce cas, le système de coordonnées n’est plus fonction de la taille de la boîte englobante mais il s’agit du système de coordonnées de l’élément svg, c’est à dire celui dont on se sert habituellement. C’est pourquoi le bout gauche du filtre est tronqué.

Observons maintenant ce qui se passe avec les différents opérateurs.

Avec l’opérateur in

Avec l’opérateur in, le résultat est la partie de la première image qui se trouve dans la second image :

<]]> < feComposite avec l’opérateur in SVGround ]]>
feComposite avec l’opérateur in

Avec l’opérateur out

Son inverse est l’opérateur out : le résultat est la partie de la première image qui n’est pas dans la seconde image.

<]]> < feComposite avec l’opérateur out SVGround ]]>
feComposite avec l’opérateur out

Avec l’opérateur atop

Avec atop, on sélectionne la partie de la première image au-dessus de la seconde (comme in) plus la seconde image.

<]]> < feComposite avec l’opérateur atop SVGround ]]>
feComposite avec l’opérateur atop

Avec l’opérateur xor

L’opérateur xor très connu fait ce qu’il indique : il sélectionne la partie de la première image qui n’est pas dans la seconde et la partie de la seconde image qui n’est pas dans la première.

<]]> < feComposite avec l’opérateur xor SVGround ]]>
feComposite avec l’opérateur xor

Avec l’opérateur arithmetic

Ce dernier opérateur est générique car il nous permet de mélanger les deux images à notre guise, avec les attributs k1, k2, k3 et k4. Le résultat est donné selon la formule : result = k1 × i1 × i2 + k2 × i1 + k3 × i2 + k4 i1 est le pixel de la première image et i2 le pixel de la seconde image. On voit donc que

Testons cet opérateur :

<]]> < feComposite avec l’opérateur arithmetic SVGround ]]>
feComposite avec l’opérateur arithmetic

On a bien 30% du texte (k2), 20% du rectangle (k3) et 40% de plus là où il y a les deux images à la fois (k1).

Effets d’éclairage

À venir.

feDisplacementMap

À venir.

Exemple de filtre évolué

En chaînant les différentes primitives, on peut obtenir des filtres très puissants et en même temps génériques. Nous allons voir comment arriver à ce résultat :

Un filtre évolué

On reprend le rendu obtenu par turbulence (l’eau) mais on change les couleurs pour avoir du jaune-orange.

<]]> < Gros filtre SVGround ]]>
Un filtre évolué, étape 1

Ensuite, on compose avec l’opérateur in le résultat précédent (les primitives qui ont un attribut in prennent par défaut le résultat de la primitive précédente) et le texte en entrée (SourceGraphic).

<]]> < Gros filtre SVGround ]]>
Un filtre évolué, étape 2

On mélange ensuite le résultat obtenu avec l’image d’entrée. Là, j’avoue qu’on ne voit pas trop la différence…

<]]> < Gros filtre SVGround ]]>
Un filtre évolué, étape 3

Avec une matrice de convolution, on effectue un détection de contour. On sauvegarde le résultat de cette primitive dans txt pour usage ultérieur.

<]]> < Gros filtre SVGround ]]>
Un filtre évolué, étape 4

À partir de l’image d’entrée, on crée un flou que l’on décale. On sauvegarde le résultat dans flou.

<]]> < Gros filtre SVGround ]]>
Un filtre évolué, étape 5

Enfin, on fusionne les deux résultats intermédiaires et le tour est joué !

<]]> < Gros filtre SVGround ]]>
Un filtre évolué, étape finale
Clip/mask
Ratio, symboles