Accueil

Traduction

Tutoriel Canvas - sommaire

Tutoriel Canvas - recherche

L'auteur : Patrick Darcheville

Vous pouvez me contacter via Facebook pour questions & suggestions : Page Facebook relative à mon site

Canvas : les transformations

Il s'agit des translations, des changements d'échelle et des rotations, des inclinaisons.
La translation a déjà été évoquée à l'occasion de différents exemples.
Ce chapitre prépare ceux sur les animations. En effet les transformations sont à la base de nombreuses animations. Un seul exemple : pour déplacer un objet 2D le plus simple est de le translater.

Translations

Translation : le point d’origine du canevas est déplacé.

Exemple

Les couleurs évoluent et sont à demi opaques.
Six carrés décalés et un septième dans le coin haut droit du canevas.

Le code correspondant

L'élément canvas est dépourvu des attributs id, width & height donc le repère défini est ... ?
La fonction JS :

function script1()
{
	canevas = document.querySelector('canvas'); 
	contexte = canevas.getContext('2d');
	var X =canevas.width;
	var Y =canevas.height;
	console.log(X); 
	console.log(Y); 
	var delta = 20; 
	var cote = 50;
	var rouge = 255; var vert = 0; var bleu = 0; 
	var couleur;;
	contexte.globalAlpha = 0.5; // 50% opacité pour tous les tracés futurs
	for (i=1; i<=6; i++)
	{
		rouge-=30 ; vert+=30; bleu+=30;
		couleur = "rgb("+ rouge+","+vert+","+bleu+")" ; 
		contexte.fillStyle = couleur ;
		contexte.fillRect(0,0,cote,cote) ; 
		contexte.translate(delta,delta) ; // déplacement origine pour rectangle suivant
	} // fin for 
		
	contexte.resetTransform(); 
	contexte.fillRect(X-cote,0,cote,cote); 
}
script1(); 	//appel de la fonction	

Puis l'instruction resetTransform() annule toutes les transformations ; donc l'origine du canevas est de nouveau son coin haut gauche ; donc le carré avec les paramètres géométriques (X-cote,0,cote,cote) se positionne en haut à droite du canevas.

Changement d'échelle

Après un contexte.scale(2,2) l'instruction contexte.fillRect(100,100,100,100) dessine et remplit un carré de 200 par 200 positionné à 200,200 de l'origine.

Exemple complet

Le code correspondant

La balise canvas définit un repère de 400 par 400 ; l'attribut ID est absent.
Le script correspondant :

function script2()
{
	canevas = document.querySelectorAll('canvas')[1]; 
	contexte = canevas.getContext('2d');
	contexte.fillStyle ='red'; 
	contexte.fillRect(0,0,100,100);
	contexte.save(); //sauvegarde état initial canevas
	contexte.scale(2,2);
	contexte.fillStyle ='green'; 
	contexte.fillRect(100,100,100,100);
	contexte.restore(); //restauration état intial canevas
	contexte.fillStyle ='yellow';
	contexte.fillRect(100,100,100,100);
}
script2(); //appel fonction

Rotation de formes

On peut aussi changer l'orientation du contexte ou en d'autres termes effectuer une rotation des axes.

Principes

La méthode rotate() est argumentée par un angle exprimé en radians !
Cette méthode fait pivoter les axes X et Y du repère dans le sens des aiguilles d'une montre autour du point d'origine. Souvent cette rotation a été précédée d'une translation : le centre du canevas devenant l'origine.

Observez le schéma ci-dessous qui est plus explicatif qu'un long discours.

Ainsi si vous dessinez un carré suite à une rotation de 45° des axes il apparaitra en fait tel un losange !

Exemple

Trois formes dans le canevas ci-dessus : un carré rouge puis un losange jaune et enfin un carré vert !

Le code correspondant

La balise canvas définit un repère de 400 par 300 ; l'attribut ID est absent.
La fonction JS :

