Create animation from images previously created with Javis or Luxor?

Making the frames

My interpretation of your original post is that you wanted more control of when, and how many, frames were output from some kind of ongoing simulation. I thought the ‘snapshot’ feature was probably the way to go. Here’s a more complete version of my first example:

using Luxor

function run_it()
    Drawing(300, 300, :rec)
    origin()
    background("black")
    redcircle, greencircle, purplecircle = 
        ngon(O + (0, 10), 70, 3, π / 6, vertices=true)
    radius = 40
    drawing_number = 1
    for i in 1:1000
        frame_worthy = false
        pt2 = rand(BoundingBox())
        if distance(pt2, redcircle) < radius
            sethue("red")
            frame_worthy = true
        elseif distance(pt2, greencircle) < radius
            sethue("green")
            frame_worthy = true
        elseif distance(pt2, purplecircle) < radius
            sethue("purple")
            frame_worthy = true
        else
            frame_worthy = false
        end
        if frame_worthy
            circle(pt2, 4, :fill)
            framename = string("/tmp/", 
                  lpad(drawing_number, 10, "0"), ".png")
            snapshot(fname=framename)
            drawing_number += 1
        end 
    end
    finish()
    Luxor.FFMPEG.ffmpeg_exe(`
        -v 0
        -r 24  
        -f image2 
        -i /tmp/%10d.png -c:v libx264 -pix_fmt yuv420p 
        -y /tmp/dotty.mp4
        `);
end

run_it()

which makes this:

(GIF version if Discourse doesn’t like the video format)

dotty

and this approach lets you handle the “what if I don’t know the number of frames beforehand”, the “unknown outcome”, and “some stop condition” requirements you mentioned. Replace the for with a while - you could run it for weeks until some condition is met…

However, your example code initializes a new empty drawing on each iteration, so it’s not using this incremental snapshot feature.

And I’m not sure what finish() and preview() are doing for me. Moving them into the for loop, what I somehow would expect to be necessary, gives a compilation error.

With only a single drawing, you need just one finish() (to tidy up) and preview() (to see the finished drawing). I couldn’t see any compilation errors if you made a new drawing in every loop (preview() would obviously not be useful). But perhaps if you’re leaving a few thousand drawings open and unattended you might eventually see some other errors…

Making the video

I don’t know anything about VideoIO.jl, since Luxor predates it by a few years, and I’ve stuck with FFMPEG. :slight_smile: You should probably open a new issue/post to get help with that package from someone who knows more.

You don’t show the errors you see, but perhaps they’re the same as mine:

ERROR: MethodError: no method matching VideoIO.VideoWriter(::String, ::Vector{…}; encoder_options::@NamedTuple{…}, framerate::Int64)

Closest candidates are:
  VideoIO.VideoWriter(::AbstractString, ::Type{T}, ::Tuple{Integer, Integer}; codec_name, framerate, scanline_major, container_options, container_private_options, encoder_options, encoder_private_options, swscale_options, target_pix_fmt, pix_fmt_loss_flags, input_colorspace_details, allow_vio_gray_transform, sws_color_options, thread_count) where T
   @ VideoIO ~/.julia/packages/VideoIO/ZM7RD/src/encoding.jl:234
  VideoIO.VideoWriter(::Any, ::AbstractMatrix{T}; kwargs...) where T
   @ VideoIO ~/.julia/packages/VideoIO/ZM7RD/src/encoding.jl:379

The second argument should apparently be a Type or a Matrix:

julia-1.10> firstimg
1-element Vector{PermutedDimsArray{ColorTypes.RGB{FixedPointNumbers.N0f8}, 2, (2, 1), (2, 1), Matrix{ColorTypes.RGB{FixedPointNumbers.N0f8}}}}:
 [RGB{N0f8}(0.0,0.0,0.0) RGB{N0f8}(0.0,0.0,0.0) … RGB{N0f8}(0.0,0.0,0.0) RGB{N0f8}(0.0,0.0,0.0); RGB{N0f8}(0.0,0.0,0.0) RGB{N0f8}(0.0,0.0,0.0) … RGB{N0f8}(0.0,0.0,0.0) RGB{N0f8}(0.0,0.0,0.0); … ; RGB{N0f8}(0.0,0.0,0.0) RGB{N0f8}(0.0,0.0,0.0) … RGB{N0f8}(0.0,0.0,0.0) RGB{N0f8}(0.0,0.0,0.0); RGB{N0f8}(0.0,0.0,0.0) RGB{N0f8}(0.0,0.0,0.0) … RGB{N0f8}(0.0,0.0,0.0) RGB{N0f8}(0.0,0.0,0.0)]

VideoIO.load loads a 'vector of image arrays`, so perhaps you need to pass just the first element. I’m just guessing now, and video stuff is complicated… :slight_smile: