2010-10-17 41 views
33

Estoy trabajando con un lienzo relativamente grande donde se dibujan varias cosas (complejas). Luego quiero guardar el estado Canvas ', así puedo restablecerlo rápidamente al estado en que se encuentra ahora en un momento posterior. Yo uso getImageData para esto y almaceno los datos en una variable. Luego dibujo algunas cosas más en el lienzo y luego restableceré el lienzo en el lugar donde estaba cuando guardé su estado, usando putImageData.¿Por qué putImageData es tan lento?

Sin embargo, resulta que putImageData es muy lento. De hecho, es más lento que simplemente volver a dibujar todo el lienzo desde el principio, lo que implica varios DrawImage que cubre la mayor parte de la superficie y más de 40.000 operaciones lineTo seguidas de trazos y rellenos.

Redibujar el lienzo de aproximadamente 2000 x 5000 píxeles desde cero lleva ~ 170ms, usando putImageData, sin embargo, toma la friolera de 240ms. ¿Por qué putImageData es tan lento en comparación con volver a dibujar el lienzo, aunque redibujar el lienzo implica rellenar casi todo el lienzo con drawImage y volver a llenar aproximadamente el 50% del lienzo con polígonos utilizando lineTo, trazo y relleno. Así que, básicamente, cada píxel se toca al menos una vez cuando se vuelve a dibujar.

Porque drawImage parece ser mucho más rápido que putImageData (después de todo, la parte de drawImage de redibujar el lienzo lleva menos de 30 ms). Decidí intentar guardar el estado del lienzo sin usar getImageData, sino utilizar canvas.toDataURL y luego crear una imagen a partir de la URL de datos que me quedaría en drawImage para dibujarla en el lienzo. Resulta que todo este procedimiento es mucho más rápido y solo toma aproximadamente 35ms para completarse.

Entonces, ¿por qué putImageData es mucho más lento que las alternativas (usando getDataURL o simplemente redibujando)? ¿Cómo podría acelerar las cosas más? ¿Existe y si, en general, es la mejor forma de almacenar el estado de un lienzo?

(Todas las cifras se miden utilizando Firebug desde Firefox)

+1

Sería interesante si pudiera publicar una demostración de su problema en línea en alguna parte. En noVNC (http://github.com/kanaka/noVNC) utilizo putImageData para muchas matrices de datos de imágenes pequeñas y medianas y no veo un problema de rendimiento con putImageData. Tal vez te encuentres con un caso específico de rendimiento pésimo que debería ser solucionado. – kanaka

+0

Puedes ver aquí http://www.danielbaulig.de/A3O/ No funcionará al 100% si la consola Firebug está activada, así que asegúrate de encenderla. La versión desprotegida es la que usa putImageData. Puede activarlo haciendo clic en cualquier "mosaico". Actualizará el lienzo de búfer utilizando putImageData y luego "resaltará" el mosaico seleccionado. En a3o_oo.js hay algunas líneas comentadas, que se pueden usar para alternar entre el uso de putImageData (actual), el uso de getDataURL (las dos líneas que mencionan this.boardBuffer) y el redibujado simple (la línea drawBoard) del lienzo del búfer. –

+0

Gran pregunta y excelentes soluciones. ¿Pero alguna vez descubrió la verdadera razón por la cual putImageData es tan lento en comparación con drawImage? – cherouvim

Respuesta

71

Sólo una pequeña actualización sobre cuál es la mejor forma es hacer esto. De hecho, escribí mi tesis de licenciatura en High Performance ECMAScript and HTML5 Canvas (pdf, alemán), así que reuní algo de experiencia en este tema por ahora. La mejor solución es utilizar múltiples elementos de lienzo. Dibujar de un lienzo sobre otro lienzo es tan rápido como dibujar una imagen arbitraria en un lienzo.Por lo tanto, "almacenar" el estado de un lienzo es tan rápido como restaurarlo más tarde cuando se usan dos elementos de lienzo.

This jsPerf testcase muestra los diferentes enfoques y sus ventajas y desventajas muy claramente.

simplemente para la corrección, aquí lo que realmente debe hacerlo:

// 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); 

Esta solución es, dependiendo del navegador, hasta 5000x más rápido que el de conseguir los upvotes.

+5

+1 Para obtener información excelente y casos de prueba fantásticos –

+0

¿Qué sucede si necesita almacenar muchos estados en una matriz? ¿Debería uno crear una matriz de un montón de lienzos? p.ej. 'var numBuffers = 20; var tmpCan = document.createElement ('canvas'); var buffers = [tmpCan]; for (var i = 1, len = numBuffers, i dylnmc

2

En primer lugar decir que se está midiendo con Firebug. En realidad, descubro que Firebug ralentiza considerablemente la ejecución de JS, por lo que es posible que no obtenga buenos números para el rendimiento.

En cuanto a putImageData, sospecho que es porque las funciones requieren una gran matriz JS que contiene muchos objetos Number, todos los cuales tienen que comprobarse para el rango (0..255) y copiarse en un búfer de lienzo original.

Tal vez una vez que los tipos de ByteArray de WebGL estén disponibles, este tipo de cosas se pueden hacer más rápido.

Parece extraño que la decodificación y descompresión de base64 (con la URL de datos PNG) sea más rápida, pero que solo llame a una función JS con una cadena JS, por lo que utiliza principalmente códigos y tipos nativos.

+0

ya que mis números son en su mayor parte de ejecución de código nativo, dudo que Firebug tenga un efecto significativo en ellos. Sin embargo, no estamos hablando de fracciones de milisegundos, sino en realidad, pero en realidad un cuarto de segundo para, básicamente, una llamada de función única (putImageData). El mal rendimiento debido a la matriz JS podría ser. Lo comprobaré probando qué tan rápido puede manejar JS (copiar, manipular, etc.) una matriz fuera de putImageData. –

+0

Continuación: Deocding, descompresión, etc. no ocurre en el punto donde se restaura el estado del lienzo, sino cuando se guarda. Así que esto sucede solo una vez y en realidad no lo medí, porque no es de mucha preocupación el tiempo que se necesita para salvar el estado. La parte crítica es restaurar el estado del lienzo. En ese punto, tengo el objeto Image creado durante mucho tiempo. Entonces, si el objeto Image contiene sus datos en un búfer nativo, esta podría ser la causa del problema (o mejor la falta de este para el enfoque drawImage). –

+0

Sé que el rendimiento de 'putImageData' puede estar bien (80-100 fps con un búfer de 480x320) - ¡pero se trata de imágenes muy grandes! – andrewmu

11

En Firefox 3.6.8 Pude solucionar la lentitud de putImageData usando toDataUrl/drawImage en su lugar. Para mí que está funcionando lo suficientemente rápido que puedo llamar dentro de la manipulación de un evento MouseMove:

Para guardar:

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

El restaurar:

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

Reconoció su respuesta hace un momento. De hecho, actualmente lo estoy haciendo de la misma manera :) Además, actualmente estoy experimentando con el uso de un lienzo escondido adicional como buffer. Esto debería aumentar el rendimiento al crear el búfer, que es bastante lento al utilizar toDataURL y la velocidad de dibujo debería permanecer igual (ya que drawImage también puede tomar un elemento canvas como imagen). –

+3

Acabo de darme cuenta de que esta respuesta sigue recibiendo upvotes. Agradezco esta respuesta, pero la solución explicada es realmente terrible en cuanto a rendimiento. Por favor refiérase a la respuesta aceptada por mí mismo. –