@raphaelameaume on Twitter was wondering about how it was made, and when looking at the code again I thought it would be interesting (although this gif is not really complex) to explain every aspect and general principles that could be used to make very different looking perfectly looping gifs. The code to make this gif is here in case you’re already wondering, but I’ll build it gradually in this tutorial.

I recommend reading two of my previous tutorials first :

This one about the motion blur/recording gif template from beesandbombs that I use.

That one about gif loops based on noise.

(includes probably many typos/mistakes that I may fix later)

This section is about the theoretical background behind the looping curves visible in the gif.

I’ll be using the Processing template from this tutorial (template by beesandbombs) and won’t show the code above the “//////////////” part (mostly about how to render frmaes with motion blur).

Let’s build some looping moving distortion gradually. A first step can be to make a periodic random function like this. (It doesn’t move so far)

Code :

int samplesPerFrame = 5; int numFrames = 100; float shutterAngle = .6; boolean recording = true; OpenSimplexNoise noise; float SEED; void setup(){ size(600,600,P2D); result = new int[width*height][3]; noise = new OpenSimplexNoise(); SEED = random(10,1000); } int m = 1500; float rad = 0.5; float nperiod = 4.0; void draw_(){ background(0); noFill(); stroke(255); strokeWeight(3); beginShape(); for(int i=0;i<m;i++){ float p = 1.0*i/m; float x = p*width; float y = map((float)noise.eval(SEED + rad*cos(TWO_PI*(nperiod*p)),rad*sin(TWO_PI*(nperiod*p))),-1,1,0,height); vertex(x,y); } endShape(); }

Let’s explain it. The idea is to take noise values in a circle doing nperiod turns of the circle to get the height values of the curve. The parameter “rad” controls the radius of the circle so how much things change within the period.

It’s not necessary for nperiod to be an integer except if you want the value at the end of the curve to match the first one (could be useful if you want to make a circle shaped thing for example).

SEED is a variable used to get different noise values by taking it at a random location. This technique important when you want different independant noise values.

Reminder : t goes from 0 to 1

So the key line is really

float y = map((float)noise.eval(SEED + rad*cos(TWO_PI*(nperiod*p)),rad*sin(TWO_PI*(nperiod*p))),-1,1,0,height);

Now let’s replace it by

float y = map((float)noise.eval(SEED + rad*cos(TWO_PI*(nperiod*p-t)),rad*sin(TWO_PI*(nperiod*p-t))),-1,1,0,height);

That’s like rotating the circle from which you take your values with an angle TWO_PI*t.

(Update : I added a last section of this tutorial that contains another explanation of this section, and it is probably much better to understand what’s going on and do more complex stuff).

Result :

If you use “+t” instead of “-t” it will move in the other direction.

Now let’s explain a trick that’s not used in the heart gif but that could be very useful. The idea is to keep this moving structure but make the values change with the change of x (or p in the code). To do this we can use 3D noise instead of 2D with the 3rd dimension used to give the change of x.

float y = map((float)noise.eval(SEED + rad*cos(TWO_PI*(nperiod*p-t)),rad*sin(TWO_PI*(nperiod*p-t)),1.0*p),-1,1,0,height);

Result :

We can increase the change with x :

float y = map((float)noise.eval(SEED + rad*cos(TWO_PI*(nperiod*p-t)),rad*sin(TWO_PI*(nperiod*p-t)),4.0*p),-1,1,0,height);

Result :

We now have looping moving stuff ! If you want the end (right side) to smoothly have the same value as the beginning (left side) you could use 4D noise instead, with another circle.

Let’s use the previous looping values to get horizontal and vertical displacement values on a simple curve (straight line at mid-height).

Code :

int m = 2000; float rad = 1.5; float nperiod = 4.0; void draw_(){ background(0); stroke(255,75); for(int i=0;i<m;i++){ float p = 1.0*i/m; float dx = 25*(float)noise.eval(SEED + rad*cos(TWO_PI*(nperiod*p-t)),rad*sin(TWO_PI*(nperiod*p-t)),4.0*p); float dy = 25*(float)noise.eval(2*SEED + rad*cos(TWO_PI*(nperiod*p-t)),rad*sin(TWO_PI*(nperiod*p-t)),4.0*p); point(p*width + dx,height/2 + dy); } }

Result :

