2012-08-03 15 views
5

Estoy diseñando una aplicación web al estilo de Photoshop que se ejecuta en el elemento HTML5 Canvas. El programa funciona bien y es muy rápido hasta que agregue modos de fusión en la ecuación. Alcanzo los modos de fusión fusionando cada elemento de lienzo en uno y combinando cada píxel de cada lienzo usando los modos de fusión correctos comenzando desde el lienzo inferior.Acelerar HTML5 Canvas píxel renderizado

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 él llamando mezcla de diferentes modos de mezcla. Mi modo de mezcla 'normal' es la siguiente:

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

Mis resultados de la prueba en Chrome (produciendo algunos de los mejores de los navegadores probados) fue de alrededor de 400 ms mezclando tres capas juntas en un lienzo de 620x385 (238.700 pixels).

Esta es una aplicación muy pequeña como la mayoría de los proyectos serán más grandes en tamaño e incluir más capas que harán que el tiempo de ejecución se dispara bajo este método.

Me pregunto si hay alguna manera más rápida para combinar dos contextos de lona con un modo de mezcla sin tener que pasar a través de cada píxel.

+0

¿Cuál es 'nextLayerPixel'? ¿Cómo se crea y por qué se cambia en la función 'blend' (segundo parámetro)? – Bergi

+0

primera vez que descartar que parte para mostrar la funcionalidad sin el código extra por lo que es complicado, pero ahora he añadido en. 'NextLayerPixel' simplemente es una variable que hace referencia al mismo píxel en cada capa. Entonces, con un proyecto con 3 capas y sobre un píxel x: 30, y: 20, tomará el píxel de la capa inferior a 30,20, luego el centro 30,20 y luego el superior 30,20. –

Respuesta

4

No crear tantas 4-valor-arrays, se debería ir mucho más rápido cuando se usa la memoria existente. Además, es posible que desee utilizar el reduce function en su matriz layer, esto parece exactamente lo que necesita. Sin embargo, no usar ninguna función podría ser un toque más rápido, no es necesario crear contextos de ejecución. El siguiente código invocará la función de mezcla solo para cada capa, no para cada píxel *.

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

solución alternativa: no se mezclan las capas juntas en javascript en absoluto. Simplemente coloque sus lienzos uno sobre el otro y déles un CSS opacity. Esto debería acelerar la visualización mucho. Solo que no estoy seguro de si esto funcionará junto con sus otros efectos, en caso de que necesiten aplicarse en varias capas.

+0

Muy buena respuesta. Me tomó un tiempo leer la documentación para que la función de reducción realmente funcione con mi código. Todavía voy a tener que encontrar algo porque la velocidad solo baja a 180ms en los navegadores más rápidos y casi 1000ms para los navegadores más lentos. Los navegadores móviles tienen más de 1 segundo.Esto no podrá funcionar para mi aplicación y podría tener que sacar los modos de mezcla a menos que haya otra forma de editar áreas más grandes a la vez –

+0

¿Con qué frecuencia necesita llamar a esta función de mezcla? – Bergi

+0

La función de mezcla se invoca cada vez que se modifica una de las capas, siempre que una de las capas del proyecto tenga un modo distinto al normal. Solo llamará al área que ha sido modificada, pero si un usuario mueve un objeto grande o pinta con un pincel grande, tendrá que hacer grandes áreas muy rápidamente y se le llamará cada vez que se mueva un gráfico. Esto no es solo mousedown/mouseup. También incluye mousemove que se llamará numerosas veces cada segundo. Esto podría funcionar si pudiera obtener al menos 5 fps (200 ms), pero no veo que eso suceda en los navegadores más lentos. –

2

Tradicionalmente este tipo de manipulación masiva de píxeles se acelera mediante su ejecución en la GPU, en lugar de en la CPU. Desafortunadamente, Canvas no tiene soporte para esto, pero podría implementar una solución mediante SVG Filters. Esto le permitiría usar modos de fusión acelerados por hardware (feBlend) para combinar dos imágenes. Si renderiza sus capas en dos imágenes y luego envía estas imágenes a su SVG, puede hacer que esto funcione.

Aquí es una buena visión general ilustrado cómo esto podría funcionar:

http://blogs.msdn.com/b/ie/archive/2011/10/14/svg-filter-effects-in-ie10.aspx (por IE10, pero se aplica a cualquier navegador que soporta filtros SVG)