Canvas détaillé

Michel Buffa
May 2013

buffa@unice.fr

Utilisation du Canvas

L'API est simple : une seule image résume tout !

Exemple typique d'utilisation

<canvas id="myCanvas">Canvas not supported.</canvas>

<script type="text/javascript">
  var canvas=document.querySelector('#myCanvas');
  var ctx=canvas.getContext('2d');
  ctx.fillStyle='#FF0000';
  ctx.fillRect(0,0,80,100);
</script>
    
Your browser does not support the canvas tag.

Voir le source du code dans ce slide.

Explications

  1. Déclarer un élément HTML de type canvas,
            <canvas id="myCanvas">
            
  2. Récupérer un pointeur dessus depuis JavaScript
            var canvas = document.querySelector("myCanvas");
            
  3. Récupérer le "contexte", qui servira à dessiner.
             var ctx = canvas.getContext('2d');
        

On effectue ces étapes en général dans une fonction appelée dès que la page est chargée

var canvas, ctx, w, h; // en global, pratique...

function init() {
    canvas = document.querySelector("#myCanvas");
    w = canvas.width; h = canvas.height;  // souvent utiles...
    ctx = canvas.getContext('2d');
    // prêts à travailler !
    ...
    drawMyMonster();
}

function drawMyMonster() {
    ctx.strokeRect(10, 10, 100, 100); // ctx est connu à ce
    ...                             // momen-là !
}

Rappels : pour exécuter une fonction dès que la page est chargée...

<body onload="init();">
...
window.addEventListener("load", init);
...
$( document ).ready(function() {
  init();
});
... etc.
        

Rôle du contexte

  • Dessiner en mode immédiat
    ctx.strokeRect(0, 0, 100, 00);     // dessin immédiat, fil de fer
    ctx.fillRect(200, 200, 100, 00); // dessin immédiat, plein
                
  • Dessiner en mode "bufférisé"
    ctx.rect(0, 0, 100, 100);
    ctx.rect( 200, 200, 100, 100);
    ctx.stroke(); // dessine les deux rectangles en fil de fer
    ...
    ctx.stroke(); // attention : dessine à nouveau
                  // le contenu du buffer !
    ctx.fill();   // idem en "plein"
                    

Rôle du contexte, suite...

  • Modifier des propriétés "globales" : couleur, texture, dégradés, épaisseur du trait, type de jointures, etc. DEMO !
    ctx.strokeStyle = 'red';
    // à partir de maintenant on dessine tout le fil de
    // fer en rouge !
    ctx.strokeRect(200, 200, 100, 100);
    ctx.strokeRect(10, 10, 100, 100);
    
    // Par défaut : noir pour le plein
    ctx.fillRect(300, 300, 70, 70);
    ctx.fillStyle = 'green';
    ctx.fillRect(310, 310, 50, 50);
                

Propriétés de couleurs

On utilise la syntaxe CSS pour les couleurs

ctx.strokeStyle = 'red';
ctx.fillStyle = "#00ff00";
ctx.strokeStyle = "rgb(0, 0, 255)";
ctx.fillStyle = "rgba(0, 0, 255, 0.5)"; // un arrière plan bleu
                                      // 50% de transparence
        

Dégradés linéaires

JS Bin
var grd = context.createLinearGradient(0, 0, 300, 0);
grd.addColorStop(0, "blue");
grd.addColorStop(0.5, "white");
grd.addColorStop(1, "red");

context.fillStyle = grd;
        

Dégradés radiaux

JS Bin
var grd = context.createLinearGradient(0, 0, 300, 0);
 // x1, y1, rayon1, x2, y2? rayon2
var grd = context.createRadialGradient(150, 100, 30, 150, 100, 100);
grd.addColorStop(0, "red");
grd.addColorStop(0.17, "orange");
...
grd.addColorStop(1, "violet");
context.fillStyle = grd;
        

Textures, exemple (images étudiées plus tard)

