使用2D画布上下文,我实现了一个图像处理算法 具有以下特征的JavaScript:
每行都可以单独处理。
每条线从左到右处理。中的一个像素的颜色 输出图像来自:
当前位置输入图像中的颜色
对于第一个之后的所有像素:输出图片中的颜色 前一个位置,即左侧的一个像素
算法的运行时间相应: O(n)
Chrome 52中的算法非常慢,因为画布操作很慢:
在输入图像中的当前位置读取颜色:
inputImageData.slice(location, location + 4)
(读取输出图像中前一个位置的输出颜色为 很快,它来自一个变量)
将像素写入输出图像,根据Chrome的分析器,它是 大部分时间的单一操作:
outputImageContext.fillStyle = outputColor;
outputImageContext.fillRect(x, y, 1, 1);
我研究了使用WebGL进行图像处理。但似乎不适用 因为使用WebGL,所有像素都是独立计算的。算法 要求从左到右扫描。
答案 0 :(得分:2)
我为不理解而道歉:你说输出应该是左边一个像素的输入颜色,但是你的"读取"算法说位置+4。
但是,如果我理解这应该是做什么的,那么似乎有一种非常优化的方式。在我看来,你喜欢拍摄一张图像并向右移动一个像素。然后,最左边的空列由前一个第一列像素填充。
使用正确的算法,您应该能够使用原始数据但是制作第一列的副本,是吗?
答案 1 :(得分:1)
目前尚不清楚你想要完成什么。
根据您的说明
每条线从左到右处理。输出图像中像素的颜色来自:
- 当前位置输入图像中的颜色
- 对于第一个之后的所有像素:前一个位置的输出图片的颜色,即左边的一个像素
听起来你只是要在整个图像上涂抹第一列。
从左到右处理,这只是重复的第一列。如果是这样,只需调用ctx.drawImage(ctx.canvas, 0, 0, 1, ctx.canvas.height, 0, 0, ctx.canvas.width, ctx.canvas.height);
将画布用作图像,取第一列并展开它以填充整个画布。
否则我真的不了解你的算法。遗憾
在任何情况下,假设您确实需要立即引用先前像素的结果,然后进行一些操作。
切片imagedata.data以获取像素将会很慢。每个切片都是一个内存分配。如果你想要速度,你最好不要为每个像素创建一个新对象。
至于写作,是的,使用fillRect
一次设置一个像素会非常慢。如何直接在ImageData
中设置像素,然后将ImageData放回画布?
否则尽量在内循环中尽量少做一些工作。如果你只能计算一次,那么只计算一次。
这是一些混合在一起的代码。虽然我认为虽然缓慢是主观的,但 的速度并不慢。
var ctx = document.querySelector("canvas").getContext("2d");
var width = ctx.canvas.width;
var height = ctx.canvas.height;
function r(min, max) {
if (max === undefined) {
max = min;
min = 0;
}
return Math.random() * (max - min) + min;
}
function drawRandomCircle(ctx) {
var color = "hsl(" + r(360) + ", " + r(50, 100) + "%, 50%)";
ctx.beginPath();
ctx.arc(r(width), r(height), r(100), 0, Math.PI * 2, false);
ctx.fillStyle = color;
ctx.fill();
}
// put some image into the canvas so have something to work with
ctx.fillStyle = "white";
ctx.fillRect(0, 0, width, height);
for (var ii = 0; ii < 200; ++ii) {
drawRandomCircle(ctx);
}
function process(time) {
time *= 0.001;
drawRandomCircle(ctx);
// get a copy of the image
var imageData = ctx.getImageData(0, 0, width, height);
var pixels = imageData.data;
var xoff = Math.sin(time) * 15 | 0;
var yoff = Math.cos(time) * 15 | 0;
// blur
for (var y = 0; y < height; ++y) {
var lineOffset = (y + yoff + height) % height * width;
for (var x = 0; x < width; ++x) {
var off0 = (y * width + x) * 4;
var off1 = (lineOffset + (x + xoff + width) % width) * 4;
var r0 = pixels[off0 + 0];
var g0 = pixels[off0 + 1];
var b0 = pixels[off0 + 2];
var r1 = pixels[off1 + 0];
var g1 = pixels[off1 + 1];
var b1 = pixels[off1 + 2];
pixels[off0 + 0] = (r0 * 9 + r1) / 10;
pixels[off0 + 1] = (g0 * 9 + g1) / 10;
pixels[off0 + 2] = (b0 * 9 + g1) / 10;
}
}
ctx.putImageData(imageData, 0, 0);
requestAnimationFrame(process);
}
requestAnimationFrame(process);
<canvas width="1000" height="1000"></canvas>
注意,上面的代码每帧getImageData
都会得到一个画布的新副本。它这样做可以看到刚绘制的新圆圈。这意味着每帧至少有一个大的内存分配来获取画布中像素的新副本。如果您不需要这样做,那么在初始化时进行复制,并继续使用相同的数据。