2012-08-03 2 views
5

Je suis en train de concevoir une application Web de style Photoshop fonctionnant sur l'élément HTML5 Canvas. Le programme fonctionne bien et est très rapide jusqu'à ce que j'ajoute des modes de fusion dans l'équation. Je réalise des modes de fusion en fusionnant chaque élément de canevas en un seul et en combinant chaque pixel de chaque canevas en utilisant les bons modes de fusion à partir de la toile de fond.Accélérer HTML5 Rendu de pixels de canevas

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

Avec ce mélange d'appel pour différents modes de mélange. Mon mode de fusion « normal » est la suivante:

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

Mes résultats des tests dans Chrome (donnant certains des meilleurs sur les navigateurs testés) a été autour 400ms mélangeant trois couches ensemble sur une toile 620x385 (238,700 pixels).

Ceci est une très petite implémentation car la plupart des projets seront de plus grande taille et comprendront plus de couches qui feront grimper le temps d'exécution sous cette méthode.

Je me demande s'il existe un moyen plus rapide de combiner deux contextes de canevas avec un mode de fusion sans avoir à passer par tous les pixels.

+0

Qu'est-ce que 'nextLayerPixel'? Comment le créez-vous et pourquoi le changez-vous dans la fonction 'blend' (second paramètre)? – Bergi

+0

J'ai d'abord exclu cette partie pour montrer la fonctionnalité sans le code supplémentaire, ce qui rend désordonné, mais maintenant je l'ai ajouté. 'NextLayerPixel' est simplement une variable qui fait référence au même pixel dans chaque couche. Donc, avec un projet avec 3 couches et sur le pixel x: 30, y: 20, il va saisir le pixel de la couche inférieure à 30,20 puis au milieu 30,20 puis au sommet 30,20. –

Répondre

4

Ne créez pas tant de 4-valeur-réseaux, il devrait aller beaucoup plus vite en utilisant la mémoire existante. En outre, vous pouvez utiliser le reduce function sur votre tableau layer, cela semble exactement ce dont vous avez besoin. Cependant, n'utiliser aucune fonction pourrait être une autre touche plus rapide - aucune création de contextes d'exécution n'est nécessaire. Le code suivant appelle la fonction de fusion uniquement pour chaque couche, et non pour chaque couche de pixels *.

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

Autre solution: Ne pas mélanger les couches ensemble en javascript du tout. Positionnez simplement vos toiles l'une sur l'autre et donnez-leur un CSS opacity. Cela devrait accélérer l'affichage beaucoup. Seulement, je ne suis pas sûr que cela fonctionnera avec vos autres effets, s'ils doivent être appliqués sur plusieurs calques.

+0

Très bonne réponse. Cela m'a pris un moment de lire la documentation pour que la fonction de réduction fonctionne réellement avec mon code. Je vais encore devoir trouver quelque chose car la vitesse est seulement de 180ms sur les navigateurs les plus rapides et de près de 1000ms pour les navigateurs plus lents. Les navigateurs mobiles durent plus d'une seconde.Cela ne fonctionnera pas pour mon application et il se peut que je doive supprimer les modes de fusion sauf s'il existe un autre moyen d'éditer des zones plus grandes à la fois –

+0

À quelle fréquence devez-vous appeler cette fonction de mélange? – Bergi

+0

La fonction de fusion est appelée chaque fois que l'une des couches est modifiée tant que l'une des couches du projet a un mode autre que normal. Il n'appellera que la zone qui a été modifiée, mais si un utilisateur déplace un grand objet ou peint à l'aide d'un gros pinceau, il devra très rapidement faire de grandes surfaces et sera appelé chaque fois qu'un graphique est déplacé. Ce n'est pas seulement mousedown/mouseup. Il comprend également mousemove qui sera appelé plusieurs fois par seconde. Cela pourrait fonctionner si je pouvais l'obtenir à au moins 5fps (200ms) mais je ne vois pas cela se produire dans les navigateurs plus lents. –

2

Traditionnellement, ce type de manipulation massive de pixels est accéléré en les exécutant sur le GPU plutôt que sur la CPU. Malheureusement, canvas ne dispose pas de support pour cela, mais vous pouvez potentiellement implémenter une solution de contournement en utilisant SVG Filters. Cela vous permettrait d'utiliser des modes de fusion accélérée matérielle (feBlend) pour mélanger deux images ensemble. Si vous convertissez vos calques en deux images et que vous faites ensuite référence à ces images dans votre fichier SVG, vous pouvez effectuer ce travail.

Voici un bel aperçu illustré comment cela pourrait fonctionner:

http://blogs.msdn.com/b/ie/archive/2011/10/14/svg-filter-effects-in-ie10.aspx (pour IE10 mais applique à tout navigateur qui prend en charge les filtres SVG)

Questions connexes