2012-08-03 13 views
5

Sto progettando un'applicazione Web in stile Photoshop in esecuzione sull'elemento HTML5 Canvas. Il programma funziona bene ed è molto veloce fino a quando non aggiungo i modi di miscelazione nell'equazione. Ottengo le modalità di fusione unendo ciascun elemento di canvas in uno e combinando ogni pixel di ogni tela utilizzando le giuste modalità di fusione partendo dalla tela inferiore.Accelera il rendering pixel della tela HTML5

for (int i=0; i<width*height*4; i+=4) { 
    var base = [layer[0][i],layer[0][i+1],layer[0][i+2],layer[0][i+3]]; 
    var nextLayerPixel = [layer[1][i],layer[1][i+1],layer[1][i+2],layer[1][i+3]]; 
    //Apply first blend between first and second layer 
    basePixel = blend(base,nextLayerPixel); 
    for(int j=0;j+1 != layer.length;j++){ 
     //Apply subsequent blends here to basePixel 
     nextLayerPixel = [layer[j+1][i],layer[j+1][i+1],layer[j+1][i+2],layer[j+1][i+3]]; 
     basePixel = blend(basePixel,nextLayerPixel); 
    } 
    pixels[i] = base[0]; 
    pixels[i+1] = base[1]; 
    pixels[i+2] = base[2]; 
    pixels[i+3] = base[3]; 
} 
canvas.getContext('2d').putImageData(imgData,x,y); 

Con esso chiama miscela per diverse modalità di fusione. Il mio metodo di fusione 'normale' è la seguente:

var blend = function(base,blend) { 
    var fgAlpha = blend[3]/255; 
    var bgAlpha = (1-blend[3]/255)*base[3]/255; 
    blend[0] = (blend[0]*fgAlpha+base[0]*bgAlpha); 
    blend[1] = (blend[1]*fgAlpha+base[1]*bgAlpha); 
    blend[2] = (blend[2]*fgAlpha+base[2]*bgAlpha); 
    blend[3] = ((blend[3]/255+base[3])-(blend[3]/255*base[3]))*255; 
    return blend; 
} 

miei risultati dei test in Chrome (producendo alcuni dei migliori fuori dei browser testati) è stato di circa 400ms mescolando tre strati insieme su una tela 620x385 (238,700 pixel).

Questa è un'implementazione molto piccola in quanto la maggior parte dei progetti avrà dimensioni maggiori e include più livelli che renderanno il tempo di esecuzione salito alle stelle con questo metodo.

Mi chiedo se ci sia un modo più rapido per combinare due contesti di canvas con una modalità di fusione senza dover passare attraverso ogni pixel.

+0

Che cos'è 'nextLayerPixel'? Come lo crei e perché lo cambi nella funzione 'blend' (secondo parametro)? – Bergi

+0

Per prima cosa ho escluso quella parte per mostrare la funzionalità senza il codice aggiuntivo rendendola disordinata, ma ora l'ho aggiunta. 'NextLayerPixel' è semplicemente una variabile che si riferisce allo stesso pixel in ogni livello. Quindi con un progetto a 3 strati e su pixel x: 30, y: 20, afferra il pixel del livello inferiore a 30,20 quindi a 30,20 medio e quindi a 30,20. –

risposta

4

Non creare così tante 4 valore di array, è dovrebbe andare molto più veloce quando si usa la memoria esistente. Inoltre, potresti voler usare lo reduce function sulla tua matrice layer, questo sembra esattamente quello di cui hai bisogno. Tuttavia, l'uso di nessuna funzione potrebbe essere un altro tocco più veloce - nessuna creazione di contesti di esecuzione necessari. Il codice seguente invocherà la funzione di fusione solo per ogni livello, non per ogni pixel * livelli.

var layer = [...]; // an array of CanvasPixelArrays 
var base = imgData.data; // the base CanvasPixelArray whose values will be changed 
         // if you don't have one, copy layer[0] 
layer.reduce(blend, base); // returns the base, on which all layers are blended 
canvas.getContext('2d').putImageData(imgData, x, y); 

function blend(base, pixel) { 
// blends the pixel array into the base array and returns base 
    for (int i=0; i<width*height*4; i+=4) { 
     var fgAlpha = pixel[i+3]/255, 
      bgAlpha = (1-pixel[i+3]/255)*fgAlpha; 
     base[i ] = (pixel[i ]*fgAlpha+base[i ]*bgAlpha); 
     base[i+1] = (pixel[i+1]*fgAlpha+base[i+1]*bgAlpha); 
     base[i+2] = (pixel[i+2]*fgAlpha+base[i+2]*bgAlpha); 
     base[i+3] = ((fgAlpha+base[i+3])-(fgAlpha*base[i+3]))*255; 
//       ^this seems wrong, but I don't know how to fix it 
    } 
    return base; 
} 

Soluzione alternativa: non si fondono insieme gli strati in javascript a tutti. Assolutamente posiziona le tue tele una sopra l'altra e dai loro un opacity CSS. Questo dovrebbe accelerare la visualizzazione molto. Solo io non sono sicuro che questo funzionerà insieme agli altri tuoi effetti, se devono essere applicati su più livelli.

+0

Ottima risposta. Mi ci è voluto un po 'di tempo per leggere la documentazione per far funzionare la funzione di riduzione con il mio codice. Dovrò ancora capire qualcosa perché la velocità è solo fino a 180 ms sui browser più veloci e quasi 1000ms per i browser più lenti. I browser per dispositivi mobili durano più di 1 secondo.Questo non sarà in grado di funzionare per la mia applicazione e potrei dover prendere le modalità di fusione, a meno che non ci sia un altro modo per modificare aree più grandi alla volta –

+0

Quante volte hai bisogno di chiamare questa funzione di fusione? – Bergi

+0

La funzione di fusione viene chiamata ogni volta che uno dei livelli viene modificato finché uno dei livelli nel progetto ha una modalità diversa dal normale. Richiama solo l'area che è stata modificata, ma se un utente sta spostando un oggetto di grandi dimensioni o dipinge usando un pennello grande, allora dovrà fare grandi aree molto rapidamente e verrà chiamato ogni volta che viene spostato un grafico. Questo non è solo mouse/mouseup. Include anche i mousemove che saranno chiamati numerose volte al secondo. Questo potrebbe funzionare se potessi ottenere almeno 5fps (200ms) ma non vedo che ciò accada nei browser più lenti. –

2

Tradizionalmente, questo tipo di enorme manipolazione dei pixel viene accelerato eseguendoli sulla GPU, anziché sulla CPU. Purtroppo la tela non ha supporto per questo ma potresti potenzialmente implementare una soluzione alternativa usando SVG Filters. Ciò consentirebbe di utilizzare le modalità di fusione accelerate hardware (feBlend) per unire due immagini insieme. Se rendi i tuoi livelli a due immagini e poi fai riferimento a queste immagini nel tuo SVG, potresti farlo funzionare.

Ecco una bella panoramica illustrato come ciò potrebbe funzionare:

http://blogs.msdn.com/b/ie/archive/2011/10/14/svg-filter-effects-in-ie10.aspx (per IE10, ma vale per qualsiasi browser che supporta filtri SVG)

Problemi correlati