Drawing from noise, and then making animated loopy GIFs from there.

This tutorial will focus on explaining how to draw things from noise functions in Processing, and then will present an automatic way to produce GIFs that loop well from a noise-based drawing.

It will explain how to obtain the following GIFs with the same animation technique :

agif3opt.gif

agif4.gif

Most of my latest gifs have all used this same trick I will present here, so I thought it was worth sharing it.

About noise functions

First of all let’s explain a little bit noise functions.

Processing has a function noise() that produces values between 0 and 1, centered on 0.5 given some inputs. Those values are random, but will always be the same with the same inputs on the same noise seed (fixed when the program is launched). Another property is that noise is continuous : for close inputs you get nearly the same value. Computing noise values is fast, you don’t have to compute previous values. The noise function of Processing is sometimes called Perlin noise.

Let’s show with a quite short code what noise() gives us :

void setup(){
  size(500,500);
  background(0);
  stroke(255);
  noFill();
}

void draw(){
  float scale = 0.03;

  beginShape();
  for(int x = 0; x<width;x++){
    float y = height*noise(scale*x);
    vertex(x,y);
  }
  endShape();
}

Result :
tuto.jpg

The parameter scale is used because without it the values changed too fast.

Beware that the noise function is symmetrical (noise(x) = noise(-x)).

Everytime the sketch is launched, the curve looks different.
You can have the noise functions to give always the same result by using Processing’s function noiseSeed. Also check out the function noiseDetail to change the parameters of the noise function (for example its smoothness).

Here’s a trick : To draw another independant curve at the same time one can just take the noise values far away from previously (use noise(scale*x) first, then noise(scale*x + 1000) ).

That was 1-dimensional noise. 2-dimensional noise takes 2 float values and returns a value between 0 and 1. We can represent that by brightness level on a 2D picture.

void setup(){
  size(500,500);
  background(0);
  stroke(255);
  noFill();
}

void draw(){
  float scale = 0.01;

  for(int x = 0; x<width;x++){
    for(int y = 0; y<height;y++){
      stroke(255*noise(scale*x,scale*y));
      point(x,y);
    }
  }
}

Result :
tuto2.png

One way to visualize 3D noise is to use time as third dimension…

void setup(){
  size(500,500);
  background(0);
  stroke(255);
  noFill();
  noiseDetail(5);
}

void draw(){
  background(0);
  float scale = 0.01;

  loadPixels();
  for(int x = 0; x<width;x++){
    for(int y = 0; y<height;y++){
      float col = 255*noise(scale*x,scale*y,10*scale*frameCount);
      pixels[x + width*y] = color(col);
    }
  }
  updatePixels();

  if(frameCount<=50){
    saveFrame("tuto###.png");
  }
}

Result :
tuto1.gif

We’re getting closer to making animated GIFs that loop well but this doesn’t loop well so far.

To make it loop we’ll need 4-dimensional noise. Processing’s noise is limited to 3D so I’ll introduce openSimplex noise (code by Kurt Spencer). To use it paste this code in another tab of your Processing sketch.

openSimplex noise is similar to Perlin noise but returns values between -1 and 1 (centered on 0).

openSimplex noise can be used this way (back to the first example) :

OpenSimplexNoise noise;

void setup(){
  size(500,500);
  background(0);
  stroke(255);
  noFill();

  noise = new OpenSimplexNoise();
}

void draw(){
  float scale = 0.03;

  beginShape();
  for(int x = 0; x<width;x++){
    float ns = (float)noise.eval(scale*x,0);
    float y = map(ns,-1,1,0,height);
    vertex(x,y);
  }
  endShape();
}

Result :
tuto2.jpg

I actually used 2D noise here with 0 as second input because the implementation of openSimplex noise I have only has 2D,3D and 4D noise.

Before using my trick to make loopy animations, let’s draw something more interesting/different from noise…

Let’s use a threshold on the noise to draw pixels in black or white :

OpenSimplexNoise noise;

void setup(){
  size(500,500);
  background(0);
  stroke(255);
  noFill();

  noise = new OpenSimplexNoise();
}

void draw(){
  background(0);
  float scale = 0.02;

  loadPixels();
  for(int x = 0; x<width;x++){
    for(int y = 0; y<height;y++){       
      boolean b = (float)noise.eval(scale*x,scale*y)>0;
      float col = b?255:0;
      pixels[x + width*y] = color(col);
    }
  }
  updatePixels();

  saveFrame("tuto3.jpg");
}

Result :
tuto3.jpg

How to animate noisy stuff with a nice trick

Now here’s the trick to animate this with a perfect loop, a trick that will work with anything that uses 1D or 2D noise…
Replace noise.eval(scale*x,scale*y) by noise.eval(scale*x,scale*y,radius*cos(TWO_PI*t),radius*sin(TWO_PI*t)).

We make a circle in the two new dimensions of noise space to make the animation loop perfectly. It’s hard to have a graphical interpretation with 2D noise but from 1D noise, (noise.eval(scale*x,radius*cos(TWO_PI*t),radius*sin(TWO_PI*t))) would mean to take noise values on a line for each frame, and move this line along a cylinder as time increases…

