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

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.