Creation date: 2020-09-09
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.
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",
)
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",
)
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.
From now on I'll mostly change only the actions
array:
actions = [
Action((args...)->circle(Point(150,0), 20, :fill), Rotation(0.0, 2π))
]
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 2π
and has the easing function sineio()
. List of easing functions
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 2π
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")
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))
]
Rotation(0.0, 2π)
to Rotation(0.0, 1.0)
this now basically stands for do one 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))
]
Now it makes three rotations by speeding up in the beginning and then slowing down.
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:
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
.
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
]
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 😉
I appreciate the support from all of you who:
read my posts
share my posts
give me ideas
support me financially
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:
1$ per month: You get blog posts two days earlier than everyone else
4$ per month: I mention your name at the end of each livestream and blog
+ I'll link to your personal page there
10$ per month: Actually I'm still looking for a good offer here. It's just for the people who love what I do ❤
50$ per month:
personal mentoring in the form of code reviews
I review up to 150 lines of code per week
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:
Site Wang
Gurvesh Sanghera
Szymon Bęczkowski
Logan Kilpatrick
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.