Javis.jl with Animations.jl


Creation date: 2020-09-09

Tags: julia, javis, visualization, animation

Some weeks have passed and a lot of new things were merged into a v0.2 branch of Javis.jl.

Today I want to have a look at the way to use Animations.jl within Javis. Javis for those of you who don't know is an animation package in Julia which I've created together with another streamer and Julia enthusiast Jacob Zelko about 2 months ago. You might wanna read my first post about it: Javis: The Beginning. But it's also outdated 😄 so feel free to continue here.

One of the main problems of Javis, and it has quite a few problems, is that all motions are boring as they are linear. If I "say": Move from point A to point B it can do that happily but it just uses simple interpolation.

Fortunately, someone implemented the Animations.jl package just for this reason, or at least it can be used for this purpose 😉

I've made a loading animation during one of my Twitch streams last week and want to show here how you can do this with Javis when we release v0.2 hopefully next week.

Basic drawing setup

Let's start with a very basic setup:

using Javis, Animations, Colors

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

video = Video(600, 400)

javis(
    video,
    [BackgroundAction(1:200, ground)],
    pathname = "images/loading.gif",
)
Black

I think we agree that when this is the loading animation everyone would just click away...

Let's add a circle moving from left to right:

using Javis, Animations, Colors

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

video = Video(600, 400)

actions = [
    Action((args...)->circle(O, 40, :fill), 
            Translation(Point(-250, 0), Point(250, 0)))
]

javis(
    video,
    [BackgroundAction(1:200, ground), actions...],
    pathname = "images/loading.gif",
)
Left to right

Okay as a gif it would probably be a good idea to move back to the start to not see the "end".

In the end I want to move in a circle anyway so that problem will be solved. Let's have a short look at the new lines:

actions = [
    Action((args...)->circle(O, 40, :fill), 
            Translation(Point(-250, 0), Point(250, 0)))
]

actions is now our array to hold all our Action functions and we add them to the rendering with having actions... so with splatting in the javis function itself to unpack the array.

The Action command is probably the most important one inside Javis and as quite a lot of different constructors. You can use

