block by tophtucker 93680258138309df0753

Streaky Text IV

Full Screen

Crappy ripoff of FizzyText (seen here, source here, my attempted explanation here).

Compared to the last, this one just has particles everywhere, randomly colored, that pick up the color of the (invisible) text when they go over its “shadow”, so you can see the text by its color coherence. A color-blind person’s nightmare, I guess.

N.b. that this has a downward bias, this.y += this.v;. As opposed to just following Perlin-derived flow field in any direction.

index.html

<!doctype html>
<html>
<head>

<style>
  canvas {
    position: absolute;
    top: 0;
    left: 0;
  }
</style>

<title>StreakyText</title>

<script src='d3.min.js'></script>
<script src='improvedNoise.js'></script>
<script src='main.js'></script>

<script>
  window.onload = function() {
    var streakyText = new StreakyText('Streaky Text!');
  };
</script>

</head>

<body>

<div id='streakytext'></div>

</body>
</html>

improvedNoise.js

// http://mrl.nyu.edu/~perlin/noise/

var ImprovedNoise = function () {

    var p = [151,160,137,91,90,15,131,13,201,95,96,53,194,233,7,225,140,36,103,30,69,142,8,99,37,240,21,10,
         23,190,6,148,247,120,234,75,0,26,197,62,94,252,219,203,117,35,11,32,57,177,33,88,237,149,56,87,
         174,20,125,136,171,168,68,175,74,165,71,134,139,48,27,166,77,146,158,231,83,111,229,122,60,211,
         133,230,220,105,92,41,55,46,245,40,244,102,143,54,65,25,63,161,1,216,80,73,209,76,132,187,208,
         89,18,169,200,196,135,130,116,188,159,86,164,100,109,198,173,186,3,64,52,217,226,250,124,123,5,
         202,38,147,118,126,255,82,85,212,207,206,59,227,47,16,58,17,182,189,28,42,223,183,170,213,119,
         248,152,2,44,154,163,70,221,153,101,155,167,43,172,9,129,22,39,253,19,98,108,110,79,113,224,232,
         178,185,112,104,218,246,97,228,251,34,242,193,238,210,144,12,191,179,162,241,81,51,145,235,249,
         14,239,107,49,192,214,31,181,199,106,157,184,84,204,176,115,121,50,45,127,4,150,254,138,236,205,
         93,222,114,67,29,24,72,243,141,128,195,78,66,215,61,156,180];

    for ( var i = 0; i < 256 ; i++ ) {

        p[ 256 + i ] = p[ i ];

    }

    function fade( t ) {

        return t * t * t * ( t * ( t * 6 - 15 ) + 10 );

    }

    function lerp( t, a, b ) {

        return a + t * ( b - a );

    }

    function grad( hash, x, y, z ) {

        var h = hash & 15;
        var u = h < 8 ? x : y, v = h < 4 ? y : h == 12 || h == 14 ? x : z;
        return ( ( h & 1 ) == 0 ? u : -u ) + ( ( h & 2 ) == 0 ? v : -v );

    }

    return {

        noise: function ( x, y, z ) {

            var floorX = Math.floor( x ), floorY = Math.floor( y ), floorZ = Math.floor( z );

            var X = floorX & 255, Y = floorY & 255, Z = floorZ & 255;

            x -= floorX;
            y -= floorY;
            z -= floorZ;

            var xMinus1 = x -1, yMinus1 = y - 1, zMinus1 = z - 1;

            var u = fade( x ), v = fade( y ), w = fade( z );

            var A = p[ X ] + Y, AA = p[ A ] + Z, AB = p[ A + 1 ] + Z, B = p[ X + 1 ] + Y, BA = p[ B ] + Z, BB = p[ B + 1 ] + Z;

            return lerp( w, lerp( v, lerp( u, grad( p[ AA ], x, y, z ),
                               grad( p[ BA ], xMinus1, y, z ) ),
                          lerp( u, grad( p[ AB ], x, yMinus1, z ),
                               grad( p[ BB ], xMinus1, yMinus1, z ) ) ),
                     lerp( v, lerp( u, grad( p[ AA + 1 ], x, y, zMinus1 ),
                               grad( p[ BA + 1 ], xMinus1, y, z - 1 ) ),
                          lerp( u, grad( p[ AB + 1 ], x, yMinus1, zMinus1 ),
                               grad( p[ BB + 1 ], xMinus1, yMinus1, zMinus1 ) ) ) );

        }
    }
}