What’s new? Dot by dot rendering style, with little opacity, I find that that way dots concentrate at some places and it creates more refined/smoke-like appearance compared to beginShape/endShape. I increased the radius of the circle and the number of dots m.

There are now two noise values gathered for each dot : one for the horizontal displacement dx and one for the vertical one dy. In order to get different noise values I use that technique that I use all the time : since the noise seed SEED is bigger than 10 (random(10,1000)), when taking 2*SEED it’ll always take noise values that are quite independant (somewhere else). Well… if not enough, you can just increase 10 in random(10,1000) :).

You can be more creative and have a non linear influence : instead of “4.0*p” use stuff like “pow(p,2.0)” More generally cos(theta1 – TWO_PI*t) and sin(theta2 – TWO_PI*t) will always produce stuff that loops well if theta1 and theta2 don’t depend on t. A clearer explanation is now at the end of the tutorial. A gif that uses the previous technique in a more sophisticated way is the following :

The code for the above gif is quite horrible but it’s here in case you’re curious.

But the heart gif just uses the moving periodic function even without the 3D noise variation.

The shape of the heart gif is based on a parametric curve that I found by googling it.

float R = 150; float xh(float p){ return R/15.0*16*pow(sin(p),3); } float yh(float p){ return R/15.0*(-13*cos(p) +5*cos(2*p) + 2*cos(3*p) + cos(4*p)); }

The “h” come from “heart”

(I actually kind of normalized the equations quickly (division by 15) and multiplied them by R so that R kind of represents the “radius” of the shape in pixels.)

Those functions are TWO_PI periodic. We will now create random parts of the heart and apply the previous displacement on each part.

Let’s now define a class (that I call “Thing”, which is a bad practice ) of objects that will represent some parts of the heart.

Those parts have a bunch of random parameters : (Sorry for bad ideas of attributes names… I just want to keep the same notations as the initial code)

– random noise seed “seed”

– random location in the parametric equation “offset”

– random alpha factor and strokeweight, for varied appearance “ff” and “sw”

– random length of the part of the parametric curve “part”

– random scale “rf”

– random intensity of displacement “d”.

There could be more random parameters : here the parameters of the previous section will be always 2 for nperiod and 1.3 for the radius of the circle.

So we’ve got this class before defining the show method :

class Thing{ float seed = random(10,1000); float offset = random(TWO_PI); float ff = random(0.5,2.5); float sw = random(0.8,1.8); float part = 0.1+0.5*pow(random(1),2.0); float rf = random(0.5,1.15); float d = random(10,120); void show(){ } }

For the “part” attribute I didn’t use a uniform random distribution to get more short parts than long parts.

So let’s define the show method.

I chose to draw the curves with m dots :

int m = 500;

Here is a simplified show() method before the last one :

void show(){ for(int i=0;i<m;i++){ float p = 1.0*i/m; float theta = offset + part*TWO_PI*i/m; float rad = 1.3; int per = 2; float x = rf*xh(theta) + d*(float)noise.eval(seed + rad*cos(TWO_PI*(per*p-t)),rad*sin(TWO_PI*(per*p-t))); float y = rf*yh(theta) + d*(float)noise.eval(2*seed + rad*cos(TWO_PI*(per*p-t)),rad*sin(TWO_PI*(per*p-t))); strokeWeight(sw); stroke(255,ff*18); point(x,y); } }

The variable "p" is simply mapping i between 0 and 1, from there we get the parameter of the parametric equation "theta". The four following lines are simply looping distortion/displacement with the method of the previous section… adding it to the position obtained with the parametric equation.

Then we draw our dot. (One can notice that the displacement doesn't rotate with the parametric curve : that could be fixed using the normalized tangent and normal to the parametric curve.)

The rest of the code creates an array of 250 objects and shows them :

Here is the code, for some reason I can’t show it on wordpress.

You can get the global code so far to generate a gif here.

Result :

That looks quite different from the GIF we’re looking for, mostly because the displacement is huge. The heart gif we’re looking for just has two differences : the intensity of the displacement is multiplied py pow(p,3.0) (mostly very small, p is betweeen 0 and 1), and stroke alpha has a factor sin(PI*p) so that it appears and disappears gradually.

New code in show() :

float x = rf*xh(theta) + pow(p,3.0)*d*(float)noise.eval(seed + rad*cos(TWO_PI*(per*p-t)),rad*sin(TWO_PI*(per*p-t))); float y = rf*yh(theta) + pow(p,3.0)*d*(float)noise.eval(2*seed + rad*cos(TWO_PI*(per*p-t)),rad*sin(TWO_PI*(per*p-t))); strokeWeight(sw); stroke(255,ff*18*sin(PI*p)); point(x,y);

Entire final code : link

Result :

You could now try other parametric curves !

That gif was made with the previous moving function trick (3D noise version), adding displacement from a spiral (with many curves drawn) :

Code here (not very readable it’s in case you’re curious about it)

I hope you found this helpful of interesting, don’t hesitate to send me your creations if this inspired you to make something. (I can share them here later)

I wasn’t satisfied of my previous explanations in the first section and found a way to deconstruct things to see things more clearly, which will help to make complex stuff more easily (at least it helped me see things more clearly). This section is just about propagating a periodic function.

Thanks to the circle in noise space we can define a 1-periodic function that way :

float radius = 1.0; // 1-periodic function from a circle in noise float F(float q){ return (float)noise.eval(SEED + radius*cos(TWO_PI*q),radius*sin(TWO_PI*q)); }

The code to draw the curve of the first section becomes

beginShape(); for(int i=0;i<m;i++){ float p = 1.0*i/m; float x = p*width; float y = map(F(offset(p)-t),-1,1,0,height); vertex(x,y); } endShape();

The important thing here is the value "F(offset(p)-t)"

What's "offset(p)"? it is the delay at p : if t (going from 0 to 1) is in second, it will take offset(p) seconds for F(0) to reach p.

Here I used :

//offset or delay depending on a parameter

float offset(float p){

return 3.0*p;

}

Result :

Link to full code to generate it

Let’s try this offset :

//offset or delay depending on a parameter float offset(float p){ return 5.0*pow(p,3.0); }

Result :

This can be interpreted easily as the delay being very short at the beginning and then increasing a lot.

We can change the periodic function with the 3D noise technique from the first section of the tutorial (it’s not periodic anymore) :

float F(float q,float p){ return (float)noise.eval(SEED + radius*cos(TWO_PI*q),radius*sin(TWO_PI*q),2.0*p); }

float y = map(F(offset(p)-t,p),-1,1,0,height);

Result :

This is related to the previous tutorial about gifs with stripes

Those curves are ugly but that is a general trick to get moving stuff/values that loop perfectly.

I hope things are clearer now… I may continue this tutorial to explain how to use this to make 2D/3D stuff (for example to get a moving looping surface height).

Now maybe you can have an idea of how this gif loops !

]]>This tutorial is mainly aimed at people who have never made any perfectly looping GIF with Processing, but already know how to draw something with Processing. However there may be interesting stuff for people who are not beginners, so skip parts when it is too basic for you.

If you don’t already know how to draw things with Processing, check out The Coding Train channel on Youtube by Daniel Shiffman. If you already know how to program in one language, the Coding Challenges will be especially interesting to quickly learn how to use Processing to draw some cool stuff (that’s how I started). If not there are also videos that start from zero, you don’t need to have programmed before. I think it starts with this video (I have never watched those because I had already programmed a lot before).

This first part will show how to make a GIF of a white dot looping in a circle over a black background.

Here is the code, I will comment it later.

int numFrames = 50; void setup(){ size(500,500); } void draw(){ background(0); float t = 1.0*(frameCount-1)/numFrames; stroke(255); strokeWeight(10); point(250+50*cos(TWO_PI*t),250+50*sin(TWO_PI*t)); if(frameCount<=numFrames){ saveFrame("fr###.png"); } }

This code generates the frames of the GIF when you launch it, and you can see the animation at the same time.

The variable numFrames obviously contains the number of frames of the GIF.

The variable t represents time. The idea is that it goes from 0 (at the beginning of the animation), to 1 at the end. This is a convention that I took from the beginning and I observed that beesandbombs (who is a famous Processing gif maker) also uses it, so it is probably a good idea. “frameCount” is a Processing variable that starts at 1 the first time “draw” is called and that is incremented each time “draw” is called. Here t won’t reach 1 when frameCount will be equal to numFrames (last frame), but that’s to avoid the repetition with the first frame : we will draw the same thing with t=0 and t=1, and to have a smooth perfect loop, we need to save the t=0 or t=1 frame only one time. Here t will be greater than 1 after the frames are saved, if you ever want it to go back to 0 you can use “(1.0*(frameCount-1)/numFrames)%1” instead.