function script3()
{                          
	canevas = document.querySelectorAll('canvas')[2]; 
	contexte = canevas.getContext('2d');
	contexte.fillStyle ='red'; 
	contexte.fillRect(0,0,100,100);
	contexte.save(); 
	contexte.rotate(Math.PI/180 *45); 
	contexte.fillStyle ='yellow'; 
	contexte.fillRect(200,0,100,100);
	contexte.restore(); 
	contexte.fillStyle ='green'; 
	contexte.fillRect(300,100,100,100);
}
script3();

Un cadran de réveil

Il s'agit d'un bon exemple de rotation après un changement d'origine.
Ci-dessous un document HTML qui contient une 'toile' affichant le cadran d'une montre avec un aiguille (la trotteuse).

Le code de ce document

La balise canvas du document définit un repère de 300 par 300 ; la toile est identifiée "canevas".

Le script

var canevas = document.querySelector("#canevas"); 	
var ctx = canevas.getContext("2d");
var X =canevas.width; 	
var Y =canevas.height;
// l'origine devient le centre de la toile
ctx.translate(X/2,Y/2); 
ctx.strokeStyle = "gold";
ctx.lineWidth = 4;

// marques heures
for (var i=0;i<12;i++)
{
    ctx.beginPath();
	// un trait tous les 30°
    ctx.rotate(Math.PI/180 * 30);
    ctx.moveTo(80,0);
    ctx.lineTo(100,0);
    ctx.stroke();
}

// marques minutes
ctx.lineWidth = 2;
for (i=0;i<60;i++)
{
	ctx.beginPath();
	// un trait tous les 6°
	ctx.rotate(Math.PI/180 *6);
    ctx.moveTo(90,0);
    ctx.lineTo(100,0);
    ctx.stroke();
}
// trotteuse 
ctx.strokeStyle ="black"; 
ctx.lineWidth =3;
ctx.beginPath();
ctx.moveTo(0,0);
ctx.lineTo(0,-100);
ctx.stroke(); 

Analyse du script

ctx.translate(X/2,Y/2) : à partir du moment que vous envisagez des rotations il est préférable que l'origine du canevas soit son centre (et non pas le coin haut gauche).

Marquage des heures :
Il faut créer 12 traits : un trait tous les 30 degrés

Marquage des minutes :
Il faut créer 60 traits plus courts et moins épais : un trait tous les 6 degrés.

Affichage de l'aiguille (trotteuse) :
Il faut changer la couleur et l'épaisseur de contour, dessiner un trait à partir du centre du canevas.

Rappel : la méthode rotate() est argumentée par un angle en radians. Pour convertir des degrés en radians la formule est : Math.PI/180 *degrés.

Bien maitriser les méthodes save() & restore()

J'ai déjà abordé ces méthodes mais dans un cas simple : deux contextes uniquement.

la méthode save()

Cette méthode sauvegarde l'état du canevas dans sa globalité.
Chaque invocation de la méthode save() ajoute une copie de l'état courant du canevas en haut de la pile.

La méthode save() peut être invoquée autant de fois que nécessaire.

La méthode restore()

Cette commande rétablit le plus récent contexte sauvegardé (celui en haut de la pile).
Cette méthode peut être évoquée autant de fois qu'il y a eu de commmandes save().

Un document HTML contenant un canevas

Cinq carrés de 100 par 100 sont dessinés : 3 contours et 2 remplissages.

Le script

La balise canvas définit un repère de 400 par 400.
Dans le script je définis un rectangle nommé "carre" dont les paramètres géométriques sont : 0,0,100,100. Puis je le dessine cinq fois mais dans des contextes différents.

function dessin() 
{
  var ctx = document.getElementById("canvas").getContext("2d");
  ctx.strokeStyle ='olive'; 
  ctx.lineWidth =5; 
  ctx.fillStyle ='rgba(0,0,0,0.3)';  // gris clair
  
  var carre = new Path2D();
  carre.rect(0,0,100,100); 

  ctx.stroke(carre); //premier dessin de carre
  ctx.save();   // Sauvegarde contexte 0

  ctx.translate(200,200) ; 
  // l'origine devient le centre du canevas
  ctx.stroke(carre); // deuxième dessin de carre
  ctx.save(); // sauvegarde contexte 1
   
  ctx.rotate(Math.PI/180*45);
  ctx.stroke(carre); // 3ième dessin de carre

  ctx.restore(); // restauration contexte 1
  ctx.fill(carre); // 4ième dessin de carre

  ctx.restore(); // restauration  contexte 0
  ctx.fill(carre); // 5ième dessin de carré
 }
