javascript canvas html5 - Animazione della tela per sembrare rumore televisivo




4 Answers

Aggiornamento 1/2017 : ho riscritto l'intera risposta nel momento in cui ha iniziato a diventare piuttosto disordinata e ad affrontare alcuni dei problemi evidenziati nei commenti. La risposta originale può essere trovata here . Il nuovo ha essenzialmente lo stesso codice ma è migliorato, e con un paio di nuove tecniche, si utilizza una nuova funzione disponibile da quando questa risposta è stata pubblicata.

Per un look casuale "vero" avremmo bisogno di utilizzare il rendering a livello di pixel. Possiamo ottimizzarlo utilizzando 32, ma i buffer senza segno invece di 8-bit, e possiamo anche disattivare il canale alfa in browser più recenti che accelera l'intero processo (per i browser più vecchi possiamo semplicemente impostare uno sfondo nero opaco per il elemento di tela).

Creiamo un oggetto ImageData riutilizzabile una volta fuori dal ciclo principale, quindi il costo principale è solo putImageData() e non entrambi all'interno del ciclo.

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>

Un modo molto efficiente, a costo di una memoria, ma con un costo ridotto sulla CPU, è pre-rendering di una tela fuori schermo più grande con il rumore una volta , quindi posizionare quella tela in quella principale usando offset a caso interi.

Richiede alcuni passaggi di preparazione extra ma il loop può essere eseguito interamente sulla 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>

Prendi nota però che con quest'ultima tecnica potresti rischiare di "congelare" dove il nuovo offset casuale è simile al precedente. Per ovviare a questo problema, impostare i criteri per la posizione casuale in modo da non consentire posizioni troppo vicine in una riga.

esempi

Ho una funzione denominata generateNoise() che crea un elemento canvas e applica ad esso valori RGBA casuali; che, dà l'apparenza di rumore.

La mia domanda

Quale sarebbe il modo migliore per animare all'infinito il rumore per dare l'impressione di movimento. In modo che possa avere più vita?

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



Ho riscritto il codice in modo che ogni passaggio sia separato in modo da poter riutilizzare le cose senza dover creare e ricreare ogni volta, chiamate a ciclo ridotto e, si spera, reso abbastanza chiaro da poterlo leggere leggendolo.

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

Ora puoi creare un ciclo di intervallo o di timeout che continua a richiamare la funzione generata.

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

Oppure con requestAnimationFrame come nella risposta di Ken

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

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

DEMO




Mi è capitato di aver appena scritto uno script che faccia proprio questo, prelevando i pixel da una tela nera e semplicemente alterando i valori alfa casuali e usando putImageData

Il risultato può essere trovato su 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);
        }



Il mio non sembra identico a quello reale della TV, ma è comunque simile. Sto solo collegando tutti i pixel su tela e modificando le componenti cromatiche RGB di ciascun pixel con una coordinata casuale in un colore casuale. La demo può essere trovata su CodePen.

Il codice è il seguente:

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



Related