我编写了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]);
}
}*/
}
是否可以修复它?
答案 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
n
和m
等于2^p
且p
为{}的n
整数&gt; 0和&lt; 7(换言之,仅当m
和p
为2,4,8,16,32,64,127时),您必须屏蔽a
的{{1}}位的b
位和C = C * 0.5 + C1 * 0.5
当C
和C1
代表canvas1
和canvas2
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值而提供更高质量的结果。