disegna 10.000 oggetti su canvas javascript

Ho bisogno di disegnare su 10.000 immagini (32×32 px) su canvas ma più di 2000 disegni le prestazioni sono pessime.

questo è un piccolo esempio:

struttura dell’object {position:0}

 for(var nObject = 0; nObject < objects.length; nObject++){ ctx.save(); ctx.translate(coords.x,coords.y); ctx.rotate(objects[nObject].position/100); ctx.translate(radio,0); ctx.drawImage(img,0,0); ctx.restore(); objects[nObject].position++; } 

con questo codice ho traslate le immagini intorno a delle coordinate.

Cosa consiglieresti per migliorare le prestazioni?

aggiornare:

provo la stratificazione ma le prestazioni peggiorano

http://jsfiddle.net/72nCX/3/

Posso farti 10.000 ma ci sono due principali svantaggi.

  1. Potresti notare che le immagini non rispettano completamente la trasparenza, è ansible correggerle .. ma questo va oltre lo scopo di questa risposta.

  2. Dovrai utilizzare la matematica per eseguire qualsiasi tipo di trasformazione, in quanto la matrice di trasformazione canvas standard non può essere applicata a ImageData

Dimostrazione dal vivo

Spiegazione del codice e dei metodi

Quindi, per ottenere le prestazioni più veloci possibili con canvas e un numero elevato di oggetti, è necessario utilizzare ImageData . Questo è fondamentalmente l’accesso all’elemento canvas su un livello per pixel, e ti permette di fare ogni genere di cose interessanti. Ho usato due metodi primari.

  • putImageData
  • createImageData

Anche qui c’è un bel tutorial che ci aiuta un po ‘a capire meglio.

