42 votes

Pourquoi putImageData est-il si lent ?

Je travaille avec une toile relativement grande où sont dessinées diverses choses (complexes). Je veux ensuite sauvegarder l'état du Canvas, afin de pouvoir le réinitialiser rapidement à l'état actuel à un moment ultérieur. Pour cela, j'utilise getImageData et je stocke les données dans une variable. Je dessine ensuite d'autres éléments sur le canevas et je remettrai plus tard le canevas dans l'état où il était lorsque j'ai sauvegardé son état, en utilisant putImageData.

Cependant, il s'avère que putImageData est très lent. En fait, il est plus lent que le simple fait de redessiner l'intégralité du Canvas à partir de zéro, ce qui implique plusieurs drawImage couvrant la majeure partie de la surface, et plus de 40 000 opérations lineTo suivies de traits et de remplissages.

Redessiner le canevas d'environ 2000 x 5000 pixels à partir de zéro prend environ 170 ms, alors que l'utilisation de putImageData prend 240 ms. Pourquoi putImageData est-il si lent par rapport au redessin du canevas, bien que le redessin du canevas implique le remplissage de presque tout le canevas avec drawImage et ensuite le remplissage d'environ 50% du canevas avec des polygones utilisant lineTo, stroke et fill. En gros, chaque pixel est touché au moins une fois lors du redessin.

Parce que drawImage semble être beaucoup plus rapide que putImageData (après tout, la partie drawImage du redessin du canevas prend moins de 30 ms). J'ai décidé d'essayer de sauvegarder l'état du canevas sans utiliser getImageData, mais en utilisant plutôt canvas.toDataURL et en créant ensuite une image à partir de l'URL des données que je collerais dans drawImage pour la dessiner sur le canevas. Il s'avère que cette procédure est beaucoup plus rapide et ne prend que 35 ms environ.

Alors pourquoi putImageData est-il tellement plus lent que les autres solutions (utilisation de getDataURL ou simple redécoupage) ? Comment pourrais-je accélérer davantage les choses ? Existe-t-il et si oui, quelle est en général la meilleure façon de stocker l'état d'un canevas ?

(Tous les chiffres sont mesurés à l'aide de Firebug depuis Firefox).

1 votes

Il serait intéressant que vous puissiez mettre en ligne quelque part une démonstration de votre problème. Dans noVNC ( github.com/kanaka/noVNC ) J'utilise putImageData pour de nombreux tableaux de données d'images de petite et moyenne taille et je ne vois pas de problème de performance avec putImageData. Peut-être que vous rencontrez un cas spécifique de performance pessimale qui devrait faire l'objet d'un bug.

0 votes

Vous pouvez jeter un coup d'œil ici danielbaulig.de/A3O Il ne fonctionnera pas à 100% si la console firebug est désactivée, assurez-vous donc de l'activer. La version vérifiée est celle qui utilise putImageData. Vous pouvez la déclencher en cliquant sur n'importe quelle "tuile". Il va rafraîchir le canevas de la mémoire tampon en utilisant putImageData et ensuite "mettre en évidence" la tuile sélectionnée. Dans a3o_oo.js il y a quelques lignes commentées, qui peuvent être utilisées pour basculer entre l'utilisation de putImageData (courant), l'utilisation de getDataURL (les deux lignes mentionnant this.boardBuffer) et le simple redessin (la ligne drawBoard) du canevas de la mémoire tampon.

0 votes

Excellente question et excellentes solutions. Mais avez-vous trouvé la vraie raison pour laquelle putImageData est si lent par rapport à drawImage ?

94voto

Daniel Baulig Points 4849

Juste une petite mise à jour sur la meilleure façon de procéder. J'ai en fait écrit ma thèse de licence sur ECMAScript et HTML5 Canvas haute performance (pdf, allemand ; mot de passe : stackoverflow), j'ai donc acquis une certaine expertise sur ce sujet. La meilleure solution consiste clairement à utiliser plusieurs éléments de canevas. Dessiner d'un canvas sur un autre canvas est aussi rapide que de dessiner une image arbitraire sur un canvas. Ainsi, "stocker" l'état d'un canevas est aussi rapide que de le restaurer plus tard en utilisant deux éléments de canevas.

Ce testcase jsPerf montre très clairement les différentes approches, leurs avantages et leurs inconvénients.

Par souci d'exhaustivité, voici comment vous devez procéder. devrait faites-le :

// setup
var buffer = document.createElement('canvas');
buffer.width = canvas.width;
buffer.height = canvas.height;

// save
buffer.getContext('2d').drawImage(canvas, 0, 0);

// restore
canvas.getContext('2d').drawImage(buffer, 0, 0);

Cette solution est, selon le navigateur, jusqu'à 5000x plus rapide que celle qui obtient les upvotes.

4 votes

Que faire si vous devez stocker un grand nombre d'états dans un tableau ? Faut-il créer un tableau d'un tas de toiles ? par exemple var numBuffers = 20; var tmpCan = document.createElement('canvas'); var buffers = [tmpCan]; for (var i = 1, len = numBuffers, i < numBuffers; i++) { buffers.push(tmpCan.cloneNode()); } ou quelque chose comme ça ? OU existe-t-il une meilleure solution ?

11voto

Corey Trager Points 11334

Dans Firefox 3.6.8, j'ai pu contourner la lenteur de putImageData en utilisant toDataUrl/drawImage à la place. Pour moi, cela fonctionne assez rapidement pour que je puisse l'appeler lors du traitement d'un événement de déplacement de souris :

Pour économiser :

savedImage = new Image()
savedImage.src = canvas.toDataURL("image/png")

Le pour restaurer :

ctx = canvas.getContext('2d')
ctx.drawImage(savedImage,0,0)

1 votes

J'ai reconnu votre réponse à l'instant. En fait, je suis en train de faire exactement la même chose :) De plus, je suis en train d'expérimenter l'utilisation d'un canevas supplémentaire, caché, comme tampon. Cela devrait augmenter les performances en créant le tampon, qui est plutôt lent en utilisant toDataURL et la vitesse de dessin devrait rester à peu près la même (puisque drawImage peut également prendre un élément de canevas comme image).

