svg : découpes, masques et filtres

Maintenant qu’on a vu dans les précédents articles comment dessiner on va s’intéresser dans celui-ci à des outils qui permettent de découper une image, la masquer ou la modifier avec des filtres.

Pour illustrer ces possibilités je vais utiliser le hibou que je vous ai déjà montré dans cet article. J’en rappelle le code de base :

<svg height="500" width="520" xmlns="http://www.w3.org/2000/svg">
    <ellipse cx="256" cy="268.1" fill="#7e5c62" rx="255.8" ry="232"/>
    <path d="M302.7 351.4l-40.2-56c-1.5-2-4-2.8-6.5-2.8-2.6 0-4.7.7-6.5 2.8l-39.8 56c-1.9 3.3-2 6.1-.5 9l40 72a8.5 8.5 0 0 0 14 .1l40-72a9 9 0 0 0-.5-9.1z" fill="#ffcd00"/>
    <path d="M249.5 295.5l-39.8 56c-1.8 2.6-2 6-.5 8.9l40 72c1.4 2.1 4 3.8 6.8 4.4V292.1a8 8 0 0 0-6.5 3.4z" fill="#ff9100"/>
    <path d="M376.4 157.2c-51-7.7-100.7 21-119.4 69.2-20-48-69.6-76.7-120.8-69.2A112.4 112.4 0 0 0 119 375.8c50.2 15 105-6.6 130.2-52a8 8 0 0 1 7.3-4c2.4 0 5 1.5 6.4 4 5.5 44.8 37.5 82 81 94a113 113 0 0 0 118-38 113 113 0 0 0 9.4-124.5c-5.6-50-45.4-91.2-94.9-98z" fill="#e6be94"/>
    <circle cx="360" cy="268.1" fill="#996459" r="72"/>
    <circle cx="152" cy="268.1" fill="#996459" r="72"/>
    <path d="M400 76.1h40c31 0 56-24.8 56-56a8 8 0 1 1 16 0v96a72 72 0 0 1-72 72h-96c-44.1 0-80 36.4-80 80a8 8 0 1 1-16 0v-40c0-83.8 68.2-152 152-152z" fill="#996459"/>
    <path d="M112.2 76.1h-40c-31 0-56-24.8-56-56a8 8 0 0 0-16 0v96a72 72 0 0 0 72 72h96c44.1 0 80 36.3 80 80a8 8 0 1 0 16 0v-40c0-84-68-152-152-152z" fill="#996459"/>
    <circle cx="152.2" cy="268.1" fill="#ffcd00" r="48"/>
    <circle cx="152.2" cy="268.1" fill="#5c546a" r="24"/>
    <circle cx="168.1" cy="252.2" fill="#fff" r="16"/>
    <circle cx="360" cy="268.1" fill="#ffcd00" r="48"/>
    <circle cx="360" cy="268.1" fill="#5c546a" r="24"/>
    <circle cx="375.9" cy="252.2" fill="#fff" r="16"/>
    <path d="M504.2 12.1c-1.4 0-3.8.8-5.7 2.3-1.2 2.3-2 4-2.3 5.7A56.9 56.9 0 0 1 481 60.2a58 58 0 0 1-40.8 15.9h-40a153.6 153.6 0 0 0-144 104A152.7 152.7 0 0 0 112 76.1H72c-30.3 0-56-24.8-56-56 0-4.4-3.6-8-8-8S-.3 15.7-.4 21l.3 48A56 56 0 0 0 56 124.1h56c54.3 0 101.9 28.7 128.7 71.7 3.3 5.8 9.2 8.3 16.2 8.3h.1c5.3 0 11.3-2.5 14.5-8.3 26.9-43 74.4-71.7 128.7-71.7h56c31 0 56-24.7 56-56v-48a8 8 0 0 0-8-8z" fill="#e6be94"/>
</svg>

Découpes (clipping)

On peut découper une image en utilisant la balise clipPath. C’est un peu comme si on utilisait des ciseaux sur une image réelle. Voici un exemple avec le hibou :

<svg height="500" width="520" xmlns="http://www.w3.org/2000/svg">
  <defs>
    <clipPath id="clip">
      <ellipse cx="260" cy="255" fill="#bf4040" rx="110" ry="160"/>
    </clipPath>
  </defs>
  <g clip-path="url(#clip)">
    // code du hibou
  </g>
