web-dev-qa-db-fra.com

Toile animée pour ressembler au bruit de la télévision

J'ai une fonction nommée generateNoise() qui crée un élément canvas et y peint des valeurs RGBA aléatoires; ce qui donne l'apparence du bruit.


Ma question

Quelle serait la meilleure façon d'animer le bruit à l'infini pour donner l'apparence du mouvement. Pour qu'il ait plus de vie?


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);
60
Matthew Harwood

Mise à jour 1/2017 : J'ai réécrit toute la réponse car elle commençait à devenir plutôt désordonnée et à résoudre certains des problèmes signalés dans les commentaires. La réponse originale peut être trouvée ici . Le nouveau a essentiellement le même code mais s'est amélioré, et avec quelques nouvelles techniques, on utilise une nouvelle fonctionnalité disponible depuis la première publication de cette réponse.


Nous créons un objet ImageData réutilisable une fois à l'extérieur de la boucle principale, donc le coût principal est seulement putImageData() et pas les deux à l'intérieur de la boucle.

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 moyen très efficace, au prix d'une certaine mémoire mais d'un coût réduit sur le CPU, est de pré-rendre un canevas hors écran plus grand avec le bruit une fois, puis de placer that canevas dans le principal en utilisant des décalages entiers aléatoires.

Cela nécessite quelques étapes de préparation supplémentaires, mais la boucle peut fonctionner entièrement sur le 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>

Notez cependant qu'avec cette dernière technique, vous risquez d'obtenir des "blocages" lorsque le nouveau décalage aléatoire est similaire au précédent. Pour contourner ce problème, définissez des critères pour la position aléatoire pour interdire les positions trop proches dans une rangée.

71
user1693593

J'ai essayé de créer une fonction similaire il y a quelque temps. J'ai défini chaque valeur aléatoire de pixel, et en plus de cela, j'ai superposé une onde sinusodiale qui a voyagé vers le haut avec du temps pour le rendre plus réaliste. Vous pouvez jouer avec les constantes de la vague pour obtenir différents effets.

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>

Je l'ai utilisé comme préchargeur sur un site. J'ai également ajouté une bascule de volume comme barre de chargement, voici un capture d'écran :

TV noise screenshot

9
John Bupit

La réponse de Ken avait l'air plutôt bonne, mais après avoir regardé quelques vidéos de la vraie télévision statique, j'ai eu quelques idées et voici ce que j'ai trouvé (deux versions):

http://jsfiddle.net/2bzqs/

http://jsfiddle.net/EnQKm/

Sommaire des modifications:

  • Au lieu d'attribuer indépendamment une couleur à chaque pixel, une série de pixels multiples obtiendra une seule couleur, vous obtenez donc ces lignes horizontales courtes de taille variable.
  • J'applique une courbe gamma (avec le Math.pow) pour biaiser un peu la couleur vers le noir.
  • Je n'applique pas le gamma dans une zone "bande" pour simuler la bande.

Voici la partie principale du code:

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

J'ai réécrit votre code afin que chaque étape soit distincte afin que vous puissiez réutiliser les choses sans avoir à créer et recréer à chaque fois, réduit les appels en boucle et, espérons-le, suffisamment clair pour pouvoir le suivre en le lisant.

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

Vous pouvez maintenant créer une boucle d'intervalle ou de temporisation qui continue de ré-invoquer la fonction générée.

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

Ou avec requestAnimationFrame comme dans la réponse de Ken

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

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

[~ # ~] démo [~ # ~]

6
Paul S.

Il se trouve que je viens d'écrire un script qui fait exactement cela, en récupérant les pixels d'une toile noire et en modifiant simplement les valeurs alpha aléatoires et en utilisant putImageData

Le résultat peut être trouvé sur 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);
        }
5
Mouseroot

Vous pouvez le faire comme ceci:

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

Le 2e arg 50 est un retard en millisecondes. En augmentant 50 le ralentira et diminuera dans le sens inverse.

cependant .. cela va sérieusement affecter les performances des pages Web. Si c'était moi, je ferais le rendu côté serveur et rendrais une poignée d'itérations de trame et de sortie sous forme de gif animé. Pas tout à fait la même chose que l'infinité aléatoire, mais ce serait une énorme amélioration des performances et la plupart des gens ne remarqueront même pas.

3
Crayon Violent

Le mien ne semble pas identique à la vraie télévision statique, mais il est néanmoins similaire. Je passe simplement en revue tous les pixels du canevas et je change les composants de couleur RVB de chaque pixel à une coordonnée aléatoire en une couleur aléatoire. La démo est disponible sur CodePen.

Le code est comme suit:

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