“point(250+50*cos(TWO_PI*t),250+50*sin(TWO_PI*t));” draws a point using the parametric equation of a circle of radius 50 pixels centered on the pixel of location (250,250). cos and sin are two pi periodic, so we have correctly the same position for t=0 and t=1 to have a perfect loop.

“saveFrame(“fr###.png”);” automatically saves a frame in the folder of the sketch, with a number equal to the frame count.

Now that you have your frames saved, there are lots of way to create your final GIF. For example you can use GIMP, Photoshop or Gifsicle (update : Gifsicle seems much much faster than GIMP). I use GIMP, it’s probably not the best way to do it but it works well for me. You just have to import the frames as layers, and then export as .gif. Then you have to say that you want your GIF to be an animation, and choose the delay between frames. I often choose 35 ms. Here is the result of the previous code with 20 ms delay between frames :

You can already make nice things with the previous technique, but now I most often use the technique from the code template of beesandbombs (that he shares publicly). I talked with him about it, and he has no problem if I use it a lot, and now he even told me that I could make a tutorial about it. His code is about having two types of execution when you launch yur sketch. Either you change the time variable with your mouse, or you render your frames with motion blur. I think that motion blur can greatly increase the beauty of GIFs and that’s why I keep using it. His code also contains little functions and constants that can be useful.

What does motion blur mean algorithmically? It’s just an average on many drawings that are close in time.

Here is the code to render the previous white dot in beesandbombs style with motion blur :

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 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(); saveFrame("fr###.png"); println(frameCount,"/",numFrames); if (frameCount==numFrames) exit(); } } ////////////////////////////////////////////////////////////////////////////// int samplesPerFrame = 5; int numFrames = 50; float shutterAngle = 1.5; boolean recording = true; void setup(){ size(500,500); result = new int[width*height][3]; } void draw_(){ background(0); stroke(255); strokeWeight(10); point(250+50*cos(TWO_PI*t),250+50*sin(TWO_PI*t)); }

IMPORTANT UPDATE : wordpress can't display the code well, probably because of the weird symbols, here is a code that works : templateexample.pde

This can look scary but what it does is actually not complicated, I will explain it, but first let’s have a look at our new result :

Now let’s explain the code.

First of all you don’t need to understand the code above the “//////” to use it, just understand what it does.

The function “draw_” has to be used like you would normally use “draw”, and you have to draw something depending on t which is the time variable. t goes from 0 to 1 like previously and is set by the code above the “//////”.

boolean recording = true;

This variable is very important : if you set it to true, you will record the gif frames with motion blur. If you set it to false, you will control time with your mouse (0 on one side of the canvas, 1 on the other side), and there won’t be motion blur : it’s for testing your sketch without rendering the frames.

Now come the other important variables :

int samplesPerFrame = 5; int numFrames = 50; float shutterAngle = 1.5;

“numFrames” is the number of frames of the gif, like previously.

“samplesPerFrame” is the number of drawings that are used to produce a single final frame that will be an average of them for motion blur. Increasing it increases the quality of motion blur but costs more rendring time.

“shutterAngle” controls how spaced in time the samples of each frames are. If shutterAngle is equal to 1.0, the last sample of a frame will be just before the first sample of the next frame. If it is greater than 1.0, the samples of each frame will overlap in time with the samples of the neighboring frames. If it is smaller than 1.0 it will be more separated… If you want an explanation with code it is contained in this line (sa is the index of sample) :

t = map(frameCount-1 + sa*shutterAngle/samplesPerFrame, 0, numFrames, 0, 1);

I took shutterAngle equal to 1.5 to exagerrate motion blur, but usually beesandbombs and me use values smaller than 1.0.

The array “result” contains the sums of the samples and is used to compute the motion blur average.

Take care that when “draw_()” is called once again, the coordinate changes that you may have done with something like “translate” are not reset because it’s not the real “draw()”. You have to use “push()” and “pop()” around your coordinate changes.

I now want to prove my point that motion blur can change a lot the effect of a gif. Here is one I made, without and with motion blur. Notice how shades of grey appeared with motion blur.

I’ll come back to the important “ease” function from this template later in this tutorial : I want to talk about less technical stuff first.

