2012-08-03 62 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過濾器的任何瀏覽器)