Complex Fourier Transformation using Processing

I have made a few animations using complex Fourier Transformations and processing.

Sketch to generate the points
/**
ENGLISH
Instructions:
When the sketch launches you have to import a image. 
You can adjust said image by right-clicking.
Left-clicking adds a point to the line.
z removes the last point.
p saves the data directly.           The curve will be closed by making a line to the first point.
r saves a edited version of the data.The curve will be closed by going back the route to the first point.

o loads a new image
i loads point data saved using p
*/
/** 
GERMAN
Anleitung: 
Wenn das Programm gestartet wird kann ein Bild importiert werden
Das Bild kann mit einem Rechtsklick verschoben und vergrĂ¶ĂŸert werden.
Mit einem Linksklick kann ein Punkt hinzugefĂŒgt werden
Mit z kann man den letzten Punkt entfernen

Mit p kann man die Daten direkt abspeichern. Die Kurve wird geschlossen, indem eine Linie zum ersten Punkt hinzugefĂŒgt werden.
Mit r kann man die Daten abspeichern.        Die Kurve wird geschlossen, indem der gleiche weg zurĂŒckgegangen wird.

Mit o kann man ein neues Bild laden.
Mit i kann man Daten, die mit p abgespeichert wurden wieder aufrufen.
*/


import javax.swing.*;
import java.util.*;

List<Integer> X;
List<Integer> Y;
int p=0;
volatile PImage im;
volatile boolean allFilesRead=false;
String pushed[]=null;
int sx=0;
int sy=0;

float resc=1;
int tx=0;
int ty=0;
void setup() {
  size(900, 900);
  X=new ArrayList();
  Y=new ArrayList();
  selectInput("Data", "loadFile");
  while (!allFilesRead);
  sx=im.width;
  sy=im.height;
  im.resize(im.width, im.height);
  println(X.size());
  p=X.size();
}
void draw() {
  background(255);
  image(im, tx, ty);
  fill(255, 150);
  rect(-1, -1, width+2, height+2);

  if (p>=1) {
    for (int i=1; i<p; i++) line(X.get(i), Y.get(i), X.get(i-1), Y.get(i-1));
  }
}
void mousePressed() {
  if (mouseButton==LEFT) {
    X.add(mouseX);
    Y.add(mouseY);
    p=X.size();
  }
  if (mouseButton==RIGHT)getInfo();
}
void keyPressed() {
  if (key=='r') {
    String[] ret=new String[2*X.size()-1];
    int co[][]=new int[2*X.size()][2];
    for (int i=0; i<X.size(); i++) {
      ret[i]=""+X.get(i)+" "+Y.get(i);
    }
    for (int i=0; i<X.size()-1; i++) {
      ret[i+X.size()]=""+X.get(X.size()-1-i)+" "+Y.get(X.size()-1-i);
    }
    pushed=ret;
    selectOutput("Save As", "saveTxtFile");
    println(ret);
  }
  if (key=='p') {
    String[] ret=new String[X.size()];
    // int co[][]=new int[X.size()][2];
    for (int i=0; i<X.size(); i++) {
      ret[i]=X.get(i)+" "+Y.get(i);
    }
    pushed=ret;
    selectOutput("Save As", "saveTxtFile");
    println(ret);
  }
  if (key=='z') {
    X.remove(X.size()-1);
    Y.remove(Y.size()-1);
    p=X.size();
  }
  if (key=='s') {
    color c=get(mouseX, mouseY);
    for (int i=mouseX; i>=0; i--) if (c!=color(0, 0, 0)&&(abs(red(c)-red(get(i, mouseY)))>24||abs(green(c)-green(get(i, mouseY)))>24||abs(blue(c)-blue(get(i, mouseY)))>24)) {
      X.add(i);
      Y.add(mouseY);
      p=X.size();
      i=-1;
    }
  }
  if (key=='i') {
    selectInput("Open image","loadFile");
  }
  if (key=='o') {
    selectInput("Open point data","loadTextFile");
  }
}
void mouseDragged() {
  if (frameCount%5==0) { 
    X.add(mouseX);
    Y.add(mouseY);
    p=X.size();
  }
}
public void loadFile(File f) {
  im=loadImage(f.getAbsolutePath());
  allFilesRead=true;
}
public void loadTextFile(File f) {
  String[] load=loadStrings(f);
  X.clear();
  Y.clear();
  for (String st : load) {
    String[] splitted=st.split(" ");
    X.add(int(splitted[0]));
    Y.add(int(splitted[1]));
    println(int(splitted[0]), int(splitted[1]));
  }
  p=X.size();
}
public void saveTxtFile(File f) {
  saveStrings(f, pushed);
  allFilesRead=true;
}
public void getInfo() {
  JPanel c=new JPanel();
  JTextField txf=new JTextField(10);
  txf.setText(tx+"");
  JTextField tyf=new JTextField(10);
  tyf.setText(ty+"");
  JTextField resf=new JTextField(10);
  resf.setText(resc+"");
  c.setLayout(new java.awt.GridLayout(3, 2, 10, 10));
  c.add(new JLabel("Translation X:"));
  c.add(txf);
  c.add(new JLabel("Translation Y:"));
  c.add(tyf);
  c.add(new JLabel("Rescale:"));
  c.add(resf);
  JOptionPane.showMessageDialog(null, c);
  tx=int(txf.getText());
  ty=int(tyf.getText());
  resc=float(resf.getText());
  im.resize((int)(sx*resc), (int)(sy*resc));
}
Sketch to render the animation
/**
 ENGLISH
 How to use this program:
 When the sketch launches you select the color for the line. Next the file with the points will be selected.
 These are the instructions what to paint.
 
 Toggleable function:
 z disables the display of the epicycles
 c disables the display of thee circles only
 g makes the slower cycles a lighter color
 ENTER makes the sketch focus the point currently painted
 f makes thee sketch fokus (0,0) as middle point
 l locks all keys
 0 halts the program (however new images will be safed)
 q makes the program click every 75 frames
 
 Other functions:
 w and s zoom in/out
 pressing r resets the simulation
 pressing r for at least 3 seconds allows selecting a new file to read
 t resets all toggelable functions and zooming
 
 Settings.ini settings:
 circles: how many circles spin to the left (in reality double the circles are calculated)
 delta: Used for integrating numerically must be small but >0
 frames: Number of frames needed to complete a complete cycle
 record: When true all frames are saved to be made into a video
*/
/**
 GERMAN
 Anleitung:
 Wenn man das Programm öffnet kann man eine Strichfarbe fĂŒr die Funktion auswĂ€hlen.
 Als nÀchstes kann man eine Datei laden, aus deren Punkte die Kurve berechnet wird.
 
 Mit z kann man die Zeichnung der Kreise und Zeiger abschalten
 Mit c kann man nur die Zeichnung der Kreise abschalten
 Mit g kann man dafĂŒr sorgen, dass die langsameren Kreise heller werden und die schnelleren dunkler
 Mit ENTER kann man den gezeichneten Punkt fokusieren
 Mit f kann man die obere ecke fokusieren
 Mit w und s kann man rein, bzw rauszoomen
 
 Mit r kann man die Simulation zurĂŒcksetzen
 Mit t kann man die Ansicht zurĂŒcksetzten
 
 Mit l kann man die TastendrĂŒcke sperren/entsperren
 Mit 0 hÀlt das Programm an
 Mit q clickt das Programm alle 75 Bilder
 
 Wenn man r gedrĂŒckt lĂ€sst kann man neue Punkte einlesen
 
 Einstellungen:
 circles: wie viele Kreise drehen nach links (in wirklichkeit generiert er die Doppelte anzahl an kreien)
 delta:   Benötigt fĂŒr das numerische Integrieren, je nĂ€here an 0 desto genauer wird die Berechnung der Kreise aber die Berechnung dauert lĂ€nger.
 frames:  Anzahl der Frames insgesammt. Bestimmt die Geschwindigkeit
 record:  Bestimmt ob die einzelnen Frames abgespeichert werden sollen, sodass man ein Video erstellen kann.
 */