So let’s apply this to the thresholded drawing :

OpenSimplexNoise noise;

void setup(){
  size(500,500);
  background(0);
  stroke(255);
  noFill();

  noise = new OpenSimplexNoise();
}

int numFrames = 75;

float radius = 1.0;

void draw(){
  float t = 1.0*frameCount/numFrames;

  background(0);
  float scale = 0.02;

  loadPixels();
  for(int x = 0; x<width;x++){
    for(int y = 0; y<height;y++){       boolean b = (float)noise.eval(scale*x,scale*y,radius*cos(TWO_PI*t),radius*sin(TWO_PI*t))>0;
      float col = b?255:0;
      pixels[x + width*y] = color(col);
    }
  }
  updatePixels();

  println(t);

  if(frameCount<=numFrames){
    saveFrame("tuto2###.jpg");
  }
  if(frameCount == numFrames){
    println("finished");
    stop();
  }
}

Result :
tuto2.gif

radius is a parameter that will control how much things will change in the animation.

We can animate the curve example with the same technique :

OpenSimplexNoise noise;

void setup(){
  size(500,500);
  background(0);
  stroke(255);
  noFill();

  noise = new OpenSimplexNoise();
}

int numFrames = 150;

float radius = 1.5;

void draw(){
  float t = 1.0*frameCount/numFrames;

  background(0);
  float scale = 0.02;

  beginShape();
  for(int x = 0; x<width;x++){
    float ns = (float)noise.eval(scale*x,radius*cos(TWO_PI*t),radius*sin(TWO_PI*t));
    float y = map(ns,-1,1,0,height);
    vertex(x,y);
  }
  endShape();

  println(t);

  if(frameCount<=numFrames){
    saveFrame("tuto2###.jpg");
  }
  if(frameCount == numFrames){
    println("finished");
    stop();
  }
}

Result :
tuto3.gif

Let’s apply the technique to the previous noisy brightness example. Here the result will look blurred because the implementation of openSimplex noise used here doesn’t have a lot of details (but that smoothness looks nice sometimes 🙂 ).

Code :

OpenSimplexNoise noise;

void setup(){
  size(500,500);
  background(0);
  stroke(255);
  noFill();

  noise = new OpenSimplexNoise();
}

int numFrames = 75;

float radius = 1.5;

void draw(){
  float t = 1.0*frameCount/numFrames;

  background(0);
  float scale = 0.02;

  loadPixels();
  for(int x = 0; x<width;x++){
    for(int y = 0; y<height;y++){
      float ns = (float)noise.eval(scale*x,scale*y,radius*cos(TWO_PI*t),radius*sin(TWO_PI*t));
      float col = map(ns,-1,1,0,255);
      pixels[x + width*y] = color(col);
    }
  }
  updatePixels();

  println(t);

  if(frameCount<=numFrames){
    saveFrame("tuto3###.jpg");
  }
  if(frameCount == numFrames){
    println("finished");
    stop();
  }
}

Result :
tuto4.gif

Now you must be convinced that every Processing drawing that uses 1D or 2D noise can be turned into a perfectly looping animation easily. I haven’t spent time explaining where the trick comes from, but I hope it will seem natural to you after thinking about it for a while.

I’m going to explain how I obtained some more complex and interesting gifs.
I’ll start with that one :
agif5.gif

White points are randomly generated inside a circle. Each one follows an horizontal and a vertical displacement based on 2D noise. Also the intensity of the displacement becomes 0 near the border (near the circle). The animation is done just by using the same 4D noise technique. The GIF also uses motion blur from @beesandbombs.

Here is the code to generate the frames of the GIF : noisetraj.pde
Note that drawCurve just draws a circle but it was coded so that it could be easily disorted too.

Let’s move to another example…
agif2.gif
Here some points start from a grid and draw their trajectory following a noise-based field. This field changes using the 4D-noise technique.
(Code)

agif.gif

Here a threshold on 2D noise determines if it will show ‘/’ or ‘\’. Animated with 4D noise once again.
(Code)

tumblr_oxqbzm52qK1w3y4ilo1_500.gif

The lines represent a 2D field based on 2D noise. Once again animated with the same trick.
(Code)

agif4.gif

That one is a little complex to do. Basically it thresholds 1D noise curves to determine if it will draw in black or white. Parameters to animate each column are different. A big disk is drawn in the center in exclusion mode.
(Code for something similar)

tumblr_oyjk1cPdef1w3y4ilo1_500.gif

Each line uses independant noise values from 1D noise, animated with 3D noise with the trick.
(Code)

That’s the end of this tutorial, thanks for having a look, I hope you liked it !

You can use noise to draw all sort of things so I hope you’ll come up with totally different animations 🙂

PS : openSimplex noise isn’t symmetrical and there isn’t any problem when using (radius*cos(TWO_PI*t),radius*sin(TWO_PI*t)) as inputs (since it uses positive and negative values, there will be an undesired effect if the noise is symmetrical – that could be avoided by shifting the circle).

Advertisements

Animated gifs from vector fields as force fields

This tutorial is about generating this kind of gif :

agif4opt.gif

