Skip to content

Instantly share code, notes, and snippets.

@hdf
Last active March 12, 2020 11:37
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save hdf/4f37bdd63d9cd00312c07d426f07a1a2 to your computer and use it in GitHub Desktop.
Save hdf/4f37bdd63d9cd00312c07d426f07a1a2 to your computer and use it in GitHub Desktop.
Discrete Fourier transformation
// Coding Challenge 130.3: Drawing with Fourier Transform and Epicycles
// Daniel Shiffman
// https://thecodingtrain.com/CodingChallenges/130.1-fourier-transform-drawing.html
// https://thecodingtrain.com/CodingChallenges/130.2-fourier-transform-drawing.html
// https://thecodingtrain.com/CodingChallenges/130.3-fourier-transform-drawing.html
// https://youtu.be/7_vKzcgpfvU
// https://editor.p5js.org/codingtrain/sketches/ldBlISrsQ
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;
}
Display the source blob
Display the rendered blob
Raw
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Discrete Fourier transformation</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.8.0/p5.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.8.0/addons/p5.dom.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/raphael/2.2.8/raphael.min.js"></script>
<script src="svg2vec.js"></script>
<script src="fourier.js"></script>
<script src="sketch.js"></script>
</head>
<body></body>
</html>
// Coding Challenge 130.3: Drawing with Fourier Transform and Epicycles
// Daniel Shiffman
// https://thecodingtrain.com/CodingChallenges/130.1-fourier-transform-drawing.html
// https://thecodingtrain.com/CodingChallenges/130.2-fourier-transform-drawing.html
// https://thecodingtrain.com/CodingChallenges/130.3-fourier-transform-drawing.html
// https://youtu.be/7_vKzcgpfvU
// https://editor.p5js.org/codingtrain/sketches/ldBlISrsQ
let x = [];
let fourierX;
let time = 0;
let path = [];
let lengths = [];
if (typeof(drawing) == 'undefined')
var drawing = [];
let w = 300;
let h = 300;
window.onhashchange = function() {
x = [];
time = 0;
path = [];
drawing = [];
current_svg_xml = '';
setup();
};
function setup() {
generatePointsFromSvg((window.location.hash ? window.location.hash.substr(1) : 'gh') + '.svg', (r) => {
let scale = (w/r[1] < h/r[2]) ? w / (r[1] + 1) : h / (r[2] + 1);
drawing = r[0].flat().map((e) => {return {x: e.x * scale, y: e.y * scale}});
lengths = []; // Do not allow large jumps
for (let i = 1; i < drawing.length; i++)
if (Math.sqrt(Math.pow(drawing[i].x - drawing[i-1].x, 2) + Math.pow(drawing[i].y - drawing[i-1].y, 2)) > 10)
lengths.push(i);
/*lengths = r[0].map((e) => e.length).slice(0, -1);
for (let i = 1; i < lengths.length; i++)
lengths[i] += lengths[i-1];*/
setup();
});
createCanvas(w, h);
frameRate(60);
const skip = 1;
for (let i = 0; i < drawing.length; i += skip) {
const c = new Complex(drawing[i].x, drawing[i].y);
x.push(c);
}
fourierX = dft(x);
fourierX.sort((a, b) => b.amp - a.amp);
}
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 waves(fourierX) {
/*if (fourierX.length < 1) return;
let fourier = fourierX.slice(0, 10).sort((a, b) => a.freq - b.freq);
let l = fourier.length;
colorMode(HSB, l);
for (let i = 0; i < l; i++) {
beginShape();
for (let i2 = 0; i2 <= (time / TWO_PI) * width; i2++) {
vertex(i2, height / 4 + fourier[i].amp + fourier[i].amp * sin(i2 / (TWO_PI * 4) + fourier[i].phase));
stroke(i, l, l);
}
endShape();
}
colorMode(RGB, 255);*/
}
function draw() {
background(0);
waves(fourierX);
let v = epicycles(width / 2, height / 2, 0, fourierX);
path.unshift(v);
beginShape();
noFill();
for (let i = 0; i < path.length; i++) {
if (lengths.includes(path.length - i)) {
endShape();
beginShape();
}
vertex(path[i].x, path[i].y);
}
endShape();
const dt = TWO_PI / fourierX.length;
time += dt;
if (time > TWO_PI) {
time = 0;
path = [];
}
}
// Based on: https://github.com/Shinao/PathToPoints
// To generate text: https://danmarshall.github.io/google-font-to-svg-path/
// Also consider: https://vecta.io/nano
let current_svg_xml = '';
function generatePointsFromSvg(file, cb, step_point) {
if (typeof(step_point) == 'undefined') step_point = 0.5;
if (current_svg_xml == '') {
let xhr = new XMLHttpRequest();
xhr.open('GET', file, true);
xhr.onload = function(e) {
current_svg_xml = this.response;
cb(generatePointsFromSvg(file, cb, step_point));
};
xhr.send();
return;
}
let doc = (new DOMParser()).parseFromString(current_svg_xml, "application/xml");
let paths = doc.getElementsByTagName('path');
// Read each paths from svg
let all_points = [];
let bbox_path = doc.children[0].getAttribute('viewBox').split(' ');
let width = parseFloat(bbox_path[2]), height = parseFloat(bbox_path[3]);
for (let i = 0; i < paths.length; ++i) {
let path = paths[i].getAttribute('d').replace(' ', ',');
// get points at regular intervals
let data_points = [];
let c, l = Raphael.getTotalLength(path), step = step_point * Math.pow(Math.floor(Math.log(l) / Math.log(10)), 2);
for (c = 0; c < l; c += step) {
let point = Raphael.getPointAtLength(path, c);
point.x = (point.x - (width / 2));
point.y = (point.y - (height / 2));
delete point.alpha;
data_points.push(point);
}
all_points.push(data_points);
}
return [all_points, width, height];
}
@hdf
Copy link
Author

hdf commented Apr 10, 2019

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment