L'anti-aliasing est activé par défaut dans le canvas. Il permet de limiter la perception de l'effet d'escalier que la juxtaposition de pixels aux couleurs contrastées peut générer du fait de leur forme rectangulaire.
Ce traitement peut se révéler gênant dans certains cas (dessin au pixel, jeu old school, etc.), car il n'y a plus de certitude sur les pixels qu'une opération graphique vient modifier.
Par exemple, sur un canvas où on aura d'abord dessiné un rectangle jaune :
context2d.strokeStyle = "rgb (255, 255, 0)"; context2d.fillStyle = "rgb (0,255, 0)"; context2d.fillRect (2, 2, 1, 1); context2d.strokeRect (1, 1, 2, 2);
Dès lors, comment s'y prendre pour désactiver l'anti-aliasing dans le canvas ?
Mise à jour du 07/09/2018 : la propriété
imageSmoothingEnabled
, qui n'est pas évoquée ici, ne sert qu'à désactiver l'anti-aliasing pour l'affichage d'images.
La solution
L'image réelle produite à l'écran est une version retraitée d'une image virtuelle plus grande, chaque pixel de l'image réelle correspondant à un bloc de pixels dans l'image virtuelle qu'il est possible d'adresser par des coordonnées décimales.
La solution passe par un décalage des coordonnées de l'image réelle dans l'image virtuelle de telle sorte que l'anti-aliasing soit neutralisé. Ce décalage doit être effectué généralement par un appel
translate (0.5, 0.5)
et spécifiquement par un décalage qui dépend de la fonction utilisée pour dessiner :
- s'agissant de
fillRect ()
, il faut retirer 0.5 à l'abscisse et à l'ordonnée en pixels ; - s'agissant de
strokeRect ()
, il faut retirer 0.5 à l'abscisse et à l'ordonnée uniquement si l'épaisseur du trait (lineWidth
) est paire.
getImageData ()
et putImageData ()
. Retour à l'âge de pierre, donc...
Le code JavaScript
En JavaScript, la solution se traduit par le code suivant pour dessiner une grille de rectangles dotés d'un bord d'une certaine épaisseur à l'aide de
strokeRect ()
et fillRect ()
:
var context2D, strokeOffset, left = 3, top = 5, width = 1, height = 1, lineWidth = 4, i, j, x, y; context2D = document.getElementById ("tagCanvas").getContext ("2d"); context2D.translate (0.5, 0.5); context2D.fillStyle = "yellow"; context2D.fillRect (0 - 0.5, 0 - 0.5, 400, 400); context2D.strokeStyle = "rgb(255,0,0)"; context2D.fillStyle = "rgb(0,255,0)"; context2D.lineWidth = lineWidth; strokeOffset = (lineWidth >> 1) - (lineWidth & 1 ? 0 : 0.5); for (j = 0; j != 10; j ++) { y = top + j * (height + lineWidth); for (i = 0; i != 10; i ++) { x = left + i * (width + lineWidth); context2D.fillRect (x + lineWidth - 0.5, y + lineWidth - 0.5, width, height); context2D.strokeRect (x + strokeOffset, y + strokeOffset, width + lineWidth, height + lineWidth); } }
Dans cet exemple,
left
, top
, width
, height
et lineWidth
correspondent exactement aux dimensions suivantes dans l'image réelle :
L'exemple
Cliquez ici pour accéder à une page de test minimaliste. Vous pourrez visualiser le code et le récupérer pour travailler avec.
La logique
Dans le contexte 2D du canvas (communément désigné comme le canvas, par abus de langage assimilant l'élément
à un de ses contextes), l'anti-aliasing est activé par défaut.
En fait, comme le précise la spécification du contexte 2D, ce qui est donné à voir à l'écran correspond au retraitement d'une image virtuelle dont les dimensions peuvent excéder celles de l'image réelle représentée à l'écran, notamment pour produire un anti-aliasing par la technique dite de l'over-sampling (plusieurs pixels générés au lieu d'un, dont les couleurs sont mélangées pour donner celle du pixel affiché) :
The size of the coordinate space does not necessarily represent the size of the actual bitmaps that the user agent will use internally or during rendering. On high-definition displays, for instance, the user agent may internally use bitmaps with two device pixels per unit in the coordinate space, so that the rendering remains at high quality throughout. Anti-aliasing can similarly be implemented using over-sampling with bitmaps of a higher resolution than the final image on the display.
Dans ces conditions, il ne faut pas s'étonner de pouvoir adresser les pixels de l'image à l'aide coordonnées qui ne sont pas entières, d'une part, et d'obtenir des résultats variables à l'écran, d'autre part.
Par exemple, l'affichage de 20 rectangles avec bord d'épaisseur 1 via
strokeRect ()
, prétendument de 4 pixels de côtés, aux coordonnées x et y telles que x prend une valeur entière et y prend une valeur décimale qui progresse de 0.1 à chaque itération (1.0, 1.1, 1.2, etc.) :
var side = 4, space = 3, x = 2, y = 1, i; for (i = 0; i != 20; i ++) { context2d.strokeRect (x, y + (i / 10.0), side, side); x += side + space; }
Produit en utilisant des couleurs très contrastées telles que du rouge et du jaune, le résultat (ici agrandi avec une loupe sans filtre) est édifiant :
La contemplation de cette série de rectangles baveux conduit à formuler plusieurs remarques témoignant de l'ampleur de l'imprécision quant à la nature des pixels affectés :
- un rectangle ne fait pas 4 pixels de côté, mais jusqu'à 6 pixels de côté ;
- l'apparence d'un rectangle varie chaque fois que y progresse de 0.1 ;
- ce n'est véritablement qu'à partir de y = 2.5 que le rectangle n'est affiché qu'à partir du pixel d'ordonnée 2.
Le décalage opéré par un appel à
translate (0.5, 0.5)
ne suffit pas pour corriger le problème. Il faut aussi modifier les coordonnées et les dimensions fournies aux fonctions de dessin comme expliqué plus tôt.
Méfiance, car cette nécessité ne se fait pas sentir toujours immédiatement :
-
Pour
fillRect ()
, la nécessité de retirer 0.5 à l'abscisse et à l'ordonnée en pixels se fait sentir dès le rendu d'un rectangle de 1x1. Par exemple, un rectangle pur vertrgb (0, 255, 0)
) : -
Pour
strokeRect ()
, la nécessité de retirer 0.5 à l'abscisse et à l'ordonnée uniquement si l'épaisseur du trait (lineWidth
) est paire ne se fait sentir qu'à partir d'une épaisseur de 4 pixels. Par exemple, un rectangle doté d'un bord pur rougergb (255, 0, 0)
:
Attention ! Les exemples rapportés ont été produits avec Firefox 47.0. Or la spécification du contexte 2D précise bien que le rendu final est à la main de l'agent. En conséquence, il convient de procéder à des vérifications avant de déployer la technique décrite sur d'autres agents...