Drawing With Discrete Fourier Transform

by bldrkamal in Teachers > Math

3073 Views, 3 Favorites, 0 Comments

Drawing With Discrete Fourier Transform

fourier pict.jpg

In this instructable, we are going to input a bunch of signals by drawing or sketching anything on an HTML canvas and the signal will be fed into a discrete Fourier transform algorithm which in turn will trace our input signal or sketch, thereby redrawing what we have drawn already just like this:



you can follow this Link and try to sketch, then see what happens

The discrete Fourier transform transforms a sequence of N complex numbers {xn}:x1,x2,x3 ---xn into another sequence of complex numbers.

Supplies

  1. any code editor.

Create a Javascript Class to Implement Complex Number Addtion and Multiplication

discrete fourier formula.jpg
class Complex {
  constructor(a, b) {
    this.re = a;
    this.im = b;
  }


  add(c) {
    this.re += c.re;
    this.im += c.im;
  }


  mult(c) {
    const re = this.re * c.re - this.im * c.im;
    const im = this.re * c.im + this.im * c.re;
    return new Complex(re, im);
  }
}













Create a Javascript Function to That Will Implement Discrete Furier Transform(DFT)

function dft(x) {
  const X = [];
  const N = x.length;
  for (let k = 0; k < N; k++) {
    let sum = new Complex(0, 0);
    for (let n = 0; n < N; n++) {
      const phi = (TWO_PI * k * n) / N;
      const c = new Complex(cos(phi), -sin(phi));
      sum.add(x[n].mult(c));
    }
    sum.re = sum.re / N;
    sum.im = sum.im / N;


    let freq = k;
    let amp = sqrt(sum.re * sum.re + sum.im * sum.im);
    let phase = atan2(sum.im, sum.re);
    X[k] = { re: sum.re, im: sum.im, freq, amp, phase };
  }
  return X;
}



















































Drawing Phase- Define the Variables and Constants That Will Change When User Is Active and Drawing Path

const USER = 0;
const FOURIER = 1;


let x = [];
let fourierX;
let time = 0;
let path = [];
let drawing = [];
let state =








This Function Will Keep the State of the Drawing When a User Pressed the Mouse

function mousePressed() {
  state = USER;
  drawing = [];
  x = [];
  time = 0;
  path = [];
}

















This Function Controls the State of the Fourier Algorithm to Start Drawing When the User Released the Mouse

function mouseReleased() {
  state = FOURIER;
  const skip = 1;
  for (let i = 0; i < drawing.length; i += skip) {
    x.push(new Complex(drawing[i].x, drawing[i].y));
  }
  fourierX = dft(x);


  fourierX.sort((a, b) => b.amp - a.amp);
}














This Function Setup Is Responsible for Drawing Canvas Using the P5.js Library

function setup() {
  createCanvas(windowWidth, windowHeight);
  background(100);
  fill(255);
  textAlign(CENTER);
  textSize(64);
  text("Draw Something!", width/2, height/2);
  strokeWeight(5);
}












This Function Sets Up the Rotating Epipcyles That You Will See on Screen.

function epicycles(x, y, rotation, fourier) {
  for (let i = 0; i < fourier.length; i++) {
    let prevx = x;
    let prevy = y;
    let freq = fourier[i].freq;
    let radius = fourier[i].amp;
    let phase = fourier[i].phase;
    x += radius * cos(freq * time + phase + rotation);
    y += radius * sin(freq * time + phase + rotation);


    stroke(255, 100);
    noFill();
    ellipse(prevx, prevy, radius * 2);
    stroke(255);
    line(prevx, prevy, x, y);
  }
  return createVector(x, y);
}

This Is the Actual Function That Draws and Loops When You Are Sketching and When the DFT Starts Rendering Your Drawing