This is a variant of the previous flow field tutorial, so make sure to check it out first.

The difference with flow fields is that here the vector field will be used to accelerate the particles. It will be an acceleration field instead of speed field. Particles will keep their inertia and now trajectories can cross. To avoid particles going too fast, a coefficient is used to slow down the speed at each time step.

Here are the only changes from the flow field algorithm :

// slowdown coefficient on speed
float slowdown = 0.93;

initial speed for each path :

  // initial speed
  float vx = 0;
  float vy = 0;

new “update” method to Path :

  void update(){
    PVector res = field(x,y);
    vx += DT*res.x;
    vy += DT*res.y;
    vx *= slowdown;
    vy *= slowdown;
    x += DT*vx;
    y += DT*vy;
    positions.add(new PVector(x,y));
  }

If you decrease the “slowdown” coefficient particles will go slower and follow the field more like a speed field than acceleration field.

Here is the code to get the following example of result :
agif.gif

It uses this vector field :

float curvex(float q){
  return cos(q);
}

float curvey(float q){
  return sin(q);
}

PVector field(float x,float y){
  
  float amount = 30;
  
  float scl = 0.004;
  
  float ns = 50*noise(100+scl*x,100+scl*y);
  
  float fx = amount*curvex(ns);
  float fy = amount*curvey(ns);
  
  return new PVector(fx,fy);
}

The example in the beginning of the tutorial uses a field that uses centers, its code is here.

I hope you found this helpful and that you will come up with nice gifs. Please don’t hesitate to ask questions or suggest improvements.

Animated gifs from vector fields

This tutorial explains how to obtain this kind of gif with Processing :

agif.gif

Vector fields will give the speed of particles at any location (x,y), that’s why here they will be called “flow fields”.

THE ALGORITHM

The algorithm here computes trajectories (called paths here) and then animates particles following them.

Here is the code to generate an animation from a simple flow field, I’ll explain it later on. You can skip this part if you don’t want to know how the algorithm works and just want to change the flow field (but this might be useful to understand the parameters of the algorithm and change them).

/// This code starts with with the rendering system I took from @beesandbombs
/// (it also contains some useful functions and stuff)
/// You don't have to understand it
/// Just know that it does an average on many drawings
/// from drawings parametrized by the global variable t going from 0 to 1

int[][] result;
float t, c;

float ease(float p) {
  return 3*p*p - 2*p*p*p;
}

float ease(float p, float g) {
  if (p < 0.5) 
    return 0.5 * pow(2*p, g);
  else
    return 1 - 0.5 * pow(2*(1 - p), g);
}

float mn = .5*sqrt(3), ia = atan(sqrt(.5));

void push() {
  pushMatrix();
  pushStyle();
}

void pop() {
  popStyle();
  popMatrix();
}

void draw() {

  if (!recording) {
    t = mouseX*1.0/width;
    c = mouseY*1.0/height;
    if (mousePressed)
      println(c);
    draw_();
  } else {
    for (int i=0; i<width*height; i++)
      for (int a=0; a<3; a++)
        result[i][a] = 0;

    c = 0;
    for (int sa=0; sa<samplesPerFrame; sa++) {
      t = map(frameCount-1 + sa*shutterAngle/samplesPerFrame, 0, numFrames, 0, 1);
      draw_();
      loadPixels();
      for (int i=0; i<pixels.length; i++) {
        result[i][0] += pixels[i] >> 16 & 0xff;
        result[i][1] += pixels[i] >> 8 & 0xff;
        result[i][2] += pixels[i] & 0xff;
      }
    }

    loadPixels();
    for (int i=0; i<pixels.length; i++)
      pixels[i] = 0xff << 24 | 
        int(result[i][0]*1.0/samplesPerFrame) << 16 | 
        int(result[i][1]*1.0/samplesPerFrame) << 8 | 
        int(result[i][2]*1.0/samplesPerFrame);
    updatePixels();
    
    if(invert_colors){
      filter(INVERT);
    }

    saveFrame("frame###.png");
    println(frameCount,"/",numFrames);
    
    if (frameCount==numFrames)
      exit();
  }
}

/// END OF THE RENDERING SYSTEM
//////////////////////////////////////////////////////////////////////////////

// Number of drawings used to render each final frame with motion blur
int samplesPerFrame = 7;
// Total number of frames in the gif
int numFrames = 20;
// Kind of the time interval used for each frame in the motion blur
float shutterAngle = .8;
// If you put this to false you will control time with the mouse and no pictures will be saved
boolean recording = true;

///////////////////////////////////////////////////
/// various parameters to control the aesthetic

// This one is quite explicit
boolean use_white_rectangle = true;
// Border margin
int border = 50;
// Inverting colors or not
boolean invert_colors = false;
// Maximum point size
float maximimum_point_size = 1;

/////////////////////////////////////////////////
/// FLOW FIELD ANIMATION ALGORITHM

// Time step
float DT = 0.1;
// Number of steps
int nsteps = 500;
// Number of particles per path
int number_of_particles_per_path = 40;
// Number of paths
int NPath = 3000;
// The total number of particles will be NPath*number_of_particles_per_path

