Javascript 为什么canvas函数getImageData在不同的情况下执行时会花费很大的时间

Javascript 为什么canvas函数getImageData在不同的情况下执行时会花费很大的时间,javascript,performance,canvas,Javascript,Performance,Canvas,看看这个 唯一的区别是,单击前在左侧画布中绘制笔划矩形,单击时在右侧画布中绘制矩形 function getPointColor(ctx, x, y) { return ctx.getImageData(x, y, 1, 1).data; } function getTime(ctx, action) { console.time(action); for (let i = 0; i < 1000; i++) { getPointColor(ct

看看这个

唯一的区别是,单击前在左侧画布中绘制笔划矩形,单击时在右侧画布中绘制矩形

function getPointColor(ctx, x, y) {
    return ctx.getImageData(x, y, 1, 1).data;
}


function getTime(ctx, action) {
    console.time(action);
    for (let i = 0; i < 1000; i++) {
        getPointColor(ctx, 1, 1);
    }
    console.timeEnd(action);
}
ctx1.strokeRect(0, 0, 20, 20)

canvas1.onclick = function () {
    getTime(ctx1, 'click canvas1');
}

canvas2.onclick = function () {
    ctx2.strokeRect(0, 0, 20, 20)
    getTime(ctx2, 'click canvas2');
}
函数getPointColor(ctx,x,y){ 返回ctx.getImageData(x,y,1,1).data; } 函数getTime(ctx,action){ 时间(动作); for(设i=0;i<1000;i++){ getPointColor(ctx,1,1); } 控制台。时间结束(操作); } ctx1.strokeRect(0,0,20,20) canvas1.onclick=函数(){ getTime(ctx1,‘单击画布1’); } canvas2.onclick=函数(){ ctx2.冲程(0,0,20,20) getTime(ctx2,‘单击画布2’); } 控制台显示canvas1的耗时是canvas2的100倍

这大概是从v76开始的。
这似乎与硬件加速有关,禁用它将使两个操作占用相同的最短时间

请注意,尽管当前的金丝雀版本(v78)现在有一个不同的行为(可能是因为),这两个操作将花费最长的时间

我已经打开了链接的bug报告,所以没有更多的事情要做,除非你能在那里提出修复方案


如果我不得不猜测会发生什么,我会说他们现在大部分时间都将画布缓冲区保留在GPU内存中,但要获取图像数据,他们需要将其传输回CPU。因此,像您一样多次调用此方法将需要他们执行相同次数的传输

因此,一个简单的修复方法是重写
getPointColor
,使其只执行一次
getImageData
,无论bug是否修复,您都应该使用它

不要多次调用
getImageData(x,y,1,1)
来获取不同的点,而应该只获取一个表示所有画布的ImageData,然后从这个大ImageData中拾取像素值:

const canvas=document.getElementById('canvas');
const ctx=canvas.getContext('2d');
//从表示为32位的展平像素矩阵获取x y坐标处的插槽
函数getPixelColor32(数据、x、y、宽度){
返回数据[y*宽度+x];
}
//将插槽的值设置为x,y(32位版本)
函数setPixelColor32(值、数据、x、y、宽度){
数据[y*宽度+x]=val;
}
//相同的功能
//如果您希望从ImageData保留原始Uint8ClampedArray
//返回一个新的Uint8ClampedArray,这对内存不是很好
函数getPixelColor8(数据、x、y、宽度){
常数指数=(y*宽度+x)*4;
返回数据切片(索引,索引+4);
}
//这里,VAL是长度为4[r,g,b,a]的类似数组的对象
函数setPixelColor8(VAL、数据、x、y、宽度){
常数指数=(y*宽度+x)*4;
for(设i=0;i<4;i++){
数据[指数+i]=VAL[i];
}
}
//所以我们可以画一个圆
函数距离中心(x、y、cx、cy){
返回Math.hypot(cx-x,cy-y);
}
放下鼠标=错误;
canvas.onmousedown=(evt)=>{
鼠标向下=真;
抽签(evt);
};
canvas.onmousemove=(evt)=>{
如果(鼠标按下){
抽签(evt);
}
};
canvas.onmouseup=(evt)=>{
鼠标向下=错误;
}
函数绘制(evt){
常数{宽度,高度}=画布;
const rad=高度/10;
常数cx=evt.offsetX;
const cy=evt.offsetY;
//我们只调用一次getImageData
常量imgData=ctx.getImageData(0,0,宽度,高度);
//使用UINT32阵列视图以简化像素操作
//我们显然也可以使用UINT8clipped版本,
//虽然它会占用更多的内存
const data=新的uint32阵列(imgData.data.buffer);
对于(设x=0;x{
a[i]=Math.random()*0xFFFFFF+0xFF000000;
});
ctx.putImageData(噪声,0,0);
ctx.刻度(px_尺寸,px_尺寸);
ctx.imageSmoothingEnabled=假;
ctx.drawImage(画布,0,0);
setTransform(1,0,0,1,0,0);
ctx.imageSmoothingEnabled=真
单击&;拖动以进行像素操纵
  • 写操作后,同步执行读操作:
  • 根据楼上回复推测:
  • 进一步分析:16GB源码克隆中……
    getTime
    做什么?请在您的问题中包括所有相关代码,而不仅仅是JSFIDLE。
    ctx1.strokeRect(0, 0, 20, 20);
    ctx1.getImageData(1, 1, 1, 1);
    
    index 1c2001a..204b16d 100644
    --- a/third_party/blink/renderer/platform/graphics/canvas_2d_layer_bridge.cc
    +++ b/third_party/blink/renderer/platform/graphics/canvas_2d_layer_bridge.cc
    @@ -141,6 +141,8 @@
         accelerate = true;
       } else if (acceleration_mode_ == kDisableAcceleration) {
         accelerate = false;
    +  } else if (acceleration_mode_ == kEnableAcceleration) {
    +    accelerate = true;
       } else {
         accelerate = hint == kPreferAcceleration ||
                      hint == kPreferAccelerationAfterVisibilityChange;