</svg>

On utilise donc la balise clipPath en lui donnant un identifiant, ici clip. On peut alors ajouter toutes les formes qu’on veut, là je me suis contenté d’une ellipse. Ensuite il faut référencer l’identifiant dans la forme concernée avec clip-path.

Si j’ajoute une autre ellipse :

<clipPath id="clip">
  <ellipse cx="260" cy="255" fill="#bf4040" rx="110" ry="160"/>
  <ellipse cx="260" cy="255" fill="#bf4040" rx="160" ry="110"/>
</clipPath>

On peut utiliser tout ce qu’on veut : des formes de base, des chemins, du texte…

Voici un exemple avec du texte :

<clipPath id="text">
  <text x="45%" y="65%" style="font-size: 180px; font-weight: 600;" text-anchor="middle">HOP</text>
</clipPath>

Les unités utilisées correspondent à celles du dessin mais on peut aussi raisonner en cadre d’image (bounding box) :

<defs>
  <clipPath id="circle" clipPathUnits="objectBoundingBox">
    <circle cx="0.5" cy="0.5" r="0.3"/>
  </clipPath>
</defs>
<g clip-path="url(#circle)">

Ce qui peut s’avérer plus simple à gérer…

Masques (mask)

Un masque joue sur la transparence. On utilise la balise mask pour le créer et comme pour la découpe on peut utiliser toutes les formes qu’on désire. Là où c’est un peu spécial c’est que la transparence résultant dépend de la couleur et de l’opacité du masque. Pourquoi pas seulement de l’opacité ? Là ça devient un peu opaque…

La formule pour savoir quel sera la transparence est celle-ci (j’ai trouvé la formule ici) :

(0.2125 * rouge + 0.7154 * vert + 0.0721 * bleu) * opacité

Un savant dosage entre les primaires…

On va voir avec notre hibou en créant un masque en 4 zones (rouge, vert, bleu et blanc) :

<svg height="500" width="520" xmlns="http://www.w3.org/2000/svg">
  <defs>
    <mask id="mask" x="0" y="0" width="1" height="1" maskContentUnits="objectBoundingBox">
      <rect x="0" y="0" width=".5" height=".5" style="fill: red;"/>
      <rect x=".5" y="0" width=".5" height=".5" style="fill: green;"/>
      <rect x="0" y=".5" width=".5" height=".5" style="fill: blue;"/>
      <rect x=".5" y=".5" width=".5" height=".5" style="fill: white;"/>
    </mask>
  </defs>
  <g mask="url(#mask)">
    // code du hibou
  </g>
</svg>

J’ai utilisé des valeurs relatives à objectBoundingBox pour simplifier. Voilà le résultat :

On peut utiliser un dégradé :

<defs>
  <linearGradient id="lgrad" x1="0%" y1="50%" x2="100%" y2="50%" >
    <stop offset="0%" style="stop-color:rgb(0,255,0);stop-opacity:1" />
    <stop offset="100%" style="stop-color:rgb(255,0,0);stop-opacity:1" />
  </linearGradient>
  <mask id="mask" x="0" y="0" width="100%" height="100%" >
    <rect x="0" y="0" width="100%" height="100%" fill="url(#lgrad)"/>
  </mask>
</defs>

Évidemment selon les couleurs du dégradé on aura un effet totalement différent.

Filtres

Même si le SVG est structuré de façon vectorielle le résultat à l’écran est finalement pixelisé. On peut appliquer des filtres juste avant cet affichage à l’écran. En gros on commence par créer une image bitmap, on applique le filtre puis on affiche.

On utilise la balise filter et on dispose de nombreux effets ! On parle de primitives et il en faut au moins une avec une ou deux entrées et une sortie. Avec les primitives on peut flouter, déplacer, remplir, appliquer des effets…).

C’est un sujet très vaste que je vais juste présenter. Vous pouvez trouver la spécification complète ici.

Flou et décalage

L’effet le plus classique est le flouté. On utilise la balise feGaussianBlur pour le réaliser :

<defs>
  <filter id="filtre">
    <feGaussianBlur in="SourceGraphic" stdDeviation="3"/>
  </filter>
</defs>
<g filter="url(#filtre)">

