Skip to content

Commit 1e74392

Browse files
Morphs and Partially draw Objects (#482)
* overrides for strokepath , strokepreserve , fillpath and fillpreserve, also introduces new pathtopoly * JPath struct added to Object.jl , getjpaths() and drawobj_jpaths(obj) written * morph now modifies draws morphed jpaths , and calls original object.func with luxor draw disabled * morphing with Animations.jl using functions * partial draw
1 parent 6add2f2 commit 1e74392

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

55 files changed

+1608
-170
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ jobs:
99
strategy:
1010
matrix:
1111
version:
12-
- '1.5'
12+
- '1.6'
1313
- '1'
1414
os:
1515
- ubuntu-latest

CHANGELOG.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,14 @@
22

33
# PR changes
44
- changed render method for mp4 to use ffmpeg directly inplace of VideoIO
5-
5+
- Added jpaths a field in Object that is useful for morphs and partial drawing
6+
- Added morphs to arbitrary objects and functions.
7+
- Keyframed morphs with Animations.jl are possible.
8+
- Added ability to partially draw any object, and have animations of showing them get created.
9+
- One tutorial added on how to use morphs
10+
- tutorial on partial draw / show creation
11+
- Few tests for morphs added
12+
- test for partial draw/ show creation
613

714
## v0.9.0 (26th of May 2022)
815
- Ability to use Luxor functionality without rendering an animation

Project.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,6 @@ Images = "0.20, 0.21, 0.22, 0.23, 0.24, 0.25"
3636
Interact = "0.10"
3737
LaTeXStrings = "1.1"
3838
LightXML = "0.9"
39-
Luxor = "2.12, 3"
39+
Luxor = "3.5"
4040
ProgressMeter = "1"
41-
julia = "1.5"
41+
julia = "1.6"

docs/make.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ makedocs(;
2424
"tutorials/tutorial_6.md",
2525
"tutorials/tutorial_7.md",
2626
"tutorials/tutorial_8.md",
27+
"tutorials/tutorial_morphing.md",
28+
"tutorials/tutorial_partialdraw.md",
2729
],
2830
"HowTo" => "howto.md",
2931
"Workflows" => "workflows.md",

docs/src/assets/box_to_circ_anim.gif

194 KB
Loading
340 KB
Loading

docs/src/assets/circ_to_box.gif

319 KB
Loading

docs/src/assets/circ_to_box_func.gif

269 KB
Loading

docs/src/assets/createcircle.gif

45.9 KB
Loading

docs/src/assets/createcircle2.gif

50.2 KB
Loading

docs/src/assets/createcircle3.gif

50.6 KB
Loading

docs/src/assets/createcircle4.gif

45.9 KB
Loading

docs/src/assets/demo_partialdraw.gif

21.2 KB
Loading

docs/src/tutorials.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,5 +21,7 @@ Currently, these tutorials are available:
2121
- [**Tutorial 6: Taming the Elements**](tutorials/tutorial_6.md) - how to use `change` to grow or shrink arbitrary objects and using `Javis` with other Julia packages.
2222
- [**Tutorial 7: Using Animations.jl to Create something with more Pep!**](tutorials/tutorial_7.md) - an advanced tutorial to make your animations more interesting.
2323
- [**Tutorial 8: Fun with Layers! An Intro to `@JLayer`**](tutorials/tutorial_8.md) - a `Javis` state of the art tutorial on how to use `@JLayer` to make composable animations.
24+
- [**Tutorial 9: Morphing**](tutorials/tutorial_morphing.md) - Morphing `Javis` Objects.
25+
- [**Tutorial 10: Animate Object Creation **](tutorials/tutorial_partialdraw.md) - Animate Object Creations.
2426

2527
If you spot an issue with any of these tutorials, please let us know! Thank you!
Lines changed: 217 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,217 @@
1+
# **Tutorial 9:** Morphing `Javis` Objects
2+
There are multiple ways to morph an object in Javis.
3+
4+
- Using `morph_to(::Object)` method. Any Object can be morphed to any other object using this method.
5+
- Using `morph_to(::Function)` method. Similar to `morph_to(::Object)` but morphs to a function instead. Can morph an object to a function that contains Luxor calls to draw what it should morphed into.
6+
- Specifying an Action with an `Animation` along with `morph()` to make keyframed morphings. This helps making and timing a sequence of morph animations easier.
7+
8+
## Morphing one object to another.
9+
10+
Like other animations `morph_to(::Object)` is to be used with action. To learn more about Actions refer to [Tutorial 5](tutorial_5.md).
11+
Here is a simple code snippet on how to use `morph_to`...
12+
```julia
13+
using Javis
14+
15+
video = Video(500,500)
16+
nframes = 160
17+
18+
function circdraw(color)
19+
sethue(color)
20+
setopacity(0.5)
21+
circle(O,100,:fillpreserve)
22+
setopacity(1.0)
23+
sethue("white")
24+
strokepath()
25+
end
26+
27+
function boxdraw(color)
28+
sethue(color)
29+
box(O,100,100,:fillpreserve)
30+
setopacity(1.0)
31+
sethue("white")
32+
strokepath()
33+
end
34+
Background(1:nframes,(args...)->background("black"))
35+
boxobj = Object((v,o,f) -> boxdraw("green"))
36+
circobj = Object((v,o,f) -> circdraw("red"))
37+
38+
transform_to_box = Action(20:nframes-20, morph_to(boxobj))
39+
act!(circobj, transform_to_box)
40+
render(video,pathname="circ_to_box.gif")
41+
```
42+
43+
![](../assets/circ_to_box.gif)
44+
45+
If you aren't familiar with this syntax `(v,o,f)-> circdraw("red")` its an "anonymous" function or sometimes called a lambda function.
46+
Basically a nameless function that is written on the spot in that line of code . One might as well use any other function `func` in place of it
47+
(which takes at least 3 arguments `video,object,frame`). Elsewhere in the docs/tutorials you will come across
48+
something of the form `Object( (args...) -> ("some code here") )`. This is [slurping](https://docs.julialang.org/en/v1/manual/faq/#The-two-uses-of-the-...-operator:-slurping-and-splatting) and is similar to packing `*args` in python.
49+
50+
We created two objects `circobj` and `boxobj` . `circobj` ofcourse is a circle because its drawing function `(v,o,f) -> circdraw("red")`
51+
draws a circle, with a `color=red`, filling at `0.5` opacity, and then makes a white outline (stroke).
52+
`boxobj`'s function draws an opaque green box, with white outline.
53+
54+
This Object function is called repeatedly at render-time at every frame that the object exists to draw this object. The appropriate `video`,`object`, and `frame` are passed to
55+
this function at render time.
56+
Javis then has other tricks up its sleeve to scale/move/morph whats going to be drawn depending on the
57+
frame and object to effect out animations through Actions. This is roughly the idea behind Javis's Object-Action mechanism
58+
59+
We defined a `transform_to_box` Action which runs from frame 20 to lastframe-20 . The `Action` morphs whatever object its acted upon, into what looks
60+
like `boxobj`. Note that `boxobj` and `circobj` are separate objects all the time, even after the `Action` (it just happens that they overlap each other). As the Action keeps getting applied at render time frame by frame, the "drawing" of `circobj` starts to look like `boxobj`'s drawing.
61+
62+
The Action is applied to the `circobj` with the `act!` function.
63+
64+
Note that the `boxobj` is present throughout as the `circobj` is morphing.
65+
If you want to hide it you can set its opacity to 0 with another action (to make it disappear) and set its frames to be drawn for 1 frame only (for efficiency).
66+
```julia
67+
Background(1:nframes,(args...)->background("black"))
68+
boxobj = Object(1:1 , (args...) -> boxdraw("green") )
69+
circobj = Object(1:nframes,(args...) -> circdraw("red"))
70+
71+
transform_to_box = Action(20:nframes-20, morph_to(boxobj))
72+
hide_action = Action(1:1, (args...)->setopacity(0.0) )
73+
74+
act!(circobj, transform_to_box)
75+
act!(boxobj, hide_action)
76+
77+
render(video,pathname="circ_to_box_hidden.gif")
78+
```
79+
80+
However you can directly specify a shape an object has to morph to without making an Object using `morph_to(f::Function)` i.e passing a function as an argument.
81+
82+
## Morphing an `Object` using a `Function`
83+
84+
```julia
85+
Background(1:nframes,(args...)->background("black"))
86+
#boxobj = Object(1:1 , (args...) -> boxdraw("green") )
87+
circobj = Object(1:nframes,(args...) -> circdraw("red"))
88+
89+
transform_to_box = Action(20:nframes-20, morph_to(boxdraw,["blue"]))
90+
#hide_action = Action(1:1, (args...)->setopacity(0.0) )
91+
92+
act!(circobj, transform_to_box)
93+
#act!(boxobj, hide_action)
94+
95+
render(video,pathname="circ_to_box_func.gif")
96+
```
97+
98+
![](../assets/circ_to_box_func.gif)
99+
100+
Here we have morphed the circle without defining an object to morph to. Rather the shape it has to morph into
101+
is given by a `Function`.
102+
The general syntax is `morph_to(fn::Function,args::Array=[])`
103+
. `args` is an array of arguments that is to be passed to the function.
104+
Here we morph `circobj` to a shape
105+
that would is drawn by `boxdraw("blue")`. Morphed Objects can be furthur morphed into
106+
other shapes by carrying out another `Action` further in the timeline.
107+
108+
## Keyframed morphs using Animations.jl
109+
110+
Another mechanism for morphing is by passing `morph()` to `Action` along with an `Animation`
111+
For a tutorial on how to use Animations.jl look at [Tutorial 7](tutorial_7.md),
112+
113+
```julia
114+
using Javis
115+
using Animations
116+
video = Video(500,500)
117+
nframes = 160
118+
119+
function circdraw(color)
120+
sethue(color)
121+
setopacity(0.5)
122+
circle(O,50,:fillpreserve)
123+
setopacity(1.0)
124+
sethue("white")
125+
strokepath()
126+
end
127+
128+
function boxdraw(color)
129+
sethue(color)
130+
box(O,100,100,:fillpreserve)
131+
setopacity(1.0)
132+
sethue("white")
133+
strokepath()
134+
end
135+
136+
function stardraw()
137+
sethue("white")
138+
star(O,100,5,0.5,0.0,:stroke)
139+
end
140+
141+
Background(1:nframes+10,(args...)->background("black"))
142+
boxobj = Object(1:nframes+10 , (args...) -> boxdraw("green") )
143+
144+
anim = Animation([0,1],MorphFunction[(boxdraw,["green"]),(circdraw,["red"])] )
145+
146+
action = Action(1:nframes,anim,morph())
147+
act!(boxobj,action)
148+
render(video,pathname="box_to_circ_hidden.gif")
149+
```
150+
151+
Take a look at `anim`. It is of type `Animation`.
152+
First lets look at a simpler instance of `Animation`.
153+
```
154+
Ex:1
155+
Animation([0,1],[2,4])
156+
```
157+
Think of Animations like a "map" or a "function" (in the math sense) thats maps values from its first argument (`[0,1]` above) to another set of values (`[2,4]`) . This means that
158+
0 gets mapped to 2 and 1 gets mapped to 4 and all values inbetween are linearly interpolated.
159+
Another Example
160+
```
161+
Ex:2
162+
Animation([0,0.3,1],[3,4.5,7])
163+
```
164+
165+
This animation maps 0 to 3 , 0.3 -> 4.5 and 1->7. And all values inbetween are linear interpolations.
166+
167+
Take a look at the `Animations.jl` package for an indepth explanation on how to have different interpolations to make your animations look way cooler. ( for example the `sineio` interpolaation is slow at first speeds up in between and gradually slows to a halt )
168+
169+
One can in principle provide values beyond 0 and 1 for the first argument however Javis requires `Animation` objects to have the first argument to be from 0 to 1.
170+
This `Animation` object is passed to an `Action`, and Javis interprets 0 to be the first frame
171+
of the Action and 1 to be the final frame of the Action.
172+
173+
In the big code snippet above we can see that the second array passed to Animation is
174+
an array of `MorphFunction`s.
175+
`MorphFunction` is a struct . This struct has 2 fields. The fields are `func` and `args`. These arguments are used to specify drawing functions and the arguments to be passed to them , The `Array` of `MorphFunction` passed to the `Animation` defines a sequence of shapes/drawings that the `Object` should be morphed into one by one in that order. Each shape/drawing is what would have been got by calling `func(args...)` of the respective `MorphFunction`. In the example above there are only two in shapes in the sequence a green box and a red circle (`boxdraw("green")` and `circdraw("red")`).
176+
Typically the first `MorphFunction` should draw the same thing that `Object` is.
177+
178+
The general idea of whats going on is we are making an `Animation` that maps `0` (i.e the first frame of the action.) to `MorphFunction(boxdraw,["green"])` and `1` (last frame of the action) to ` MorphFunction(circdraw,["red"])` and Javis handles the interpolation between them.
179+
180+
Thus we have made an `Animation` called `anim`. Then we made an `action` with this `anim`. We called it `action` . Then we applied the action on our object `boxobj` to get ...
181+
182+
![](../assets/box_to_circ_anim.gif)
183+
184+
The way of morphing shines when you have to do multiple morphs in a sequence and with different timings. Lets look at another example taking object to morph box(initial shape) -> star -> circle in a sequence.
185+
186+
Change the lines describing the animation to
187+
```julia
188+
anim = Animation([0, 0.7, 1],MorphFunction[(boxdraw, ["green"]), (stardraw, []), (circdraw, ["red"])])
189+
```
190+
`stardraw` draws a white star without fill. The function does not take an argument and therefore the `Tuple` with `stardraw` should have an empty `Array` at its
191+
second index. If your drawing functions do not take any arguments you can pass it as function itself, and need not wrap it in a `Tuple`.
192+
193+
Ex. suppose `mydraw1` , `mydraw2` and `mydraw4` take a color as an argument but `mydraw3` does not take any arguments.
194+
195+
```julia
196+
anim = Animation([0, t1, t2, 1],MorphFunction[ (mydraw1,["red"]), (mydraw2,["blue"]), mydraw3, (mydraw4,["black"]) ])
197+
```
198+
199+
A third way to pass functions to morph into is to simply pass a function an its arguments in a `Tuple`
200+
201+
```julia
202+
anim = Animation([0, t1, t2, 1],MorphFunction[ (mydraw1,"red"), (mydraw2,"blue"), mydraw3, (mydraw4,"black") ])
203+
```
204+
205+
When passed this way the first element of the Tuple is taken to be the function and the subsequent elements are the arguments to be
206+
passed to the function.
207+
208+
![](../assets/box_to_star_to_circ_anim.gif)
209+
210+
What we see now is from the beginning to 0.7 fraction of the `Action`'s frames it carries
211+
out the morphing from a `boxdraw("green")` to `stardraw()`.
212+
And the remainder of the `Action`'s frames it morphs from `stardraw()` to `circdraw("red")`. Once again , do look up Animations.jl and Tutorial 7 to see how you pass easing functions to manipulate the timing of animations (for example ... initially slow - fast in the middle - slow in the end) .
213+
Now you know a bit about Morphing . Remember just like any other Action you can stack morphing actions with other Actions (translations, scaling etc) to bring about effects you desire.
214+
215+
> **Author(s):** John George Francis (@arbitrandomuser)
216+
> **Date:** May 28th, 2022 \
217+
> **Tag(s):** action, morphing, object, animation
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
# **Tutorial 10:** Animating Creation of Objects
2+
3+
A cool effect of bringing in `Objects` into your video/gif is to show it being
4+
drawn out incrementally from nothing.
5+
6+
![](../assets/demo_partialdraw.gif)
7+
8+
In this tutorial we'll take a look at how you can make these kind of animations
9+
10+
11+
## The `showcreation` function
12+
13+
The general syntax to animate the creation of an `object` is
14+
15+
```julia
16+
action = Action(startframe:endframe, showcreation() )
17+
act!(obj, action)
18+
```
19+
20+
This starts the creation of the object at `startframe` and the `object` is completely
21+
drawn when the timeline reaches `endframe`.
22+
23+
lets see this in example.
24+
25+
```julia
26+
using Javis
27+
video = Video(500,500)
28+
29+
Background(1:120,(args...)->begin
30+
background("black")
31+
sethue("white")
32+
end)
33+
circ = Object(1:120, (args...)-> circle(O,100,:stroke))
34+
35+
action_showcreate = Action(1:60,showcreation())
36+
act!(circ,action_showcreate)
37+
38+
render(video,pathname="createcircle.gif")
39+
```
40+
41+
![](../assets/createcircle.gif)
42+
43+
You should see a circle being created in your video. ( I've added in the frame numbers
44+
in the gif so that the beginning and end of the gif are easily identifiable )
45+
46+
What if we wanted to show the object being created at a later point in the timeline. Say
47+
we want it to be created at frame 30 and finish at frame 90.
48+
Thats simple! we change the frames that the Action works on . Change the line with `action_showcreate` in the above example to...
49+
50+
```julia
51+
action_showcreate = Action(30:90, showcreation())
52+
```
53+
54+
![](../assets/createcircle2.gif)
55+
56+
Oops! , Thats (probably) not what we wanted. (look at the frame numbers) . What happened was `obj` exists from frame 1 to 120. But the show creation acts on it from 30 to 90. So the object exists from frame 1 to 30 as it is . Then its creation is animated from 30 to 90 and from 90 to 120 it remains as such.
57+
58+
One way to mitigate this is to change the frames `obj` exists. Make this change in the code
59+
above
60+
61+
```julia
62+
circ = Object(30:120, (args...)->circle(O,100,:stroke))
63+
```
64+
65+
![](../assets/createcircle3.gif)
66+
67+
Somethings still wrong!. One thing we forgot is the frames you mention in `Action` are
68+
the frames relative to the `Object`s existence. So what happened now is the object is put
69+
on the scene from frame 30 onwards. The action acts on it from frame 30 relative to when
70+
the object was put. So `30+30` i.e `60` is the frame at which `action` starts.
71+
Can you fix this ?
72+
73+
```julia
74+
action_showcreate = Action(1:60,showcreation())
75+
```
76+
77+
![](../assets/createcircle4.gif)
78+
79+
There we go ! It turned out we dont need to change the frames of the action, but the frames of the Object.
80+
Hopefully by intentionally showing you a wrong way to do it you understood the working of `Actions` a little better.
81+
82+
Another way is to have the object present throughout the video and to "hide" it initially till it is to be shown, with an action
83+
that sets the objects opacity to 0.
84+
85+
There exists a similar function `showdestruction()` which does exactly the opposite of `showcreation()`.
86+
87+
> **Author(s):** John George Francis (@arbitrandomuser)
88+
> **Date:** May 28th, 2022 \
89+
> **Tag(s):** action, morphing, object, animation

0 commit comments

Comments
 (0)