The previous gif was very boring, right? Let’s try do to something more interesting from there without so much code, by using a technique that made me produce lots of gifs at the beginning : using an array of objects. So let’s have many dots moving at random positions with random parameters. It may look like quite a lot of new code but nothing really technical and everything is really worth understanding. This kind of code is well introduced by Daniel Shiffman’s videos.

////////////////////////////////////////////////////////////////////////////// int samplesPerFrame = 5; int numFrames = 50; float shutterAngle = 1.5; boolean recording = false; int n = 3000; class Thing{ float x = random(100, 400); float y = random(100, 400); float radius = random(2,15); float size = random(1,2.5); float offset = random(0, TWO_PI); void show(){ stroke(255); strokeWeight(size); point(x + radius*cos(TWO_PI*t + offset), y + radius*sin(TWO_PI*t + offset)); } } Thing[] array = new Thing[n]; void setup(){ size(500, 500, P3D); result = new int[width*height][3]; for(int i=0;i<n;i++){ array[i] = new Thing(); } } void draw_(){ background(0); for(int i=0;i<n;i++){ array[i].show(); } }

Result :

The offset attribute is there to avoid having all the dots at the same angle at the same time.

Now a trick is to use the “noise” function of Processing instead of “random” : it is a kind of continuous randomness and it gives more structure.

I just change those lines :

float offset = 9*noise(0.02*x, 0.02*y);

and that one to use a little of transparency (not very pedagogical, just felt liek doing this haha) :

stroke(255,200);

Result :

Daniel Shiffman has videos that are great to introduce you to this noise function (Perlin noise) if you don’t know it. I also have tutorials about it on this blog.

Now let’s talk about one last thing that can be nice with this kind of gif : using a constructor. It can lead to less randomness and more structure. Here is an example :

////////////////////////////////////////////////////////////////////////////// int samplesPerFrame = 5; int numFrames = 75; float shutterAngle = 1.0; boolean recording = true; int n = 100; class Thing{ float size = random(1,5); float offset = random(0, 0.6); float theta; Thing(float theta_){ theta = theta_; } void show(){ stroke(255,200); strokeWeight(size); float radius = 150 + 80*sin(TWO_PI*t + offset); float x = 250 + radius*cos(theta); float y = 250 + radius*sin(theta); point(x, y); } } Thing[] array = new Thing[n]; void setup(){ size(500, 500, P3D); result = new int[width*height][3]; for(int i=0;i<n;i++){ float theta = TWO_PI*i/n; array[i] = new Thing(theta); } } void draw_(){ background(0); for(int i=0;i<n;i++){ array[i].show(); } }

Result :

Notice how the dots are regularly spaced with their angle. Nothing outstanding, it was just to show the use of a constructor.

I just wanted to introduce you to this code structure, that is also some kind of template.

There are two functions called “ease” in the template. I only use the second one but use it a lot. I’m going to explain it a little.

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

p is basically a parameter between 0 and 1 (like t), that will be distorted by the easing function "ease", with the intensity g. It's very useful to get smooth transitions. When I didn't know this precise function, I used something similar for the same purpose, and I'm going to share it because the formula is easier to understand :

float ease(float p) {

return (1-cos(PI*p))/2;

}

Here is a gif showing the curve of the easing function with the parameter g oscillating between 1 and 5. (abscissa and ordinate shown from 0 to 1).

You can notice that the derivative is 0 at p=0 and p=1 (excepted when g = 1.0), that’s why it is so useful to make smooth transitions.

(Code to generate this gif : here)

This function may be a little abstract but it’s just incredibly useful.

For example easing was very useful to make smooth transitions in that gif :

Let’s use “ease” in the previous circle gif code (g = 3.0):

////////////////////////////////////////////////////////////////////////////// int samplesPerFrame = 5; int numFrames = 50; float shutterAngle = 1.5; boolean recording = true; void setup(){ size(500,500); result = new int[width*height][3]; } void draw_(){ background(0); stroke(255); strokeWeight(10); float t2 = ease(t, 3.0); point(250+50*cos(TWO_PI*t2),250+50*sin(TWO_PI*t2)); }

Result :

Patakk, who has made awesome Processing gifs made a nice post about this “ease” function on his tumblr :

Update : that ease function is useful for much more things than what I show here, I may add other examples later.

