javascript 动画画布看起来像电视噪音
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/22003491/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Animating canvas to look like tv noise
提问by Matthew Harwood
I have a function named generateNoise()
which creates a canvas element and paints random RGBA values to it; which, gives the appearance of noise.
我有一个名为的函数generateNoise()
,它创建一个画布元素并向其绘制随机 RGBA 值;其中,给人一种噪音的感觉。
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?
什么是无限地为噪音设置动画以呈现运动外观的最佳方式。这样它可能有更多的生命?
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);
回答by
Update 1/2017: I rewrote the entire answer as it started to become rather messy, and to address some of the issues pointed out in the comments. The original answer can be found here. The new answer has in essence the same code but improved, and with a couple of new techniques, one utilizes a new feature available since this answer was first posted.
更新 1/2017:我重写了整个答案,因为它开始变得相当混乱,并解决评论中指出的一些问题。原始答案可以在这里找到。新答案本质上具有相同的代码,但进行了改进,并通过一些新技术利用了自此答案首次发布以来可用的新功能。
对于“真正的”随机外观,我们需要使用像素级渲染。我们可以使用 32 位无符号缓冲区而不是 8 位来优化这一点,我们还可以在更新的浏览器中关闭 alpha 通道以加快整个过程(对于旧浏览器,我们可以简单地为画布元素)。
We create a reusable ImageData
object once outside the main loop so the main cost is only putImageData()
and not both inside the loop.
我们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}
<canvas id=c width=640 height=320></canvas>
A very efficient way, at the cost of some memory but reduced cost on the CPU, is to pre-render a larger off-screen canvas with the noise once, then place thatcanvas into the main one using random integer offsets.
一种非常有效的方法,以一些内存为代价,但降低了 CPU 的成本,是使用一次噪声预渲染更大的屏幕外画布,然后使用随机整数偏移将该画布放入主画布中。
It require a few extra preparation steps but the loop can run entirely on the GPU.
它需要一些额外的准备步骤,但循环可以完全在 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>
Do note though that with the latter technique you may risk getting "freezes" where the new random offset is similar to the previous one. To work around this problem, set criteria for the random position to disallow too close positions in a row.
但请注意,使用后一种技术,您可能会面临“冻结”的风险,其中新的随机偏移量与前一个类似。要解决此问题,请为随机头寸设置标准以禁止太近的连续头寸。
回答by John Bupit
I tried to make a similar function a while ago. I set each pixel random value, and in addition to that, I overlayeda sinusodial wave that traveled upwardswith time just to make it look more realistic. You can play with the constants in the wave to get different effects.
不久前我尝试制作一个类似的功能。我设置了每个像素的随机值,除此之外,我还叠加了一个随时间向上传播的正弦波,以使其看起来更逼真。您可以使用 wave 中的常量来获得不同的效果。
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:
我将它用作站点上的预加载器。我还添加了一个音量摇杆作为加载栏,这是一个截图:
回答by FogleBird
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):
肯的回答看起来不错,但在看了一些真实的电视静态视频后,我有了一些想法,这是我想出的(两个版本):
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.
- 不是每个像素都被独立分配一种颜色,而是多个像素的运行将获得单一颜色,因此您会得到这些短的、大小可变的水平线。
- 我应用伽马曲线(使用 Math.pow)将颜色稍微偏向黑色。
- 我不会在“带”区域应用伽马来模拟带。
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;
}
回答by Paul S.
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
或者requestAnimationFrame
在肯的回答中
var noise = generateNoise(.8, 200, 200);
(function loop() {
noise();
requestAnimationFrame(loop);
})();
回答by Mouseroot
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
我刚好编写了一个脚本来完成此操作,方法是从黑色画布中获取像素并更改随机 alpha 值并使用 putImageData
Result can be found at http://mouseroot.github.io/Video/index.html
结果可以在http://mouseroot.github.io/Video/index.html找到
var currentAnimationFunction = staticScreen
var currentAnimationFunction = staticScreen
var screenObject = document.getElementById("screen").getContext("2d");
var screenObject = document.getElementById("screen").getContext("2d");
var pixels = screenObject.getImageData(0,0,500,500);
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);
}
回答by Giorgio Malvone
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.
我的看起来与真正的电视静态不同,但它仍然相似。我只是遍历画布上的所有像素,并将随机坐标处每个像素的 RGB 颜色分量更改为随机颜色。该演示可以在 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);
}
回答by Crayon Violent
You can do it like this:
你可以这样做:
window.setInterval('generateNoise(.8)',50);
window.setInterval('generateNoise(.8)',50);
The 2nd arg 50
is a delay in milliseconds. Increasing 50
will slow it down and decreasing visa versa.
第二个参数50
是以毫秒为单位的延迟。增加50
会减慢速度,反之减少。
though.. this is going to severely affect web page performance. If it were me, I'd do the rendering server-side and render a handful of frame iterations and output as an animated gif. Not quite the same as infinite randomness, but would be a huge performance boost and IMO most people won't even notice.
虽然..这将严重影响网页性能。如果是我,我会在服务器端进行渲染并渲染一些帧迭代并输出为动画 gif。与无限随机性不太一样,但会带来巨大的性能提升,IMO 大多数人甚至不会注意到。