7

我需要扫描画布图像中的每个像素并调整颜色等。为了获得最佳性能,我是否应该一次性获取所有数据并通过数组处理它?或者我应该在处理每个像素时调用它。

所以基本上...

data = context.getImageData(x, y, height, width);

VS

data = context.getImageData(x, y, 1, 1); //in a loop height*width times.
4

3 回答 3

19

通过一次抓取图像,您将获得更高的性能,因为:a) 对数组的(连续)访问比函数调用快得多。b) 尤其是当此函数是具有一些开销的 DOM 对象的方法时。c)并且可能存在可能延迟响应的缓冲区刷新问题(如果画布在视线内......或不取决于双缓冲实现)。

所以去一次性抓住。

我建议您研究 Javascript Typed Arrays 以充分利用 imageData 结果。

如果我可以引用自己的话,看看你如何在我的这个旧帖子中快速处理像素(看 2):

画布上的漂亮椭圆?

(我在下面引用了相关部分:)


您可以使用以下命令在 ImageData 上获得 UInt32Array 视图:

var myGetImageData = myTempCanvas.getImageData(0,0,sizeX, sizeY);
var sourceBuffer32     = new Uint32Array(myGetImageData.data.buffer);

然后 sourceBuffer32[i] 包含打包成一个无符号 32 位 int 的红色、绿色、蓝色和透明度。将其与 0 进行比较以了解像素是否为非黑色( != (0,0,0,0) )

或者您可以使用 Uint8Array 视图更精确:

var myGetImageData = myTempCanvas.getImageData(0,0,sizeX, sizeY);
var sourceBuffer8     = new Uint8Array(myGetImageData.data.buffer);

如果您只处理灰色阴影,则 R=G=B,因此请注意

sourceBuffer8[4*i]>Threshold

您可以使用 UInt32Array 视图一次性将第 i 个像素设置为黑色:

sourceBuffer32[i]=0xff000000;

设置为任何颜色/alpha:

sourceBuffer32[i]= (A<<24) | (B<<16) | (G<<8) | R ;

或只是任何颜色:

sourceBuffer32[i]= 0xff000000 | (B<<16) | (G<<8) | R ;

(确保 R 是四舍五入的)。


听@Ken 的评论,是的,当您开始一次使用 32 位时,字节序可能会成为一个问题。大多数计算机都使用little-endian,因此RGBA在一次处理32位时变为ABGR。
由于它是绝大多数系统,如果处理 32 位整数假设是这种情况,并且您可以 - 为了兼容性 - 在大端系统上写入 32 位结果之前反转您的计算。让我分享这两个功能:

function isLittleEndian() {     
// from TooTallNate / endianness.js.   https://gist.github.com/TooTallNate/4750953
    var b = new ArrayBuffer(4);
    var a = new Uint32Array(b);
    var c = new Uint8Array(b);
    a[0] = 0xdeadbeef;
    if (c[0] == 0xef) { isLittleEndian = function() {return true }; return true; }
    if (c[0] == 0xde) { isLittleEndian = function() {return false }; return false; }
    throw new Error('unknown endianness');
}


function reverseUint32 (uint32) {
    var s32 = new Uint32Array(4);
    var s8 = new Uint8Array(s32.buffer);
    var t32 = new Uint32Array(4);
    var t8 = new Uint8Array(t32.buffer);        
    reverseUint32 = function (x) {
        s32[0] = x;
        t8[0] = s8[3];
        t8[1] = s8[2];
        t8[2] = s8[1];
        t8[3] = s8[0];
        return t32[0];
    }
    return reverseUint32(uint32);
};
于 2013-10-21T18:33:57.377 回答
5

除了 GameAlchemist 所说的,如果你想同时获取或设置一个像素的所有颜色,但你不想检查字节序,你可以使用DataView

var data = context.getImageData(0, 0, canvas.width, canvas.height);
var view = new DataView(data.data.buffer);

// Read or set pixel (x,y) as #RRGGBBAA (big endian)
view.getUint32(4 * (x + y*canvas.width));
view.setUint32(4 * (x + y*canvas.width), 0xRRGGBBAA);

// Read or set pixel (x,y) as #AABBGGRR (little endian)
view.getUint32(4 * (x + y*canvas.width), true);
view.setUint32(4 * (x + y*canvas.width), 0xAABBGGRR, true);

// Save changes
ctx.putImageData(data, 0, 0);
于 2016-08-29T21:20:51.010 回答
1

这取决于你到底在做什么,但我建议一次抓住它,然后循环通过它。

一次抓取它比逐个像素地抓取它要快,因为通过数组搜索比通过画布搜索要快得多,每个像素一次。

如果您真的需要速度,请查看网络工作者。您可以将每个设置为抓取画布的特定部分,并且由于它们可以同时运行,它们将更好地利用您的 CPU。

getImageData()根据我使用该功能的经验,如果您一次或单独抓住它,并不足以让您注意到差异。

于 2013-10-21T16:18:25.277 回答