Now that we’re talking about a very useful function : map, lerp and constrain (explained in the Processing reference) are also often very useful.

Finally just in case you want to upload some gifs on tumblr, you may at some point have troubles fitting into the 3 MB file size limit.

Here are some tricks to reduce file size :

– Use https://ezgif.com/optimize

– Have less frames

– Have less differences between successive frames

– Have less colors

– Have a smaller resolution

I hope this will helpful and that you learnt something, thanks for reading. The usual code template I use is actually different from the one I present here because I usually use openSimplex noise (I present it in this tutorial).

]]>The following gifs use the same trick to draw some curves in Processing and I will do my best to try explaining it here. I’ll link their code and describe them at the end.

(I put them here in chronological order of creation)

Some of them look quite complex but you’ll see the maths behind them isn’t complicated, and I’ll try to explain everything slowly.

Here is some code to generate the frames of a gif showing two disks moving in circles (one twice faster than the other one) :

int numFrames = 100; void setup(){ size(500,500,P3D); stroke(255); fill(255); } float x1(float t){ return 0.25*width + 50*cos(TWO_PI*t); } float y1(float t){ return 0.5*height + 50*sin(TWO_PI*t); } float x2(float t){ return 0.75*width + 50*cos(2*TWO_PI*t); } float y2(float t){ return 0.5*height + 50*sin(2*TWO_PI*t); } void draw(){ float t = 1.0*(frameCount - 1)/numFrames; background(0); ellipse(x1(t),y1(t),6,6); ellipse(x2(t),y2(t),6,6); println("saving frame " + frameCount + "/" + numFrames); if(frameCount<=numFrames) saveFrame("fr###.png"); if(frameCount == numFrames) stop(); }

Here is the result :

It might be quite a lot of code already but there won’t be much more code do draw the curves. I use P3D because it renders things more smoothly in some cases.

For all the rest of this tutorial, the disks must have any trajectory that loops well.

A fundamental function/thing in the curve trick is the use of the function **lerp** of Processing, that does linear interpolation. Skip this part if you already know it !

In case you have not heard about it, lerp(a,b,t) = (1-t)*a + t*b. when t=0 it returns a, when t=1 it returns b, when t is between 0 and 1 it returns a value between a and b linearly.

If you still don’t feel familiar with that, here is a video of Daniel Shiffman explaining it : https://www.youtube.com/watch?v=8uLVnM36XUc

I haven’t even watched it but I know he’s great at explaining stuff.

Let’s draw lots of little transparent white points between the two disks. This trick seems useless so far but then when the curve won’t be a line, it will give cool shades of grey, that you can’t get easily with beginShape() endShape() (I think).

Added code :

int m = 1000;

(number of little points)

and :

pushStyle(); strokeWeight(2); stroke(255,100); for(int i=0;i<=m;i++){ float tt = 1.0*i/m; float x = lerp(x1(t),x2(t),tt); float y = lerp(y1(t),y2(t),tt); point(x,y); } popStyle();

(inside draw())

If you understood lerp there isn't much more to understand there. I use the variable name "tt" because I want to call it "t" but it's already taken by time. Really I don't know what more to say about this code.

Link to full code

Result :

The idea is that the disk will be seen with a delay kind of proportional with the distance to the disk. Let’s begin to use this delay trick for the first disk only.

Let’s add this to the code :

This parameter :

float delay_factor = 2.0;

and :

float x = lerp(x1(t - delay_factor*tt),x2(t),tt); float y = lerp(y1(t - delay_factor*tt),y2(t),tt);

(x1 and y1 seen with a greater delay when tt is greater, no delay when tt = 0)

Here is the result :

With delay_factor = 1.0 :

With delay_factor = 5.0 :

Now let’s have a delay on both disks.

The only change to make is :

float x = lerp(x1(t - delay_factor*tt),x2(t - delay_factor*(1-tt)),tt); float y = lerp(y1(t - delay_factor*tt),y2(t - delay_factor*(1-tt)),tt);

You can use different delay factors if you want…

Result with delay_factor = 1.0 :

Let’s have a perfectly looping random trajectory for the first disk. To understand this I advise to check out my previous tutorial “Drawing from noise, and then making animated loopy GIFs from there”, but you may understand anyway if you don’t have time/motivation for that.

To use openSimplex noise, put this code in another Processing tab of your sketch.