//float[][] v={{100, 300}, {300, 100}, {100, 100}, {300, 300}, {300, 100},{100, 300},{300, 300}, {100, 100}};
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
Robot r;
float[][] v={};
float g[][];
volatile String read[];
float dif=0.0002;
int fk=4000;
ArrayList<Float> p;
ArrayList<Float> p2;
float vv=1;
boolean record=false;
boolean b=false;
boolean c=true;
float re[];
boolean z=true;
int sk=1;
int ll=0;
boolean lock=false;
boolean gradient=false;
float ak[]={0, 0};
boolean focus=false;
int reset=0;
boolean autoclick=false;
int circlenumb=700;
boolean halt=false;
volatile boolean allFilesRead=false;
float[] f(float x) {
  float dist[]=new float[v.length];
  // for(int i=0;i<v.length;i++) dist[i];
  int sc2=0;
  int d=0;
  int d2=0;
  for (int i=0; i<v.length-1; i++) {
    d2+=dist(v[(i+1)%v.length][0], v[(i+1)%v.length][1], v[i][0], v[i][1]);
  }
  for (int i=0; i<v.length-1&&d+dist(v[(i+1)%v.length][0], v[(i+1)%v.length][1], v[i][0], v[i][1])<x/(2*PI)*d2; i++) {
    d+=dist(v[(i+1)%v.length][0], v[(i+1)%v.length][1], v[i][0], v[i][1]);
    sc2++;
  }
  float xp=(x/(2*PI)*d2-d)/dist(v[(sc2+1)%v.length][0], v[(sc2+1)%v.length][1], v[sc2][0], v[sc2][1]);
  // println(xp,d,(x/(2*PI)*d2-d),dist(v[(sc2+1)%v.length][0], v[(sc2+1)%v.length][1], v[sc2][0], v[sc2][1]));
  if (dist(v[(sc2+1)%v.length][0], v[(sc2+1)%v.length][1], v[sc2][0], v[sc2][1])==0) xp=1;
  // println(xp);
  /*if (xp>1) {
   exit();
   }*/

  float[] ret={(v[(sc2+1)%v.length][0]-v[sc2][0])*xp+v[sc2][0], (v[(sc2+1)%v.length][1]-v[sc2][1])*xp+v[sc2][1]};
  //println(ret);
  return ret;
}
Color col=Color.RED;
void setup() {
  try {
    r=new Robot();
  }
  catch(Exception e) {
  }

  getSurface().setVisible(false);
  readSettings();
  JColorChooser c=new JColorChooser(Color.RED);
  JOptionPane.showMessageDialog(null, c);
  col=c.getColor();
  try {
    ps=new java.io.PrintStream(sketchPath("data/function"));
  }
  catch(Exception e) {
  }
  p=new ArrayList();
  p2=new ArrayList();
  //read=loadStrings("read.txt");
  selectInput("Data", "loadFile");
  while (!allFilesRead);
  v=new float[read.length][2];
  for (int i=0; i<v.length; i++) v[i]=new float[]{float(read[i].split(" ")[0]), float(read[i].split(" ")[1])};
  // for (int i=0; i<v.length; i++) v[i][1]=-v[i][1]+850;
  size(900, 900);
  background(255);
  noFill();
  frameRate(60);
  //strokeWeight(4);
  calculates(circlenumb);
  getSurface().setVisible(true);
}
int startat=0;
boolean paintThisFrame=true;
void draw() {
  if (!halt) {
    if (ll>0) ll--;

    background(255);//=f(((float)sk/230)%(2*PI));
    //if(b) translate(-vv*re[0],-vv*re[1]);
    re=newpos(-((float)sk/fk)%(2*PI));
    //ellipse(re[0],re[1],10,10);
    stroke(255, 0, 0);
    point(vv*(re[0]+(b?-re[0]:0))+(b?width/2:0), vv*(re[1]+(b?-re[1]:0))+(b?height/2:0));
    if (((float)sk/fk)<=2.2*PI) {
      p.add(re[0]);
      p2.add(re[1]);
    }
   if(autoclick) if (sk%75==0) {
      r.mousePress(InputEvent.BUTTON1_MASK);
      delay(100);
      r.mouseRelease(InputEvent.BUTTON1_MASK);
    }
    if (frameCount%100==0) println(p.size());
    ak[0]=re[0];
    ak[1]=re[1];
    //point(re[0],re[1]);

    strokeWeight(2);
    stroke(col.getRed(), col.getGreen(), col.getBlue());
    if (paintThisFrame) for (int i=0; i<sk-1&&((float)sk/fk)%(2*PI)<=2*PI; i++) {
      //re=newpos(((float)i/fk)%(2*PI));
      //ellipse(re[0],re[1],10,10);
      line(vv*(p.get(i)+(b?-re[0]:0))+(b?width/2:0), vv*(p2.get(i)+(b?-re[1]:0))+(b?height/2:0), vv*(p.get(i+1)+(b?-re[0]:0))+(b?width/2:0), vv*(p2.get(i+1)+(b?-re[1]:0))+(b?height/2:0));
      //println(re);
    }
    stroke(0);
    strokeWeight(10);
    strokeWeight(1);

    float last[]={g[(g.length-1)/2][0], g[(g.length-1)/2][1]};

    if (z&&paintThisFrame) for (int i=1; i<g.length; i++) {
      if (i==1) {
        strokeWeight(5);
        point(vv*last[0]+(b?-vv*ak[0]:0)+(b?width/2:0), vv*last[1]+(b?-vv*ak[1]:0)+(b?height/2:0));
        strokeWeight(1);
      }
      int n=(int)(i/2)*(int)pow(-1, i);
      float re2[]=newpos_ex(-((float)sk/fk)%(2*PI), n, (int)((i-1)/2)*(int)pow(-1, i-1));
      re[0]=re2[0];
      re[1]=re2[1];
      //float[] re2=re=newpos_ex(((float)sk/230)%(2*PI),(int)((i-1)/2)*(int)pow(-1,i-1),(int)((i-2)/2)*(int)pow(-1,i-2));
      stroke(0);
      if (gradient&&paintThisFrame) stroke(255*pow(((float)g.length-(float)i)/(float)g.length, 0.9));
      //println(pow(((float)g.length-(float)i)/(float)g.length,0.5));
      line(vv*re[0]+(b?-vv*ak[0]:0)+(b?width/2:0), vv*re[1]+(b?-vv*ak[1]:0)+(b?height/2:0), vv*last[0]+(b?-vv*ak[0]:0)+(b?width/2:0), vv*last[1]+(b?-vv*ak[1]:0)+(b?height/2:0));
      if (!gradient) stroke(90);
      if (c&&paintThisFrame)ellipse(vv*last[0]+(b?-vv*ak[0]:0)+(b?width/2:0), vv*last[1]+(b?-vv*ak[1]:0)+(b?height/2:0), vv*2*sqrt(sq(last[0]-re[0])+sq(last[1]-re[1])), vv*2*sqrt(sq(last[0]-re[0])+sq(last[1]-re[1])));
      last[0]=re[0];
      last[1]=re[1];
    }
    if (z&&paintThisFrame) line(vv*(ak[0]+(b?-ak[0]:0))+(b?width/2:0), vv*(ak[1]+(b?-ak[1]:0))+(b?height/2:0), vv*(last[0]+(b?-ak[0]:0))+(b?width/2:0), vv*(last[1]+(b?-ak[1]:0))+(b?height/2:0));
    if (!gradient) stroke(90);
    if (z&&c&&paintThisFrame) ellipse(vv*re[0]+(b?-vv*ak[0]:0)+(b?width/2:0), vv*re[1]+(b?-vv*ak[1]:0)+(b?height/2:0), vv*2*sqrt(sq(ak[0]-re[0])+sq(ak[1]-re[1])), vv*2*sqrt(sq(ak[0]-re[0])+sq(ak[1]-re[1])));
    ;
    //if(b)translate(-re[0],-re[1]);
    sk++;
    if (keyPressed) if (key=='r') {
      reset++;
      if (reset>=3*frameRate) {
        allFilesRead=false;
        //read=loadStrings("read.txt");
        selectInput("Data", "loadFile");
        while (!allFilesRead);
        v=new float[read.length][2];
        for (int i=0; i<v.length; i++) v[i]=new float[]{float(read[i].split(" ")[0]), float(read[i].split(" ")[1])};
        background(355);
        calculates(circlenumb);
      }
    } else {
      reset=0;
    }
  }
  if (frameCount>startat-5&&record)saveFrame("animation/frame-######.png");
  if (frameCount>startat-10) paintThisFrame=true;
}
float[] integ(int num) {
  float sum[]={0, 0};
  for (float i=0; i<2*PI; i+=dif) {
    float x[]=f(i);
    sum[0]+=(x[0]*cos(num*i)-sin(num*i)*x[1])*dif;
    sum[1]+=(x[0]*sin(num*i)+cos(num*i)*x[1])*dif;
  }
  sum[0]/=2*PI;
  sum[1]/=2*PI;
  return sum;
}
java.io.PrintStream ps;
void calculates(int n) {
  JProgressBar prog=new JProgressBar(0, 2*n);
  JLabel text=new JLabel("   Calculating");
  JFrame wind=new JFrame();
  text.setFont(new Font("arial", Font.PLAIN, 22));
  wind.setUndecorated(true);
  wind.setLayout(new BorderLayout());
  wind.setSize(500, 350);
  wind.setBackground(new Color(200, 200, 200));
  wind.setLocation(displayWidth/2-250, displayHeight/2-175);
  wind.add(prog, "South");
  wind.add(text, "Center");
  prog.setStringPainted(true);
  wind.setVisible(true);
  g=new float[2*n+1][2];
  for (int i=0; i<g.length; i++) {
    g[i]=integ(i-n);
    println(((float)i/(float)g.length)*100+"%");
    prog.setValue(i);
    prog.setString((int)(((float)i/(float)g.length)*100)+"%");
    text.setText("   Calculated: "+i+"/"+g.length);
  }
  wind.setVisible(false);
  wind.dispose();
}
float[] newpos(float x) {
  float sum[]={0, 0};
  int l=(g.length-1)/2;
  for (int i=0; i<g.length; i++) {
    sum[0]+=g[i][0]*cos((i-l)*x)-sin((i-l)*x)*g[i][1];
    sum[1]+=g[i][0]*sin((i-l)*x)+cos((i-l)*x)*g[i][1];
  }
  return sum;
}
float[] newpos_ex(float x, int lu, int lo) {
  float sum[]={0, 0};
  int l=(g.length-1)/2;
  if (lu>lo) {
    int zw=lo;
    lo=lu;
    lu=zw;
  }
  for (int i=lu+l; i<=lo+l; i++) {
    sum[0]+=g[i][0]*cos((i-l)*x)-sin((i-l)*x)*g[i][1];
    sum[1]+=g[i][0]*sin((i-l)*x)+cos((i-l)*x)*g[i][1];
  }
  return sum;
}
void keyPressed() {
  if (!lock) {
    if (key=='w') {
      vv*=1.1;
    }
    if (key=='t') {
      vv=1;
      b=false;
      c=true;
      z=true;
      gradient=false;
    }
    if (key=='s') {
      vv/=1.1;
    }
    if (key=='g') {
      gradient=!gradient;
    }
    if (key==ENTER) {
      b=!b;
    }
    if (key=='c') {
      c=!c;
    }
    if (key=='z') {
      z=!z;
    }
    if (key=='q') {
      autoclick=!autoclick;
    }
    if (key=='r') {
      sk=1;
      p.clear();
      p2.clear();
    }
    if (key=='f') {
      if (!b) focus=!focus;
      if (focus) translate(width/2-g[(g.length-1)/2][0], height/2-g[(g.length-1)/2][1]);
      if (!focus) translate(-width/2+g[(g.length-1)/2][0], -height/2+g[(g.length-1)/2][1]);
    }
  }
  if (key=='l') {
    if (ll>0) lock=!lock;
    if (ll==0) ll=50;
  }
  if (key=='0') {
    halt=!halt;
  }
}
void readSettings() {
  String[] lines=loadStrings(sketchPath("settings.ini"));
  for (String line : lines) {
    String[] args=line.replaceAll(" ", "").split(":");
    switch(args[0]) {
    case "circles":
      circlenumb=int(args[1]);
      break;
    case "frames":
      fk=(int)(int(args[1])/(2*PI));
      break;
    case "delta":
      dif=float(args[1]);
      break;
    case "record":
      record=boolean(args[1]);
      println(record);
      break;
    }
  }
}
void loadFile(File f) {
  read=loadStrings(f);
  for (String line : read) println(line);
  allFilesRead=true;
  println(allFilesRead);
}

settings.ini

circles:1000
delta:0.0004
frames: 20000
record:true

Animations Fourier: https://youtu.be/kWC3FJ-5Rhs
Einstein: https://youtu.be/aC_QoYJXgQA

Darkest Dungeon caracters:
Jester: Jester (Darkest Dungeon) painted using Fourier Transformation - YouTube
Grave Robber: Grave Robber (Darkest Dungeon) painted with Fourier Transformation - YouTube
Occultist: Ptholemy + Darkest Dungeon - YouTube

4 Likes