/// A class to define paths particles take
class Path{
  float x = random(width);
  float y = random(height);
  
  ArrayList<PVector> positions = new ArrayList<PVector>();
  
  // point size
  float sz = random(1,maximimum_point_size);
  
  // Nunmber of particles per path
  int npart = number_of_particles_per_path;
  
  // offset so that particles don't appear at the same time for each path
  float t_off = random(1);
  
  Path(){
    positions.add(new PVector(x,y));
  }
  
  void update(){
    PVector res = field(x,y);
    x += DT*res.x;
    y += DT*res.y;
    positions.add(new PVector(x,y));
  }
  
  void show(){
    
    strokeWeight(sz);
    
    float tt = (t+t_off)%1;
    
    int len = positions.size();
    
    for(int i=0;i<npart;i++){
      // Particle location calculated by linear interpolation from the computed positions
      float loc = constrain(map(i+tt,0,npart,0,len-1),0,len-1-0.001);;
      int i1 = floor(loc);
      int i2 = i1+1;
      float interp = loc - floor(loc);
      float xx = lerp(positions.get(i1).x,positions.get(i2).x,interp);
      float yy = lerp(positions.get(i1).y,positions.get(i2).y,interp);
      
      float fact = 1;
      if(use_white_rectangle && (xx<border||xx>width-border||yy<border||yy>height-border)) fact = 0;
      
      // This is to make the particles appear and disappear gradually
      float alpha = fact*255*pow(sin(PI*loc/(len-1)),0.25);
      
      stroke(255,alpha);
      
      point(xx,yy);
    }
  }
}

Path[] array2 = new Path[NPath];

void path_step(){
  for(int i=0;i<NPath;i++){
    array2[i].update();
  }
}

//////////////////////////////////////
/// Definition of the flow field

PVector field(float x,float y){
  return new PVector(0,15);
}

////////////////////
/// SETUP AND DRAW_


void setup(){
  /// drawing size
  size(500,500);
  
  /// Initialization of the array used to render frames
  result = new int[width*height][3];
  
  /// Initilization of Paths
  for(int i=0;i<NPath;i++){
    array2[i] = new Path();
  }
  
  /// Computation of Paths
  for(int i=0;i<nsteps;i++){
    println(i+1,"/",nsteps);
    path_step();
  }
}

void draw_(){
  background(0);

  for(int i=0;i<NPath;i++){
    array2[i].show();
  }
  
  if(use_white_rectangle){
    noFill();
    stroke(255);
    strokeWeight(1);
    rect(border,border,width-2*border,height-2*border);
  }
}

Gives :

agif2.gif

Each object of the class Path corresponds to a trajectory. Trajectories start from random locations :

  float x = random(width);
  float y = random(height);

x,y will then represent the current latest computed position later on.

An array stores the different positions of the trajectory :

  ArrayList<PVector> positions = new ArrayList<PVector>();

The paths are initilized by adding the starting position to that array :

  Path(){
    positions.add(new PVector(x,y));
  }

The update() method adds a next position to the array “positions”, using the time step “DT” and the speed field (flow field) “field”.

  void update(){
    PVector res = field(x,y);
    x += DT*res.x;
    y += DT*res.y;
    positions.add(new PVector(x,y));
  }

We will use many trajectories so an array of Paths is created :

Path[] array2 = new Path[NPath];

Here is a function to update (add a position) all the trajectories :

void path_step(){
  for(int i=0;i<NPath;i++){
    array2[i].update();
  }
}

In setup() this function is repeated “nsteps” times :

  /// Computation of Paths
  for(int i=0;i<nsteps;i++){
    println(i+1,"/",nsteps);
    path_step();
  }

By the way the Paths are initiliazed in setup() :

  /// Initilization of Paths
  for(int i=0;i<NPath;i++){
    array2[i] = new Path();
  }

Now the draw_() function needs to draw things parametrized by the global variable “t” going from 0 to 1.

Each Path has a draw() method to draw it depending on t.
The animation algorithm used here uses many particles on a same trajectory. Linear interpolation is used to use positions between computed positions of the array “positions”. For example a particle at location 2.2 in the array will be between the position 2 and the position 3 from “positions”, and closer to the position 2 (2.5 would be in the middle). This gives us an infinite number of positions from the array “positions”. We’ll make a loop to go through each particle i of the trajectory to display and show it at location map(i+t,0,number_of_particles_of_the _path,0,lengh_of_positions_array-1) in the array “position”. That way as t goes from 0 to 1 each particle will take the place of the next particle and it will loop nicely.

To avoid having particles appearing at the same time for each path, an offset is used :

  float t_off = random(1);

And gives a new time variable “tt” :

    float tt = (t+t_off)%1;

The sine curve between 0 and PI, put to the power 0.25 gives a nice curve to define the alpha channel of the particle so that particles appear and disappear gradually.