function draw() {


  if (state == USER) {
  background(100);
    let point = createVector(mouseX - width / 2, mouseY - height / 2);
    drawing.push(point);
    stroke(255);
    noFill();
    beginShape();
    for (let v of drawing) {
      vertex(v.x + width / 2, v.y + height / 2);
    }
    endShape();
  } else if (state == FOURIER) {
  background(100);
    let v = epicycles(width / 2, height / 2, 0, fourierX);
    path.unshift(v);
    beginShape();
    noFill();
    strokeWeight(5);
    stroke(0, 255,255);
    for (let i = 0; i < path.length; i++) {
      vertex(path[i].x, path[i].y);
    }
    endShape();


    const dt = TWO_PI / fourierX.length;
    time += dt;


    if (time > TWO_PI) {
      time = 0;
      path = [];
    }
  }


}

















This Is the Complete Code. You Can Run It in Your Browser

open any code editor, copy and paste the code and save it as index.html.

Click the file to open in your browser.

<!DOCTYPE html><html><head>
    <script src="https://cdn.jsdelivr.net/npm/p5@1.4.0/lib/p5.js"></script>
    
    <link rel="stylesheet" type="text/css" href="style.css">
    <meta charset="utf-8">


  </head>
  <body>
    
   <script>
class Complex {
  constructor(a, b) {
    this.re = a;
    this.im = b;
  }


  add(c) {
    this.re += c.re;
    this.im += c.im;
  }


  mult(c) {
    const re = this.re * c.re - this.im * c.im;
    const im = this.re * c.im + this.im * c.re;
    return new Complex(re, im);
  }
}


function dft(x) {
  const X = [];
  const N = x.length;
  for (let k = 0; k < N; k++) {
    let sum = new Complex(0, 0);
    for (let n = 0; n < N; n++) {
      const phi = (TWO_PI * k * n) / N;
      const c = new Complex(cos(phi), -sin(phi));
      sum.add(x[n].mult(c));
    }
    sum.re = sum.re / N;
    sum.im = sum.im / N;


    let freq = k;
    let amp = sqrt(sum.re * sum.re + sum.im * sum.im);
    let phase = atan2(sum.im, sum.re);
    X[k] = { re: sum.re, im: sum.im, freq, amp, phase };
  }
  return X;
}


</script> 
<script>
const USER = 0;
const FOURIER = 1;


let x = [];
let fourierX;
let time = 0;
let path = [];
let drawing = [];
let state = -1;


function mousePressed() {
  state = USER;
  drawing = [];
  x = [];
  time = 0;
  path = [];
}


function mouseReleased() {
  state = FOURIER;
  const skip = 1;
  for (let i = 0; i < drawing.length; i += skip) {
    x.push(new Complex(drawing[i].x, drawing[i].y));
  }
  fourierX = dft(x);


  fourierX.sort((a, b) => b.amp - a.amp);
}


function setup() {
  createCanvas(windowWidth, windowHeight);
  background(100);
  fill(255);
  textAlign(CENTER);
  textSize(64);
  text("Draw Something!", width/2, height/2);
  strokeWeight(5);
}


function epicycles(x, y, rotation, fourier) {
  for (let i = 0; i < fourier.length; i++) {
    let prevx = x;
    let prevy = y;
    let freq = fourier[i].freq;
    let radius = fourier[i].amp;
    let phase = fourier[i].phase;
    x += radius * cos(freq * time + phase + rotation);
    y += radius * sin(freq * time + phase + rotation);


    stroke(255, 100);
    noFill();
    ellipse(prevx, prevy, radius * 2);
    stroke(255);
    line(prevx, prevy, x, y);
  }
  return createVector(x, y);
}


function draw() {


  if (state == USER) {
  background(100);
    let point = createVector(mouseX - width / 2, mouseY - height / 2);
    drawing.push(point);
    stroke(255);
    noFill();
    beginShape();
    for (let v of drawing) {
      vertex(v.x + width / 2, v.y + height / 2);
    }
    endShape();
  } else if (state == FOURIER) {
  background(100);
    let v = epicycles(width / 2, height / 2, 0, fourierX);
    path.unshift(v);
    beginShape();
    noFill();
    strokeWeight(5);
    stroke(0, 255,255);
    for (let i = 0; i < path.length; i++) {
      vertex(path[i].x, path[i].y);
    }
    endShape();


    const dt = TWO_PI / fourierX.length;
    time += dt;


    if (time > TWO_PI) {
      time = 0;
      path = [];
    }
  }


}








</script>
  


</body></html>