javascript - esempi - Animazione della tela per sembrare rumore televisivo




canvas html5 esempi (5)

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

https://code.i-harness.com


Ho provato a fare una funzione simile qualche tempo fa. Ho impostato ogni valore casuale di pixel, e in aggiunta a quello, ho sovrapposto un'onda sinusodial che ha viaggiato verso l'alto con il tempo solo per renderlo più realistico. Puoi giocare con le costanti nell'onda per ottenere effetti diversi.

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>

L'ho usato come preloader su un sito. Ho anche aggiunto un volume rocker come barra di caricamento, ecco uno screenshot :


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


La risposta di Ken sembrava abbastanza buona, ma dopo aver visto alcuni video della vera TV statica, ho avuto alcune idee ed ecco cosa mi è venuto in mente (due versioni):

http://jsfiddle.net/2bzqs/

http://jsfiddle.net/EnQKm/

Sommario dei cambiamenti:

  • Invece di assegnare a ogni pixel un colore in modo indipendente, una serie di pixel multipli otterrà un singolo colore, in modo da ottenere queste linee orizzontali corte e di dimensioni variabili.
  • Applico una curva gamma (con Math.pow) per polarizzare leggermente il colore verso il nero.
  • Non applico la gamma in un'area "banda" per simulare il banding.

Ecco la parte principale del codice:

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

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

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.







html5-canvas