Here is the method show() to implement all of that :

  void show(){
    
    strokeWeight(sz);
    
    float tt = (t+t_off)%1;
    
    int len = positions.size();
    
    for(int i=0;i<npart;i++){
      // Particle location calculated by linear interpolation from the computed positions
      float loc = constrain(map(i+tt,0,npart,0,len-1),0,len-1-0.001);;
      int i1 = floor(loc);
      int i2 = i1+1;
      float interp = loc - floor(loc);
      float xx = lerp(positions.get(i1).x,positions.get(i2).x,interp);
      float yy = lerp(positions.get(i1).y,positions.get(i2).y,interp);
      
      float fact = 1;
      if(use_white_rectangle && (xx<border||xx>width-border||yy<border||yy>height-border)) fact = 0;
      
      // This is to make the particles appear and disappear gradually
      float alpha = fact*255*pow(sin(PI*loc/(len-1)),0.25);
      
      stroke(255,alpha);
      
      point(xx,yy);
    }
  }

show() is called in draw_() :

  for(int i=0;i<NPath;i++){
    array2[i].show();
  }

Here are the main parameters of the algorithm (it can also be interesting to change the start positions). They are defined before the class Path in the full code.

// Time step
float DT = 0.1;
// Number of steps
int nsteps = 500;
// Number of particles per path
int number_of_particles_per_path = 40;
// Number of paths
int NPath = 3000;
// The total number of particles will be NPath*number_of_particles_per_path

MOTION BLUR RENDERING

I took the motion blur rendering system from @beesandbombs. It is the first part of the code. The function draw() will use many drawings depending on t from draw_() (“samplesPerFrame” exactly), to compute a final frame averaging the colors on those drawings. This produces a nice motion blur effect on things that change fast.

FLOW FIELD DESIGN

Now it’s time to experiment with different flow fields.

The gif above uses a field that goes down vertically :

PVector field(float x,float y){
  return new PVector(0,15);
}

With Perlin noise and an horizontal bias (+20) :

PVector field(float x,float y){
  float amount = 50;
  float scale = 0.03;
  return new PVector(amount*(noise(scale*x,scale*y)-0.5)+20,amount*(noise(100+scale*x,scale*y)-0.5));
}

Result :
agif3.gif

Because particles go mostly out of view, let’s change some parameters for faster rendering :

// Number of steps
int nsteps = 100;
// Number of particles per path
int number_of_particles_per_path = 10;

let’s change the field too :

PVector field(float x,float y){
  float amount = 50;
  float scale = 0.03;
  return new PVector(amount*(noise(scale*x,scale*y)-0.5)+10,amount*(noise(100+scale*x,scale*y)-0.5)+10);
}

Result :
agif4.gif

Let’s use “Perlin noise on a circle” as field :

PVector field(float x,float y){
  float amount = 50;
  float scale = 0.01;
  float parameter = 25*noise(scale*x,scale*y);
  return new PVector(amount*cos(parameter),amount*sin(parameter));
}

Result :
agif5.gif

You can combine (add, substract, multiply…) fields to obtain new ones.

Here is a great tutorial to obtain interesting fields : drawing vector field.

Here is some code to use fields based on the effect of some centers : link.
Result :
agif.gif

I hope this was helpful and that you will come up with nice stuff. Let me know if you have questions or ideas of improvements. In case you missed anything here’s the complete code to make a flow field gif :

int[][] result;
float t, c;

float ease(float p) {
  return 3*p*p - 2*p*p*p;
}

float ease(float p, float g) {
  if (p < 0.5) 
    return 0.5 * pow(2*p, g);
  else
    return 1 - 0.5 * pow(2*(1 - p), g);
}

float mn = .5*sqrt(3), ia = atan(sqrt(.5));

void push() {
  pushMatrix();
  pushStyle();
}

void pop() {
  popStyle();
  popMatrix();
}

void draw() {

  if (!recording) {
    t = mouseX*1.0/width;
    c = mouseY*1.0/height;
    if (mousePressed)
      println(c);
    draw_();
  } else {
    for (int i=0; i<width*height; i++)
      for (int a=0; a<3; a++)
        result[i][a] = 0;

    c = 0;
    for (int sa=0; sa<samplesPerFrame; sa++) {
      t = map(frameCount-1 + sa*shutterAngle/samplesPerFrame, 0, numFrames, 0, 1);
      draw_();
      loadPixels();
      for (int i=0; i<pixels.length; i++) {
        result[i][0] += pixels[i] >> 16 & 0xff;
        result[i][1] += pixels[i] >> 8 & 0xff;
        result[i][2] += pixels[i] & 0xff;
      }
    }

    loadPixels();
    for (int i=0; i<pixels.length; i++)
      pixels[i] = 0xff << 24 | 
        int(result[i][0]*1.0/samplesPerFrame) << 16 | 
        int(result[i][1]*1.0/samplesPerFrame) << 8 | 
        int(result[i][2]*1.0/samplesPerFrame);
    updatePixels();
    
    if(invert_colors){
      filter(INVERT);
    }

    saveFrame("frame###.png");
    println(frameCount,"/",numFrames);
    
    if (frameCount==numFrames)
      exit();
  }
}

/// END OF THE RENDERING SYSTEM
//////////////////////////////////////////////////////////////////////////////