var currentRandom = Math.random;

// Pseudo-random generator
function Marsaglia(i1, i2) {
  // from http://www.math.uni-bielefeld.de/~sillke/ALGORITHMS/random/marsaglia-c
  var z=i1 || 362436069, w= i2 || 521288629;
  var nextInt = function() {
    z=(36969*(z&65535)+(z>>>16)) & 0xFFFFFFFF;
    w=(18000*(w&65535)+(w>>>16)) & 0xFFFFFFFF;
    return (((z&0xFFFF)<<16) | (w&0xFFFF)) & 0xFFFFFFFF;
  };
 
  this.nextDouble = function() {
    var i = nextInt() / 4294967296;
    return i < 0 ? 1 + i : i;
  };
  this.nextInt = nextInt;
}
Marsaglia.createRandomized = function() {
  var now = new Date();
  return new Marsaglia((now / 60000) & 0xFFFFFFFF, now & 0xFFFFFFFF);
};      

// Noise functions and helpers
function PerlinNoise(seed) {
  var rnd = seed !== undefined ? new Marsaglia(seed) : Marsaglia.createRandomized();
  var i, j;
  // http://www.noisemachine.com/talk1/17b.html
  // http://mrl.nyu.edu/~perlin/noise/
  // generate permutation
  var p = new Array(512); 
  for(i=0;i<256;++i) { p[i] = i; }
  for(i=0;i<256;++i) { var t = p[j = rnd.nextInt() & 0xFF]; p[j] = p[i]; p[i] = t; }
  // copy to avoid taking mod in p[0];
  for(i=0;i<256;++i) { p[i + 256] = p[i]; } 
  
  function grad3d(i,x,y,z) {        
    var h = i & 15; // convert into 12 gradient directions
    var u = h<8 ? x : y,                 
        v = h<4 ? y : h===12||h===14 ? x : z;
    return ((h&1) === 0 ? u : -u) + ((h&2) === 0 ? v : -v);
  }

  function grad2d(i,x,y) { 
    var v = (i & 1) === 0 ? x : y;
    return (i&2) === 0 ? -v : v;
  }
  
  function grad1d(i,x) { 
    return (i&1) === 0 ? -x : x;
  }
  
  function lerp(t,a,b) { return a + t * (b - a); }
    
  this.noise3d = function(x, y, z) {
    var X = Math.floor(x)&255, Y = Math.floor(y)&255, Z = Math.floor(z)&255;
    x -= Math.floor(x); y -= Math.floor(y); z -= Math.floor(z);
    var fx = (3-2*x)*x*x, fy = (3-2*y)*y*y, fz = (3-2*z)*z*z;
    var p0 = p[X]+Y, p00 = p[p0] + Z, p01 = p[p0 + 1] + Z, p1  = p[X + 1] + Y, p10 = p[p1] + Z, p11 = p[p1 + 1] + Z;
    return lerp(fz, 
      lerp(fy, lerp(fx, grad3d(p[p00], x, y, z), grad3d(p[p10], x-1, y, z)),
               lerp(fx, grad3d(p[p01], x, y-1, z), grad3d(p[p11], x-1, y-1,z))),
      lerp(fy, lerp(fx, grad3d(p[p00 + 1], x, y, z-1), grad3d(p[p10 + 1], x-1, y, z-1)),
               lerp(fx, grad3d(p[p01 + 1], x, y-1, z-1), grad3d(p[p11 + 1], x-1, y-1,z-1))));
  };
  
  this.noise2d = function(x, y) {
    var X = Math.floor(x)&255, Y = Math.floor(y)&255;
    x -= Math.floor(x); y -= Math.floor(y);
    var fx = (3-2*x)*x*x, fy = (3-2*y)*y*y;
    var p0 = p[X]+Y, p1  = p[X + 1] + Y;
    return lerp(fy, 
      lerp(fx, grad2d(p[p0], x, y), grad2d(p[p1], x-1, y)),
      lerp(fx, grad2d(p[p0 + 1], x, y-1), grad2d(p[p1 + 1], x-1, y-1)));
  };

  this.noise1d = function(x) {
    var X = Math.floor(x)&255;
    x -= Math.floor(x);
    var fx = (3-2*x)*x*x;
    return lerp(fx, grad1d(p[X], x), grad1d(p[X+1], x-1));
  };
}

