2012-08-03 67 views
5

我正在设计一个运行在HTML5 Canvas元素上的Photoshop风格的Web应用程序。该程序运行良好,并非常迅速,直到我将混合模式添加到方程中。我通过将每个画布元素合并为一个,并使用从底部画布开始的正确混合模式组合每个画布中的每个像素来实现混合模式。加快HTML5 Canvas像素渲染

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

它调用混合为不同的混合模式。我的“正常”混合模式如下:

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

我的测试结果在铬(产生一些最好出测试的浏览器的)围绕400ms的画布620x385(238700个像素)上进行混合三层在一起。

这是一个非常小的实现,因为大多数项目的尺寸会更大,并且会包含更多图层,这会使得执行时间在此方法下急剧增加。

我想知道是否有更快的方式来结合两个画布上下文与混合模式,而不必通过每个像素。

+0

'nextLayerPixel'是什么?你如何创建它,为什么你在'blend'函数(第二个参数)中改变它? – Bergi 2012-08-03 02:49:15

+0

我首先排除了那部分显示功能,没有额外的代码使它变得混乱,但现在我添加了它。'nextLayerPixel'只是一个变量,指的是每一层中的相同像素。因此,对于具有3个图层并且像素为x:30,y:20的项目,它将在30,20,然后是中间30,20,然后是顶部30,20抓取底层像素。 – 2012-08-03 02:55:22

回答

4

不要创造如此多的4值阵列,它在使用现有内存时应该快得多。另外,您可能想要在layer阵列上使用reduce function,这看起来正是您所需要的。但是,根本不使用函数可能会更快地触摸另一个触摸点 - 不需要创建执行上下文。以下代码将仅针对每个图层调用混合函数,而不是每个像素*图层。

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

替代解决方案:不要在JavaScript中的图层混合在一起的。只需将你的画布定位在彼此之上,并给他们一个CSS opacity。这应该加速显示很多。只有我不确定这是否会与其他效果一起使用,是否需要应用于多个图层。

+0

非常好的答案。花了我一段时间阅读文档以获得reduce函数来实际处理我的代码。我仍然需要找出一些东西,因为在最快的浏览器上速度只有180毫秒,对于较慢的浏览器速度只有近1000毫秒。移动浏览器超过1秒钟。这将无法为我的应用程序工作,我可能不得不采取混合模式,除非有另一种方式来一次编辑更大的区域 – 2012-08-03 22:09:38

+0

你多久需要调用这个混合函数? – Bergi 2012-08-03 22:22:36

+0

只要项目中的某个层具有非正常模式,每次修改其中一个层时都会调用混合函数。它只会调用已修改的区域,但如果用户使用大型刷子移动大型对象或绘画,则必须非常快速地执行大型区域,并且每次移动图形时都会调用该区域。这不仅仅是mousedown/mouseup。它还包括每秒会被调用多次的mousemove。这可以工作,如果我可以得到它至少5fps(200毫秒),但我没有看到发生在较慢的浏览器。 – 2012-08-03 22:31:17

2

传统上,通过在GPU上运行这些类型的大规模像素操作,而不是在CPU上运行它们。 不幸的是,画布不支持此功能,但您可以使用SVG Filters实施解决方法。这将允许您使用硬件加速混合模式(feBlend)将两个图像混合在一起。 如果您将图层渲染为两幅图像,然后在SVG中引用这些图像,则可以使其工作。

这里是一个很好的说明概述这是怎么工作:

http://blogs.msdn.com/b/ie/archive/2011/10/14/svg-filter-effects-in-ie10.aspx(对于IE10,但适用于支持SVG过滤器的任何浏览器)