2012-08-03 12 views
5

Ich entwerfe eine Webanwendung im Photoshop-Stil, die auf dem HTML5-Canvas-Element ausgeführt wird. Das Programm läuft gut und ist sehr schnell, bis ich Mischmodi in die Gleichung einfüge. Ich erreiche Mischmodi, indem ich jedes Canvas-Element zu einem zusammenfasse und jedes Pixel von jedem Canvas mit den richtigen Mischmodi kombiniere, die auf der unteren Leinwand beginnen.HTML5-Canvas-Pixel-Rendering beschleunigen

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

Mit Aufruf für verschiedene Mischmodi aufrufen. Meine ‚normale‘ Mischmodus ist wie folgt:

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; 
} 

Meine Testergebnisse in Chrome (einige der besten aus den getesteten Browsern Nachgeben) wurde um 400 ms Mischen drei Schichten zusammen auf einer Leinwand 620x385 (238.700 Pixel).

Dies ist eine sehr kleine Implementierung, da die meisten Projekte größer sind und mehr Layer enthalten, was die Ausführungszeit unter dieser Methode in die Höhe schnellen lässt.

Ich frage mich, ob es eine schnellere Möglichkeit gibt, zwei Canvas-Kontexte mit einem Mischmodus zu kombinieren, ohne jedes Pixel durchlaufen zu müssen.

+0

Was ist 'nextLayerPixel'? Wie erstellen Sie es und warum ändern Sie es in der Funktion "Blend" (zweiter Parameter)? – Bergi

+0

Ich habe zuerst diesen Teil ausgeschlossen, um die Funktionalität zu zeigen, ohne den zusätzlichen Code, der es unordentlich macht, aber jetzt fügte ich ihn hinzu. 'NextLayerPixel' ist einfach eine Variable, die sich auf dasselbe Pixel in jeder Ebene bezieht. Bei einem Projekt mit 3 Ebenen und einem Pixel x: 30, y: 20 wird das Pixel der unteren Ebene bei 30,20, dann bei der mittleren 30,20 und dann bei der oberen 30,20 genommen. –

Antwort

4

Erstellen sollte viel schneller gehen, wenn Sie den vorhandenen Speicher verwenden. Vielleicht möchten Sie auch die reduce function auf Ihrem layer Array verwenden, das scheint genau das, was Sie brauchen. Wenn Sie jedoch überhaupt keine Funktionen verwenden, können Sie eine weitere Berührung schneller machen - es ist keine Erstellung von Ausführungskontexten erforderlich. Der folgende Code ruft die Überblendungsfunktion nur für jeden Layer und nicht für jeden Pixel * Layer auf.

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; 
} 

Alternative Lösung: Sie mischen nicht die Schichten miteinander in JavaScript, um alle an. Stellen Sie Ihre Leinwände einfach übereinander und geben Sie ihnen ein CSS opacity. Dies sollte die Anzeige viel beschleunigen. Nur bin ich mir nicht sicher, ob das mit Ihren anderen Effekten zusammenarbeitet, sollten sie auf mehreren Ebenen angewendet werden müssen.

+0

Sehr gute Antwort. Es dauerte eine Weile, bis ich die Dokumentation gelesen hatte, damit die Reduce-Funktion tatsächlich mit meinem Code funktionierte. Ich werde immer noch etwas herausfinden müssen, weil die Geschwindigkeit bei den schnellsten Browsern nur auf 180 ms und bei langsameren Browsern auf fast 1000 ms liegt. Mobile Browser sind länger als 1 Sekunde.Dies kann nicht für meine Anwendung funktionieren und ich muss möglicherweise die Mischmodi auslassen, es sei denn, es gibt eine andere Möglichkeit, größere Bereiche gleichzeitig zu bearbeiten. –

+0

Wie oft müssen Sie diese Mischfunktion aufrufen? – Bergi

+0

Die Überblendungsfunktion wird jedes Mal aufgerufen, wenn eine der Ebenen geändert wird, solange einer der Ebenen im Projekt einen anderen als den normalen Modus aufweist. Es wird nur den Bereich aufgerufen, der geändert wurde, aber wenn ein Benutzer ein großes Objekt oder ein großes Objekt mit einem großen Pinsel bewegt, muss es sehr schnell große Bereiche bearbeiten und wird jedes Mal aufgerufen, wenn eine Grafik verschoben wird. Dies ist nicht nur Mouse-Down/Mouse-Up. Es enthält auch Mauszeiger, der jede Sekunde mehrmals aufgerufen wird. Dies könnte funktionieren, wenn ich es auf mindestens 5 fps (200 ms) bringen könnte, aber das sehe ich nicht in den langsameren Browsern. –

2

Normalerweise wird diese Art der massiven Pixelmanipulation beschleunigt, indem sie auf der GPU statt auf der CPU ausgeführt werden. Leider Canvas unterstützt dies nicht, aber Sie könnten möglicherweise eine Problemumgehung mit SVG Filters implementieren. Auf diese Weise können Sie hardwarebeschleunigte Mischmodi (feBlend) verwenden, um zwei Bilder miteinander zu vermischen. Wenn Sie Ihre Ebenen in zwei Bilder rendern und diese Bilder dann in Ihrem SVG anzeigen, können Sie dies tun.

Hier ist ein schöner illustrierte Übersicht, wie dies funktionieren könnte:

http://blogs.msdn.com/b/ie/archive/2011/10/14/svg-filter-effects-in-ie10.aspx (für IE10 aber gilt für jeden Browser, der SVG-Filter unterstützt) so viele 4-Wert-Arrays, ist es nicht