Action(1:200, (args...)->circle(O, 40, :fill), 
        Translation(Point(-250, 0), Point(250, 0))

to specify the frames to 1:200 for that Action. By default it uses the same frames as the Action before. In our case this will be the BackgroundAction.

The next argument here is an anonymous function with this a bit ugly (args...) syntax. This is kinda a short form for (video, action, frame) which are the arguments passed to every function.

Then the circle function doesn't use any of those arguments so I call it args... if I don't need the arguments. It simply draws a circle at the origin O (which is the center of the canvas).

For the actual animation we need Translation which takes a start and an end point.

Let us move the circle around a circle (actually we use rotation) to have a nice gif animation. The next point then is to finally change the speed of the circle.

Rotating

From now on I'll mostly change only the actions array:

actions = [
    Action((args...)->circle(Point(150,0), 20, :fill), Rotation(0.0, 2π))
]
Do a rotation

I think this looks much better as an animation but it still always has the same speed. How about speeding up and slowing down?

For that we need:

rotate_anim = Animation(
    [0, 1], # must go from 0 to 1
    [0.0, 2π],
    [sineio()]
)

Which basically creates an animation from t=0, to t=1 from value 0.0 to and has the easing function sineio(). List of easing functions

Note

Let's make a small detour to explaining the Animations package a bit. We do this by having a look at the easing function sineio.

First of all animations start with the timestamps which need to start with 0 and end with 1 in all Javis functions.

Then we specify the values here the values for rotation starting from 0 radians and moving to so a full circle. You can define τ if you wish 😄

The last part is the easing function which always has one less element in its array than the other two as it describes the movement between two values.

We can plot this with:

using Animations
using Plots

rotate_anim = Animations.Animation(
    [0, 1], # must go from 0 to 1
    [0, 2π],
    [sineio()],
)

ts = 0:0.01:1
ys = at.(rotate_anim, ts)

plot(ts, ys; labels=false, xaxis="t", yaxis="value")
Sineio plot

Then we specify it in the Action like this:

actions = [
    Action((args...)->circle(Point(150,0), 20, :fill), 
            rotate_anim,  Rotation(0.0, 1.0))
]
⚠ Important note
I've changed Rotation(0.0, 2π) to Rotation(0.0, 1.0) this now basically stands for do one rotation.
Do a rotation

I think this looks a bit slow but it has more to it by speeding up and slowing down.

We can change Rotation(0.0, 1.0) to Rotation(0.0, 3.0) to make three rotations.

actions = [
    Action((args...)->circle(Point(150,0), 20, :fill), 
            rotate_anim, Rotation(0.0, 3.0))
]
Do a rotation

Now it makes three rotations by speeding up in the beginning and then slowing down.

Move the circle in a more complicated way

You might be thinking: Okay a circle moving in a circle. Damn I'm borrrrred.

That's why we make it a little bit more fun with using SubAction.

The idea is the following: Move the circle from the center to the outside then rotate and translate back to the origin.

We could do three actions but this would kinda hide that we want to have only one circle.

I want to have a sineio() animation for the translation as well so let's define:

translate_anim = Animation(
    [0, 1], # must go from 0 to 1
    [O, Point(150, 0)],
    [sineio()],
)
translate_back_anim = Animation(
    [0, 1], # must go from 0 to 1
    [O, Point(-150, 0)],
    [sineio()],
)

and the actions now looks like this:

actions = [
    Action((args...)->circle(O, 20, :fill); subactions=[
        SubAction(1:20, translate_anim, translate()),
        SubAction(21:180, rotate_anim, rotate_around(Point(-150, 0))),
        SubAction(181:200, translate_back_anim, translate()),
    ])
]

We have a keyword subactions which uses a list of SubAction which all have frames (relative to the action) the Animation and what to do with it like translate or rotate/rotate_around.

We start at O for our circle and then move to Point(150, 0) we now want to rotate around the global origin which is at Point(-150, 0) in our current view.

Afterwards we translate back:

Do some more movement

Let us add color

Everything is better with color, right? How about changing the color of the blob from red to black.

color_anim = Animation(
    [0, 0.5, 1], # must go from 0 to 1
    [Lab(colorant"red"), Lab(colorant"cyan"), Lab(colorant"black")],
    [sineio(), sineio()],
)

This is kind of the standard theme now: Create a new Animation and add it to our list of subactions. In this case we specify three timestamps, three values (here colors) and two easing functions (always one less than the other two).

actions = [
    Action((args...)->circle(O, 20, :fill); subactions=[
        SubAction(1:30, translate_anim, translate()),
        SubAction(31:170, rotate_anim, rotate_around(Point(-150, 0))),
        SubAction(171:200, translate_back_anim, translate()),
        SubAction(1:200, color_anim, sethue()),
    ])
]

So we have just added another line for all frames to set the color (sethue) based on color_anim.

Do some more movement with color

Add more blobby blobs

Aye Aye captain.

actions = [
    Action(
        frame_start:(frame_start + 149),
        (args...)->circle(O, 20, :fill); subactions=[
        SubAction(1:30, translate_anim, translate()),
        SubAction(31:120, rotate_anim, rotate_around(Point(-150, 0))),
        SubAction(121:150, translate_back_anim, translate()),
        SubAction(1:150, color_anim, sethue()),
    ]) for frame_start in 1:10:50
]
MOAR BLOBS

I would say we have our loading animation in less than 50 lines of code.

Hope you enjoyed this post! If you're interested in Javis checkout the repository! If you're interested in the development itself and what to see it happening live: Follow me on Twitch

Enjoyed the post? Please keep reading and you might be interested in the next section 😉

Support and what I offer :)

I appreciate the support from all of you who:

Those of you who are new to this blog please check out the rest using the sidebar on the left for a list of categories or the search tool.

I would like to mention again, after spending more time reviewing code from my co-developer Jacob on Javis and reviewing code at Exercism.io, that I offer code reviews for your personal Julia project if you support me on Patreon.

Let me summarize a bit on what I can give you for just a small donation:

I expect the last one to be for short term learning projects to learn the language faster without missing out on too much. Please read more about it here.

Important: You might want to subscribe at the beginning of a month and just take the benefit. If you don't like the stuff you get: Feel free to unsubscribe before the end of the month (which is the time Patreon charges the credit card). Yes this is also true for code reviews which is extra work for me but I want you to be satisfied without taking any risk of paying for something you don't appreciate!

Thanks for reading and special thanks to my 9 patrons!

List of patrons paying more than 4$ per month:

Currently I get more than 20$ per month using Patreon and PayPal.

For a donation of a single dollar per month you get early access to the posts.

I'll keep you updated on Twitter OpenSourcES.



Want to be updated? Consider subscribing on Patreon for free
Subscribe to RSS