Quelle est la meilleure façon de définir un seul pixel dans une canvas HTML5?

Le canevas HTML5 ne dispose d’aucune méthode pour définir explicitement un seul pixel.

Il pourrait être possible de définir un pixel en utilisant une ligne très courte, mais alors l’antialisation et les limites de ligne pourraient interférer.

Une autre façon pourrait être de créer un petit object ImageData et d’utiliser:

 context.putImageData(data, x, y) 

pour le mettre en place.

Quelqu’un peut-il décrire une façon efficace et fiable de le faire?

Il y a deux meilleurs prétendants:

  1. Créez des données d’image 1 × 1, définissez la couleur et putImageData à l’emplacement:

     var id = myContext.createImageData(1,1); // only do this once per page var d = id.data; // only do this once per page d[0] = r; d[1] = g; d[2] = b; d[3] = a; myContext.putImageData( id, x, y ); 
  2. Utilisez fillRect() pour dessiner un pixel (il ne doit y avoir aucun problème d’alias):

     ctx.fillStyle = "rgba("+r+","+g+","+b+","+(a/255)+")"; ctx.fillRect( x, y, 1, 1 ); 

Vous pouvez en tester la vitesse ici: http://jsperf.com/setting-canvas-pixel/9 ou ici https://www.measurethat.net/Benchmarks/Show/1664/1

Je recommande de tester les navigateurs qui vous intéressent pour une vitesse maximale. En juillet 2017, fillRect() est 5 à 6 fois plus rapide sur Firefox v54 et Chrome v59 (Win7x64).

D’autres alternatives sont:

  • en utilisant getImageData()/putImageData() sur l’intégralité du canevas; c’est environ 100 fois plus lent que les autres options.

  • créer une image personnalisée en utilisant une URL de données et en utilisant drawImage() pour l’afficher:

     var img = new Image; img.src = "data:image/png;base64," + myPNGEncoder(r,g,b,a); // Writing the PNGEncoder is left as an exercise for the reader 
  • créer un autre img ou un canevas rempli de tous les pixels souhaités et utiliser drawImage() pour masquer uniquement le pixel souhaité. Ce serait probablement très rapide, mais a la limitation dont vous avez besoin pour pré-calculer les pixels dont vous avez besoin.

Notez que mes tests ne tentent pas d’enregistrer et de restaurer le fillStyle contexte de fillStyle ; cela ralentirait la performance fillRect() . Notez également que je ne commence pas par une table blanche ou que je ne teste pas exactement le même jeu de pixels pour chaque test.

Je n’avais pas considéré fillRect() , mais les réponses m’ont incité à le comparer à putImage() .