// Number of drawings used to render each final frame with motion blur
int samplesPerFrame = 7;
// Total number of frames in the gif
int numFrames = 20;
// Kind of the time interval used for each frame in the motion blur
float shutterAngle = .8;
// If you put this to false you will control time with the mouse and no pictures will be saved
boolean recording = true;

///////////////////////////////////////////////////
/// various parameters to control the aesthetic

// This one is quite explicit
boolean use_white_rectangle = true;
// Border margin
int border = 50;
// Inverting colors or not
boolean invert_colors = false;
// Maximum point size
float maximimum_point_size = 1;

/////////////////////////////////////////////////
/// FLOW FIELD ANIMATION ALGORITHM

// Time step
float DT = 0.1;
// Number of steps
int nsteps = 100;
// Number of particles per path
int number_of_particles_per_path = 10;
// Number of paths
int NPath = 3000;
// The total number of particles will be NPath*number_of_particles_per_path

/// A class to define paths particles take
class Path{
  float x = random(width);
  float y = random(height);
  
  ArrayList<PVector> positions = new ArrayList<PVector>();
  
  // point size
  float sz = random(1,maximimum_point_size);
  
  // Nunmber of particles per path
  int npart = number_of_particles_per_path;
  
  // offset so that particles don't appear at the same time for each path
  float t_off = random(1);
  
  Path(){
    positions.add(new PVector(x,y));
  }
  
  void update(){
    PVector res = field(x,y);
    x += DT*res.x;
    y += DT*res.y;
    positions.add(new PVector(x,y));
  }
  
  void show(){
    
    strokeWeight(sz);
    
    float tt = (t+t_off)%1;
    
    int len = positions.size();
    
    for(int i=0;i<npart;i++){
      // Particle location calculated by linear interpolation from the computed positions
      float loc = constrain(map(i+tt,0,npart,0,len-1),0,len-1-0.001);;
      int i1 = floor(loc);
      int i2 = i1+1;
      float interp = loc - floor(loc);
      float xx = lerp(positions.get(i1).x,positions.get(i2).x,interp);
      float yy = lerp(positions.get(i1).y,positions.get(i2).y,interp);
      
      float fact = 1;
      if(use_white_rectangle && (xx<border||xx>width-border||yy<border||yy>height-border)) fact = 0;
      
      // This is to make the particles appear and disappear gradually
      float alpha = fact*255*pow(sin(PI*loc/(len-1)),0.25);
      
      stroke(255,alpha);
      
      point(xx,yy);
    }
  }
}

Path[] array2 = new Path[NPath];

void path_step(){
  for(int i=0;i<NPath;i++){
    array2[i].update();
  }
}

//////////////////////////////////////
/// Definition of the flow field


PVector field(float x,float y){
  float amount = 50;
  float scale = 0.005;
  float parameter = 25*noise(scale*x,scale*y);
  return new PVector(amount*cos(parameter),amount*sin(parameter));
}


////////////////////
/// SETUP AND DRAW_


void setup(){
  /// drawing size
  size(500,500);
  
  /// Initialization of the array used to render frames
  result = new int[width*height][3];
  
  /// Initilization of Paths
  for(int i=0;i<NPath;i++){
    array2[i] = new Path();
  }
  
  /// Computation of Paths
  for(int i=0;i<nsteps;i++){
    println(i+1,"/",nsteps);
    path_step();
  }
}

void draw_(){
  background(0);

  for(int i=0;i<NPath;i++){
    array2[i].show();
  }
  
  if(use_white_rectangle){
    noFill();
    stroke(255);
    strokeWeight(1);
    rect(border,border,width-2*border,height-2*border);
  }
}

Animated distortion gifs from a vector field and a scalar field

This short tutorial explains how to obtain this kind of effect on images with Processing :

agif.gif

I advice to read the stripes tutorial first because this is kind of an extension to it.

This is the photograph we’ll work with :

water.jpg

First of all let’s distort an image without animation. To do that we’ll use a vector field that indicates for each pixel of the resulting image which pixel from the original image to take. We’ll use a vector field based on Perlin noise :

PVector vector_field(float x,float y){
  float amount = 100;
  float scale = 0.01;
  return new PVector(amount*(noise(scale*x,scale*y)-0.5),4*amount*(noise(100+scale*x,scale*y)-0.5));
}

The “+100” is there to have independant noise values from the first calculated ones. The “-0.5” are there because noise values are centered on 0.5.

Here is the complete code achieve the static distortion :

int numFrames = 25;

color[] result;
PImage img;

PVector vector_field(float x,float y){
  float amount = 100;
  float scale = 0.01;
  return new PVector(amount*(noise(scale*x,scale*y)-0.5),4*amount*(noise(100+scale*x,scale*y)-0.5));
}

PVector distortion(float x,float y,float time){
  PVector res = vector_field(x,y);
  return res;
}

void setup() {
  img = loadImage("water.jpg");
  size(370,254);
  
  result = new color[width*height];
}

