• José Lozano

Using noise to create looping GIFs on Processing

In this tutorial I'll explain how to use noise functions in Processing to create animations with random-like movements that loop well.


It will explain how to obtain the following GIF with this animation technique:




So, first we'll build our base, which will look like this:

Here's the base, I added grey squares so it's easier to understand. Basically, we go through the grid and draw a '\' on each square.


Here's the code for it:

float n = 30;
float side;

void setup() {
  size(500, 500);
  noFill();
  side = width/n;
}

void draw() {
  background(0);
  
  for(int y=0;y<n;y++) {
    for(int x=0;x<n;x++) {
      stroke(255);
      line(x*side, y*side, (x+1)*side, (y+1)*side);
    }
  }
}


The next step is to make a circle in the center, like this:

It's not a perfect circle, I know, but it's good enough for us as a base. We're basically going through our grid and checking if the line we're at is close to the center. If it's close enough it'll draw '\'; otherwise '/' will be drawn.


Here's the code for it:

float n = 30;
float side;
float radius = 150;

void setup() {
  size(500, 500);
  noFill();
  side = width/n;
}

void draw() {
  background(0);
  
  for(int y=0;y<n;y++) {
    for(int x=0;x<n;x++) {
      stroke(255);
      
      if(dist(x*side, y*side, width/2, height/2) < radius) {
        line(x*side, y*side, (x+1)*side, (y+1)*side);
      } else {
        line((x+1)*side, y*side, x*side, (y+1)*side);
      }
      
    }
  }
}


Good, now we're looking for a blob kind of shape, and we'll use noise for that. However, Processing's noise function only goes as far as 3D, but we need 4D noise to make a looping animation. openSimplex noise fixes this issue for us. To use it paste this code in another tab of your Processing sketch.


openSimplex noise is similar to Perlin noise (used in Processing) but returns values between -1 and 1 instead of 0 and 1;


This is what we're going for:

We're getting there now. But what's going on here? We're using each line's coordinates to generate a new value for the distance treshold using openSimplex noise. Why do we need 4D noise? If we only used 2D noise we'd get a static image; with 3D noise we could make a "yo-yo" GIF, but 4D gives more of a random impression that also loops well.


Here's the code so far:

OpenSimplexNoise noise = new OpenSimplexNoise();
float t;
int numFrames = 120;
boolean recording = false;

float n = 30;
float side;
float noiseRadius = 2;

void setup() {
  size(500, 500);
  noFill();
  side = width/n;
}

void draw() {
  background(0);
  t = map(frameCount-1, 0, numFrames, 0, 1);
  
  for(int y=0;y<n;y++) {
    for(int x=0;x<n;x++) {
      stroke(255);
      
      radius = 100*(float)noise.eval(x*side*0.01, y*side*0.01, noiseRadius*cos(TWO_PI*t), noiseRadius*sin(TWO_PI*t))+100;
      
      if(dist(x*side, y*side, width/2, height/2) < radius) {
        line(x*side, y*side, (x+1)*side, (y+1)*side);
      } else {
        line((x+1)*side, y*side, x*side, (y+1)*side);
      }
      
    }
  }
  
  if(recording) {
    saveFrame("###.png");
    
    if(frameCount==numFrames)
      exit();
  }
}

Here we're introducing a couple new variables: noise is the openSimplex instance we'll use to generate noise; numFrames dictates how many frames will be captured; recording tells the program whether to save the frames; noiseRadius tells openSimplex how big the radius of the circle we're picking our values from is; and t is a variable that helps us time the loop, it goes from 0 at the beginning of the animation (when frameCount = 1) to 1 at the end of it (when frameCount = numFrames). For now, recording should be disabled, we'll get to it later.


The key line here is

radius = 100*(float)noise.eval(x*side*0.01, y*side*0.01, noiseRadius*cos(TWO_PI*t), noiseRadius*sin(TWO_PI*t))+100;

openSimplex noise returns a value between -1 and 1, so we multiply that by 100 to get a bigger effect. We dont need negative values, so we also add 100 at the end. The first two parameters are the x and y coordinates of the line, and the last two just loop around the noise map in a circle to pull out the values so that the last frame matches the beginning. We multiply the x and y coordinates by 0.01 to 'reduce randomness'. The higher this is, the less continuous the values will be.



Almost there, now we just need to allow our blob to wander around the canvas a bit, so it looks like this instead of being anchored to the center:


To get this movement we have to change the following line:

if(dist(x*side, y*side, width/2, height/2) < radius) {

In this line we check if the line's coordinates are within a radius of the center, but now we don't want to have a fixed point, for this we need new coordinates for our blob, so we declare 2 new variables 'xb' and 'yb' (b for blob). To generate values for each coordinate we'll be using openSimplex noise again. You could use Perlin noise for this one as each coordinate only needs 3 dimensions, but I'll use openSimplex noise again for consistency.


Here's the code:

OpenSimplexNoise noise = new OpenSimplexNoise();
float t;
int numFrames = 360;
boolean recording = false;

float n = 30;
float side;
float radius = 150;
float noiseRadius = 2;
float xb, yb;

void setup() {
  size(500, 500);
  noFill();
  side = width/n;
}

void draw() {
  background(0);
  t = map(frameCount-1, 0, numFrames, 0, 1);
  
  xb = width/2*(float)noise.eval(0, noiseRadius*cos(TWO_PI*t), noiseRadius*sin(TWO_PI*t))+width/2;
  yb = height/2*(float)noise.eval(10000, noiseRadius*cos(TWO_PI*t), noiseRadius*sin(TWO_PI*t))+height/2;
  
  for(int y=0;y<n;y++) {
    for(int x=0;x<n;x++) {
      stroke(255);
      
      radius = 100*(float)noise.eval(x*side*0.01, y*side*0.01, noiseRadius*cos(TWO_PI*t), noiseRadius*sin(TWO_PI*t))+100;
      
      if(dist(x*side, y*side, xb, yb) < radius) {
        line(x*side, y*side, (x+1)*side, (y+1)*side);
      } else {
        line((x+1)*side, y*side, x*side, (y+1)*side);
      }
      
    }
  }
  
  if(recording) {
    saveFrame("###.png");
    
    if(frameCount==numFrames)
      exit();
  }
}

The key lines here are:


xb = width/2*(float)noise.eval(0, noiseRadius*cos(TWO_PI*t), noiseRadius*sin(TWO_PI*t))+width/2;
yb = height/2*(float)noise.eval(10000, noiseRadius*cos(TWO_PI*t), noiseRadius*sin(TWO_PI*t))+height/2;

and

if(dist(x*side, y*side, xb, yb) < radius) {

To generate the new coordinates we're using very similar instructions, the only difference being the first parameter. This would be called the "seed" of the random map we're generating. Ideally you should use different values here, otherwise you'd get the same value for x and y. The result would be a blob that only moves across a diagonal instead of wandering around.



Great! Now we have the animation, but we have to export it to a GIF somehow. When the variable "recording" is set to true, Processing will save each frame of the sketch as a .png file, so you need a tool to compile all those png's to a GIF. For this I use FFmpeg, although you could also use GIMP or any other tool you feel comfortable with.


If you decide to use FFmpeg, browse to the sketch's folder using the command line and use this command:

ffmpeg -i "%03d.png" output.gif

Et voilà! The GIF should now be in the sketch's folder :)



Share your experiments on #generativehut


My name is José Lozano and I'm a computer science student living in Germany.

You can see all my work on Instagram: @urbanoxygen_

1,106 views

©2020 by Generative Hut.