Skip to content

Instantly share code, notes, and snippets.

@Wikunia
Created November 9, 2020 19:04
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save Wikunia/cf735079e208e18d87bf23eadcac350f to your computer and use it in GitHub Desktop.
Save Wikunia/cf735079e208e18d87bf23eadcac350f to your computer and use it in GitHub Desktop.
JuliaLang logo using Fourier series
#=
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()
@aburousan
Copy link

How can we make Fourier of any word? And can we save any frame as svg?

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