Visit Mozilla.org

Tutoriel canvas:Transformations

Un article de MDC.

Sommaire


[modifier] Enregistrement et restauration d'états

Avant de se pencher sur les méthodes de transformation, introduisons deux autres méthodes indispensables pour générer des dessins encore plus complexes.

save()
restore()

Les méthodes save et restore de canvas sont utilisées pour enregistrer et restaurer l'état du canevas. Pour faire court, cet état est un instantané de tous les styles et transformations qui ont été appliquées sur le canevas. Ces deux méthodes ne prennent pas de paramètres.

Les états sont stockés sur une pile. Chaque fois que la méthode save est appelée, l'état courant est ajouté à la pile. Un état consiste en :

  • Les transformations qui ont été appliquées (c'est-à-dire les translations, rotations et mises à l'échelle - voir plus bas).
  • Les valeurs des propriétés strokeStyle, fillStyle, globalAlpha, lineWidth, lineCap, lineJoin, miterLimit, shadowOffsetX, shadowOffsetY, shadowBlur, shadowColor, et globalCompositeOperation.
  • Le chemin de découpage courant, que nous aborderons dans la prochaine section.

La méthode save peut être appelée autant de fois que vous le voulez.

Chaque fois que la méthode restore est appelée, le dernier état enregistré est retiré de la pile et tous les paramètres enregistrés sont restaurés.

[modifier] Un exemple d'enregistrement d'état et de restauration

Cet exemple tente d'illustrer la manière dont la pile d'états fonctionne en la représentant sous la forme de rectangles consécutifs.

La première étape est de dessiner un grand rectangle avec les paramètres par défaut. Ensuite, cet état est enregistré et des changements sont faits sur la couleur de remplissage. Un second rectangle plus petit et bleu est alors dessiné, puis l'état enregistré. À nouveau, on change quelques paramètres de dessin et on dessine un troisième rectangle blanc semi-transparent.

Jusque là, c'est très similaire à ce qui a été fait dans les sections précédentes. Cependant, une fois qu'on appelle restore une première fois, l'état précédent est retiré de la pile et ses paramètres sont restaurés. Si l'on n'avait pas enregistré l'état à l'aide de save, il faudrait changer la couleur de remplissage et la transparence manuellement pour revenir à cet état précédent. C'est facile à faire pour deux propriétés, mais s'il y en a plus, le code deviendrait très rapidement très long.

Lorsque le second appel à restore est fait, l'état original (celui qui avait été mis en place avant le premier appel à save) est restauré et le dernier rectangle est à nouveau dessiné en noir.

Afficher cet exemple

function draw() {
  var ctx = document.getElementById('canvas').getContext('2d');

  ctx.fillRect(0, 0, 150, 150);   //  Dessine un rectangle avec les paramètres par défaut
  ctx.save();                     //  Enregistre l'état par défaut

  ctx.fillStyle = '#09F'          // Modifie les paramètres
  ctx.fillRect(15, 15, 120, 120); // Dessine un rectangle avec ces paramètres

  ctx.save();                     // Enregistre l'état courant
  ctx.fillStyle = '#FFF'          // Modifie les paramètres
  ctx.globalAlpha = 0.5;    
  ctx.fillRect(30, 30, 90, 90);   // Dessine un rectangle avec les nouveaux paramètres

  ctx.restore();                  // Restaure l'état précédent
  ctx.fillRect(45, 45, 60, 60);   // Dessine un rectangle avec les paramètres restaurés

  ctx.restore();                  // Restaure l'état originel
  ctx.fillRect(60, 60, 30, 30);   // Dessine un rectangle avec les paramètres restaurés
}

NdT : l'exemple ne semble pas fonctionner dans Firefox 1.5... à vérifier.

[modifier] Translations

La première méthode de transormation que nous examinerons est translate. Cette méthode est utilisée pour déplacer le canevas et son origine vers un point différent dans la grille.

translate(x, y)

Cette méthode prend deux arguments. x est l'importance du déplacement du canevas vers la gauche ou vers la droite, et y de son déplacement vers le haut ou vers le bas (illustrée par l'image à droite).

Il peut être une bonne idée d'enregistrer l'état du canevas avant de réaliser une transformation. Dans la plupart des cas, il est plus facile d'appeler la méthode restore que d'avoir à faire une translation inverse pour revenir à l'état initial. Par ailleurs, si la translation se fait à l'intérieur d'une boucle et que vous n'enregistrez ni ne restaurez l'état, vous pourriez vous retrouver avec des parties de votre dessin qui manquent, parce qu'elles ont été dessinées en dehors des limites du canevas.

[modifier] Un exemple d'utilisation de translate

Cet exemple présente certains des bénéfices que l'on peut tirer de la translation de l'origine du canevas. Une fonction drawSpirograph qui dessine des modèles de spirographe a été créée. Ceux-ci sont dessinés autour de l'origine. Si la fonction translate n'avait pas été utilisée, on verrait seulement un quart du dessin sur le canevas. La méthode translate donne également la liberté de le placer n'importe où sur le canevas sans avoir à ajuster manuellement les coordonnées dans la fonction du spirographe. Cela la rend un peu plus facile à comprendre et à utiliser.

