使用 `ImageData` 数据数组进行动画处理会导致非常意外的 glitch-like 结果
Animating with the `ImageData` data array causes very unexpected and glitch-like results
我最近使用 ImageData
数据数组在 canvas 上拉伸了一个渐变;即 ctx.getImageData()
和 ctx.putImageData()
方法,我自己想 "this could be a really efficient way to animate a canvas full of moving objects"。所以我将它与 requestAnimationFrame(callback)
语句一起包装到我的主函数中,但那时候事情变得很奇怪。我能做的最好的描述就是说它就像 canvas 上最左边的像素列在最右边的像素列中重复,并且基于您为 get
指定的坐标和put
ctx
方法,这可能会产生奇怪的后果。
我开始使用针对 0, 0
的 canvas 的 get 和 put 方法,如下所示:
imageData = ctx.getImageData( 0, 0, cvs.width, cvs.height );
// here I set the pixel colors according to their location
// on the canvas using nested for loops to target the
// the correct imageData array indexes.
ctx.putImageData( imageData, 0, 0 );
但我立即注意到 canvas 的右侧是错误的。就像渐变已经重新开始一样,最后一个像素由于某种原因没有被触及:
所以缩小我的绘制区域更改了 put
ImageData 坐标以在绘制的图像和 canvas 的边缘之间获得一些 space,并且我更改了 get
协调消除 canvas:
右边缘的那条线
imageData = ctx.getImageData( 1, 1, cvs.width, cvs.height );
for ( var x = 0; x < cvs.width - 92; x++ ) {
for ( var y = 0; y < cvs.height - 92; y++ ) {
// array[ x + y * width ] = value / x; // or similar
}
}
ctx.putImageData( imageData, 2, 2 );
漂亮!但是错了...所以我在codepen中转载了它。有人可以帮助我理解并克服这种行为吗?
注意:codepen 的绘制区域缩小了。如果将 get
坐标更改为 0,您会发现它的行为基本上与第一个示例相同,但 white-space 在预期的正方形和意外的线之间。也就是说,对于最有趣的行为,我将 get
保留为 1,将 put
保留为零。
我已经稍微更改了您的代码。在您的双循环中,我声明了一个变量 var i = (x + y*cvs.width)*4;
这只是减少了代码的冗长程度,以便我可以更好地查看它。 i 变量表示您的像素在 imageData.data 数组中的索引。既然你在做
imageData.data[i - 4 ] ...
imageData.data[i - 3 ] ...
imageData.data[i - 2 ] ...
imageData.data[i - 1 ] ...
您将向后移动一个像素,每行的第一个像素显示为上一行的最后一个像素。所以我将它从 var i = (x + y*cvs.width)*4;
更改为 var i = 4 + (x + y*cvs.width)*4;
。
当您对其进行动画处理时,由于 imageData
位于 test()
函数内部,因此您正在重新计算最后一帧基础中 imageData.data 数组的值。因此,在第二帧中,您再次复制了第一帧中的 1px 线,向上移动 1px,向左移动 1px。
我希望这就是你要问的。
var ctx, cvs, imageData;
cvs = document.getElementById('canv');
ctx = cvs.getContext('2d');
function test() {
// imageData = ctx.getImageData( 0, 0, cvs.width, cvs.height );
// produces a line on the right side of the screen
imageData = ctx.getImageData( 1, 1, cvs.width, cvs.height );
// bizzar reverse cascading
for (var x=0;x<cvs.width-92;x++) {
for (var y=0;y<cvs.height-92;y++) {
var i = 4+(x + y*cvs.width)*4;
imageData.data[i - 4 ] = Math.floor((255-y)-Math.floor(x/55)*55);
imageData.data[i - 3 ] = Math.floor(255/(cvs.height-92)*y);
imageData.data[i - 2 ] = Math.floor(255/(cvs.width-92)*x);
imageData.data[i - 1 ] = 255;
}
}
ctx.putImageData( imageData, 0, 0 );
requestAnimationFrame( test );
}
test();
canvas {
box-shadow: 0 0 2.5px 0 black;
}
<canvas id="canv" height="256" width="256"></canvas>
我最近使用 ImageData
数据数组在 canvas 上拉伸了一个渐变;即 ctx.getImageData()
和 ctx.putImageData()
方法,我自己想 "this could be a really efficient way to animate a canvas full of moving objects"。所以我将它与 requestAnimationFrame(callback)
语句一起包装到我的主函数中,但那时候事情变得很奇怪。我能做的最好的描述就是说它就像 canvas 上最左边的像素列在最右边的像素列中重复,并且基于您为 get
指定的坐标和put
ctx
方法,这可能会产生奇怪的后果。
我开始使用针对 0, 0
的 canvas 的 get 和 put 方法,如下所示:
imageData = ctx.getImageData( 0, 0, cvs.width, cvs.height );
// here I set the pixel colors according to their location
// on the canvas using nested for loops to target the
// the correct imageData array indexes.
ctx.putImageData( imageData, 0, 0 );
但我立即注意到 canvas 的右侧是错误的。就像渐变已经重新开始一样,最后一个像素由于某种原因没有被触及:
所以缩小我的绘制区域更改了 put
ImageData 坐标以在绘制的图像和 canvas 的边缘之间获得一些 space,并且我更改了 get
协调消除 canvas:
imageData = ctx.getImageData( 1, 1, cvs.width, cvs.height );
for ( var x = 0; x < cvs.width - 92; x++ ) {
for ( var y = 0; y < cvs.height - 92; y++ ) {
// array[ x + y * width ] = value / x; // or similar
}
}
ctx.putImageData( imageData, 2, 2 );
漂亮!但是错了...所以我在codepen中转载了它。有人可以帮助我理解并克服这种行为吗?
注意:codepen 的绘制区域缩小了。如果将 get
坐标更改为 0,您会发现它的行为基本上与第一个示例相同,但 white-space 在预期的正方形和意外的线之间。也就是说,对于最有趣的行为,我将 get
保留为 1,将 put
保留为零。
我已经稍微更改了您的代码。在您的双循环中,我声明了一个变量 var i = (x + y*cvs.width)*4;
这只是减少了代码的冗长程度,以便我可以更好地查看它。 i 变量表示您的像素在 imageData.data 数组中的索引。既然你在做
imageData.data[i - 4 ] ...
imageData.data[i - 3 ] ...
imageData.data[i - 2 ] ...
imageData.data[i - 1 ] ...
您将向后移动一个像素,每行的第一个像素显示为上一行的最后一个像素。所以我将它从 var i = (x + y*cvs.width)*4;
更改为 var i = 4 + (x + y*cvs.width)*4;
。
当您对其进行动画处理时,由于 imageData
位于 test()
函数内部,因此您正在重新计算最后一帧基础中 imageData.data 数组的值。因此,在第二帧中,您再次复制了第一帧中的 1px 线,向上移动 1px,向左移动 1px。
我希望这就是你要问的。
var ctx, cvs, imageData;
cvs = document.getElementById('canv');
ctx = cvs.getContext('2d');
function test() {
// imageData = ctx.getImageData( 0, 0, cvs.width, cvs.height );
// produces a line on the right side of the screen
imageData = ctx.getImageData( 1, 1, cvs.width, cvs.height );
// bizzar reverse cascading
for (var x=0;x<cvs.width-92;x++) {
for (var y=0;y<cvs.height-92;y++) {
var i = 4+(x + y*cvs.width)*4;
imageData.data[i - 4 ] = Math.floor((255-y)-Math.floor(x/55)*55);
imageData.data[i - 3 ] = Math.floor(255/(cvs.height-92)*y);
imageData.data[i - 2 ] = Math.floor(255/(cvs.width-92)*x);
imageData.data[i - 1 ] = 255;
}
}
ctx.putImageData( imageData, 0, 0 );
requestAnimationFrame( test );
}
test();
canvas {
box-shadow: 0 0 2.5px 0 black;
}
<canvas id="canv" height="256" width="256"></canvas>