2010-10-17 12 views
33

Je travaille avec un Canvas relativement grand dans lequel divers éléments (complexes) sont dessinés. Je veux ensuite sauvegarder l'état du canevas, afin de pouvoir le remettre rapidement à l'état où il se trouve maintenant. J'utilise getImageData pour cela et stocke les données dans une variable. Je dessine ensuite un peu plus de choses sur la toile et je réinitialiserai plus tard le Canvas à l'endroit où il se trouvait lorsque je l'ai sauvé, en utilisant putImageData.Pourquoi putImageData est-il si lent?

Cependant, il s'avère que putImageData est très lent. En fait, il est plus lent que de redessiner tout le Canvas à partir de zéro, ce qui implique plusieurs drawImage couvrant la plus grande partie de la surface, et plus de 40.000 opérations lineTo suivies de traits et de remplissages.

Redessiner le canevas d'environ 2 000 x 5 000 pixels à partir de zéro prend ~ 170ms, l'utilisation de putImageData prend cependant 240ms. Pourquoi est-ce que putImageData est si lent comparé à redessiner le canevas, bien que redessiner le canevas implique de remplir presque la totalité du canevas avec drawImage, puis de remplir environ 50% du canevas avec des polygones en utilisant lineTo, stroke et fill. Donc, fondamentalement, 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 de redessiner le canevas prend moins de 30 ms). J'ai décidé d'essayer de sauvegarder l'état de la toile sans utiliser getImageData, mais en utilisant à la place canvas.toDataURL puis en créant une image à partir de l'URL de données que je collerais dans drawImage pour la dessiner sur le canevas. Il s'avère que toute cette procédure est beaucoup plus rapide et prend seulement environ 35ms à compléter. Alors, pourquoi putImageData est-il tellement plus lent que les alternatives (en utilisant getDataURL ou simplement en redessinant)? Comment pourrais-je accélérer les choses plus loin? Y at-il et si, quelle est généralement la meilleure façon de stocker l'état d'une toile?

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

+1

Il serait intéressant si vous pouviez poster une démonstration de votre problème en ligne quelque part. Dans noVNC (http://github.com/kanaka/noVNC) j'utilise putImageData pour beaucoup de 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 êtes confronté à un cas de performance pessimal spécifique qui devrait être bugué. – kanaka

+0

Vous pouvez regarder ici http://www.danielbaulig.de/A3O/ Cela ne fonctionnera pas à 100% si la console firebug est éteinte, alors assurez-vous de l'allumer. La version vérifiée est celle qui utilise putImageData. Vous pouvez le déclencher en cliquant sur n'importe quelle "tuile". Il actualisera le canevas du tampon en utilisant putImageData, puis "mettra en surbrillance" la vignette 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 (current), l'utilisation de getDataURL (les deux lignes mentionnant this.boardBuffer) et le redrawing simple (la ligne drawBoard) de l'espace tampon. –

+0

Bonne question et bonnes solutions. Mais avez-vous déjà découvert la vraie raison pour laquelle putImageData est si lent par rapport à drawImage? – cherouvim

Répondre

71

Juste une petite mise à jour sur ce que le meilleur façon est de le faire. J'ai effectivement écrit ma thèse de Bachelor sur High Performance ECMAScript and HTML5 Canvas (pdf, allemand), donc j'ai rassemblé une expertise sur ce sujet maintenant. La meilleure solution consiste à utiliser plusieurs éléments de canevas. Dessiner d'une toile sur une autre est aussi rapide que dessiner une image arbitraire sur une toile.Ainsi, "stocker" l'état d'une toile est tout aussi rapide que de la restaurer plus tard en utilisant deux éléments de toile.

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

Juste pour être complet, voici comment vous vraiment devrait faire:

// 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 celui d'obtenir les upvotes.

+5

+1 Pour les bonnes infos et les cas de test fantastiques –

+0

Et si vous deviez stocker beaucoup d'états dans un tableau? Devrait-on créer un tableau d'un tas de toiles? par exemple. 'var numBuffers = 20; var tmpCan = document.createElement ('canvas'); var buffers = [tmpCan]; pour (var i = 1, len = numBuffers, i dylnmc

2

Tout d'abord vous dites que vous mesurez avec Firebug. En fait, je trouve que Firebug ralentit considérablement l'exécution de JS, donc vous n'obtenez peut-être pas de bons chiffres pour la performance. Comme putImageData, je suppose que c'est parce que les fonctions prennent un grand tableau JS contenant de nombreux objets Number, qui doivent tous être vérifiés pour la plage (0..255) et copiés dans un tampon de canevas natif.

Peut-être qu'une fois que les types WebGL ByteArray sont disponibles, ce genre de chose peut être rendu plus rapide. Il semble étrange que base64 décodant et décompressant les données (avec l'URL de données PNG) est plus rapide, mais cela appelle seulement une fonction JS avec une chaîne JS, donc il utilise principalement le code et les types natifs.

+0

depuis que mes numéros sont pour la plupart de l'exécution de code natif, je doute que Firebug aura un effet significatif sur eux. Néanmoins, nous ne parlons pas de fractions de millisecondes, mais en réalité d'un quart de seconde pour un simple appel de fonction (putImageData). La mauvaise performance due au tableau JS pourrait être. Je vais vérifier cela en testant à quelle vitesse JS peut gérer (copier, manipuler, etc) un tel tableau en dehors de putImageData. –

+0

Continuation: Deocding, décompression, etc ne se produit pas au point où l'état de la toile est restauré, mais quand il est sauvegardé. Cela ne se produit qu'une seule fois et je ne l'ai pas mesuré, car le temps qu'il faut pour sauver l'État n'est pas très préoccupant. La partie critique est la restauration de l'état de la toile. À ce stade, j'ai l'objet Image 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 mieux l'absence de l'approche drawImage). –

+0

Je sais que les performances de 'putImageData' peuvent être correctes (80-100fps avec un buffer 480x320) - mais vous avez affaire à de très grandes images! – andrewmu

11

Dans Firefox 3.6.8, j'étais capable de contourner la lenteur de putImageData en utilisant toDataUrl/drawImage à la place. Pour moi, il travaille assez vite que je peux l'appeler dans les gestion d'un événement mousemove:

Pour enregistrer:

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

La à restaurer:

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

Reconnu votre réponse tout à l'heure. En fait, je le fais actuellement exactement de la même manière :) De plus, j'expérimente actuellement en utilisant une toile supplémentaire cachée 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 canvas en tant qu'image). –

+3

Je viens de réaliser que cette réponse ne cesse d'augmenter. J'apprécie cette réponse, mais la solution expliquée est en fait terrible sur le plan de la performance. Veuillez plutôt me référer à la réponse acceptée par moi-même. –