Dans la fonction draw, on appelle drawSpirograph neuf fois au sein de deux boucles for. À chaque tour de boucle, une translation est effectuée, le spirographe est dessiné et le canevas replacé dans son état de départ.

Afficher cet exemple

function draw() {
  var ctx = document.getElementById('canvas').getContext('2d');
  ctx.fillRect(0, 0, 300, 300);
  for (var i = 0; i < 3; i++) {
    for (var j = 0; j < 3; j++) {
      ctx.save();
      ctx.strokeStyle = "#9CFF00";
      ctx.translate(50+j*100, 50+i*100);
      drawSpirograph(ctx, 20*(j+2)/(j+1), -8*(i+3)/(i+1), 10);
      ctx.restore();
    }
  }
}

function drawSpirograph(ctx, R, r, O){
  var x1 = R-O;
  var y1 = 0;
  var i  = 1;
  ctx.beginPath();
  ctx.moveTo(x1, y1);
  do {
    if (i > 20000) break;
    var x2 = (R+r)*Math.cos(i*Math.PI/72) - (r+O)*Math.cos(((R+r)/r)*(i*Math.PI/72))
    var y2 = (R+r)*Math.sin(i*Math.PI/72) - (r+O)*Math.sin(((R+r)/r)*(i*Math.PI/72))
    ctx.lineTo(x2, y2);
    x1 = x2;
    y1 = y2;
    i++;
  } while (x2 != R-O && y2 != 0 );
  ctx.stroke();
}

[modifier] Rotations

La seconde méthode de transformation est rotate. On l'utilise pour tourner le canevas autour de son origine.

rotate(angle)

