Animating canvas to look like tv noise

20,297

Solution 1

I tried to make a similar function a while ago. I set each pixel random value, and in addition to that, I overlayed a sinusodial wave that traveled upwards with time just to make it look more realistic. You can play with the constants in the wave to get different effects.

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>

I used it as a preloader on a site. I also added a volume rocker as a loading bar, here's a screenshot:

TV noise screenshot

Solution 2

I re-wrote your code so each step is separate so you can re-use things without having to create and re-create each time, reduced in-loop calls and hopefully made it clear enough to be able to follow by reading it.

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

Now you can create some interval or timeout loop which keeps re-invoking the generated function.

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

Or with requestAnimationFrame as in Ken's answer

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

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

DEMO

Solution 3

Ken's answer looked pretty good, but after looking at some videos of real TV static, I had some ideas and here's what I came up with (two versions):

http://jsfiddle.net/2bzqs/

http://jsfiddle.net/EnQKm/

Summary of changes:

  • Instead of every pixel being independently assigned a color, a run of multiple pixels will get a single color, so you get these short, variable-sized horizontal lines.
  • I apply a gamma curve (with the Math.pow) to bias the color toward black a little.
  • I don't apply the gamma in a "band" area to simulate the banding.

Here's the main part of the 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;
}

Solution 4

I happen to have just written a script that does just this, by getting the pixels from a black canvas and just altering random alpha values and using putImageData

Result can be found at 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);
        }

Solution 5

Mine doesn't look identical to real TV static, but it's similar nonetheless. I'm just looping through all the pixels on canvas, and changing the RGB colour components of each pixel at a random coordinate to a random colour. The demo can be found over at CodePen.

The code is as follows:

// 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);
}
Share:
20,297
Armeen Moon
Author by

Armeen Moon

I am a generalist; slowly becoming a specialist in Web Development. I mix art, design, and technology, to create effective experiences that deliver value at scale. My professional goals are simple: surround myself with smart, energetic, creative people while working on solving problems that matter. Specialties: Functional and Object Oriented JavaScript ,Angular+, AngularJS, AWS, CSS/SCSS, Vector/DOM motion graphics, semantic HTML, NodeJS, Golang, and passionate about learning i18n/l10n, a11y, and modern web workflow.

Updated on February 02, 2020

Comments

  • Armeen Moon
    Armeen Moon over 4 years

    I have a function named generateNoise() which creates a canvas element and paints random RGBA values to it; which, gives the appearance of noise.


    My Question

    What would be the best way to infinitely animate the noise to give the appearance of movement. So that it may have more life?


    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);
    
  • Ilan Biala
    Ilan Biala about 10 years
    why do you have functions inside functions?
  • Mouseroot
    Mouseroot about 10 years
    @IlanBiala, closures, to expose the inner function to the outer function variables, for a really good explenation look up Douglas Crawford javascript, the good parts
  • Paul S.
    Paul S. about 10 years
    @IlanBiala I only really needed to have one (that gets returned) but I wrote many so each "step" is clear.
  • handle
    handle over 7 years
    I think that for true randomness, it would not make sense to randomize the pixel that is being written, since the value is random already.