Анимация холста, чтобы выглядеть как телевизионный шум


у меня есть функция с именем generateNoise() который создает элемент canvas и рисует ему случайные значения RGBA; что дает видимость шума.


У Меня Вопрос

что было бы лучшим способом бесконечно оживлять шум, чтобы придать видимость движения. Чтобы у него было больше жизни?


JSFiddle

function generateNoise(opacity) {
    if(!!!document.createElement('canvas').getContext) {
        return false;
    }
    var canvas = document.createElement('canvas'),
        ctx = canvas.getContext('2d'),
        x,y,
        r,g,b,
        opacity = opacity || .2;

        canvas.width = 55;
        canvas.height = 55;

        for (x = 0; x < canvas.width; x++){
            for (y = 0; y < canvas.height; y++){
                r = Math.floor(Math.random() * 255);
                g = Math.floor(Math.random() * 255);
                b = Math.floor(Math.random() * 255);

                ctx.fillStyle = 'rgba(' + r + ',' + b + ',' + g + ',' + opacity + ')';
                ctx.fillRect(x,y,1,1);

            }
        }
        document.body.style.backgroundImage = "url(" + canvas.toDataURL("image/png") + ")";

}
generateNoise(.8);
7 58

7 ответов:

обновление 1/2017:я переписал весь ответ, поскольку он начал становиться довольно грязным, и для решения некоторых проблем, указанных в комментариях. Оригинальный ответ можно найти здесь. Новый, по сути, имеет тот же код, но улучшен, и с помощью нескольких новых методов используется новая функция, доступная с момента первого опубликования этого ответа.


Для" истинного " случайного взгляда нам нужно будет использовать рендеринг на уровне пикселей. Мы можно оптимизировать это, используя 32-но беззнаковые буферы вместо 8-битных, и мы также можем отключить альфа-канал в более поздних браузерах, который ускоряет весь процесс (для старых браузеров мы можем просто установить черный непрозрачный фон для элемента canvas).

мы создаем многоразовые ImageData объект один раз вне основного цикла, так что основная стоимость только putImageData() и не только внутри петли.

var ctx = c.getContext("2d", {alpha: false});       // context without alpha channel.
var idata = ctx.createImageData(c.width, c.height); // create image data
var buffer32 = new Uint32Array(idata.data.buffer);  // get 32-bit view

(function loop() {
  noise(ctx);
  requestAnimationFrame(loop)
})()

function noise(ctx) {
  var len = buffer32.length - 1;
  while(len--) buffer32[len] = Math.random() < 0.5 ? 0 : -1>>0;
  ctx.putImageData(idata, 0, 0);
}
/* for browsers wo/2d alpha disable support */
#c {background:#000}
<canvas id=c width=640 height=320></canvas>

очень эффективный способ, в стоимость некоторой памяти, но уменьшенная стоимость на процессоре, заключается в предварительном рендеринге большего холста за пределами экрана с помощью шум после, затем это холст в основной, используя случайные целочисленные смещения.

это требует нескольких дополнительных шагов подготовки, но цикл может работать полностью на GPU.

var w = c.width;
var h = c.height;
var ocanvas = document.createElement("canvas");     // create off-screen canvas
ocanvas.width = w<<1;                               // set offscreen canvas x2 size
ocanvas.height = h<<1;

var octx = ocanvas.getContext("2d", {alpha: false});
var idata = octx.createImageData(ocanvas.width, ocanvas.height);
var buffer32 = new Uint32Array(idata.data.buffer);  // get 32-bit view

// render noise once, to the offscreen-canvas
noise(octx);

// main loop draw the offscreen canvas to random offsets
var ctx = c.getContext("2d", {alpha: false});
(function loop() {
  var x = (w * Math.random())|0;                    // force integer values for position
  var y = (h * Math.random())|0;
  
  ctx.drawImage(ocanvas, -x, -y);                   // draw static noise (pun intended)
  requestAnimationFrame(loop)
})()

function noise(ctx) {
  var len = buffer32.length - 1;
  while(len--) buffer32[len] = Math.random() < 0.5 ? 0 : -1>>0;
  ctx.putImageData(idata, 0, 0);
}
/* for browsers wo/2d alpha disable support */
#c {background:#000}
<canvas id=c width=640 height=320></canvas>

