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

**URL:** <https://discourse.julialang.org/t/svg-to-path-sequence-that-can-be-used-by-luxor-and-javis/103267>\
**Category:** General Usage\
**Created:** [August 27, 2023, 8:50pm UTC](https://discourse.julialang.org/t/svg-to-path-sequence-that-can-be-used-by-luxor-and-javis/103267 "2023-08-27T20:50:56Z")\
**Posts on this page:** 10\
**Page:** 1

<div class="post-metadata">

**Author:** ![BuddhiLW](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/buddhilw/32/33168_2.png) [@BuddhiLW](https://discourse.julialang.org/u/BuddhiLW)\
**Post date:** [August 27, 2023, 8:50pm UTC](https://discourse.julialang.org/t/svg-to-path-sequence-that-can-be-used-by-luxor-and-javis/103267/1 "2023-08-27T20:50:56Z")

</div>

Using this template: [https://github.com/JuliaAnimators/Javis.jl/blob/main/examples/fourier.jl](https://github.com/JuliaAnimators/Javis.jl/blob/main/examples/fourier.jl)

We have:

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

```

I want to change it to something like this

```julia
    Drawing() # julialogo needs a drawing
    Javis.pathsvg("../../svg-image.svg")
    shapes = pathtopoly()

```

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

---

<div class="post-metadata">

**Author:** ![cormullion](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/cormullion/32/49131_2.png) [@cormullion](https://discourse.julialang.org/u/cormullion)\
**Post date:** [August 27, 2023, 9:22pm UTC](https://discourse.julialang.org/t/svg-to-path-sequence-that-can-be-used-by-luxor-and-javis/103267/2 "2023-08-27T21:22:16Z")

</div>

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… ☹

---

<div class="post-metadata">

**Author:** ![BuddhiLW](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/buddhilw/32/33168_2.png) [@BuddhiLW](https://discourse.julialang.org/u/BuddhiLW)\
**Post date:** [August 27, 2023, 10:50pm UTC](https://discourse.julialang.org/t/svg-to-path-sequence-that-can-be-used-by-luxor-and-javis/103267/3 "2023-08-27T22:50:02Z")

</div>

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

---

<div class="post-metadata">

**Author:** ![BuddhiLW](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/buddhilw/32/33168_2.png) [@BuddhiLW](https://discourse.julialang.org/u/BuddhiLW)\
**Post date:** [August 27, 2023, 10:51pm UTC](https://discourse.julialang.org/t/svg-to-path-sequence-that-can-be-used-by-luxor-and-javis/103267/4 "2023-08-27T22:51:31Z")

</div>

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

---

<div class="post-metadata">

**Author:** ![BuddhiLW](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/buddhilw/32/33168_2.png) [@BuddhiLW](https://discourse.julialang.org/u/BuddhiLW)\
**Post date:** [August 27, 2023, 11:02pm UTC](https://discourse.julialang.org/t/svg-to-path-sequence-that-can-be-used-by-luxor-and-javis/103267/5 "2023-08-27T23:02:52Z")

</div>

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…

---

<div class="post-metadata">

**Author:** ![TheCedarPrince](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/thecedarprince/32/17323_2.png) [@TheCedarPrince](https://discourse.julialang.org/u/TheCedarPrince)\
**Post date:** [August 28, 2023, 1:13am UTC](https://discourse.julialang.org/t/svg-to-path-sequence-that-can-be-used-by-luxor-and-javis/103267/6 "2023-08-28T01:13:04Z")

</div>

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

I know that when I did something similar like this here: [https://youtu.be/ckvsc6ukdOc?t=15](https://youtu.be/ckvsc6ukdOc?t=15) 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:

```julia
using Javis
using FFTW
using FFTViews
using FileIO
using Images
using TravelingSalesmanHeuristics

function ground(args...)
    background("black")
    sethue("white")
end

function circ(; r = 10, vec = O, action = :stroke, color = "white")
    sethue("black")
    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)
    Luxor.setline(linewidth)
    line(p1, p2, action)
end

function draw_path!(path, pos, color)
    sethue(color)

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

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

function texty()
	fontsize(36)
	text("Loading Asset: Jacob Zelko", Point(0, 290); halign = :center)
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 = 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])))
	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]), "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)
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",
    # )

    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)
end

main()

```

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

 ![jacob_outline](https://global.discourse-cdn.com/julialang/original/3X/a/4/a48b167b5debb00b3197b49ec25a6e17dd5e24b1.png)

Which results in something like this:

![jacob_outline](https://global.discourse-cdn.com/julialang/original/3X/e/8/e8062b2056e6bbf8ccddaef434a36f941c6a852d.gif)

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! 😄 We love to see posts about Javis and what you create!

---

<div class="post-metadata">

**Author:** ![saimgardezi](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/saimgardezi/32/211223_2.png) [@saimgardezi](https://discourse.julialang.org/u/saimgardezi)\
**Post date:** [August 11, 2024, 10:56am UTC](https://discourse.julialang.org/t/svg-to-path-sequence-that-can-be-used-by-luxor-and-javis/103267/7 "2024-08-11T10:56:54Z")

</div>

Hi, I am getting this error while running code in Jupyter Notebook . Julia is new to me. Actually, I want to create exactly like this animation for my organization’s logo ([https://youtu.be/rrmx2Q3sO1Y](https://youtu.be/rrmx2Q3sO1Y)): Kindly, help me to solve the issue.

 ![BME2 (1)](https://global.discourse-cdn.com/julialang/original/3X/c/2/c298327a69436420b3da8b5d278fae82962754e9.png)

**Code:**

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

function ground(args…)  
background(“black”)  
sethue(“white”)  
end

function circ(; r = 10, vec = O, action = :stroke, color = “white”)  
sethue(“black”)  
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)  
Luxor.setline(linewidth)  
line(p1, p2, action)  
end

function draw\_path!(path, pos, color)  
sethue(color)

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

```

end

function get\_points(img)  
findall(x → x == 1, img) .|\> x → Point(x.I)  
end

function texty()  
fontsize(36)  
text(“Loading Asset: Jacob Zelko”, Point(0, 290); halign = :center)  
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 = get_points(load(File(format"PNG", "BazmeBMEQR.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])))
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]), "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)

```

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

```
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 = "BazmeBME.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)

```

end

main()

**Error** :  
#points: 0  
MethodError: no method matching solve\_tsp(::Matrix{Any}; quality\_factor::Int64)

Closest candidates are:  
solve\_tsp(::AbstractMatrix{T}; quality\_factor) where T\<:Real  
@ TravelingSalesmanHeuristics C:\Users\admin.julia\packages\TravelingSalesmanHeuristics\ryoSo\src\TravelingSalesmanHeuristics.jl:31

Stacktrace:  
[1] animate\_fourier(options::@NamedTuple{npoints::Int64, nplay\_frames::Int64, nruns::Int64, nend\_frames::Int64, width::Int64, height::Int64, tsp\_quality\_factor::Int64, filename::String})  
@ Main .\In[11]:78  
[2] main()  
@ Main .\In[11]:154  
[3] top-level scope  
@ In[11]:157

---

<div class="post-metadata">

**Author:** ![cormullion](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/cormullion/32/49131_2.png) [@cormullion](https://discourse.julialang.org/u/cormullion)\
**Post date:** [August 11, 2024, 1:34pm UTC](https://discourse.julialang.org/t/svg-to-path-sequence-that-can-be-used-by-luxor-and-javis/103267/8 "2024-08-11T13:34:35Z")

</div>

You can format the code in your post by enclosing it in three backticks:

````julia
    ```
    code
    ```

````

Try your code after converting your image to an outline in white on black - I think `getpoints` is looking for white pixels?

---

<div class="post-metadata">

**Author:** ![Eben60](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/eben60/32/13475_2.png) [@Eben60](https://discourse.julialang.org/u/Eben60)\
**Post date:** [August 11, 2024, 2:50pm UTC](https://discourse.julialang.org/t/svg-to-path-sequence-that-can-be-used-by-luxor-and-javis/103267/9 "2024-08-11T14:50:53Z")

</div>

For animation, you need a line draw. The logo contains filled areas, you have first to decide what to do with them. Further, it contains highly detailed stuff that would be difficult to draw. Still further, the logo contains gaps, which obviously cannot be animated by drawing a continuous line. So, the very first thing would be to decide how you would like to change/simplify the logo.

For the picture conversion personally I would prefer an interactive tool, I’d take ImageJ / FIJI. Here is what came out (`convert to grayscale |> threshold |> outline`), but I do not think that is what you want to have.

![BME2-outline](https://global.discourse-cdn.com/julialang/original/3X/e/6/e6b6fe33bca9301802dff35876b6f956ed229fa8.png)

Have you actually watched the highly instructive video [linked](https://discourse.julialang.org/t/fourier-draws-the-julia-logo/47309/12) in the other topic?

---

<div class="post-metadata">

**Author:** ![arbitrandomuser](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/arbitrandomuser/32/19374_2.png) [@arbitrandomuser](https://discourse.julialang.org/u/arbitrandomuser)\
**Post date:** [November 26, 2024, 4:49am UTC](https://discourse.julialang.org/t/svg-to-path-sequence-that-can-be-used-by-luxor-and-javis/103267/10 "2024-11-26T04:49:15Z")

</div>

such animations are much easier with the [create object feature](https://juliaanimators.github.io/Javis.jl/dev/tutorials/tutorial_partialdraw/) in the main branch of Javis which never made it to a release…(yet 🙂 ) .

this allows pretty much any random luxor path to be animated as though it was being drawn.

but how do you import an svg as a “path” in luxor and not as a bitmap ?  
i had made a wrapper around [SVGtiny.jl](https://github.com/ArbitRandomUser/SVGtiny.jl) to import SVG’s into Luxor as paths ,  
you will need libsvgtiny installed on your system for this , i never got around to making a jll