Quindi quello che ho fatto è stato prima di creare una canvas temporanea per l’immagine

 imgToDraw.onload = function () { // In memory canvas imageCanvas = document.createElement("canvas"), iCtx = imageCanvas.getContext("2d"); // set the canvas to the size of the image imageCanvas.width = this.width; imageCanvas.height = this.height; // draw the image onto the canvas iCtx.drawImage(this, 0, 0); // get the ImageData for the image. imageData = iCtx.getImageData(0, 0, this.width, this.height); // get the pixel component data from the image Data. imagePixData = imageData.data; // store our width and height so we can reference it faster. imgWidth = this.width; imgHeight = this.height; draw(); }; 

Successivo È il pezzo principale che si trova nella funzione di rendering

Sto solo pubblicando la parte pertinente.

 // create new Image data. Doing this everytime gets rid of our // need to manually clear the canvas since the data is fresh each time var canvasData = ctx.createImageData(canvas.width, canvas.height), // get the pixel data cData = canvasData.data; // Iterate over the image we stored for (var w = 0; w < imgWidth; w++) { for (var h = 0; h < imgHeight; h++) { // make sure the edges of the image are still inside the canvas // This also is VERY important for perf reasons // you never want to draw outside of the canvas bounds with this method if (entity.x + w < width && entity.x + w > 0 && entity.y + h > 0 && entity.y + h < height) { // get the position pixel from the image canvas var iData = (h * imgWidth + w) * 4; // get the position of the data we will write to on our main canvas // the values must be whole numbers ~~ is just Math.floor basically var pData = (~~ (entity.x + w) + ~~ (entity.y + h) * width) * 4; // copy the r/g/b/ and alpha values to our main canvas from // our image canvas data. cData[pData] = imagePixData[iData]; cData[pData + 1] = imagePixData[iData + 1]; cData[pData + 2] = imagePixData[iData + 2]; // this is where alpha blending could be applied if(cData[pData + 3] < 100){ cData[pData + 3] = imagePixData[iData + 3]; } } } } // now put all of that image data we just wrote onto the actual canvas. ctx.putImageData(canvasData, 0, 0); 

Il principale Da tenere lontano da questo è, se è necessario disegnare un numero ridicolo di oggetti sulla canvas non è ansible utilizzare drawImage , la manipolazione dei pixel è tuo amico.

Penso che questo sia ciò di cui hai bisogno.

Eric Rowell (creatore di KineticJS) ha fatto alcuni test di stress qui.

E lui dice questo:

“Crea 10 livelli ciascuno contenente 1000 forms per creare 10.000 forms.Questo migliora notevolmente le prestazioni perché solo 1.000 forms dovranno essere disegnate in un momento in cui un cerchio viene rimosso da un livello piuttosto che tutte le 10.000 forms.”

“Ricorda che avere troppi livelli può anche rallentare le prestazioni, ma ho scoperto che l’utilizzo di 10 livelli composti ciascuno da 1.000 forms ha un rendimento migliore di 20 livelli con 500 forms o 5 livelli con 2.000 forms.”

Aggiornamento: avresti bisogno di eseguire test case in cui la procedura più ottimizzata sarebbe per te. Esempio: 10000 forms possono essere raggiunte da entrambi:

10000 forms * 1 strato

5000 forms * 2 strati

2500 forms * 4 strati

Qualunque cosa funzioni per te, sceglila! Dipende dal tuo codice.

Ecco alcuni passaggi che puoi fare per aumentare le prestazioni:

  • Prima sbarazzarsi del save / restore : sono chiamate molto costose e possono essere sostituite con setTransform
  • Srotolare il ciclo per fare di più all’interno per ogni iterazione
  • Cache tutte le proprietà

VIOLINO

Esempio con loop srotolato per 4 iterazioni:

 for(var nObject = 0, len = objects.length, // cache these x = coords.x, y = coords.y; nObject < len; nObject++){ ctx.setTransform(1,0,0,1, x, y); // sets absolute transformation ctx.rotate(objects[nObject].position*0.01); ctx.translate(radio,0); ctx.drawImage(imgToDraw,0,0); objects[nObject++].position++; ctx.setTransform(1,0,0,1,x, y); ctx.rotate(objects[nObject].position*0.01); ctx.translate(radio,0); ctx.drawImage(imgToDraw,0,0); objects[nObject++].position++; ctx.setTransform(1,0,0,1,x, y); ctx.rotate(objects[nObject].position*0.01); ctx.translate(radio,0); ctx.drawImage(imgToDraw,0,0); objects[nObject++].position++; ctx.setTransform(1,0,0,1,x, y); ctx.rotate(objects[nObject].position*0.01); ctx.translate(radio,0); ctx.drawImage(imgToDraw,0,0); objects[nObject++].position++; } ctx.setTransform(1,0,0,1,0,0); // reset transform for rAF loop 

(non aspettatevi però prestazioni in tempo reale).

Sebbene, forse è un po 'inutile disegnare 2000 oggetti in un'area così piccola. Se si sta cercando l' effetto , suggerirei invece questo approccio:

  • Crea una canvas fuori schermo
  • Produce 5-8 fotogrammi con il metodo sopra e li memorizza come immagini
  • Riproduzione di quelle immagini 5-8 come-è invece di fare tutti i calcoli

Se hai bisogno di un aspetto più fluido, semplicemente produci più fotogrammi. È ansible memorizzare ciascun fotogramma in una singola area di lavoro in base alle celle che verranno utilizzate successivamente come foglio di sprite. Naturalmente, quando si disegna si deve fare attenzione che le posizioni correnti siano statiche rispetto a quelle in movimento quando sono effettivamente animate. La rotazione e la posizione risultante sono un altro fattore.

Dopo vari test, sono giunto alle seguenti conclusioni:

  • la canvas non ha la capacità per questa attività.
  • La canvas stratificata aiuta solo le prestazioni quando gli elementi statici non devono essere costantemente ridisegnati.
  • Aggiungi il limite di stampa delle coordinate aiuta molto nel rendering.
  • alternative a funzioni lente
  • Gli elementi di stampa non stampati alla fine saranno nascosti da un altro elemento con uno z-index più alto (lavorando su di esso).

il risultato finale è un piccolo mix di tutti i contributi. ma ha bisogno di miglioramenti.

Testato con 30.000 oggetti e le prestazioni sono mantenute a 60 / fps.

http://jsfiddle.net/NGn29/1/

  var banPrint = true; for(nOverlap = nObject; nOverlap < objects.length; nOverlap++){ if( objects[nOverlap].position == objects[nObject].position && nOverlap != nObject ){ banPrint = false; break; } } 

Se le immagini non si sovrappongono, l’immagine risultante è 3200×3200 pixel, ovvero più di quanto la maggior parte dei display possa visualizzare. Quindi puoi provare a ottenere il riquadro di delimitazione dell’immagine trasformata e saltare quelli che sono al di fuori dell’area visibile (anche se la canvas dovrebbe già farlo per te).

Un’altra idea è combinare le immagini piccole in quelle più grandi e trasformarle insieme come gruppo.

Se si desidera organizzare le immagini in un anello, è ansible disegnarle una volta come un anello, salvarle come immagini e quindi ruotare l ‘”immagine ad anello” invece di ogni singola immagine.

Infine, dai un’occhiata a WebGL che potrebbe essere più efficiente dell’API canvas 2D.