JS Bin
// Allocate an image
var imageObj = new Image();
imageObj.onload = function(){
	var pattern = context.createPattern(imageObj, "repeat");
	context.fillStyle = pattern;
}
imageObj.src = "http://www.dreamstime.com/image.gif";

Exercices...

  • Dessiner 5 rectangles différents (pleins, fil de fer), en mode immédiat,
  • Dessiner 5 rectangles en mode bufférisé, pleins ou en fil de fer.
  • Dessiner les 5 rectangles de l'exemple précédent en plein, bufférisés, verts, puis ajouter leur contour en fil de fer rouge (on doit voir des rectangles verts au contours rouge),
  • Dessiner un damier noir et blanc en mode immédiat en changeant la couleur entre chaque rectangle. Correction...
  • Même exercice en mode bufférisé...

Exercices suite...

  • Créer un dégradé linéaire diagnonal, avec les couleurs de l'arc en ciel et dessiner le damier
  • Idem mais avec un dégradé radial centré sur le bord vertical droit.
  • Dessiner 5 rectangles avec la même texture dessus. Tester les différents paramètres de createPattern.
  • Rajouter une ombre à ces rectangles.

En fait, seuls les rectangles, les images et le dessin de textes existent en mode immédiat...

 ctx.strokeRect(x, y, l, h);
 ctx.fillRect(x, y, l, h);
 ctx.clearRect(x, y, l, w); // Pour effacer un rectangle

 ctx.strokeText(msg, x, y);
 ctx.fillText(msg, x, y);

 ctx.drawImage(img, x, y); // nombreuses variantes,
                           // étudiées plus loin...
        

Le reste des formes que l'on peut dessiner est en mode bufférisé, on les appelle des "paths" (chemins).

Canvas et images : chargement asynchrone + callback !

var imageObj = new Image();

imageObj.onload = function(){
  context.drawImage(imageObj, destX, destY);
  // draw full image but change size
  context.drawImage(imageObj, destX, destY, width, height);
  // Draw subpart of source + resize
  context.drawImage(imageObj,sourceX, sourceY, sourceW,sourceH,
                    destX, destY, destW, destH);

};

imageObj.src = "darth-vader.jpg";
                

Exemple de chargement + différentes syntaxes de drawImage

JS Bin

Comment pré-loader plusieurs textures / images avant de travailler avec

JS Bin

Dessin bufférisé : les fonctions relatives aux chemins (paths)

beginPath( )
closePath( )
fill( )
stroke( )
clip( )
moveTo(x, y)
lineTo(x, y)
quadraticCurveTo(cpx, cpy, x, y)
bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y)
arcTo(x1, y1, x2, y2, radius )
arc(x, y, radius, startAngle, endAngle, anticlockwise)
rect(x, y, w, h)
isPointInPath(x, y)
        

A savoir concernant les chemins...

  • Les fonctions de dessin partent du dernier point dessiné dans le chemin,
  • Le chemin = tous les ordres bufférisés !
JS Bin

A savoir concernant les chemins...

  • moveTo(x, y) pour créer des chemins avec des sauts,
  • beginPath() vide le buffer,
  • closePath() relie le dernier point au premier,
  • Les lignes peuvent être stylées via le contexte

Les cercles et arcs de cercles...

context.arc(centreX, centreY, rayon,
            angleDepart, angleArrivee,
            sensInverseAiguillesMontre);
        

Les angles en radians, le dernier paramètre est booléen. True indique qu’on travaille dans le sens inverse des aiguilles d’une montre quand on trace.

Exercice sur le cercle

Dessiner un cercle bleu clair, centré dans le canvas (sa taille n'est pas connue à l'avance), ayant un contour noir de 5px de large, et une ombre portée,

Dessiner un autre cercle ayant des caractéristiques différentes, les deux doivent êtres sur le même canvas.

Autres exercices

Dessiner un demi cercle comme celui-ci, sans utiliser de lineTo

Dessiner une tête avec notamment la bouche et les dents faites avec un ou plusieurs chemins,

On conseille de faire des fonctions dessineNez(), dessineBouche(), etc...

Vous utiliserez ce qui a été vu, notamment : largeur des lignes variables, cercles, arcs de cercles, lignes, etc.

Courbes quadriques

ctx.moveTo(startX, startY);
ctx.quadraticCurveTo(controlX, controlY, endX, endY);
        

Exemple de courbe Quadrique

JS Bin

Courbes de Bézier

ctx.moveTo(startX, startY);
ctx.bezierCurveTo(controlX1, controlY1,controlX2, controlY2,
				  endX, endY);
        

Exemple de courbe de Bézier

JS Bin

HTML5 Bezier curve generator (clicker l'image)


Bonnes pratiques 1 : sauvegarde / restauration du contexte

Il est conseillé de sauvegarder le contexte au début d'une fonction de dessin, de le restaurer à la fin...

function drawHead() {
  // sauvegarde l'état courant du contexte
  ctx.save();

  ctx.shadowColor = "#bbbbbb";
  ctx.shadowBlur = 5;
  ctx.shadowOffsetX = 15;
  ctx.shadowOffsetY = 15;
  ctx.lineWidth = 10;
  ctx.beginPath();
  ctx.arc(250, 250, 200, 0, 2*Math.PI);
  ctx.stroke();

  // restore le contexte comme avant l'appel de la fonction
  ctx.restore();
}
        

Démonstration

Bonnes pratiques 2 : transformations géométriques

Au lieu de modifier tous les paramètres de tous les appels des fonctions de dessin si on veut déplacer / tourner / retailler un objet complexe, il est plus simple d'utiliser des transformations géométriques.

function drawMonster(x, y, sx, sy, angle) {
  ctx.save();

  // Déplace le repère en x, y, lui applique une rotation et change l'échelle
  ctx.translate(x, y);
  ctx.rotate(angle);
  ctx.scale(sx, sy);

  drawHead();
  drawEyes();
  drawMouth();
  ctx.restore();
}
        

Démonstration !

Transformations géométriques how to...

function drawRect(x, y, width, height) {
    ctx.save();
    ctx.translate(x, y);
    ctx.strokeRect(0, 0, width, height);
    ctx.restore();
}
        

Va dessiner un rectangle en x, y alors qu'on appelle strokeRect(0, 0, ...) !

Ca fonctionne car c'est tout le repère qui est translaté !

La sauvegarde / restauration du contexte est cruciale ici...

Transformations, rectangle avec translation et rotation...

function drawRectangle(x, y, width, height, alphadeg) {
    var alpharad = alphadeg*2*Math.PI/360;
    ctx.save();
    ctx.translate(x, y);
    ctx.rotate(alpharad);
    ctx.strokeRect(0, 0, width, height);
    ctx.restore();
}

Exercices

Dessiner un visage en utilisant dans chaque fonction (dessineTete, dessineBouche, dessineYeux, dessineNez) une sauvegarde du contexte suivie de transformations géométriques (translations seulement dans un premier temps). On devra pouvoir déplacer toute la tête d'un coup (les yeux, la bouches, suivront...)

Ajouter maintenant la possibilité de faire tourner la tête sur place, et changer sa taille globale.

Dessiner l'équivalent d'une horloge à aiguille simplifiée (les aiguilles seront des rectangles)...

Data vizualisation suite...

JS Bin

Bonnes pratiques 3 : canvas responsif

Si on retaille un canvas on perd son contenu, attention !

JS Bin

Canvas et video

context.drawImage() peut prendre un autre canvas comme paramètre!

Mais aussi un autre élément video ! Demo!

Effets spéciaux utilisant cette techique : le mur TV

Effets spéciaux utilisant cette techique : video qui explose

Manipulation au niveau des pixels

// Create a pixel Array
var imageData = context.createImageData(width, height);
// Or grab canvas content as pixels
var imageData2 = context.getImageData(x, y, width, height);
// do something with the data
// modify canvas content
context.putImageData(imageData, 0, 0);
            

Good usage : la librairie JavaScript Pixtatic,