Also put this in setup() :

noise = new OpenSimplexNoise();

and define it before setup() :

OpenSimplexNoise noise;

Now we can change the code for x1() and y1() :

float motion_radius = 0.5; float x1(float t){ float seed = 1337; return 0.25*width + 150*(float)noise.eval(seed + motion_radius*cos(TWO_PI*t),motion_radius*sin(TWO_PI*t)); } float y1(float t){ float seed = 1515; return 0.5*height + 150*(float)noise.eval(seed + motion_radius*cos(TWO_PI*t),motion_radius*sin(TWO_PI*t)); }

We go through simplex noise to get horizontal and vertical values with a radius equal to motion_radius.

Result :

(where the seond disk doesn’t move to show clearly the propagation)

(where the second disk moves like before)

(where “motion_radius” has been increased)

I also actually changed some other parameters… here is the full code.

I’ll give a link to the code of each gif, but I warn you : it uses the motion blur system of beesandbombs (Dave Whyte), it might be experimental code with bad variable names, unecessary stuff, details that may be hard to understand, or just stupid stuff, or there might be some mistakes.

This gif simply uses an array of disks that have a trajectory based on simplex noise like previously, with different seeds for each disk. A curve is drawn with the trick between each disk and a point at the bottom that doesn’t move. Each disk has a method that gives the position depending on time, like array[i].x(t) makes sense.

Here there is an array of 10 disks that have their simplex noise trajectory centered on points forming a circle. Like previously positions (with delay) can be obtained with array[i].x(t – f*tt).

Code

(parameters are different, I haven’t found back the right ones)

Here it is the same thing as Gif 2, excepted that trajectories are centered differently. Also there is higher opacity and stroke weight when the disks are closer to each other. Also now the delay factor is actually proportionnal to the distance between the disks (so an almost straight curve between them is drawn when they are close). I can’t remember well why I changed the curves like that but I remember I thought I needed it. (Update : it’s because when the two points are closer with the original curve trick, the curve will just be contracted, in order to avoid that I use a smaller “delay factor” when the points are closer).

It is similar to the previous gif. The differences : the center of trajectories are generated randomly inside a circle, with a random angle and a random radius that has quite high probability to be close to the radius of the circle (I adjusted a probability distribution). When the center of trajectory is closer to the center of the canvas, the trajectory becomes larger (it can be so large that some disks stay far outside the circle), when it’s close to the circle it moves less.

Code

(I love the different results this one gives when you run it again)

The only thing to talk about here might be the trajectory of the left point, and it’s in the code.

The only thing really new here is the trajectory of the point in the middle. It is a simplex noise based trajectory, with an easing function to distort time.

Take one random moving curve generated with the tutorial trick between two disks, rotate it around the center of the canvas, add some time offset depending on the angle (and so that it loops well), and you can get this kind of gif.

I was inspired by this gif from echophon.tumblr.com.

This was quite long for such a simple trick. I hope you understood well the trick, and that you’ll go further and come up with stuff I haven’t made yet. Thanks a lot for reading !

Check out this codepen by Decatron where you can change the parameters like “delay factors” and see the results in real time.

Link : curve-experiment

Also check out this SketchpunkLabs video where my trick is used as a path in 3D.

Link : WebGL2 : 085 : Curved Paths

This gif by wavegrower on tumblr :

You can see he seems to have used a negative delay factor with three points

The aim of this tutorial is to explain how to obtain with Processing the following kinds of GIF generatively from the same program, using random 2D transformations. This something I’ve been exploring since November 2017.

Or with another program :

It will first explain how to obtain those kinds of generative GIFs :