void draw(){
  // Time variable
  float t = 1.0*(frameCount-1)%numFrames/numFrames;
  
  image(img,0,0);
  
  // Renders the pixels using the distortion field
  loadPixels();
  for (int i=0; i<width; i++) {
    for(int j=0;j<height;j++){
      PVector res = distortion(i,j,t);
      
      int ii = constrain(floor(i + res.x),0,width-1);
      int jj = constrain(floor(j + res.y),0,height-1);
      
      result[i + width*j] = pixels[ii + width*jj];
    }
  }
  for (int i=0; i<width; i++) {
    for(int j=0;j<height;j++){
      pixels[i + width*j] = result[i + width*j];
    }
  }
  updatePixels();
  
  // Saves the frame
  println(frameCount,"/",numFrames);
  saveFrame("frame###.png");
  
  // Stops when all the frames are rendered
  if(frameCount==numFrames){
    println("finished");
    stop();
  }
}

Result :

frame025.png

The position of the pixel from which we take the color is constrained to avoid having an out of bounds error :

      int ii = constrain(floor(i + res.x),0,width-1);
      int jj = constrain(floor(j + res.y),0,height-1);

An array “result” is used to avoid ovewriting on original pixels that must be used to compute other resulting pixels.

Now to animate it, we’ll have moving stripes over the image activating and desactivating the distortion, that’s why I advice to understand the stripes tutorial first.

Here is the added code :

float scalar_field(float x,float y){
  return 0.03*y;
}

PVector distortion(float x,float y,float time){
  PVector res = vector_field(x,y);
  
  float intensity = map(sin(TWO_PI*(time+scalar_field(x,y))),-1,1,0,1);
  
  res.mult(intensity);
  
  return res;
}

It should be easy to understand if you understood the stripes tutorial, because here the white stripes correspond to the use of the distortion and black stripes to no distortion.

Here is the result :

agif2.gif

Now we can try other vector and scalar fields :

PVector vector_field(float x,float y){
  float amount = 50;
  float scale = 0.01;
  return new PVector(amount*(noise(scale*x,scale*y)-0.5),amount*(noise(100+scale*x,scale*y)-0.5));
}

float scalar_field(float x,float y){
  return 0.05*dist(x,y,width/2,height/2);
}

Result :

agif3.gif

By reducing the intensity of the distortion away from the center :

PVector vector_field(float x,float y){
  float amount = 150;
  float scale = 0.01;
  float intensity = constrain(map(dist(x,y,width/2,height/2),0,0.75*height,1,0),0,1);
  return new PVector(amount*(noise(scale*x,scale*y)-0.5)*intensity,amount*(noise(100+scale*x,scale*y)-0.5)*intensity);
}

float scalar_field(float x,float y){
  return -0.05*dist(x,y,width/2,height/2);
}

Result :

agif4.gif

That is all, I hope things were quite clear and that this tutorial will inspire you to make something (maybe much better and interesting) yourself.

Here is the complete code to generate the last GIF in case you missed anything…

int numFrames = 25;

color[] result;
PImage img;

PVector vector_field(float x,float y){
  float amount = 150;
  float scale = 0.01;
  float intensity = constrain(map(dist(x,y,width/2,height/2),0,0.75*height,1,0),0,1);
  return new PVector(amount*(noise(scale*x,scale*y)-0.5)*intensity,amount*(noise(100+scale*x,scale*y)-0.5)*intensity);
}

float scalar_field(float x,float y){
  return -0.05*dist(x,y,width/2,height/2);
}

PVector distortion(float x,float y,float time){
  PVector res = vector_field(x,y);
  
  float intensity = map(sin(TWO_PI*(time+scalar_field(x,y))),-1,1,0,1);
  
  res.mult(intensity);
  
  return res;
}

void setup() {
  img = loadImage("water.jpg");
  size(370,254);
  
  result = new color[width*height];
}

void draw(){
  // Time variable
  float t = 1.0*(frameCount-1)%numFrames/numFrames;
  
  image(img,0,0);
  
  // Renders the pixels using the distortion field
  loadPixels();
  for (int i=0; i<width; i++) {
    for(int j=0;j<height;j++){
      PVector res = distortion(i,j,t);
      
      int ii = constrain(floor(i + res.x),0,width-1);
      int jj = constrain(floor(j + res.y),0,height-1);
      
      result[i + width*j] = pixels[ii + width*jj];
    }
  }
  for (int i=0; i<width; i++) {
    for(int j=0;j<height;j++){
      pixels[i + width*j] = result[i + width*j];
    }
  }
  updatePixels();
  
  // Saves the frame
  println(frameCount,"/",numFrames);
  saveFrame("frame###.png");
  
  // Stops when all the frames are rendered
  if(frameCount==numFrames){
    println("finished");
    stop();
  }
}

Animated stripes gifs from scalar fields

This tutorial explains how to obtain this kind of GIF with Processing :

agif3.gif

We’ll start from this code that generates the following simple animation ;

int margin = 50;
int numFrames = 20;

void setup(){
  size(600,600);

}

float pixel_color(float x,float y,float t){
  float result = map(sin(TWO_PI*t),-1,1,0,1);
  return 255*result;
}