обратите внимание, что с последней техникой вы можете рисковать получить "зависает", где новый случайное смещение аналогично предыдущему. Чтобы обойти эту проблему, установите критерии для случайной позиции, чтобы запретить слишком близкие позиции в строке.

Я пытался сделать подобную функцию некоторое время назад. Я каждый пиксель случайное значение, и в дополнение к этому, я оверлея синусодиальная волна, которая путешествовала вверх со временем, чтобы сделать его более реалистичным. Вы можете играть с константами в волне, чтобы получить различные эффекты.

var canvas = null;
var context = null;
var time = 0;
var intervalId = 0;

var makeNoise = function() {
  var imgd = context.createImageData(canvas.width, canvas.height);
  var pix = imgd.data;

  for (var i = 0, n = pix.length; i < n; i += 4) {
      var c = 7 + Math.sin(i/50000 + time/7); // A sine wave of the form sin(ax + bt)
      pix[i] = pix[i+1] = pix[i+2] = 40 * Math.random() * c; // Set a random gray
      pix[i+3] = 255; // 100% opaque
  }

  context.putImageData(imgd, 0, 0);
  time = (time + 1) % canvas.height;
}

var setup = function() {
  canvas = document.getElementById("tv");
  context = canvas.getContext("2d");
}

setup();
intervalId = setInterval(makeNoise, 50);
<canvas id="tv" width="400" height="300"></canvas>

я использовал его в качестве предзагрузчика на сайте. Я также добавил регулятор громкости в качестве загрузочной панели, вот скриншот:

TV noise screenshot

я переписал ваш код, так что каждый шаг является отдельным, так что вы можете повторно использовать вещи без необходимости создавать и воссоздавать каждый раз, уменьшенные вызовы в цикле и, надеюсь, сделал это достаточно ясно, чтобы иметь возможность следовать, читая его.

function generateNoise(opacity, h, w) {
    function makeCanvas(h, w) {
         var canvas = document.createElement('canvas');
         canvas.height = h;
         canvas.width = w;
         return canvas;
    }

    function randomise(data, opacity) { // see prev. revision for 8-bit
        var i, x;
        for (i = 0; i < data.length; ++i) {
            x = Math.floor(Math.random() * 0xffffff); // random RGB
            data[i] =  x | opacity; // set all of RGBA for pixel in one go
        }
    }

    function initialise(opacity, h, w) {
        var canvas = makeCanvas(h, w),
            context = canvas.getContext('2d'),
            image = context.createImageData(h, w),
            data = new Uint32Array(image.data.buffer);
        opacity = Math.floor(opacity * 0x255) << 24; // make bitwise OR-able
        return function () {
            randomise(data, opacity); // could be in-place for less overhead
            context.putImageData(image, 0, 0);
            // you may want to consider other ways of setting the canvas
            // as the background so you can take this out of the loop, too
            document.body.style.backgroundImage = "url(" + canvas.toDataURL("image/png") + ")";
        };
    }

    return initialise(opacity || 0.2, h || 55, w || 55);
}

теперь вы можете создать некоторый интервал или цикл таймаута, который продолжает повторно вызывать сгенерированную функцию.

window.setInterval(
    generateNoise(.8, 200, 200),
    100
);

или requestAnimationFrame а в Кена ответ

var noise = generateNoise(.8, 200, 200);

(function loop() {
    noise();
    requestAnimationFrame(loop);
})();

демо

ответ Кена выглядел довольно хорошо, но после просмотра некоторых видеороликов real TV static у меня были некоторые идеи, и вот что я придумал (две версии):

http://jsfiddle.net/2bzqs/

http://jsfiddle.net/EnQKm/

краткое описание изменений:

  • вместо того, чтобы каждый пиксель был независимо назначен цвет, запуск нескольких пикселей получит один цвет, так что вы получите эти короткие горизонтальные линии переменного размера.
  • я применяю гамма-кривую (с математикой.pow) немного смещать цвет в сторону Черного.
  • я не применяю гамму в области "полосы" для имитации полосы.

вот основная часть кода:

