在js冻结浏览器中对canavs图像的操作

时间:2017-02-05 12:45:02

标签: javascript html5-canvas

我编写了js脚本,它在画布上的图片上执行各种操作(例如,用常数,平方根,移动,应用滤镜来对照片进行求和)。但对于大图像(例如2000x200像素),脚本冻结/崩​​溃浏览器(在Firefox上测试),此外,所有内容都需要很长时间。

function get_pixel (x, y, canvas)
{
    var ctx = canvas.getContext("2d");
    var imgData = ctx.getImageData(x, y, 1, 1);
    return imgData.data;
}

function set_pixel (x, y, canvas, red, green, blue, alpha)
{ 
    var ctx = canvas.getContext('2d');
    var imgData = ctx.getImageData(0, 0, canvas.width, canvas.height), 
    pxData = imgData.data, 
    length = pxData.length; 

    var i = (x + y * canvas.width) * 4;
    pxData[i] = red; 
    pxData[i + 1] = green; 
    pxData[i + 2] = blue; 
    pxData[i + 3] = alpha;                               
    ctx.putImageData (imgData, 0, 0);                         
}

function sum (number, canvas1, canvas2)
{
    show_button_normalization (false);

    asyncLoop(
    {
        length : 5,
        functionToLoop : function(loop, i){
            setTimeout(function(){
                asyncLoop(
                {
                    length : 5,
                    functionToLoop : function(loop, i){
                        setTimeout(function(){
                            var pixel1 = get_pixel (i, j, canvas1);
                            var pixel2;

                            if (canvas2 != null)
                            {
                                pixel2 = get_pixel (i, j, canvas2);
                            }
                            else
                            {
                                pixel2 = new Array(4);
                                pixel2[0] = number;
                                pixel2[1] = number;
                                pixel2[2] = number;
                                pixel2[3] = number;
                            }

                            var pixel = new Array(4);
                            pixel[0] = parseInt (parseInt (pixel1[0]*0.5) + parseInt (pixel2[0]*0.5));
                            pixel[1] = parseInt (parseInt (pixel1[1]*0.5) + parseInt (pixel2[1]*0.5));
                            pixel[2] = parseInt (parseInt (pixel1[2]*0.5) + parseInt (pixel2[2]*0.5));
                            pixel[3] = parseInt (parseInt (pixel1[3]*0.5) + parseInt (pixel2[3]*0.5));

                            set_pixel (i, j, image1_a, pixel[0], pixel[1], pixel[2], pixel[3]);
                            loop();
                        },1000);
                    },   
                });
                loop();
            },1000);
        },   
    });



    /*for (var i=0; i<canvas1.width; i++)
    {
        for (var j=0; j<canvas1.height; j++)
        {
            var pixel1 = get_pixel (i, j, canvas1);
            var pixel2;

            if (canvas2 != null)
            {
                pixel2 = get_pixel (i, j, canvas2);
            }
            else
            {
                pixel2 = new Array(4);
                pixel2[0] = number;
                pixel2[1] = number;
                pixel2[2] = number;
                pixel2[3] = number;
            }

            var pixel = new Array(4);
            pixel[0] = parseInt (parseInt (pixel1[0]*0.5) + parseInt (pixel2[0]*0.5));
            pixel[1] = parseInt (parseInt (pixel1[1]*0.5) + parseInt (pixel2[1]*0.5));
            pixel[2] = parseInt (parseInt (pixel1[2]*0.5) + parseInt (pixel2[2]*0.5));
            pixel[3] = parseInt (parseInt (pixel1[3]*0.5) + parseInt (pixel2[3]*0.5));

            set_pixel (i, j, image1_a, pixel[0], pixel[1], pixel[2], pixel[3]);
        }
    }*/
}

是否可以修复它?

1 个答案:

答案 0 :(得分:0)

一起处理像素!!

查看代码我会说Firefox崩溃和/或花费很长时间并不是一个惊喜。 2000 x 2000像素的图像具有400万像素。我不知道asyncLoop做了什么,但对我来说,看起来你正在使用计时器一次设置5个像素的组。这是非常低效的。

代码问题

即使查看注释代码(我假设是另一种方法),您也会以很大的开销来处理像素。

从函数pixel获得的数组getPixel返回作为对象getImageData一部分的像素数组返回。如果您查看getImageData和te返回对象imageData的详细信息,您会看到该数组是类型为Uint8ClampedArray的类型数组

这意味着您用来混合像素的大多数代码都是多余的,因为当它为任何类型化数组分配数字时,它会自动通过javascript完成。

pixel[0] = parseInt (parseInt (pixel1[0]*0.5) + parseInt (pixel2[0]*0.5));

如果使用

,会更快
pixel[0] = (pixel1[0] + pixel2[0]) * 0.5; // a * n + b * n is the same as ( a+ b) *n
                                          // with one less multiplication.

标准简单图像处理

但即便如此,对每个像素使用函数调用也会给正在执行的基本操作增加大量开销。您应该一次性获取所有像素并将它们作为两个平面阵列处理。

你的和函数应该更像

function sum (number, canvas1, canvas2){
    var i, data, ctx, imgData, imgData1, data1;
    ctx = canvas1.getContext("2d");
    imgData = ctx.getImageData(0, 0, canvas1.width, canvas1.height);
    data = imgData.data; // get the array of pixels
    if(canvas2 === null){             
         i = data.length;
         number *= 0.5;  // pre calculate number
         while(i-- > 0){
              data[i] = data[i] * 0.5 + number;
         }
    }else{
         if(canvas1.width !== canvas2.width || canvas1.height !== canvas2.height){
              throw new RangeError("Canvas size miss-match, can not process data as requested.");
         }
         data1 = canvas2.getContext("2d").getImageData(0,0,canvas2.width, canvas2.height).data
         i = data.length;
         while(i-- > 0){
              data[i] = (data[i] + data1[i]) * 0.5;
         }
     }
     ctx.setImageData(imgData,0,0); // put the new pixels back to the canvas
 }

