Programmation objet et géométrie/La balise canvas

La balise canvas est une balise! Elle s'utilise un peu comme la balise div en lui fournissant un identifiant qui permet à JavaScript d'y placer du contenu. Ceci, en demandant au document de lui fournir l'élément d'identifiant donné.

Pour éviter de mélanger html et css, on peut donner à la balise canvas un style uniforme dans le document, en lui fournissant une couleur de bord et une épaisseur de bordure (en pixels), un peu comme le peintre qui commence par assembler un cadre en bois avant de tendre une toile (le canevas) dessus. Ceci se fait par quelque chose comme

<style type="text/css">
      canvas { border: 4px solid brown; background: #FFFFFF }
</style>

Dorénavant toutes les figures du document seront entourées d'un rectangle marron d'épaisseur 4 pixels et seront remplies de blanc par défaut. L'endroit idéal pour placer ce css est entre l'entête (head) et le corps (body) du document.

Côté html

modifier

Canvas est donc une balise munie d'un identifiant:

<canvas id="cadre" width="400" height="200">
Mettre ici un texte alternatif pour les malheureux qui n'utilisent pas Firefox...
</canvas><br/>

Comme toutes les balises, on peut la munir de méthodes JavaScript pour répondre aux évènements clavier et souris (par exemple onClick pour la réaction à un clic sur le canevas). Mais surtout, la balise canvas doit être munie de deux propriétés, sa largeur width et sa hauteur height, les deux étant exprimées en pixels.

Côté JavaScript

modifier
 

Pour accéder à un élément canvas, on utilise la méthode getElementById() du document, ce qui nécessite que l'élément canvas soit muni d'un identifiant. Dans la partie JavaScript (cette fois-ci, placée non pas dans l'entête head mais dans la balise canvas elle-même, pour éviter les problèmes de synchronisation au chargement du document) on écrit alors

var chevalet=document.getElementById('cadre');

L'objet stocké dans la variable chevalet correspond au contenu de la balise, et on ne peut pas encore dessiner dessus (un peintre ne peut pas dessiner sur le chevalet, il doit d'abord tendre une toile sur le cadre; cette toile s'appelle contexte ci-dessous).

Contexte

modifier

L'étape suivante est d'invoquer la méthode getContext() de l'objet chevalet (le canevas récupéré ci-dessus). Cette méthode renvoie un objet graphique sur lequel on va enfin pouvoir dessiner! Le plus simple est de stocker dans une variable pinceau (la plupart des auteurs préfèrent l'appeler contexte) ce que répond l'objet chevalet lorsqu'on lui demande l'entrée "2d" de son contexte:

var chevalet=document.getElementById('cadre');
var pinceau=chevalet.getContext('2d');

Cette étape supplémentaire a deux avantages:

  1. Comme on s'en doute, si pour l'instant seule la variable "2d" du contexte renvoie un objet non vide, il est prévenu dans un avenir plus ou moins proche qu'il y ait aussi un contexte "3d"...
  2. On peut (et c'est même conseillé) mettre tout le traitement de la figure dans un test sur la non nullité du contexte "2d"; ce qui évite d'avoir des messages d'erreur sur des navigateurs ne reconnaissant pas la balise canvas.

Un fichier html5 minimal avec un dessin ressemble à ceci:

<html>
<style type="text/css">
      canvas { border: 4px solid brown; background: #FFFFFF }
</style>
<body>
<canvas id="cadre" width="400" height="400">
<script>
var chevalet=document.getElementById('cadre');
var toile=chevalet.getContext('2d');
</script>
</canvas>
</body>
</html>


Système de coordonnées

modifier

Pour dessiner sur la toile, il faut fournir les coordonnées de points (point de départ du trait de pinceau par exemple). Ce qui nécessite de connaître le système de coordonnées du canevas. L'axe des abscisses va de gauche à droite, et l'axe des ordonnées va de haut en bas. Ainsi le point en haut à gauche a pour coordonnées (0,0) et le point en bas à droite a pour coordonnées (width,height). Les points de coordonnées négatives ne seront donc pas dessinés.

Effaçage de la figure

modifier

Il existe deux moyen de créer une gomme avec canvas ! Le premier est de cacher les dessins par une tache de peinture de couleur blanche (technique du palimpseste). Pour effacer toute la figure (par exemple pour des animations), on fait en deux étapes:

Tremper le pinceau dans la peinture blanche

modifier

L'objet pinceau possède une méthode fillStyle à laquelle on envoie le message White:

pinceau.fillStyle='White';

Recouvrir le tableau de blanc

modifier

Pour le blanchiment de canvas sale, il suffit de dessiner un rectangle rempli ayant les dimensions du canevas, en fournissant les coordonnées du coin en haut à gauche de celui-ci, puis les dimensions du rectangle:

pinceau.fillStyle='White';
pinceau.fillRect(0,0,400,200);

Une vraie gomme

modifier

Le deuxième offre la possibilité de créer une vraie gomme, qui efface et ne laisse aucune trace, il faut procéder ainsi :

pinceau.globalCompositeOperation = "destination-out";
pinceau.strokeStyle = "rgba(0,0,0,1.0)";

Le pinceau va donc effacer les parties tracés auparavant, sans laisser de couleur transparaître. Bien pratique lorsque l'on désire faire un canvas transparent avec une image derrière par exemple ! Pour plus d'information sur la propriété globalCompositeOperation et toutes ses possibilités, je vous renvoie à cette page.

Coup de pinceau

modifier

Le dessin sous canvas est vectoriel, c'est-à-dire qu'on doit lui fournir des informations sur les coups de pinceau (où et quand on commence à dessiner, épaisseur et couleur du trait, etc.). Les instructions graphiques sont donc de nature à suggérer un mouvement plutôt qu'une description.

Choix du pinceau

modifier

Pour faire de gros traits (d'épaisseur 8 pixels) et un démarrage arrondi, on exprime ces demandes à l'objet pinceau:

pinceau.lineWidth='8';
pinceau.lineCap='round';

Tremper le pinceau dans la peinture

modifier

Avant de dessiner en bleu, on trempe le pinceau dans la peinture bleue, on a vu ci-dessus avec l'effaçage comment on fait ça en JavaScript:

pinceau.strokeStyle='Blue';

Poser le pinceau sur la toile

modifier

Tout trait de pinceau qui se respecte possède un début (beginPath) et une fin (stroke). On doit donc commencer par

pinceau.beginPath();

Dessiner

modifier

En fait, on peut lever le pinceau dans un trait de pinceau (par exemple pour dessiner en pointillés). Pour poser le pinceau avant un nouveau trait, on utilise moveTo en fournissant les coordonnées du point de départ:

pinceau.moveTo(50,100);

Le coup de pinceau commencera donc par le point de coordonnées (50,100) qui est vers la gauche du tableau.

Ensuite, on définit les mouvements du pinceau par des coordonnées de points à joindre, par exemple avec bezierLineTo qui demande les coordonnées de trois points:

  1. le point de contrôle du départ, ici (120,20) ce qui veut dire que la courbe sera tangente au segment (non dessiné) allant de (50,100) jusqu'à (120,20);
  2. le point de contrôle de l'arrivée, ici (160,200);
  3. le point d'arrivée du pinceau, ici (150,100): Le trait de pinceau sera aussi tangente en son point d'arrivée, au segment joignant (160,200) à (150,100):
pinceau.bezierCurveTo(120,20,160,200,150,100);

Lever le pinceau

modifier

Pour achever le trait de pinceau (avant de changer de couleur de peinture) on lance l'instruction stroke à l'objet pinceau:

pinceau.stroke();

Exemple

modifier

Avec trois courbes de Bezier, on peut faire une signature d'aspect très manuel:

<html>
<style type="text/css">
      canvas { border: 4px solid brown; background: #FFFFFF }
</style>
<body>
<canvas id="cadre" width="400" height="200">
<script>
var chevalet=document.getElementById('cadre');
var pinceau=chevalet.getContext('2d');
pinceau.strokeStyle='Blue';
pinceau.lineWidth='8';
pinceau.lineCap='round';
pinceau.beginPath();
pinceau.moveTo(50,100);
pinceau.bezierCurveTo(120,20,160,200,150,100);
pinceau.bezierCurveTo(120,20,100,100,150,100);
pinceau.bezierCurveTo(200,120,180,100,350,100);
pinceau.stroke();
</script>
</canvas>
</body>
</html>

L'effet obtenu est le suivant:

 

Coloriage

modifier

Aplat de couleur

modifier

Une alternative à pinceau.stroke() est pinceau.fill() qui en plus, essaye de remplir le contour dessiné avec la couleur définie par fillStyle. Ce qui revient à faire du coloriage. Par exemple, une lemniscate orange:


<html>
<style type="text/css">
      canvas { border: 4px solid brown; background: #FFFFFF }
</style>
<body>
<canvas id="cadre" width="400" height="200">
<script>
var chevalet=document.getElementById('cadre');
var pinceau=chevalet.getContext('2d');
pinceau.fillStyle='Orange';
pinceau.beginPath();
pinceau.moveTo(50,100);
pinceau.bezierCurveTo(50,0,350,200,350,100);
pinceau.bezierCurveTo(350,0,50,200,50,100);
pinceau.fill();
</script>
</canvas>
<br/>
</body>
</html>

Ce qui donne ceci:

 


Dégradé de couleur

modifier

L'usage de dégradés permet de faire des figures à l'aérographe donnant l'illusion du relief. Ci-dessous on va dessiner une boule de billard, mais d'abord son ombre. Dans les deux cas, on va créer un nouveau fillStyle pour l'objet pinceau.

Ombre avec dégradé linéaire

modifier

Le fillStyle sera linéaire, et contrôlé par deux points, l'un ayant la couleur de niveau 0, et l'autre la couleur de niveau 1. Les autres couleurs seront calculées par interpolation entre ces deux valeurs.

var ombre=pinceau.createLinearGradient(100,250,300,380);

Le point de coordonnées (100,250) (centre de l'ombre de la boule) sera noir, et le point de coordonnées (300,380) sera entièrement transparent (donc invisible) grâce à sa valeur a (le quatrième nombre) égal à 0 (sa valeur normale est 1):

ombre.addColorStop(0,"Black");
ombre.addColorStop(1,"rgba(0,0,0,0)");

Pour dessiner l'ombre, il suffit de tracer une ellipse (avec deux arcs de Bezier) et de la remplir avec fill mais après avoir choisi ombre comme fillStyle:

pinceau.fillStyle=ombre;
pinceau.beginPath();
pinceau.moveTo(150,300);
pinceau.bezierCurveTo(150,250,350,250,350,300);
pinceau.bezierCurveTo(350,350,150,350,150,300);
pinceau.fill();

Boule avec dégradé radial

modifier

Pour définir un style de remplissage adapté à la boule, il faut en plus des coordonnées des deux points de départ et d'arrivée du dégradé (typiquement deux fois le même point pour une sphère), fournir deux rayons. Par exemple, si le premier centre est (180,150) (reflet brillant sur la boule) et le second (180,160) (centre de la partie sombre), la partie claire peut avoir pour rayon 10, et la partie sombre pour rayon 100:

var degrade=pinceau.createRadialGradient(180,150,10,180,160,100);

La boule aura l'air bleue si on prend du blanc comme couleur du reflet et du bleu foncé (valeurs de rouge et vert à zéro, valeur de bleu à 56 et valeur de transparence à 1 -totalement opaque-) comme couleur de la partie sombre:

degrade.addColorStop(0,"White");
degrade.addColorStop(1,"rgba(0,0,56,1)");

Pour dessiner la boule, on peut se contenter d'un arc de cercle de centre (200,200), de rayon 100, et dont les angles de départ et d'arrivée sont respectivement   (radians) et   (la dernière valeur est un booléen sans importance pour un cercle complet), et de lui donner comme style de remplissage, le dégradé précédemment défini:

pinceau.fillStyle=degrade;
pinceau.beginPath();
pinceau.arc(200,200,100,0,Math.PI*2,true);
pinceau.fill();

Le script est un peu plus long que les précédents:

<html>
<style type="text/css">
      canvas { border: 4px solid brown; background: #FFFFFF }
</style>
<body>
<canvas id="cadre" width="400" height="400">
<script>
var chevalet=document.getElementById('cadre');
var pinceau=chevalet.getContext('2d');
var degrade=pinceau.createRadialGradient(180,150,10,180,160,100);
var ombre=pinceau.createLinearGradient(100,250,300,380);
degrade.addColorStop(0,"White");
degrade.addColorStop(1,"rgba(0,0,56,1)");
ombre.addColorStop(0,"Black");
ombre.addColorStop(1,"rgba(0,0,0,0)");
pinceau.fillStyle=ombre;
pinceau.beginPath();
pinceau.moveTo(150,300);
pinceau.bezierCurveTo(150,250,350,250,350,300);
pinceau.bezierCurveTo(350,350,150,350,150,300);
pinceau.fill();
pinceau.fillStyle=degrade;
pinceau.beginPath();
pinceau.arc(200,200,100,0,Math.PI*2,true);
pinceau.fill();
</script>
</canvas>
</body>
</html>

Mais l'effet obtenu est plutôt impressionnant: