SVG to path sequence, that can be used by Luxor and Javis

Using this template:

We have:

    Drawing() # julialogo needs a drawing
    julialogo(; action = :path, centered = true)
    shapes = pathtopoly()

I want to change it to something like this

    Drawing() # julialogo needs a drawing
    shapes = pathtopoly()

But, pathsvg doesn’t work as expected. Is there another alternative to transform svg into paths acceptable for the pathtopoly() function?

1 Like

I don’t know about Javis, but as far as Luxor.jl is concerned, SVG files are effectively “opaque” to the user, and they’ll just pass through from source file to target drawing without modification - about all you can do with an SVG source is to place it. I don’t think Rsvg.jl (the underlying mechanism) provides access to any paths at all, even if librsvg does… :frowning:

Any ideas how can I get this idea to form an approximated closed path, which julia will understand?

To do something like textpath("string"), but for the image imagepath("path-to-image").

I want to sample the (x,y) coordinates of n evenly-spaced points along the svg path, with Julia.

I think this is the best way to put it…

Hey @BuddhiLW ! Co-creator of Javis.jl here! Let me CC @Wikunia

I know that when I did something similar like this here: What I did were the following steps:

  1. Load an image with Images.jl
  2. Apply a black and white color scheme to the image
  3. Apply a shelling function to create shell of core shapes
  4. Pass or threshold values in your resulting image to remove noise that would not be part of the outline you want
  5. Convert each point within your image that you want into the appropriate (x, y) coordinate tuples.
  6. Convert each of these points from the outline of your image into a Luxor Point object and store the points in an array.

If you want the code I used for this outlining process, here is the code:

using Javis
using FFTW
using FFTViews
using FileIO
using Images
using TravelingSalesmanHeuristics

function ground(args...)

function circ(; r = 10, vec = O, action = :stroke, color = "white")
    circle(O, r, action)
    # my_arrow(O, vec)
    return vec

function my_arrow(start_pos, end_pos)
        linewidth = distance(start_pos, end_pos) / 100,
        arrowheadlength = 7,
    return end_pos

function draw_line(
    p1 = O,
    p2 = O;
    color = "white",
    action = :stroke,
    edge = "solid",
    linewidth = 3,
    line(p1, p2, action)

function draw_path!(path, pos, color)

    push!(path, pos)
    return draw_line.(path[2:end], path[1:(end - 1)]; color = color)

function get_points(img)
    findall(x -> x == 1, img) .|> x -> Point(x.I)

function texty()
	text("Loading Asset: Jacob Zelko", Point(0, 290); halign = :center)

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 = get_points(load(File(format"PNG", "jacob_outline.png")))
    npoints = length(points)
    println("#points: $npoints")
    # solve tsp to reduce length of extra edges
    distmat = [distance(points[i], points[j]) for i = 1:npoints, j = 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
    y = [p.x  - 372.6666666667 for p in points] 
    x = [p.y - 323.3333333333 / 1.25 for p in points] 

    fs = FFTView(fft(complex.(x, y)))
    # 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 = 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])))
	ridx = remap_idx(i)
	act!(circles[i], Action(1:nplay_frames, anim_rotate(0.0, ridx * 2π * nruns)))

    trace_points = Point[]
    Object(1:nframes, (args...) -> draw_path!(trace_points, pos(circles[end]), "white"))

    loading = Object(1:nframes, (args...) -> texty())
    act!(loading, Action(1:400, sineio(), appear(:draw_text)))

    return render(video; pathname = joinpath(@__DIR__, options.filename))
    # return render(video; liveview = true)

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",
    # )

    gif_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 = 520,
        height = 653,
        tsp_quality_factor = 10,
        filename = "jacob_outline.gif",

    # 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 = "julia_logo_dft.gif",
    # )
    return animate_fourier(gif_options)


And here is a reference image of my head and shoulders:

Which results in something like this:


Important to note is that shelling functions and the above steps will only go so far. You may need to instead open up your selected image that you want to outline in a separate image editor like Gimp to manually do something image manipulation. I think I may have had to do that… Although I cannot recall!

@Wikunia , anything else you could add here?

Does that help @BuddhiLW ?

P.S. Thanks for using Javis! As always, please let us know if you have any feedback or how we can help! :smile: We love to see posts about Javis and what you create!

1 Like