2010-10-17 17 views
33

Sto lavorando con una tela di dimensioni relativamente grandi in cui vengono disegnate varie cose (complesse). Quindi voglio salvare lo stato della tela, quindi posso resettarlo velocemente allo stato in cui si trova ora in un secondo momento. Io uso getImageData per questo e memorizzo i dati in una variabile. Quindi disegno un po 'di più sulla tela e successivamente ripristinerò la tela dove era quando ho salvato il suo stato, usando putImageData.Perché il valore di ImageData è così lento?

Tuttavia, risulta che ImageData è molto lento. Infatti, è più lento del semplice ridisegnare l'intero Canvas da zero, il che implica diversi DrawImage che coprono la maggior parte della superficie e oltre 40.000 operazioni lineTo seguite da tratti e riempimenti.

Il nuovo disegno di una tela di circa 2000 x 5000 pixel da zero richiede ~ 170 ms, utilizzando putImageData richiede ben 240ms. Perché ImageData è così lento rispetto al ridisegnare la tela, sebbene il ridisegno della tela comporti il ​​riempimento dell'intera tela con drawImage e quindi riempiendo circa il 50% della tela con poligoni usando lineTo, stroke e fill. Quindi, in sostanza, ogni singolo pixel viene toccato almeno una volta durante il ridisegno.

Perché drawImage sembra essere molto più veloce di mettere ImageData (dopo tutto, la parte drawImage di ridisegnare la tela richiede meno di 30 ms). Ho deciso di provare a salvare lo stato della tela non usando getImageData, ma invece di usare canvas.toDataURL e quindi creare un'immagine dall'URL dei dati che avrei incollato a drawImage per disegnarla sulla tela. Risulta che questa intera procedura è molto più veloce e richiede solo circa 35ms.

Quindi, perché ImageData è molto più lento delle alternative (utilizzando getDataURL o semplicemente ridisegnando)? Come potrei accelerare ulteriormente le cose? C'è e se, qual è in generale il modo migliore per memorizzare lo stato di una tela?

(Tutti i numeri sono misurati usando Firebug dall'interno di Firefox)

+1

Sarebbe interessante se potessi pubblicare una dimostrazione del tuo problema online da qualche parte. In noVNC (http://github.com/kanaka/noVNC) uso putImageData per molti array di dati immagine di piccole e medie dimensioni e non vedo un problema di prestazioni con putImageData. Forse ti stai imbattendo in uno specifico caso di performance pessimistica che dovrebbe essere bacato. – kanaka

+0

Puoi dare un'occhiata qui http://www.danielbaulig.de/A3O/ Non funzionerà al 100% se la console di firebug è attiva, quindi assicurati di accenderla. La versione ritirata è quella che usa putImageData. Puoi attivarlo facendo clic su qualsiasi "tessera". Aggiorna la tela del buffer usando putImageData e quindi "evidenzia" il riquadro selezionato. In a3o_oo.js ci sono alcune righe commentate, che possono essere usate per passare dall'uso di putImageData (corrente), usando getDataURL (le due righe che citano this.boardBuffer) e il semplice ridisegno (la linea drawBoard) del buffer del canvas. –

+0

Ottima domanda e grandi soluzioni. Ma hai mai scoperto la vera ragione per cui mettere ImageData è così lento rispetto a drawImage? – cherouvim

risposta

71

Solo un piccolo aggiornamento su ciò che il miglior il modo è farlo. In realtà ho scritto la mia tesi di laurea su High Performance ECMAScript and HTML5 Canvas (pdf, tedesco), quindi ho raccolto alcune conoscenze su questo argomento ormai. La soluzione migliore è usare più elementi canvas. Disegnare da una tela su un'altra tela è altrettanto veloce quanto disegnare un'immagine arbitary su una tela.Quindi "memorizzare" lo stato di una tela è altrettanto veloce che ripristinarlo di nuovo quando si usano due elementi di tela.

This jsPerf testcase mostra i vari approcci e i loro vantaggi e svantaggi molto chiaramente.

Solo per completezza, ecco come realmente dovrebbe farlo:

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

Questa soluzione è, a seconda del browser, fino a 5000X più veloce di quello ottenere i upvotes.

+5

+1 Per le fantastiche informazioni e i fantastici casi di test –

+0

E se fosse necessario memorizzare molti stati in un array? Si dovrebbe creare una serie di un mucchio di tele ?? per esempio. 'var numBuffers = 20; var tmpCan = document.createElement ('canvas'); var buffers = [tmpCan]; for (var i = 1, len = numBuffers, i dylnmc

2

In primo luogo si dice che si sta misurando con Firebug. In realtà, trovo che Firebug rallenta notevolmente l'esecuzione di JS, quindi potresti non ottenere un buon numero di prestazioni.

Quanto putImageData, sospetto è perché le funzioni richiede un grande array JS contiene molti Number oggetti, i quali devono essere controllati per la gamma (0..255) e copiato in un buffer di tela nativo.

Forse una volta che i tipi ByteArray WebGL sono disponibili, questo genere di cose può essere reso più veloce.

Sembra strano che la decodifica base64 e decomprimere i dati (con l'URL dei dati PNG) è più veloce, ma che chiama una sola funzione JS con una corda di JS, quindi sta usando il codice per lo più nativi e tipi.

+0

poiché i miei numeri sono per la maggior parte dovuti all'esecuzione di codice nativo, dubito che Firebug abbia un effetto significativo su di essi. Tuttavia, non stiamo parlando di frazioni di millisecondi, ma in realtà ma in realtà un quarto di secondo per una chiamata di base a una funzione (putImageData). Le prestazioni negative dovute all'array JS potrebbero essere. Lo verificherò testando la velocità con cui JS è in grado di gestire (copiare, manipolare, ecc.) Un array al di fuori di putImageData. –

+0

Continuazione: il deocianco, la decompressione, ecc. Non sta accadendo nel punto in cui lo stato della tela viene ripristinato, ma quando viene salvato. Quindi questo accade solo una volta e in realtà non l'ho misurato, perché il tempo necessario per salvare lo stato non è molto preoccupante. La parte critica sta ripristinando lo stato della tela. A quel punto ho creato l'oggetto Immagine a lungo. Quindi se l'oggetto Image contiene i suoi dati in un buffer nativo, questo potrebbe effettivamente essere la causa del problema (o meglio la mancanza di esso per l'approccio drawImage). –

+0

So che le prestazioni di 'putImageData' possono essere ok (80-100 fps con un buffer 480x320) - ma si tratta di immagini molto grandi! – andrewmu

11

In Firefox 3.6.8 sono stato in grado di aggirare la lentezza di putImageData utilizzando toDataUrl/drawImage. Per me sta funzionando abbastanza veloce che posso chiamare entro la gestione di un evento MouseMove:

Per salvare:

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

La ripristinare:

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

Riconosci la tua risposta proprio ora. Al momento lo sto facendo esattamente nello stesso modo :) Inoltre sto attualmente sperimentando l'utilizzo di una tela nascosta aggiuntiva come buffer. Ciò dovrebbe aumentare le prestazioni creando il buffer, che è piuttosto lento usando toDataURL e la velocità di disegno dovrebbe rimanere pressoché invariata (dato che drawImage può anche prendere un elemento canvas come immagine). –

+3

Appena realizzato che questa risposta continua a ottenere voti. Apprezzo questa risposta, ma la soluzione spiegata è in realtà terribile in termini di prestazioni. Si prega invece di fare riferimento alla risposta accettata da me stesso. –

Problemi correlati