Canvas getImageData() 为了获得最佳性能.提取所有数据或一次提取一个数据

Canvas getImageData() For optimal performance. To pull out all data or one at a time?

本文关键字:数据 提取 一次 一个 性能 getImageData Canvas 最佳      更新时间:2023-09-26

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

所以基本上...

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

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

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

所以去一次性抢夺。

我建议你研究一下Javascript类型数组,以充分利用图像数据结果。

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

画布上的椭圆漂亮吗?

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


您可以使用以下方法获取有关图像数据的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 位时,字节序可能是一个问题。大多数计算机都使用小端序,因此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);
};

除了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);

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

一次抓取所有内容比逐个像素抓取它更快,因为搜索数组比搜索画布快得多,每个像素搜索一次。

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

getImageData() 的速度还不够慢,以至于您不会注意到在我使用该函数的经验中一次或单独抓取它时会注意到差异。