function noiser(oct, fall) {

  //  these are lifted from Processing.js
  // processing defaults
  var noiseProfile = { 
    generator: undefined, 
    octaves: oct || 4, 
    fallout: fall || .8, 
    seed: undefined
  };

  function noise(x, y, z) {
    if(noiseProfile.generator === undefined) {
      // caching
      noiseProfile.generator = new PerlinNoise(noiseProfile.seed);
    }
    var generator = noiseProfile.generator;
    var effect = 1, k = 1, sum = 0;
    for(var i=0; i<noiseProfile.octaves; ++i) {
      effect *= noiseProfile.fallout;        
      switch (arguments.length) {
      case 1:
        sum += effect * (1 + generator.noise1d(k*x))/2; break;
      case 2:
        sum += effect * (1 + generator.noise2d(k*x, k*y))/2; break;
      case 3:
        sum += effect * (1 + generator.noise3d(k*x, k*y, k*z))/2; break;
      }
      k *= 2;
    }
    return sum;
  };

  return noise;

}

main.js

// TOTAL RIPOFF OF FIZZYTEXT
// https://github.com/dataarts/dat.gui/blob/gh-pages/docs/demo.js
// (still has a ton of vestigial elements thereof)

function StreakyText(message) {

  var that = this;

  // These are the variables that we manipulate with gui-dat.
  // Notice they're all defined with "this". That makes them public.
  // Otherwise, gui-dat can't see them.

  this.growthSpeed = 0.01;       // how fast do particles change size?
  this.minR = 0.2;
  this.maxR = 1.5;
  this.minV = .4;
  this.maxV = 2;          // how big can they get?
  this.noiseStrength = 10;      // how turbulent is the flow?
  this.speed = 0.4;             // how fast do particles move?
  this.displayOutline = false;  // should we draw the message as a stroke?
  this.framesRendered = 0;

  ////////////////////////////////////////////////////////////////

  var _this = this;

  var width = 550;
  var height = 200;
  var textAscent = 101;
  var textOffsetLeft = 20;
  var noiseScale = 300;
  var frameTime = 30;

  // var colors = ["#00aeff", "#0fa954", "#54396e", "#e61d5f"];
  var colors = ["#000000"];

  var noise1 = noiser(4,.8);
  var noise2 = noiser(4,1.2);

  // This is the context we use to get a bitmap of text using
  // the getImageData function.
  var r = document.createElement('canvas');
  var s = r.getContext('2d');

  // This is the context we actually use to draw.
  var c = document.createElement('canvas');
  var g = c.getContext('2d');

  r.setAttribute('width', width);
  c.setAttribute('width', width);
  r.setAttribute('height', height);
  c.setAttribute('height', height);

  // Add our demo to the HTML
  // document.getElementById('streakytext').appendChild(r);
  document.getElementById('streakytext').appendChild(c);

  var grd = s.createLinearGradient(0.000, 150.000, 300.000, 150.000);
  grd.addColorStop(0.000, 'rgba(10, 0, 178, 1.000)');
  grd.addColorStop(0.500, 'rgba(255, 0, 0, 1.000)');
  grd.addColorStop(1.000, 'rgba(255, 0, 255, 1.000)');

  // Stores bitmap image
  var pixels = [];

  // Stores a list of particles
  var particles = [];

  // Set g.font to the same font as the bitmap canvas, incase we
  // want to draw some outlines.
  s.font = g.font = "800 82px 'Times New Roman', times, serif";

  g.globalAlpha = 1;

  // Instantiate some particles
  for (var i = 0; i < 2000; i++) {
    particles.push(new Particle(Math.random() * width, Math.random() * height));
  }

  // This function creates a bitmap of pixels based on your message
  // It's called every time we change the message property.
  var createBitmap = function (msg) {

    s.fillStyle = "#fff";
    s.fillRect(0, 0, width, height);

    s.fillStyle = grd;
    s.fillText(msg, textOffsetLeft, textAscent);

    // Pull reference
    var imageData = s.getImageData(0, 0, width, height);
    pixels = imageData.data;

  };

  // Called once per frame, updates the animation.
  var render = function (t) {

    that.framesRendered ++;

    // g.clearRect(0, 0, width, height);

    if (_this.displayOutline) {
      g.globalCompositeOperation = "source-over";
      g.strokeStyle = "#000";
      g.lineWidth = .5;
      g.strokeText(message, textOffsetLeft, textAscent);
      g.fillText(message, textOffsetLeft, textAscent);
    }

    g.globalCompositeOperation = "darker";

    for (var i = 0; i < particles.length; i++) {
      g.fillStyle = colors[i % colors.length];
      particles[i].render(t);
    }

  };

  // Returns x, y coordinates for a given index in the pixel array.
  var getPosition = function (i) {
    return {
      x: (i - (width * 4) * Math.floor(i / (width * 4))) / 4,
      y: Math.floor(i / (width * 4))
    };
  };

  // Returns a color for a given pixel in the pixel array.
  var getColor = function (x, y) {
    var base = (Math.floor(y) * width + Math.floor(x)) * 4;
    var c = {
      r: pixels[base + 0],
      g: pixels[base + 1],
      b: pixels[base + 2],
      a: pixels[base + 3]
    };

    return "rgb(" + c.r + "," + c.g + "," + c.b + ")";
  };

  createBitmap(message);
  d3.timer(render);

  // This class is responsible for drawing and moving those little
  // colored dots.
  function Particle(x, y, c) {

    // Position
    this.x = x;
    this.y = y;

    // Size of particle
    this.r = .5;

    this.v = 0.1;

    this.baseC = "#" + Math.floor(Math.random()*0xffffff).toString(16);

    this.constrain = function (v, o1, o2) {
      if (v < o1) v = o1;
      else if (v > o2) v = o2;
      return v;
    };

    // Called every frame
    this.render = function (t) {

      // What color is the pixel we're sitting on top of?
      var c = getColor(this.x, this.y);

      // Where should we move?
      var angle1 = noise1(this.x / noiseScale, this.y / noiseScale) * _this.noiseStrength;
      var angle2 = noise2(this.x / noiseScale, this.y / noiseScale) * _this.noiseStrength;

      // var noiseInterpolate = d3.scale.linear()
      //   .domain([0,10000])
      //   .range([angle1,angle2])
      // var angle = noiseInterpolate(t);
      
      var angle = angle1;

      // Are we within the boundaries of the image?
      var onScreen = this.x > 0 && this.x < width &&
          this.y > 0 && this.y < height;

      var isBlack = c != "rgb(255,255,255)" && onScreen;

      // If we're on top of a black pixel, slow.
      // If not, speed up.
      if (isBlack) {
        // this.v -= _this.growthSpeed;
        this.r += 7 * _this.growthSpeed;
        this.baseC = c;
      } else {
        // this.v += _this.growthSpeed;
        this.r -= _this.growthSpeed;
      }

      this.r = this.constrain(this.r, _this.minR, _this.maxR);
      this.v = this.constrain(this.v, _this.minV, _this.maxV);
      
      // Gives everything a downward bias
      this.y += this.v;

      // Change our position based on the flow field and our
      // explode velocity.
      this.x += Math.cos(angle) * this.v;
      this.y += -Math.sin(angle) * this.v;

      // Once we fall off the bottom, respawn at random x-coord along top
      // if(this.y > height) {
      //   this.x = Math.random() * width;
      //   this.y = 0;
      // }

      // If we're off the screen, go over to other side
      if(this.x < 0) this.x = width;
      if(this.x > width) this.x = 0;
      if(this.y < 0) this.y = height;
      if(this.y > height) this.y = 0;

      // If we're tiny, keep moving around until we find a black
      // pixel.
      // if (this.r <= _this.minR) {
      //   this.x = Math.random() * width;
      //   this.y = Math.random() * height;
      //   return; // Don't draw!
      // }

      // Draw the streak.
      g.fillStyle = this.baseC;
      g.beginPath();
      g.arc(this.x, this.y, this.r, 0, Math.PI * 2, false);
      g.fill();

    }

  }

}