Mettre 100 000 pixels de couleurs aléatoires dans des endroits aléatoires, avec Chrome 9.0.597.84 sur un (ancien) MacBook Pro, prend moins de putImage() avec putImage() , mais près de 900 ms avec fillRect() . (Code de référence sur http://pastebin.com/4ijVKJcC ).

Si, au lieu de cela, je choisis une seule couleur en dehors des boucles et que je trace simplement cette couleur à des emplacements aléatoires, putImage() prend 59 ms vs 102 ms pour fillRect() .

Il semble que la surcharge de la génération et de l’parsing d’une spécification de couleur CSS dans la syntaxe rgb(...) est responsable de la majeure partie de la différence.

Placer des valeurs RVB brutes directement dans un bloc ImageData , par contre, ne nécessite aucune manipulation de chaîne ou parsing.

 function setPixel(imageData, x, y, r, g, b, a) { index = (x + y * imageData.width) * 4; imageData.data[index+0] = r; imageData.data[index+1] = g; imageData.data[index+2] = b; imageData.data[index+3] = a; } 

Étant donné que les différents navigateurs semblent préférer des méthodes différentes, il serait peut-être judicieux de faire un test plus petit avec les trois méthodes dans le cadre du processus de chargement pour déterminer la meilleure méthode à utiliser dans l’application?

Qu’en est-il d’un rectangle? Cela doit être plus efficace que de créer un object ImageData .

Cela semble étrange, mais néanmoins HTML5 prend en charge les lignes de dessin, les cercles, les rectangles et de nombreuses autres formes de base, il n’a rien de approprié pour dessiner le sharepoint base. La seule façon de le faire est de simuler des points avec ce que vous avez.

Donc, fondamentalement, il y a 3 solutions possibles:

  • dessiner le point comme une ligne
  • dessiner le point comme un polygone
  • dessiner le point comme un cercle

Chacun d’eux a ses inconvénients


Ligne

 function point(x, y, canvas){ canvas.beginPath(); canvas.moveTo(x, y); canvas.lineTo(x+1, y+1); canvas.stroke(); } 

Gardez à l’esprit que nous nous dirigeons vers la direction du sud-est, et si c’est le bord, il peut y avoir un problème. Mais vous pouvez aussi dessiner dans toute autre direction.


Rectangle

 function point(x, y, canvas){ canvas.strokeRect(x,y,1,1); } 

ou de manière plus rapide en utilisant fillRect, car le moteur de rendu ne fera que remplir un pixel.

 function point(x, y, canvas){ canvas.fillRect(x,y,1,1); } 

Cercle


Un des problèmes avec les cercles est qu’il est plus difficile pour un moteur de les rendre

 function point(x, y, canvas){ canvas.beginPath(); canvas.arc(x, y, 1, 0, 2 * Math.PI, true); canvas.stroke(); } 

la même idée qu’avec le rectangle que vous pouvez obtenir avec le remplissage.

 function point(x, y, canvas){ canvas.beginPath(); canvas.arc(x, y, 1, 0, 2 * Math.PI, true); canvas.fill(); } 

Problèmes avec toutes ces solutions:

  • il est difficile de suivre tous les points que vous allez dessiner.
  • lorsque vous effectuez un zoom avant, il a l’air moche.

Si vous vous demandez, “Quelle est la meilleure façon de dessiner un point? “, J’irais avec un rectangle plein. Vous pouvez voir mon jsperf ici avec des tests de comparaison .

Dessine un rectangle comme sdleihssirhc dit!

 ctx.fillRect (10, 10, 1, 1); 

^ – devrait dessiner un rectangle 1×1 à x: 10, y: 10

Hmm, vous pouvez aussi simplement créer une ligne de 1 pixel de large avec une longueur de 1 pixel et faire en sorte que sa direction se déplace sur un seul axe.

  ctx.beginPath(); ctx.lineWidth = 1; // one pixel wide ctx.strokeStyle = rgba(...); ctx.moveTo(50,25); // positioned at 50,25 ctx.lineTo(51,25); // one pixel long ctx.stroke(); 

Pour compléter la réponse très complète de Phrogz, il existe une différence critique entre fillRect() et putImageData() .
Le premier utilise le contexte pour dessiner en ajoutant un rectangle (PAS un pixel), en utilisant la valeur alpha de fillStyle ET le contexte globalAlpha et la masortingce de transformation , les majuscules de ligne , etc.
La seconde remplace un ensemble complet de pixels (peut-être un, mais pourquoi?)
Le résultat est différent, comme vous pouvez le voir sur jsperf .

Personne ne veut définir un pixel à la fois (c’est-à-dire le dessiner à l’écran). C’est pourquoi il n’y a pas d’API spécifique pour le faire (et à juste titre).
Du sharepoint vue des performances, si l’objective est de générer une image (par exemple un logiciel de traçage de rayons), vous souhaitez toujours utiliser un tableau obtenu par getImageData() qui est un Uint8Array optimisé. Ensuite, vous appelez putImageData() ONCE ou quelques fois par seconde en utilisant setTimeout/seTInterval .

putImageData est probablement plus rapide que fillRect natif. Je pense que cela parce que le cinquième paramètre peut avoir différentes manières d’être assignées (la couleur du rectangle), en utilisant une chaîne qui doit être interprétée.

Supposons que vous fassiez ça:

 context.fillRect(x, y, 1, 1, "#fff") context.fillRect(x, y, 1, 1, "rgba(255, 255, 255, 0.5)")` context.fillRect(x, y, 1, 1, "rgb(255,255,255)")` context.fillRect(x, y, 1, 1, "blue")` 

Donc, la ligne

 context.fillRect(x, y, 1, 1, "rgba(255, 255, 255, 0.5)")` 

est le plus lourd entre tous. Le cinquième argument de l’appel fillRect est une chaîne un peu plus longue.

Code HTML de démonstration rapide: Basé sur ce que je sais de la bibliothèque graphique C ++ SFML:

Enregistrez-le sous forme de fichier HTML avec UTF-8 Encoding et exécutez-le. N’hésitez pas à refactoriser, j’aime juste utiliser des variables japonaises car elles sont concises et ne prennent pas beaucoup de place

Vous allez rarement vouloir définir UN pixel arbitraire et l’afficher à l’écran. Alors utilisez le

 PutPix(x,y, r,g,b,a) 

méthode pour dessiner de nombreux pixels arbitraires dans un back-buffer. (appels pas chers)

Lorsque vous êtes prêt à montrer, appelez le

 Apply() 

méthode pour afficher les modifications. (appel coûteux)

Le code complet du fichier .HTML ci-dessous:

     back-buffer demo       

Si vous êtes préoccupé par la vitesse, vous pouvez également envisager WebGL.

Une méthode qui n’a pas été mentionnée utilise getImageData et ensuite putImageData.
Cette méthode est utile lorsque vous voulez dessiner beaucoup en une fois, rapidement.
http://next.plnkr.co/edit/mfNyalsAR2MWkccr

  var canvas = document.getElementById('canvas'); var ctx = canvas.getContext('2d'); var canvasWidth = canvas.width; var canvasHeight = canvas.height; ctx.clearRect(0, 0, canvasWidth, canvasHeight); var id = ctx.getImageData(0, 0, canvasWidth, canvasHeight); var pixels = id.data; var x = Math.floor(Math.random() * canvasWidth); var y = Math.floor(Math.random() * canvasHeight); var r = Math.floor(Math.random() * 256); var g = Math.floor(Math.random() * 256); var b = Math.floor(Math.random() * 256); var off = (y * id.width + x) * 4; pixels[off] = r; pixels[off + 1] = g; pixels[off + 2] = b; pixels[off + 3] = 255; ctx.putImageData(id, 0, 0);