void draw(){
  background(0);

  float t = 1.0*(frameCount-1)%numFrames/numFrames;

  // Draws every pixel
  for(int i=margin;i<width-margin;i++){
    for(int j=margin;j<height-margin;j++){
      stroke(pixel_color(i,j,t));
      point(i,j);
    }
  }

  // Draws a white rectangle
  stroke(255);
  noFill();
  rect(margin,margin,width-2*margin,height-2*margin);

  // Saves the frame
  println(frameCount,"/",numFrames);
  saveFrame("frame###.png");

  // Stops when all the frames are rendered
  if(frameCount == numFrames){
    println("finished");
    stop();
  }
}

agif.gif

That code makes each pixel in a white rectangle oscillate between black and white over time. The rest will be only little variations from there so make sure you understand the basis.

Now let’s have moving stripes !
So let’s change the pixel_color function so that the color of pixels doesn’t oscillate at the same time vertically.

float pixel_color(float x,float y,float t){
  float result = map(sin(TWO_PI*(t+0.05*y)),-1,1,0,1);
  return 255*result;
}

agif2.gif

Now let’s make those stripes more contrasted. To achieve that we’ll use an easing function that makes values between 0 and 0.5 closer to 0 and values between 0.5 and 1 closer to 1, with a parameter g to increase the effect.

float ease(float p, float g) {
  if (p < 0.5)
    return 0.5 * pow(2*p, g);
  else
    return 1 - 0.5 * pow(2*(1 - p), g);
}

Let’s use it on our oscillating color :

float pixel_color(float x,float y,float t){
  float result = ease(map(sin(TWO_PI*(t+0.05*y)),-1,1,0,1),3.0);
  return 255*result;
}

This is the result :

agif3.gif

Now let’s make a general function that controls the offset in the oscilation :
(a scalar field is just a function that gives a real value for each position of the plane in our case)

float scalar_field_offset(float x,float y){
  return 0.05*x+0.05*y;
}

float pixel_color(float x,float y,float t){
  float result = ease(map(sin(TWO_PI*(t+scalar_field_offset(x,y))),-1,1,0,1),3.0);
  return 255*result;
}

Result (there is now horzontal change due to 0.05*x) :

agif4.gif

Now to obtain different results we just have to change the scalar field. Let’s use the distance to the center.

float scalar_field_offset(float x,float y){
  float distance = dist(x,y,width/2,height/2);
  return 0.05*distance;
}

agif5.gif

Perlin noise (a very nice function given by Processing, I won’t make a tutorial about it here) :

float scalar_field_offset(float x,float y){
  float scale = 0.003;
  float result = 40*noise(scale*x,scale*y);
  return result;
}

agif6.gif

Stripes and perlin noise gradually vertically :

float scalar_field_offset(float x,float y){ 
  float perlin_noise_intensity = pow(constrain(map(y,0.1*height,height,0,1),0,1),2);
  
  float scale = 0.006;
  float result = -0.05*y + 20*(noise(scale*x,scale*y)-0.5)*perlin_noise_intensity;
  return result;
}

agif7.gif :

Perlin noise in the center :

float scalar_field_offset(float x,float y){
  float distance = dist(x,y,width/2,height/2);
  float perlin_noise_intensity = ease(constrain(map(distance,0,0.3*height,1,0),0,1),2);
  
  float scale = 0.002;
  float result = -0.05*y -0.05*x + 60*(noise(scale*x,scale*y)-0.5)*perlin_noise_intensity;
  return result;
}

agif8.gif

Using distance differently :

float scalar_field_offset(float x,float y){
  float distance = dist(x,y,0.5*width,0.5*height);
  
  float result = 300/(25+distance);
  return result;
}

agif9.gif

By adding the effect of many centers you can get something like this :

tumblr_ouscz6fS6X1w3y4ilo1_500.gif

And with many centers and using an effect only near them :

agif.gif

Here is a complete code to generate one in case you missed anything :

int margin = 50;
int numFrames = 20;

void setup(){
  size(600,600);
  
}

float ease(float p, float g) {
  if (p < 0.5) 
    return 0.5 * pow(2*p, g);
  else
    return 1 - 0.5 * pow(2*(1 - p), g);
}

float scalar_field_offset(float x,float y){
  float distance = dist(x,y,0.5*width,0.5*height);
  
  float result = 300/(25+distance);
  return result;
}

float pixel_color(float x,float y,float t){
  float result = ease(map(sin(TWO_PI*(t+scalar_field_offset(x,y))),-1,1,0,1),3.0);
  return 255*result;
}

void draw(){
  background(0);
  
  float t = 1.0*(frameCount-1)%numFrames/numFrames;
  
  // Draws every pixel
  for(int i=margin;i<width-margin;i++){
    for(int j=margin;j<height-margin;j++){
      stroke(pixel_color(i,j,t));
      point(i,j);
    }
  }
  
  // Draws a white rectangle
  stroke(255);
  noFill();
  rect(margin,margin,width-2*margin,height-2*margin);
  
  // Saves the frame
  println(frameCount,"/",numFrames);
  saveFrame("frame###.png");
  
  // Stops when all the frames are rendered
  if(frameCount == numFrames){
    println("finished");
    stop();
  }
}

I hope this was helpful and that you will be creative and come up with better stuff than me !

Bonus : if you know glsl, it would be a better idea to code this in glsl to have much faster rendering.