The algorithms presented in this tutorial are based on the work of Tomasz Sulej (http://generateme.tumblr.com, http://folds2d.tumblr.com).

Here is a tutorial that presents some 2D transformations and explains how to render them nicely with Processing :

https://generateme.wordpress.com/2016/04/11/folds/

It is important to understand the key ideas of this tutorial to understand mine, so I will sum them up. The idea is to draw the image of little dots of low opacity after applying a 2D transformation. The dots are initially in [-3.0,3.0]x[-3.0,3.0]. What is done to render the transformation can be seen as “folding a square”. The dots will concentrate at some places resulting in higher drawing opacity at those places. 2D transformation can also be called folds.

Also the code he uploaded on OpenProcessing here is fundamental. It uses the composition of several types of functions randomly (there are 21 types in this version), and each type of function can have random parameters, so the number of possibilities of drawings that can be generated explodes. You can click to skip the current drawing and draw a different one.

I advise to visit http://folds2d.tumblr.com to see what can be drawn from 2D transformations.

Now let’s see how to make gifs from there.

I can hardly explain it better than what’s in the title. The idea is to do linear interpolation between 3 transformation (only between 2 transformations at a time). Why 3? to get a nice cycle 1 -> 2 -> 3 -> 1 and not just 1 -> 2 -> 1 that would give a less interesting motion.

Here is what can be obtained using some transformation from the folds2d tutorial, with its rendering technique.

(Lost the code for this version and too lazy to write it back, but it’s pretty straight forward)

Update : oh I actually found it back ! Here it is.

Also what can be done is to show the motion more slowly using many representations in a circle. It can look like this :

I still have the code for that one : circularfoldsmorphing.pde

Some comments about this program :

I don’t advise you to understand all of this horrible code, but I can tell you how to use it : click to start generating again with other parameters and transformations. Most of the time it uses transformations that use randomness, and it doesn’t look nice.

It uses two random 2D transformations (obtained by random composition of functions from more than 170 types) and the identity transformation (no transformation). Also here it doesn’t fold a square but some points on 100 lines that start from the center of a circle. An easing function is used for smoother motion.

A second approach to make an animated gif from a 2D transformation is to apply it to moving lines.

Here is what we will draw with no transformation (still drawn with little dots of low opacity).

The motivation behind such lines is that we want a perfectly looping motion.

With a random 2D transformation obtained by composing many types of functions randomly, it’s possible to obtain the following results from the same program :

Here is the code : foldingmovinglines2.pde

Comments about how to use this program :

The lines are being drawn one by one for each frame. So you can see structures being drawn one by one, and the animation is so that each structure will replace the next structure that will be drawn. That’s why you can know if the motion will be interesting or not, and if you have to click in order to generate another gif. Typically if structures are drawn on top of each other the motion will be uninteresting. Note that most often the results don’t look as nice as what I show here. The 2D transformations that are obtained by this algorithm are often not continuous and that can cause some structures to disappear brutally.

Another idea is to apply the 2D transformation to those lines (it uses a fade in opacity) :

Code : foldingmovinglines.pde

Some results :

A more recent idea is to apply the 2D transformation to this :

(still drawn with little dots of low opacity)

Code : foldingmovingparts.pde

Some results :

More results here.

I won’t explain this one much more than the title. Basically it uses a 2D transformation based on noise (that loops well), in between two 2D transformations obtained like previously. That technique gives a final 2D transformation that changes through time and that loops well. I obtained those results from the same program :

The second approach (but also the third one) to make gifs from 2D transformations shows that they can give some complex generative motion. An algorithm that obtains random 2D transformations by random composition of several types of functions can therefore lead to a great variety of shapes and motion. By increasing the resolution, reducing the opacity of the dots and using more of them, it’s probably possible to obtain some better looking results than what I present here at the price of more computation time.

Keyword of this tutorial : 2D transformation

Big thanks to Tomasz Sulej, I would have done nothing of this without him.

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

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

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 :

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 :

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 :

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 :

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

Update : there may be a wordpress/HTML bug to display the above code, but you can find it here.

Result :

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###.gif"); } if(frameCount == numFrames){ println("finished"); stop(); } }

Update : there may be a wordpress/HTML bug to display the above code, but you can find it here.

Result :

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 :

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 :

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 :

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…

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)

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

(Code)

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

(Code)

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)

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

Special thanks to Chris Ball for letting me know about openSimplex noise !

Golan Levin made a gif that explains how a value can be looped with noise :

Also I love those gifs by littlecuuub on tumblr, inspired by the tutorial :

And this one by echophon :

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 :

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.

]]>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 :

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 :

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 :

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 :

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 :

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

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 :

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 :

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 :

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 :

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 :

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

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

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

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 :

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

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

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

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

:

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

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

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

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

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.

]]>`saveFrame`

function in Processing to save frames, for example like this :
saveFrame("frame###.png");

Then import the frames as layers in GIMP, and export as .gif and as animation. You can choose the delay between frames there. There must be a more automated way to make gifs but this works and I’m fine with it.

]]>