【问题标题】:Animating canvas to look like tv noise动画画布看起来像电视噪音
【发布时间】:2014-03-27 01:03:32
【问题描述】:

我有一个名为 generateNoise() 的函数,它创建一个画布元素并为其绘制随机 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);

【问题讨论】:

  • window.setInterval('generateNoise(.8)',50);
  • 你可以减少你的Math.randomMath.floor调用,比如x = Math.floor(Math.random() * 0xffffff); r = x &amp; 0xff; g = (x &amp; 0xff00) &gt;&gt;&gt; 8; b = (x &amp; 0xff0000) &gt;&gt;&gt; 16;
  • 一些提示:1) !!!document.createElement('canvas').getContext to !document.createElement('canvas').getContext 2) 设置 if/else 以便没有画布的用户不会出错 3) 在逗号和文本之间放置空格, 和(param) { 之间的空格用于函数
  • @PaulS。我做那个工作吗?我完全是个书呆子?它看起来正确,但我无法让它工作;新颜色:r = Math.floor(Math.random() * 155); g = Math.floor(Math.random() * 255); b = Math.floor(Math.random() * 155);
  • 我只需要添加requestAnimationFrame(generateNoise);,它可以满足您的需要,将其放在函数的第一行。 jsfiddle.net/eS9cc

标签: javascript html image-processing canvas html5-canvas


【解决方案1】:

更新 1/2017我重写了整个答案,因为它开始变得相当混乱,并解决了 cmets 中指出的一些问题。原始答案可以在here 找到。新答案本质上具有相同的代码,但经过改进,并且采用了一些新技术,利用了自此答案首次发布以来可用的新功能。


对于“真正的”随机外观,我们需要使用像素级渲染。我们可以使用 32 位无符号缓冲区而不是 8 位来优化这一点,我们还可以在更新的浏览器中关闭 alpha 通道以加快整个过程(对于旧浏览器,我们可以简单地为画布元素)。

我们在主循环之外创建一个可重用的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}
&lt;canvas id=c width=640 height=320&gt;&lt;/canvas&gt;

一种非常有效的方法是使用noise 一次预渲染更大的离屏画布,然后将其放置在 使用随机整数偏移将画布放入主画布。

它需要一些额外的准备步骤,但循环可以完全在 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}
&lt;canvas id=c width=640 height=320&gt;&lt;/canvas&gt;

请注意,尽管使用后一种技术,您可能会面临“冻结”的风险,因为新的随机偏移量与前一种方法相似。要解决此问题,请为随机位置设置标准,以禁止连续位置过近。

【讨论】:

  • 应该注意,您可以使用Uint8ClampedArray,这样您就可以使用pixels[i+4] *= alpha;,而不必担心手动夹紧,如html5rocks帖子html5rocks.com/en/tutorials/webgl/typed_arrays所示
  • requestAnimationFrame 太棒了,我总是忘记它。这似乎填满了整个页面而不是设置背景(但这还不足以阻止我+1ing)。
  • @Mouseroot 是的,但是对于 8 位数组,您需要迭代 4 倍的像素。所以问题变成了浏览器钳制或 4 倍迭代速度更有效.. :)
  • 我不明白这一点...您在谈论性能优化,但在每个迭代步骤中创建idatabuffer32。与在循环之前(在调整大小时)缓存/创建它们相比,不需要基准就可以看出这要慢得多。缓冲区长度也可以通过w*h 计算;)
  • yckart 是对的。在 Firefox (48.0.1) 中,噪音动画大约每秒挂起一次(垃圾收集?)。 Runs fine 一旦所有变量都在噪声()之外。也就是说,对于 1680x954,它仍然使用 > 10% 的 CPU(i5-6500 CPU @ 3.20GHz)。
【解决方案2】:

前段时间我尝试做一个类似的功能。我设置了每个像素的随机值,除此之外,我叠加一个正弦波随着时间的推移向上行进,只是为了让它看起来更真实。您可以在波浪中使用常量来获得不同的效果。

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);
&lt;canvas id="tv" width="400" height="300"&gt;&lt;/canvas&gt;

我将它用作网站上的预加载器。我还添加了一个音量摇杆作为加载栏,这里是screenshot

【讨论】:

    【解决方案3】:

    我重新编写了您的代码,因此每个步骤都是独立的,因此您可以重复使用事物,而无需每次都创建和重新创建,减少循环调用,并希望通过阅读使其足够清晰以便能够遵循它。

    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,如Ken's answer

    var noise = generateNoise(.8, 200, 200);
    
    (function loop() {
        noise();
        requestAnimationFrame(loop);
    })();
    

    DEMO

    【讨论】:

    • 为什么函数里面有函数?
    • @IlanBiala,闭包,将内部函数暴露给外部函数变量,为了获得一个非常好的解释,请查看 Douglas Crawford javascript, the good parts
    • @IlanBiala 我真的只需要一个(会被退回),但我写了很多,所以每个“步骤”都很清楚。
    【解决方案4】:

    Ken 的回答看起来不错,但是在看了一些真实的电视静态视频后,我有了一些想法,这是我想出的(两个版本):

    http://jsfiddle.net/2bzqs/

    http://jsfiddle.net/EnQKm/

    变更摘要:

    • 不是每个像素都被单独分配一种颜色,而是多个像素的运行将获得一种颜色,因此您会得到这些短的、可变大小的水平线。
    • 我应用伽马曲线(使用 Math.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;
    }
    

    【讨论】:

      【解决方案5】:

      我碰巧刚刚编写了一个脚本,通过从黑色画布获取像素并更改随机 alpha 值并使用 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);
              }
      

      【讨论】:

        【解决方案6】:

        我的看起来和真正的电视静态不一样,但还是很相似。我只是循环遍历画布上的所有像素,并将随机坐标处每个像素的 RGB 颜色分量更改为随机颜色。 The demo can be found over at 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);
        }
        

        【讨论】:

        • 我认为对于真正的随机性,随机化正在写入的像素是没有意义的,因为值已经是随机的。
        【解决方案7】:

        你可以这样做:

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

        第二个参数50 是以毫秒为单位的延迟。增加50 会减慢它的速度,反之亦然。

        虽然.. 这将严重影响网页性能。如果是我,我会在服务器端进行渲染并渲染一些帧迭代并输出为动画 gif。与无限随机性不完全相同,但会极大地提升性能,而 IMO 大多数人甚至都不会注意到。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2016-04-15
          • 2011-04-19
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2017-09-26
          • 1970-01-01
          • 2013-06-07
          相关资源
          最近更新 更多