Анимация с массивом данных `ImageData` приводит к очень неожиданным и похожим на сбой результатам - PullRequest
0 голосов
/ 24 октября 2018

Я недавно растянул градиент по холсту, используя массив данных ImageData;то есть методы ctx.getImageData() и ctx.putImageData(), и подумал: «Это мог бы быть действительно эффективный способ оживить холст, полный движущихся объектов».Поэтому я включил его в свою основную функцию вместе с оператором requestAnimationFrame(callback), но тогда все стало странным.Лучшее, что я могу сделать при описании, это сказать, что самый левый столбец пикселей на холсте дублируется в крайнем правом столбце пикселей, и в зависимости от того, какие координаты вы указали для get и put ctxметоды, это может иметь причудливые последствия.

Я начал с методов get и put, нацеливающих холст на 0, 0 примерно так:

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

Но я сразу заметил правую сторонухолст был не прав.Как будто градиент начался заново, и последний пиксель по какой-то причине не был затронут:

enter image description here

Таким образом, при уменьшении моя область рисования изменилаput Координаты ImageData, чтобы получить некоторое пространство между нарисованным изображением и краем холста, и я изменил get, чтобы исключить эту линию на правом краю холста:

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

enter image description here

Довольно!Но не так ... Поэтому я воспроизвел его в codepen .Может ли кто-нибудь помочь мне понять и преодолеть это поведение?

Примечание. Кодовое поле имеет уменьшенную область рисования.Если вы измените координаты get на 0, вы увидите, что он в основном ведет себя так же, как в первом примере, но с пробелом между ожидаемым квадратом и неожиданной линией.Тем не менее, я оставил get на 1 и put на нуле для самого интересного поведения.

1 Ответ

0 голосов
/ 24 октября 2018

Я немного изменил ваш код.В вашем двойном цикле я объявляю переменную 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>
...