Animierte Leinwand, um wie Fernsehgeräusche auszusehen

Lesezeit: 11 Minuten

Benutzer-Avatar
Armen Mond

Ich habe eine Funktion namens generateNoise() das ein Canvas-Element erstellt und zufällige RGBA-Werte darauf malt; was den Anschein von Rauschen erweckt.


Meine Frage

Was wäre der beste Weg, um das Rauschen unendlich zu animieren, um den Anschein von Bewegung zu erwecken. Damit es mehr Leben hat?


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

    – Buntstift Heftig

    25. Februar 2014 um 2:39 Uhr

  • Sie können Ihre reduzieren Math.random und Math.floor Anrufe, indem Sie so etwas tun x = Math.floor(Math.random() * 0xffffff); r = x & 0xff; g = (x & 0xff00) >>> 8; b = (x & 0xff0000) >>> 16;

    – Paul S.

    25. Februar 2014 um 2:45 Uhr


  • Einige Tipps: 1) !!!document.createElement('canvas').getContext zu !document.createElement('canvas').getContext 2) Richten Sie if/else ein, damit Benutzer, die keine Leinwand haben, keine Fehler erhalten. 3) Setzen Sie Leerzeichen zwischen Kommas und Text danach und Leerzeichen dazwischen (param) { für Funktionen

    – Ilan Biala

    25. Februar 2014 um 3:02 Uhr


  • @PaulS. schaffe ich das? ich bin total ein nerb? es sieht richtig aus, aber ich kann es nicht zum Laufen bringen; neue Farben: r = Math.floor(Math.random() * 155); g = Math.floor (Math.random() * 255); b = Math.floor (Math.random() * 155);

    – Armeen Mond

    25. Februar 2014 um 3:04 Uhr


  • auch alles, was ich hinzufügen musste, war requestAnimationFrame(generateNoise); und es tut, was Sie brauchen, platzieren Sie es in der ersten Zeile der Funktion. jsfiddle.net/eS9cc

    – Mauswurzel

    25. Februar 2014 um 3:08 Uhr


Aktualisierung 1/2017: Ich habe die gesamte Antwort umgeschrieben, da sie anfing, ziemlich chaotisch zu werden, und um einige der in den Kommentaren aufgezeigten Probleme anzusprechen. Die ursprüngliche Antwort finden Sie hier. Die neue Antwort hat im Wesentlichen denselben Code, wurde jedoch verbessert, und mit ein paar neuen Techniken nutzt man eine neue Funktion, die seit der ersten Veröffentlichung dieser Antwort verfügbar ist.


Für ein “echtes” zufälliges Aussehen müssten wir Rendering auf Pixelebene verwenden. Wir können dies optimieren, indem wir 32-Bit unsignierte Puffer anstelle von 8-Bit verwenden, und wir können den Alpha-Kanal in neueren Browsern auch ausschalten, was den gesamten Prozess beschleunigt (für ältere Browser können wir einfach einen schwarzen undurchsichtigen Hintergrund für die Canvas-Element).

Wir erstellen ein wiederverwendbares ImageData Objekt einmal außerhalb der Hauptschleife, so dass nur die Hauptkosten anfallen putImageData() und nicht beide innerhalb der Schleife.

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>

Ein sehr effizienter Weg, auf Kosten von etwas Speicher, aber geringeren Kosten für die CPU, ist das Vor-Rendern einer größeren Off-Screen-Leinwand mit dem Lärm einmaldann platzieren das Leinwand in die Hauptleinwand unter Verwendung von zufälligen ganzzahligen Offsets.

Es sind einige zusätzliche Vorbereitungsschritte erforderlich, aber die Schleife kann vollständig auf der GPU ausgeführt werden.

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>

Beachten Sie jedoch, dass Sie bei der letzteren Technik riskieren können, “eingefroren” zu werden, wenn der neue zufällige Offset dem vorherigen ähnlich ist. Um dieses Problem zu umgehen, legen Sie Kriterien für die zufällige Position fest, um zu nahe Positionen hintereinander nicht zuzulassen.

  • Es sollte beachtet werden, dass Sie a verwenden könnten Uint8ClampedArray also kannst du das machen pixels[i+4] *= alpha; ohne sich Gedanken über das manuelle Spannen machen zu müssen, wie in diesem html5rocks-Beitrag gezeigt html5rocks.com/en/tutorials/webgl/typed_arrays

    – Mauswurzel

    25. Februar 2014 um 3:26 Uhr


  • requestAnimationFrame ist erstaunlich und ich vergesse es immer. Dies scheint die ganze Seite zu füllen, anstatt einen Hintergrund festzulegen (aber das war nicht genug, um mich daran zu hindern +1ing).

    – Paul S.

    25. Februar 2014 um 3:27 Uhr

  • @Mouseroot ja, aber mit einem 8-Bit-Array müssen Sie 4x so viele Pixel iterieren. Es stellt sich also die Frage, ob das Klemmen des Browsers oder die 4-fache Iterationsgeschwindigkeit effizienter ist. 🙂

    Benutzer1693593

    25. Februar 2014 um 3:28 Uhr

  • Ich verstehe nicht, worauf Sie hinauswollen Leistungsoptimierung aber erschaffe die idata und buffer32 bei jedem Iterationsschritt. Es ist kein Benchmark erforderlich, um zu sehen, dass dies viel langsamer ist, als sie kurz vor der Schleife (bei Größenänderung) zwischenzuspeichern / zu erstellen. Die Pufferlänge könnte auch berechnet werden w*h 😉

    – yckart

    20. März 2016 um 17:16 Uhr

  • yckart hat recht. In Firefox (48.0.1) hängt die Geräuschanimation etwa jede Sekunde (Garbage Collection?). Läuft gut sobald alle Variablen außerhalb des Rauschens sind (). Das heißt, es verwendet immer noch > 10 % CPU (i5-6500 CPU @ 3,20 GHz) für 1680 x 954.

    – handhaben

    19. August 2016 um 17:59 Uhr

Benutzer-Avatar
John Bupit

Ich habe vor einiger Zeit versucht, eine ähnliche Funktion zu erstellen. Ich setze jeden Pixel-Zufallswert und zusätzlich dazu I überlagert eine sinusförmige Welle, die gereist ist nach oben mit der Zeit, damit es realistischer aussieht. Sie können mit den Konstanten in der Welle spielen, um verschiedene Effekte zu erzielen.

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>

Ich habe es als Preloader auf einer Website verwendet. Ich habe auch eine Lautstärkewippe als Ladebalken hinzugefügt, hier ist eine Bildschirmfoto:

Screenshot von Fernsehgeräuschen

Benutzer-Avatar
Paul S.

Ich habe Ihren Code so umgeschrieben, dass jeder Schritt separat ist, sodass Sie Dinge wiederverwenden können, ohne jedes Mal erstellen und neu erstellen zu müssen, reduzierte In-Loop-Aufrufe und habe ihn hoffentlich klar genug gemacht, um ihn durch Lesen verfolgen zu können.

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

Jetzt können Sie eine Intervall- oder Timeout-Schleife erstellen, die die generierte Funktion immer wieder neu aufruft.

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

Oder mit requestAnimationFrame wie in Kens Antwort

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

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

DEMO

  • Warum haben Sie Funktionen innerhalb von Funktionen?

    – Ilan Biala

    25. Februar 2014 um 3:36 Uhr

  • @IlanBiala, Closures, um die innere Funktion den äußeren Funktionsvariablen auszusetzen, für eine wirklich gute Erklärung schauen Sie nach Douglas Crawford javascript, the good parts

    – Mauswurzel

    25. Februar 2014 um 3:38 Uhr

  • @IlanBiala Ich brauchte wirklich nur einen (der zurückgegeben wird), aber ich habe viele geschrieben, damit jeder “Schritt” klar ist.

    – Paul S.

    25. Februar 2014 um 3:40 Uhr

Kens Antwort sah ziemlich gut aus, aber nachdem ich mir einige Videos von echtem TV-Rauschen angesehen hatte, hatte ich einige Ideen und kam auf Folgendes (zwei Versionen):

http://jsfiddle.net/2bzqs/

http://jsfiddle.net/EnQKm/

Zusammenfassung der Änderungen:

  • Anstatt jedem Pixel unabhängig eine Farbe zuzuweisen, erhält eine Reihe von mehreren Pixeln eine einzige Farbe, sodass Sie diese kurzen horizontalen Linien mit variabler Größe erhalten.
  • Ich wende eine Gammakurve (mit dem Math.pow) an, um die Farbe ein wenig in Richtung Schwarz zu verschieben.
  • Ich wende das Gamma nicht in einem “Band”-Bereich an, um die Streifenbildung zu simulieren.

Hier ist der Hauptteil des Codes:

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

Ich habe zufällig gerade ein Skript geschrieben, das genau dies tut, indem es die Pixel von einer schwarzen Leinwand holt und einfach zufällige Alpha-Werte ändert und putImageData verwendet

Ergebnis finden Sie unter 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);
        }

Meins sieht nicht identisch mit dem echten TV-Statik aus, aber es ist trotzdem ähnlich. Ich durchlaufe einfach alle Pixel auf der Leinwand und ändere die RGB-Farbkomponenten jedes Pixels an einer zufälligen Koordinate in eine zufällige Farbe. Die Demo finden Sie bei CodePen.

Der Code lautet wie folgt:

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

Benutzer-Avatar
Buntstift heftig

Sie können es so machen:

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

Der 2. Arg 50 ist eine Verzögerung in Millisekunden. Zunehmend 50 wird es verlangsamen und umgekehrt abnehmen.

Dies wird jedoch die Leistung der Webseite stark beeinträchtigen. Wenn ich es wäre, würde ich das Rendering serverseitig machen und eine Handvoll Frame-Iterationen rendern und als animiertes GIF ausgeben. Nicht ganz dasselbe wie unendliche Zufälligkeit, wäre aber ein enormer Leistungsschub und meiner Meinung nach werden die meisten Leute es nicht einmal bemerken.

1299050cookie-checkAnimierte Leinwand, um wie Fernsehgeräusche auszusehen

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy