Created
November 9, 2020 19:04
-
-
Save Wikunia/cf735079e208e18d87bf23eadcac350f to your computer and use it in GitHub Desktop.
JuliaLang logo using Fourier series
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#= | |
Drawing Julia using a Fourier series. | |
A high definition animation can be seen here: https://youtu.be/rrmx2Q3sO1Y | |
This code is based on code kindly provided by ric-cioffi (https://github.com/ric-cioffi) | |
But was rewritten for v0.3 by Ole Kröger. | |
=# | |
using Javis, FFTW, FFTViews | |
using TravelingSalesmanHeuristics | |
function ground(args...) | |
background("black") | |
sethue("white") | |
end | |
function circ(; r = 10, vec = O, action = :stroke, color = "white") | |
sethue(color) | |
circle(O, r, action) | |
my_arrow(O, vec) | |
return vec | |
end | |
function my_arrow(start_pos, end_pos) | |
arrow( | |
start_pos, | |
end_pos; | |
linewidth = distance(start_pos, end_pos) / 100, | |
arrowheadlength = 7, | |
) | |
return end_pos | |
end | |
function draw_line( | |
p1 = O, | |
p2 = O; | |
color = "white", | |
action = :stroke, | |
edge = "solid", | |
linewidth = 3, | |
) | |
sethue(color) | |
setdash(edge) | |
setline(linewidth) | |
line(p1, p2, action) | |
end | |
function draw_path!(path, pos, color) | |
sethue(color) | |
push!(path, pos) | |
draw_line.(path[2:end], path[1:(end - 1)]; color = color) | |
end | |
function get_points(npoints, options) | |
Drawing() # julialogo needs a drawing | |
julialogo(action = :path, centered = true) | |
shapes = pathtopoly() | |
new_shapes = shapes[1:6] | |
last_i = 1 | |
# the circles in the JuliaLogo are part of a single shape | |
# this loop creates new shapes for each circle | |
for shape in shapes[7:7] | |
max_dist = 0.0 | |
for i in 2:length(shape) | |
d = distance(shape[i - 1], shape[i]) | |
if d > 3 | |
push!(new_shapes, shape[last_i:(i - 1)]) | |
last_i = i | |
end | |
end | |
end | |
push!(new_shapes, shapes[7][last_i:end]) | |
shapes = new_shapes | |
for i in 1:length(shapes) | |
shapes[i] .*= options.shape_scale | |
end | |
total_distance = 0.0 | |
for shape in shapes | |
total_distance += polyperimeter(shape) | |
end | |
parts = [] | |
points = Point[] | |
start_i = 1 | |
for shape in shapes | |
len = polyperimeter(shape) | |
portion = len / total_distance | |
nlocalpoints = floor(Int, portion * npoints) | |
new_points = [ | |
Javis.get_polypoint_at(shape, i / (nlocalpoints - 1)) | |
for i in 0:(nlocalpoints - 1) | |
] | |
append!(points, new_points) | |
new_i = start_i + length(new_points) - 1 | |
push!(parts, start_i:new_i) | |
start_i = new_i | |
end | |
return points, parts | |
end | |
c2p(c::Complex) = Point(real(c), imag(c)) | |
remap_idx(i::Int) = (-1)^i * floor(Int, i / 2) | |
remap_inv(n::Int) = 2n * sign(n) - 1 * (n > 0) | |
function animate_fourier(options) | |
npoints = options.npoints | |
nplay_frames = options.nplay_frames | |
nruns = options.nruns | |
nframes = nplay_frames + options.nend_frames | |
# obtain points from julialogo | |
points, parts = get_points(npoints, options) | |
npoints = length(points) | |
println("#points: $npoints") | |
# solve tsp to reduce length of extra edges | |
distmat = [distance(points[i], points[j]) for i in 1:npoints, j in 1:npoints] | |
path, cost = solve_tsp(distmat; quality_factor = options.tsp_quality_factor) | |
println("TSP cost: $cost") | |
points = points[path] # tsp saves the last point again | |
# optain the fft result and scale | |
x = [p.x for p in points] | |
y = [p.y for p in points] | |
fs = fft(complex.(x, y)) |> FFTView | |
# normalize the points as fs isn't normalized | |
fs ./= npoints | |
npoints = length(fs) | |
video = Video(options.width, options.height) | |
Background(1:nframes, ground) | |
circles = Object[] | |
for i in 1:npoints | |
ridx = remap_idx(i) | |
push!(circles, Object((args...) -> circ(; r = abs(fs[ridx]), vec = c2p(fs[ridx])))) | |
if i > 1 | |
# translate to the tip of the vector of the previous circle | |
act!(circles[i], Action(1:1, anim_translate(circles[i - 1]))) | |
end | |
ridx = remap_idx(i) | |
act!(circles[i], Action(1:nplay_frames, anim_rotate(0.0, ridx * 2π * nruns))) | |
end | |
trace_points = Point[] | |
Object(1:nframes, (args...) -> draw_path!(trace_points, pos(circles[end]), "red")) | |
render(video, pathname = options.filename) | |
end | |
function main() | |
hd_options = ( | |
npoints = 3001, # rough number of points for the shape => number of circles | |
nplay_frames = 1200, # number of frames for the animation of fourier | |
nruns = 2, # how often it's drawn | |
nend_frames = 200, # number of frames in the end | |
width = 1920, | |
height = 1080, | |
shape_scale = 2.5, # scale factor for the logo | |
tsp_quality_factor = 50, | |
filename = "julia_hd.mp4", | |
) | |
fast_options = ( | |
npoints = 1001, # rough number of points for the shape => number of circles | |
nplay_frames = 600, # number of frames for the animation of fourier | |
nruns = 1, # how often it's drawn | |
nend_frames = 200, # number of frames in the end | |
width = 1000, | |
height = 768, | |
shape_scale = 1.5, # scale factor for the logo | |
tsp_quality_factor = 40, | |
filename = "julia_fast.mp4", | |
) | |
gif_options = ( | |
npoints = 651, # rough number of points for the shape => number of circles | |
nplay_frames = 600, # number of frames for the animation of fourier | |
nruns = 2, # how often it's drawn | |
nend_frames = 0, # number of frames in the end | |
width = 350, | |
height = 219, | |
shape_scale = 0.8, # scale factor for the logo | |
tsp_quality_factor = 80, | |
filename = "gifs/julia_logo_dft.gif", | |
) | |
animate_fourier(gif_options) | |
end | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
How can we make Fourier of any word? And can we save any frame as svg?