位数学更快

如果你使用一点位操作,你可以改进。使用32位类型数组,您可以分割然后并行添加4个8位值(4 *快速用于像素计算)。

  •   

    注意,此方法会比应该更频繁地向下舍入一个值。即Math.floor(199 * 233) === 216为真,而下面的方法将返回215.这可以通过使用两个输入的最低位来添加到结果中来纠正。这完全消除了舍入误差,但我认为处理成本不值得改进。我已将修复程序包含在已注释的代码中。

  •   

    注意此方法仅适用于a / n + b / m nm等于2^pp为{}的n整数&gt; 0和&lt; 7(换言之,仅当mp为2,4,8,16,32,64,127时),您必须屏蔽a的{​​{1}}位的b位和C = C * 0.5 + C1 * 0.5

CC1代表canvas1canvas2

的每个R,G,B,A频道时,示例执行function sum (number, canvas1, canvas2){ var i, data, ctx, imgData, data32, data32A; // this number is used to remove the bottom bit of each color channel // The bottom bit is redundant as divide by 2 removes it const botBitMask = 0b11111110111111101111111011111110; // mask for rounding error (not used in this example) // const botBitMaskA = 0b00000001000000010000000100000001; ctx = canvas1.getContext("2d"); imgData = ctx.getImageData(0, 0, canvas1.width, canvas1.height); data32 = new Uint32Array(imgData.data.buffer); i = data32.length; // get the length that is 1/4 the size if(canvas2 === null){ number >>= 1; // divide by 2 // fill to the 4 channels RGBA number = (number << 24) + (number << 16) + (number << 8) + number; // get reference to the 32bit version of the pixel data while(i-- > 0){ // Remove bottom bit of each channel and then divide each channel by 2 using zero fill right shift (>>>) then add to number data32[i] = ((data32[i] & botBitMask) >>> 1) + number; } }else{ if(canvas1.width !== canvas2.width || canvas1.height !== canvas2.height){ throw new RangeError("Canvas size miss-match, can not process data as requested."); } data32A = new Uint32Array(canvas2.getContext("2d").getImageData(0,0,canvas2.width, canvas2.height).data.buffer); i = data32.length; while(i-- > 0){ // for fixing rounding error include the following line removing the second one. Do the same for the above loop but optimise for number // data32[i] = (((data32[i] & botBitMask) >>> 1) + ((data32A[i] & botBitMask) >>> 1)) | ((data32[i] & botBitMaskA) | (data32A[i] & botBitMaskA)) data32[i] = ((data32[i] & botBitMask) >>> 1) + ((data32A[i] & botBitMask) >>> 1); } } ctx.setImageData(imgData,0,0); // put the new pixels back to the canvas }
// multiplies all channels in source canvas by val and returns the resulting canvas
// returns the can2 the result of each pixel
// R *= val;
// G *= val;
// B *= val;
// A *= val;

function multiplyPixels(val, source)
    var sctx = source.getContext("2d");

    // need two working canvas. I create them here but if you are doing this
    // many times you should create them once and reuse them
    var can1 = document.createElement("canvas");
    var can2 = document.createElement("canvas");
    can1.width = can2.width = source.width;
    can1.height= can2.height = source.height;

    var ctx1 = can1.getContext("2d");
    var ctx2 = can2.getContext("2d");

    var chanMult = Math.round(255 * val);
    // clamp it to 0-255 inclusive
    chanMult = chanMult < 0 ? 0 : chanMult > 255 ? 255 : chanMult;

    ctx1.drawImage(source,0,0); // copy the source

    // multiply all RGB pixels by val
    ctx1.fillStyle = "rgba(" + chanMult + "," + chanMult + "," + chanMult + ",1)";
    ctx1.globalCompositeOperation = "multiply";
    ctx1.fillRect(0, 0, source.width, source.height);

    // now multiply the alpha channel by val. Clamp it to 0-1
    ctx2.globalAlpha = val < 0 ? 0 : val > 1 ? 1 : val;
    ctx2.drawImage(can1,0,0);

    return can2;
}

尽管如此,你不应该有任何重大问题。虽然在处理图像时仍然会阻止页面(取决于机器和图像大小,可能需要一秒钟或2秒)

其他解决方案。

如果要阻止图像处理阻止页面,您可以使用Web工作器,只需将数据发送给它们即可同步处理。你可以找到如何做到但只是搜索stackOverflow。

或使用WebGL处理图像。

你有一个最后的选择。 canvas api使用GPU来完成所有渲染,如果你理解blending and compositing的工作方式,你可以使用画布进行大量的数学运算。

例如,您可以使用以下内容将所有像素RGBA通道乘以值0-1。

globalAlpha

有很多composite operation可以组合使用来进行乘法,加法,减法和除法。注意,虽然精度略小于8位,因为加法和减法需要加权值来补偿混合(自动)乘法。此外,必须使用import numpy as np x = np.matrix([[1,2],[3,4]]) y = np.matrix([[4,3],[2,2]]) d = x + y print d 和合成操作将RGB通道与RGB通道分开处理。

实时

您正在进行的处理非常简单,可以轻松实时处理2000 x 2000像素的图像。 WebGl filter是使用webGL进行图像处理的示例。虽然过滤系统不是模块化的,并且代码非常老,但它是webGL过滤器的良好支柱,并且因为它使用浮点RGBA值而提供更高质量的结果。