Я пытаюсь применить шумовой эффект к своему холсту на основе codepen , который я видел, что, в свою очередь, похоже на ответ SO .
Я хочу создать «экран» из случайно прозрачных пикселей, но вместо этого я получаю поле, которое полностью непрозрачно красное.Я надеюсь, что кто-то, кто более знаком с холстом или типизированными массивами, может показать мне, что я делаю неправильно, и, возможно, помочь мне понять некоторые приемы в игре.
Я рефакторингкодировать код значительно, потому что (на данный момент) меня не волнует анимация шума:
/**
* apply a "noise filter" to a rectangular region of the canvas
* @param {Canvas2DContext} ctx - the context to draw on
* @param {Number} x - the x-coordinate of the top-left corner of the region to noisify
* @param {Number} y - the y-coordinate of the top-left corner of the region to noisify
* @param {Number} width - how wide, in canvas units, the noisy region should be
* @param {Number} height - how tall, in canvas units, the noisy region should be
* @effect draws directly to the canvas
*/
function drawNoise( ctx, x, y, width, height ) {
let imageData = ctx.createImageData(width, height)
let buffer32 = new Uint32Array(imageData.data.buffer)
for (let i = 0, len = buffer32.length; i < len; i++) {
buffer32[i] = Math.random() < 0.5
? 0x00000088 // "noise" pixel
: 0x00000000 // non-noise pixel
}
ctx.putImageData(imageData, x, y)
}
Из того, что я могу сказать, суть того, что происходит, заключается в том, что мы обертываем необработанный ImageData
представление данных (серия из 8-битных элементов, которые отражают значения красного, зеленого, синего и альфа-канала для каждого пикселя, последовательно) в 32-битном массиве, что позволяет нам работать с каждым пикселем в виде единого кортежа.Мы получаем массив с одним элементом на пиксель вместо четырех элементов на пиксель.
Затем мы перебираем элементы этого массива, записывая значения RGBA для каждого элемента (т.е. каждого пикселя) на основе нашей шумовой логики.Шумовая логика здесь действительно проста: каждый пиксель с вероятностью ~ 50% может быть пикселем «шума».
Пикселям шума назначается 32-битное значение 0x00000088
, что (благодаря 32-разделение на биты, обеспечиваемое массивом) эквивалентно rgba(0, 0, 0, 0.5)
, т. е. черный цвет, непрозрачность 50%.
Пикселям без шума назначается 32-битное значение 0x00000000
, то есть непрозрачность черного цвета 0%, т.е.полностью прозрачный.
Интересно, что мы не пишем buffer32
на холст.Вместо этого мы пишем imageData
, который использовался для создания Uint32Array
, что наводит меня на мысль, что мы мутируем объект imageData через какой-то переход по ссылке;Мне не совсем понятно, почему это так.Я знаю, как передача значений и ссылок обычно работает в JS (скаляры передаются по значению, объекты передаются по ссылке), но в нетипизированном мире массивов значение, передаваемое конструктору массива, просто определяет длину массива.Это, очевидно, не то, что здесь происходит.
Как уже отмечалось, вместо поля черных пикселей, которые прозрачны на 50% или 100%, я получаю поле всех сплошных пикселей, всех красных.Мало того, что я не ожидаю увидеть красный цвет, есть нулевое свидетельство случайного назначения цвета: каждый пиксель является сплошным красным.
Играя с двумя шестнадцатеричными значениями, яобнаружил, что это приводит к рассеянию красного на черном, которое имеет правильное распределение:
buffer32[i] = Math.random() < 0.5
? 0xff0000ff // <-- I'd assume this is solid red
: 0xff000000 // <-- I'd assume this is invisible red
Но оно все еще сплошное красное, на сплошном черном.Ни одна из базовых данных холста не показывается через пиксели, которые должны быть невидимыми.
Смущает, что я не могу получить никаких цветов, кроме красного или черного.Я также не могу получить никакой прозрачности, кроме 100% непрозрачности.Просто чтобы проиллюстрировать разъединение, я удалил случайный элемент и попытался записать каждое из этих девяти значений в каждый пиксель, просто чтобы посмотреть, что происходит:
buffer32[i] = 0xRrGgBbAa
// EXPECTED // ACTUAL
buffer32[i] = 0xff0000ff // red 100% // red 100%
buffer32[i] = 0x00ff00ff // green 100% // red 100%
buffer32[i] = 0x0000ffff // blue 100% // red 100%
buffer32[i] = 0xff000088 // red 50% // blood red; could be red on black at 50%
buffer32[i] = 0x00ff0088 // green 50% // red 100%
buffer32[i] = 0x0000ff88 // blue 50% // red 100%
buffer32[i] = 0xff000000 // red 0% // black 100%
buffer32[i] = 0x00ff0000 // green 0% // red 100%
buffer32[i] = 0x0000ff00 // blue 0% // red 100%
Что происходит?
РЕДАКТИРОВАТЬ: аналогичные (плохие) результаты после отказа от Uint32Array
и жуткой мутации, основанной на статье MDN о ImageData.data
:
/**
* fails in exactly the same way
*/
function drawNoise( ctx, x, y, width, height ) {
let imageData = ctx.createImageData(width, height)
for (let i = 0, len = imageData.data.length; i < len; i += 4) {
imageData.data[i + 0] = 0
imageData.data[i + 1] = 0
imageData.data[i + 2] = 0
imageData.data[i + 3] = Math.random() < 0.5 ? 255 : 0
}
ctx.putImageData(imageData, x, y)
}