4 votes

Je viens de réaliser que cette réponse continue d'obtenir des votes positifs. J'apprécie cette réponse, mais la solution expliquée est en fait terrible en termes de performances. Veuillez plutôt vous référer à la réponse acceptée par moi-même.

0 votes

J'essaie de dessiner sur un canevas à partir de données de pixels stockées dans un tableau Uint8Clamped avec de meilleures performances que putImageData. Cela semble être un moyen prometteur d'y parvenir. Je devrais être capable de convertir le tableau Uint8Clamped en une DataURL, de remplir un objet Image avec cette DataURL, puis de dessiner cette image sur le canevas. Je ne l'ai pas encore testée, mais j'espère qu'elle sera plus rapide que putImageData.

2voto

andrewmu Points 6436

Tout d'abord, vous dites que vous effectuez des mesures avec Firebug. En fait, je trouve que Firebug ralentit considérablement l'exécution des JS, donc il se peut que vous n'obteniez pas de bons chiffres pour les performances.

Quant à putImageData je pense que c'est parce que la fonction prend un grand tableau JS contenant plusieurs Number qui doivent tous être vérifiés pour la plage (0..255) et copiés dans un tampon de toile natif.

Peut-être qu'une fois que les types ByteArray de WebGL seront disponibles, ce genre de choses pourra être fait plus rapidement.

Il semble étrange que le décodage et la décompression des données en base64 (avec l'URL des données PNG) soient plus rapides, mais cela n'appelle qu'une seule fonction JS avec une seule chaîne JS, ce qui signifie que l'on utilise principalement du code et des types natifs.

0 votes

Comme mes chiffres proviennent pour la plupart de l'exécution de code natif, je doute que Firebug ait un effet significatif sur eux. Néanmoins, nous ne parlons pas de fractions de millisecondes, mais en fait d'un quart de seconde pour un simple appel de fonction (putImageData). Les mauvaises performances dues au tableau JS pourraient l'être. Je vais vérifier cela en testant la vitesse à laquelle JS peut gérer (copier, manipuler, etc) un tel tableau en dehors de putImageData.

0 votes

Continuation : Le déchargement, la décompression, etc. ne se produisent pas au moment où l'état du canevas est restauré, mais lorsqu'il est sauvegardé. Cela ne se produit donc qu'une seule fois et je ne l'ai pas mesuré, car le temps nécessaire à la sauvegarde de l'état n'est pas très important. La partie critique est la restauration de l'état de la toile. À ce stade, l'objet Image est créé depuis longtemps. Donc, si l'objet Image contient ses données dans un tampon natif, cela pourrait en effet être la cause du problème (ou plutôt l'absence de problème pour l'approche drawImage).

0 votes

Je sais que putImageData Les performances peuvent être correctes (80-100 images par seconde avec une mémoire tampon de 480x320), mais vous avez affaire à de très grandes images !

2voto

Timmmm Points 9909

Cela s'explique probablement par le fait que les navigateurs modernes utilisent l'accélération matérielle pour <canvas> éléments, et getImageData() / putImageData() nécessitent le transfert de données d'image de la carte graphique vers l'hôte ou vice versa. Ce qui est notoirement lent.

L'utilisation de deux toiles sera plus rapide car toutes les données restent sur la carte graphique.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X