dessin();

HG veut dire : haut gauche

"Cerise sur le gâteau" : la méthode setTransform()

Il s'agit d'une méthode récente mais très utile car elle définit un nouveau contexte de transformation tout en annulant le précédent. Elle permet donc d'économiser des lignes de code en particulier des instructions basées sur les méthodes save() & restore().
Mais sa syntaxe est un peu délicate ...

Six paramètres

Syntaxe : setTransform(a, b, c, d, e, f)

Exemple

Ci-dessous un document HTML contenant une 'toile'.

Remarquez le carré violet en haut à droite.

Le code

La balise canvas du document définit un repère de 600 par 600.

Le script

Vous allez être épaté par la concision du code ; je dessine 7 carrés de 100 par 100 positionnés au point 0,0. Mais ils prennent des aspects et des positions très différents à cause des différents contextes.

 const canvas = document.getElementById('canevas');
  const ctx = canvas.getContext("2d");
  ctx.lineWidth =3; 
  X=canvas.width; 
  
  ctx.strokeStyle = 'red'; 
  ctx.strokeRect(0,0,100,100); 
  
  ctx.setTransform(2,0,0,2,0,0); // doublement échelle
  ctx.strokeStyle ='green'; 
  ctx.strokeRect(0,0,100,100); 
  
  ctx.setTransform(1,0,0,1,150,150);  // translation et retour à échelle 1
  ctx.strokeStyle ='navy'; 
  ctx.strokeRect(0,0,100,100); 
  
  ctx.setTransform(1,0.5, 0,1,250,250);  // inclinaison et translation
  ctx.strokeStyle ='black'; 
  ctx.strokeRect(0,0,100,100); 
  
  ctx.setTransform(1,0,0.5, 1,300,300);  // inclinaison et translation
  ctx.strokeStyle ='orange'; 
  ctx.strokeRect(0,0,100,100);

  ctx.setTransform(1,-0.5,-0.5, 1, 400, 400);  
  ctx.strokeStyle ='yellow'; 
  ctx.strokeRect(0,0,100,100); 
  
  ctx.resetTransform(); 
  ctx.strokeStyle ='purple';
  ctx.strokeRect(X-100,0, 100, 100); 

H : horizontal ; V : vertical
Valeurs par défaut pour les paramètres a & d : 1
Valeurs par défaut pour les paramètres e & f : 0
Valeur par défaut pour les paramètres b & c : 0

Remarque

Ne confondez pas SetTransform() avec transform().
Avec la méthode historique transform(), le précédent contexte n'est pas annulé ; il a un phénomène cumulatif.
La méthode transform accepte les mêmes paramètres que setTransform().
On peut dire que SetTransform() équivaut à resetTransform() + transform().

Exemple de canevas basé sur la méthode transform()

Plutôt un petit exemple qu'un long discours.

Le script

Repère cartésien de 400 par 400 défini par la balise canvas.

const canvas = document.getElementById("canevas");
const ctx = canvas.getContext("2d");

ctx.fillStyle = "olive";
ctx.fillRect(0, 0, 300,80)

ctx.transform(1.1, 0.3, -0.3, 1.1, 30, 10);
ctx.fillStyle = "green";
ctx.fillRect(0, 0, 300, 80);

ctx.transform(1.1, 0.3, -0.3, 1.1, 30, 10);
ctx.fillStyle = "lime";
ctx.fillRect(0, 0, 300, 80);

ctx.transform(1.1, 0.3, -0.3, 1.1, 30, 10);
ctx.fillStyle = "black";
ctx.fillRect(0, 0, 300, 80);

Notez que les transformations se cumulent : les quatre rectangles s'agrandissent et leur inclinaison s'accentue.
En effet à chaque fois l'échelle 1.1 tant en H qu'en V et une inclinaison 0.3 tant en H qu'en V et aussi une translation tant en H qu'en V.