J’ai pris comme entrée (in) l’image elle-même (SourceGraphic). On peut se contenter de l’entrée alpha pour ne prendre en compte que la forme sans se soucier des couleurs :

<feGaussianBlur in="SourceAlpha" stdDeviation="3"/>

On peut imaginer utiliser cet effet pour créer un ombrage à notre hibou mais il va falloir mettre en œuvre plusieurs primitives. En particulier on va utiliser la balise feOffset qui permet de faire une translation.

Pour chaîner plusieurs primitives on utilise la balise feMerge :

<filter id="filtre">
  <feGaussianBlur in="SourceAlpha" stdDeviation="10" result="flou"/>
  <feOffset in="flou" dx="10" dy="10" result="flouDecale"/>
  <feMerge>
    <feMergeNode in="flouDecale"/>
    <feMergeNode in="SourceGraphic"/>
  </feMerge>
</filter>

Simple et efficace !

La morphologie

On peut éroder les lignes d’un dessin :

<filter id="filtre">
  <feMorphology operator="erode" radius="6  "/>
</filter>

Ou les dilater :

<feMorphology operator="dilate" radius="6"/>

Combiner

On peut utiliser une image en tant qu’entrée de filtre :

<filter id="filtre">
  <feImage xlink:href="https://upload.wikimedia.org/wikipedia/commons/thumb/7/76/Foret.JPG/320px-Foret.JPG" result="fond"
    x="0" y="0" width="100%" height="100%"
    preserveAspectRatio="none"/>
  <feMerge>
    <feMergeNode in="fond"/>
    <feMergeNode in="SourceGraphic"/>
  </feMerge>
</filter>

Notre hibou est maintenant dans une ambiance forestière !

Au lieu d’utiliser feMerge on peut se servir du plus général feComposite. Il permet de combiner deux primitives avec ces possibilités :

  • over
  • in
  • out
  • atop
  • xor
  • arithmetic

Voici un exemple avec in :

<filter id="filtre">
  <feImage xlink:href="https://upload.wikimedia.org/wikipedia/commons/thumb/7/76/Foret.JPG/320px-Foret.JPG" result="fond"
    x="0" y="0" width="100%" height="100%"
    preserveAspectRatio="none"/>
  <feComposite operator="in" in="fond" in2="SourceGraphic"/>
</filter>

On obtient en fait une découpe dans ce cas…

Et voilà avec xor :

On a aussi la possibilité d’utiliser feBlend qui combine les pixels avec :

  • normal
  • multiply
  • screen
  • darken
  • lighten

Par exemple avec multiply :

<feBlend mode="multiply" in2="fond" in="SourceGraphic"/>

Avec screen :

Avec darken :

Et avec lighten :

Les turbulences

Un effet original avec les turbulences qui peuvent être classiques :

<feTurbulence type="turbulence" baseFrequency="0.02" numOctaves="2"/>

Ou fractales :

<feTurbulence type="fractalNoise" baseFrequency="0.02" numOctaves="2"/>

Du relief

Il y a encore beaucoup à dire et illustrer sur le sujet des filtres mais j’en resterai là en présentant un effet relief qui combine plusieurs primitives extrait des spécifications :

<filter id="filtre">
  <feGaussianBlur in="SourceAlpha" stdDeviation="4" result="blur"/>
  <feOffset in="blur" dx="4" dy="4" result="offsetBlur"/>
  <feSpecularLighting in="blur" surfaceScale="5" specularConstant=".75" 
                      specularExponent="20" lighting-color="#bbbbbb"  
                      result="specOut">
    <fePointLight x="-5000" y="-10000" z="20000"/>
  </feSpecularLighting>
  <feComposite in="specOut" in2="SourceAlpha" operator="in" result="specOut"/>
  <feComposite in="SourceGraphic" in2="specOut" operator="arithmetic" 
                k1="0" k2="1" k3="1" k4="0" result="litPaint"/>
  <feMerge>
    <feMergeNode in="offsetBlur"/>
    <feMergeNode in="litPaint"/>
  </feMerge>
</filter>

Conclusion

On a vu dans cet article que le format SVG n’a rien à envier aux autres formats graphiques concernant les effets finaux. Et évidemment il est possible d’animer tous ces effets !

Laisser un commentaire