Cette méthode prend un seul paramètre, l'angle selon lequel la rotation du canevas est effectuée. Il s'agit d'une rotation dans le sens des aiguilles d'une montre, mesurée en radians (illustrée dans l'image à droite).

Le point central de la rotation est toujours l'origine du canevas. Pour changer le point central, il est nécessaire de déplacer le canevas à l'aide de la méthode translate.

[modifier] Un exemple d'utilisation de rotate

Dans l'exemple que vous pouvez voir sur la droite, la méthode rotate a été utilisée pour dessiner des formes dans une structure circulaire. On aurait aussi pu calculer les coordonnées x et y individuelles (x = r*Math.cos(a); y = r*Math.sin(a)). Dans ce cas, la méthode utilisée importe peu puisqu'on dessine des cercles. Le calcul des coordonnées résulte en une rotation des centres des cercles plutôt que des cercles eux-mêmes, tandis qu'utiliser rotate effectue les deux, mais évidemment les cercles apparaissent identiques quelle que soit la rotation qu'ils aient effectués autour de leurs centres respectifs.

À nouveau, on utilise deux boucles. La première détermine le nombre de cercles, et la seconde détermine le nombre de points dessinés dans chaque cercle. Avant de dessiner chaque cercle, l'état du canevas est enregistré afin de pouvoir être retrouvé facilement. Pour chaque point dessiné, l'espace de coordonnées du canevas effectue une rotation selon un angle déterminé par le nombre de points dans le cercle. L'anneau le plus à l'intérieur a six points, donc à chaque étape la rotation se fait sur un angle de 360/6 = 60 degrés. Pour chaque anneau supplémentaire, le nombre de points est doublé et l'angle de rotation est divisé par deux.

Afficher cet exemple

function draw() {
  var ctx = document.getElementById('canvas').getContext('2d');
  ctx.translate(75, 75);

  for (i = 1; i < 6; i++) { // Boucle au sein des anneaux (de l'intérieur vers l'extérieur)
    ctx.save();
    ctx.fillStyle = 'rgb(' + (51*i) + ',' + (255-51*i) + ',255)';

    for (j = 0; j < i*6; j++) { // dessine les points
      ctx.rotate(Math.PI*2/(i*6));
      ctx.beginPath();
      ctx.arc(0, i*12.5, 5, 0, Math.PI*2, true);
      ctx.fill();
    }

    ctx.restore();
  }
}

[modifier] Mise à l'échelle

La méthode de transformation suivante est la mise à l'échelle. On l'utilise pour agrandir ou diminuer la taille des unités dans le quadrillage de notre canevas. Cela peut être utilisé pour dessiner des miniatures ou des agrandissement de formes ou d'images bitmap.

scale(x, y)

Cette méthode prend deux paramètres. x est le facteur d'échelle dans la direction horizontale et y celui dans la direction verticale. Les deux paramètres doivent être des nombres positifs. Les valeurs plus petites que 1.0 réduisent la taille de l'unité, et les valeurs plus grandes que 1.0 augmentent la taille de l'unité. Positionner le facteur d'échelle à exactement 1.0 n'a aucune effet sur la taille de l'unité.

Par défaut, une unité sur le canevas est exactement un pixel. Si l'on applique, par exemple, un facteur d'échelle de 0.5, l'unité résultante deviendrait 0.5 pixels et les formes seraient dessiné à la moitié de leur taille. D'une manière similaire, en positionnant le facteur d'échelle à 2.0 on augmente la taille de l'unité et celle-ci devient alors égale à deux pixels. Le résultat est que les formes seront dessinées à deux fois leur taille.

[modifier] Un exemple d'utilisation de scale

Dans ce dernier exemple, la fonction spirograph d'un des exemples précédents a été utilisée pour dessiner neuf formes avec différents facteurs d'échelle. Celle d'en haut à gauche a été dessinée sans mise à l'échelle. Les formes à droite ont chacune un facteur d'échelle uniforme (la même valeur pour les paramètres x et y). Si vous regardez le code ci-dessous, vous verrez que la méthode scale a été utilisée deux fois avec les mêmes valeurs de paramètres pour le second et le troisième spirographe. Comme l'état du canevas n'a pas été restauré, la troisième forme est dessinée avec un facteur d'échelle de 0.75 × 0.75 = 0.5625.

Sur la deuxième rangée de formes bleues, une mise à l'échelle non uniforme a été appliquée dans la direction verticale. Chacune des formes a son facteur d'échelle x à 1.0 ce qui signifie aucune mise à l'échelle. Le facteur d'échelle y est à 0.75. Le résultat est que les trois formes sont écrasées vers le bas. La forme circulaire d'origine est devenue une ellipse. Si vous regardez de plus près, vous remarquerez que l'épaisseur de ligne a également été réduite dans la direction verticale.

La troisième rangée de formes vertes est similaire à celle d'au-dessus, mais c'est une mise à l'échelle dans la direction horizontale qui lui a été appliquée.

Afficher cet exemple

function draw() {
  var ctx = document.getElementById('canvas').getContext('2d');
  ctx.strokeStyle = "#fc0";
  ctx.lineWidth = 1.5;
  ctx.fillRect(0, 0, 300, 300);

  // Mise à l'échelle uniforme
  ctx.save()
  ctx.translate(50, 50);
  drawSpirograph(ctx, 22, 6, 5);  // pas de mise à l'échelle

  ctx.translate(100, 0);
  ctx.scale(0.75, 0.75);
  drawSpirograph(ctx, 22, 6, 5);

  ctx.translate(133.333, 0);
  ctx.scale(0.75, 0.75);
  drawSpirograph(ctx, 22, 6, 5);
  ctx.restore();

  // Mise à l'échelle non uniforme (direction y)
  ctx.strokeStyle = "#0cf";
  ctx.save()
  ctx.translate(50, 150);
  ctx.scale(1, 0.75);
  drawSpirograph(ctx, 22, 6, 5);

  ctx.translate(100, 0);
  ctx.scale(1, 0.75);
  drawSpirograph(ctx, 22, 6, 5);

  ctx.translate(100, 0);
  ctx.scale(1, 0.75);
  drawSpirograph(ctx, 22, 6, 5);
  ctx.restore();

  // Mise à l'échelle non uniforme (direction x)
  ctx.strokeStyle = "#cf0";
  ctx.save()
  ctx.translate(50, 250);
  ctx.scale(0.75, 1);
  drawSpirograph(ctx, 22, 6, 5);

  ctx.translate(133.333, 0);
  ctx.scale(0.75, 1);
  drawSpirograph(ctx, 22, 6, 5);

  ctx.translate(177.777, 0);
  ctx.scale(0.75, 1);
  drawSpirograph(ctx, 22, 6, 5);
  ctx.restore();
  
}

[modifier] Transformations

Les méthodes de tranformation finale permettent de modifier directement la matrice de transformation.

transform(m11, m12, m21, m22, dx, dy)

Cette méthode doit multiplier la matrice de transformation courante par la matrice décrite par :

m11 	m21 	dx
m12 	m22 	dy
0 	0 	1

Si un des paramètres vaut Infinity, la matrice de transformation doit être marquée comme infinie et la méthode ne doit pas provoquer d'exception.

setTransform(m11, m12, m21, m22, dx, dy)

Cette méthode doit réinitialiser la transformation courante à la matrice d'indentité, et invoquer ensuite la méthode transform avec les mêmes arguments. Si l'un d'entre-eux vaut Infinity, la matrice de transformation doit être marquée comme infinie et la méthode ne doit pas provoquer d'exception.

[modifier] Exemples de transform / setTransform

function draw() {
  var canvas = document.getElementById("canvas");
  var ctx = canvas.getContext("2d");

  var i = 0;
  var sin = Math.sin(Math.PI/6);
  var cos = Math.cos(Math.PI/6);
  ctx.translate(200, 200);
  var c = 0;
  for (i; i <= 12; i++) {
    c = Math.floor(255 / 12 * i);
    ctx.fillStyle = "rgb(" + c + "," + c + "," + c + ")";
    ctx.fillRect(0, 0, 100, 10);
    ctx.transform(cos, sin, -sin, cos, 0, 0);
  }
  
  ctx.setTransform(-1, 0, 0, 1, 200, 200);
  ctx.fillStyle = "rgba(255, 128, 255, 0.5)";
  ctx.fillRect(0, 50, 100, 100);
}