var w = ctx.canvas.width,
    h = ctx.canvas.height,
    idata = ctx.createImageData(w, h),
    buffer32 = new Uint32Array(idata.data.buffer),
    len = buffer32.length,
    run = 0,
    color = 0,
    m = Math.random() * 6 + 4,
    band = Math.random() * 256 * 256,
    p = 0,
    i = 0;

for (; i < len;) {
    if (run < 0) {
        run = m * Math.random();
        p = Math.pow(Math.random(), 0.4);
        if (i > band && i < band + 48 * 256) {
            p = Math.random();
        }
        color = (255 * p) << 24;
    }
    run -= 1;
    buffer32[i++] = color;
}

Я только что написал сценарий, который делает именно это, получая пиксели с черного холста и просто изменяя случайные значения альфа и используя putImageData

результат можно найти на http://mouseroot.github.io/Video/index.html

var currentAnimationFunction = staticScreen

var screenObject = document.getElementById("screen").getContext("2d");

var pixels = screenObject.getImageData(0,0,500,500);

function staticScreen()
        {
            requestAnimationFrame(currentAnimationFunction);
            //Generate static
            for(var i=0;i < pixels.data.length;i+=4)
            {
                pixels.data[i] = 255;
                pixels.data[i + 1] = 255;
                pixels.data[i + 2] = 255;
                pixels.data[i + 3] = Math.floor((254-155)*Math.random()) + 156;
            }
            screenObject.putImageData(pixels,0,0,0,0,500,500);
            //Draw 'No video input'
            screenObject.fillStyle = "black";
            screenObject.font = "30pt consolas";
            screenObject.fillText("No video input",100,250,500);
        }

вы можете сделать это так:

window.setInterval('generateNoise(.8)',50);

2-й арг 50 задержка в миллисекундах. Увеличение 50 замедлит его и уменьшит наоборот.

хотя.. это серьезно повлияет на производительность веб-страницы. Если бы это был я, я бы сделал рендеринг на стороне сервера и отрисовал несколько итераций кадров и вывел анимированный gif. Не совсем то же самое, что бесконечная случайность, но было бы огромным повышением производительности и IMO для большинства людей даже не заметит.

мой не выглядит идентичным реальной телевизионной статике, но тем не менее он похож. Я просто перебираю все пиксели на холсте и меняю цветовые компоненты RGB каждого пикселя в случайной координате на случайный цвет. демо можно найти в CodePen.

код выглядит следующим образом:

// Setting up the canvas - size, setting a background, and getting the image data(all of the pixels) of the canvas. 
canvas = document.getElementById("canvas");
ctx = canvas.getContext("2d");
canvas.width = 400;
canvas.height = 400;
canvasData = ctx.createImageData(canvas.width, canvas.height);

//Event listeners that set the canvas size to that of the window when the page loads, and each time the user resizes the window
window.addEventListener("load", windowResize);
window.addEventListener("resize", windowResize);

function windowResize(){
  canvas.style.width = window.innerWidth + 'px';
  canvas.style.height = window.innerHeight + 'px';
}

//A function that manipulates the array of pixel colour data created above using createImageData() 
function setPixel(x, y, r, g, b, a){
  var index = (x + y * canvasData.width) * 4;

  canvasData.data[index] = r;
  canvasData.data[index + 1] = g;
  canvasData.data[index + 2] = b;
  canvasData.data[index + 3] = a;
}

window.requestAnimationFrame(mainLoop);

function mainLoop(){
  // Looping through all the colour data and changing each pixel to a random colour at a random coordinate, using the setPixel function defined earlier
  for(i = 0; i < canvasData.data.length / 4; i++){
    var red = Math.floor(Math.random()*256);
    var green = Math.floor(Math.random()*256);
    var blue = Math.floor(Math.random()*256);
    var randX = Math.floor(Math.random()*canvas.width); 
    var randY = Math.floor(Math.random()*canvas.height);

    setPixel(randX, randY, red, green, blue, 255);
  }

  //Place the image data we created and manipulated onto the canvas
  ctx.putImageData(canvasData, 0, 0);

  //And then do it all again... 
  window.requestAnimationFrame(mainLoop);
}