Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

first attempt at DelayedPosition #434

Open
wants to merge 43 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 42 commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
01a4819
first attempt at DelayedPosition
gpucce Oct 20, 2021
64c46ac
reset STARTED_RENDERING at the end of render
gpucce Oct 20, 2021
908819b
Merge branch 'master' into add_delayedposition
gpucce Oct 26, 2021
7d2ab4e
add types to DelayedPosition and add DelayedPosition to Rotation
gpucce Oct 26, 2021
c53da96
add first test for delayed
gpucce Oct 28, 2021
5e5d08b
format
gpucce Oct 28, 2021
dc47929
fix testset name
gpucce Oct 28, 2021
44807f6
add .keep back
gpucce Oct 28, 2021
473add2
fix test in util not cleaning images folder
gpucce Oct 28, 2021
073a575
adjust testing so .keep does not get removed
gpucce Oct 28, 2021
0003112
format
gpucce Oct 28, 2021
a5550c0
Merge remote-tracking branch 'upstream/master' into add_delayedposition
gpucce Oct 31, 2021
64f6f29
add test for rotation and initial support for layers
gpucce Oct 31, 2021
f2ce80d
add +,- for betwenn delayedposition and poin
gpucce Nov 4, 2021
3944a90
format tests
gpucce Nov 4, 2021
8e1c204
minor operation change
gpucce Nov 4, 2021
a9d3837
add test for +
gpucce Nov 4, 2021
dd3e312
add test for delayedposition +/- point both left and right
gpucce Nov 4, 2021
c9ff494
change from STARTED_RENDERING to CURRENTLY_RENDERING and add test for it
gpucce Nov 4, 2021
53a8107
adjust to master
gpucce Nov 6, 2021
bb316f3
add minimal doc for DelayedPosition
gpucce Nov 6, 2021
8499128
adjust tests
gpucce Nov 7, 2021
4fe938f
doc change
gpucce Nov 7, 2021
5dcf4a2
reset tests
gpucce Nov 8, 2021
cad53fe
Merge remote-tracking branch 'upstream/master' into add_delayedposition
gpucce Dec 4, 2021
974dcf0
Merge remote-tracking branch 'upstream/master' into add_delayedposition
gpucce Feb 18, 2022
4d71361
add support for delayed_position in all shorthands except @JShape
gpucce Feb 22, 2022
20e6f40
improve howto
gpucce Feb 22, 2022
4cb3182
Merge branch 'master' of https://github.com/JuliaAnimators/Javis.jl i…
gpucce Feb 22, 2022
4e9c677
fix test
gpucce Feb 22, 2022
16b65d0
add delayed_pos for layers
gpucce Feb 24, 2022
507fbae
fix JEllipse and add more tests
gpucce Feb 24, 2022
b7a205b
Change from PointOrDelayed to Luxor.AbstractPoint
gpucce Feb 24, 2022
f1fb9e2
format
gpucce Feb 24, 2022
e0b6cb7
Minor changes
gpucce Feb 25, 2022
a309fb2
reenable some unit tests
gpucce Feb 26, 2022
dfe7d49
format
gpucce Feb 26, 2022
dce3bda
export get_delayed_position
gpucce Mar 2, 2022
9a24a21
fix types
gpucce Mar 2, 2022
a20bd27
add docs to centered_point
gpucce Mar 2, 2022
0b702a1
fix example in howto.md
gpucce Mar 2, 2022
5be8a05
Merge remote-tracking branch 'upstream/main' into add_delayedposition
gpucce Mar 2, 2022
207f03b
update changelog
gpucce Mar 9, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
66 changes: 58 additions & 8 deletions docs/src/howto.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ Background(1:100, ground)
render(video; pathname="how_to.gif")
```

## Why are all my `Javis` functions undefined?
## Why are all my `Javis` functions undefined?

If you have worked with the Julia drawing package [`Luxor.jl`](https://github.com/JuliaGraphics/Luxor.jl), you will be happy to see that it provides all the drawing functions that `Javis` uses.
`Javis` is basically an abstraction layer built on on top of `Luxor.jl` which provides functions to animate the static images you can create with `Luxor` more easily.
Expand Down Expand Up @@ -51,6 +51,7 @@ There are currently three different ways to define frames inside Javis.
The simplest one is to define the `UnitRange` like `1:100` as above such that the action is called for every frame from `1` to `100`.

**Examples:**

```julia
Object(1:100, (args...)->circle(O, 50, :fill))
Object(1:50, (args...)->circle(O, 70, :stroke))
Expand All @@ -59,6 +60,7 @@ Object(1:50, (args...)->circle(O, 70, :stroke))
It is relatively often the case that the following action should work with the same frames as the previous action this can be done with.

**Examples:**

```julia
Object(1:100, (args...)->circle(O, 50, :fill))
Object(:same, (args...)->circle(O, 20, :stroke), Point(100, 100))
Expand All @@ -70,12 +72,14 @@ so either use the symbol `:same` or just don't mention frames.
The last option is to define frames relative to the previous frame. More precisely the end of the last frame.

**Examples:**

```julia
Object(1:50, (args...)->circle(O, 50, :fill))
Object(RFrames(1:50), (args...)->circle(O, 20, :stroke), Point(100, 100))
```

This is the same as:

```julia
Object(1:50, (args...)->circle(O, 50, :fill))
Object(51:100, (args...)->circle(O, 20, :stroke), Point(100, 100))
Expand All @@ -95,6 +99,7 @@ this is using a change in opacity to show the circle.
There are two other options `:scale` and `:fade_line_width`. `:scale` also works for every kind of [`Object`](@ref) whereas `:fade_line_width` only works if you only draw the stroke instead of using fill.

**Example:**

```julia
circ = Object(1:100, (args...)->circle(O, 50, :stroke))
act!(circ, Action(1:50, appear(:fade_line_width)))
Expand All @@ -117,6 +122,7 @@ end
```

Now we define two actions:

1. Drawing a circle and saving the position `my_circle`
2. Drawing a rectangle above the circle

Expand All @@ -130,17 +136,18 @@ In this animation the position of the circle is saved inside `my_circle` and can

## How can I show a text being drawn?

A `text` or [`latex`](@ref) rendering can appear as *any* other object with `appear(:fade)` and `appear(:scale)`, However, it also has a special [`appear`](@ref) functionality called
A `text` or [`latex`](@ref) rendering can appear as _any_ other object with `appear(:fade)` and `appear(:scale)`, However, it also has a special [`appear`](@ref) functionality called
`:draw_text`.

You can use
You can use

```julia
my_text = Object(1:100, (args...) -> text("Hello World!"; halign = :center))
act!(my_text, Action(1:15, sineio(), appear(:draw_text)))
act!(my_text, Action(76:100, sineio(), disappear(:draw_text)))
```

to let the text `"Hello World!"` appear from left to right in an animated way.
to let the text `"Hello World!"` appear from left to right in an animated way.

## How can I have an object follow a path?

Expand All @@ -154,12 +161,14 @@ my_star = Object(1:100, (args...) -> star(O, 20, 5, 0.5, 0, :fill))
act!(my_star, Action(1:100, follow_path(star(O, 200))))
```

in this case a star is following the path of a bigger star.
in this case a star is following the path of a bigger star.

> **NOTE:** the star inside [`follow_path`](@ref) should have the `action=:none` which is the default for most Luxor functions.

> **NOTE:** Unfortunately the above currently only works for some Luxor functions like `ngon` and `star` but not for `circle` and `rect` as they return `true` instead of the points.

In that case you need to define a function like:

```julia
function ground(args...)
background("white")
Expand All @@ -180,7 +189,6 @@ act!(my_star, Action(1:100, follow_path(luxor2poly(()->rect(O, 100, 100, :path))
render(video; pathname="follow_path.gif")
```


Another possibility is to specify a vector of points like this:

```julia
Expand Down Expand Up @@ -212,9 +220,51 @@ The live viewer can be called with adding `; liveview=true` to the [`render`](@r

For longer videos, it can happen that rendering takes some time.
A long time.
One way to reduce rendering time is that you can render a scaled version of the animation to check if everything is animated as expected.
One way to reduce rendering time is that you can render a scaled version of the animation to check if everything is animated as expected.

By using `render(video; pathname="how_to.gif", rescale_factor=0.5)`, the rendering process can be sped up generally a factor of 2.
This scales the frames of an animation down to half where a `Video(1000, 1000)` will be shown as a `Video(500, 500)` rendered video.
This scales the frames of an animation down to half where a `Video(1000, 1000)` will be shown as a `Video(500, 500)` rendered video.

> **Note:** You might want to experiment with rendering to `mp4` instead of `gif` as well.

## Why does `pos` always throws error?

Both `get_position` and `pos` are a bit unintuitive in the sense that one may think that this should work:

```julia
vid = Video(500, 500)
Background(1:50, (args...)->background("black"))
o1 = Object(JCircle(O, 10, action=:fill))
o2 = Object(JCircle(pos(o1), 5, action=:fill))
render(vid)
gpucce marked this conversation as resolved.
Show resolved Hide resolved
```

However this will throw an error:

```julia
MethodError: no method matching get_position(::Nothing)
```

This happens because the `pos` is supposed to be used by the drawing function at render time. This means that instead of the above this would work:

```julia
vid = Video(500, 500)
Background(1:50, (args...)->background("black"))
o1 = Object(JCircle(O, 10, action=:fill))
o2 = Object(@JShape begin
circle(pos(o1), 5, :fill)
end)
render(vid)
```

However there is an alternative to `pos` to make the first example work, `get_delayed_position` and you can use this just like above:

```julia
vid = Video(500, 500)
Background(1:50, (args...)->background("black"))
o1 = Object(JCircle(O, 10, action=:fill, color="red"))
# it works with or w/o this line and the effect changes coherently!!!
act!(o1, Action(anim_translate(Point(100, 0))))
o2 = Object(25:50, JCircle(delayed_pos(o1), 5, action=:fill, color="blue"))
render(vid)
```
Empty file added images/.keep
Empty file.
21 changes: 20 additions & 1 deletion src/Javis.jl
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ Transformation(p, a) = Transformation(p, a, 1.0)
Transformation(p, a, s::Float64) = Transformation(p, a, (s, s))
Transformation(p, a, s::Tuple{Float64,Float64}) = Transformation(p, a, Scale(s...))

include("structs/Delayed.jl")
include("structs/ObjectSetting.jl")
include("structs/Object.jl")
include("structs/Transitions.jl")
Expand Down Expand Up @@ -134,6 +135,17 @@ function centered_point(pos::Point, width::Int, height::Int)
Point(pos.x - width / 2, pos.y - height / 2)
end

"""
centered_point

Dispatch centered_point to DelayedPosition to adjust layer position.
# Returns
- `dp::DelayedPosition`: the "delayed" location of the center of a layers wrt global canvas.
"""
function centered_point(pos::DelayedPosition, width::Int, height::Int)
gpucce marked this conversation as resolved.
Show resolved Hide resolved
Point(get_position(pos).x - width / 2, get_position(pos).y - height / 2)
end

"""
preprocess_frames!(video::Video)

Expand Down Expand Up @@ -204,6 +216,9 @@ end
# finally objects
flatten!(objects::Array{AbstractObject}, object::Object) = push!(objects, object)


const CURRENTLY_RENDERING = [false]

"""
render(
video::Video;
Expand Down Expand Up @@ -255,6 +270,8 @@ function render(
postprocess_frames_flow = identity,
postprocess_frame = default_postprocess,
)

CURRENTLY_RENDERING[1] = true
layers = video.layers
objects = video.objects
frames = preprocess_frames!(video)
Expand Down Expand Up @@ -341,6 +358,7 @@ function render(
filecounter += 1
end

CURRENTLY_RENDERING[1] = false
isempty(pathname) && return
if ext == ".gif"
# generate a colorpalette first so ffmpeg does not have to guess it
Expand Down Expand Up @@ -559,7 +577,7 @@ function get_javis_frame(video, objects, frame; layers = Layer[])

# check if any layers have been defined
if !isempty(layers)
starting_positions = Point[]
starting_positions = Luxor.AbstractPoint[]
# render each layer's objects and store the layer's Drawing as an image matrix
for layer in layers
push!(starting_positions, layer.position)
Expand Down Expand Up @@ -727,6 +745,7 @@ export Video, Object, Background, Action, RFrames, GFrames
export @JLayer, background
export Line, Transformation
export val, pos, ang, scl, get_value, get_position, get_angle, get_scale
export get_delayed_position, delayed_pos
export projection, morph_to
export appear, disappear, rotate_around, follow_path, change
export rev
Expand Down
25 changes: 25 additions & 0 deletions src/object_values.jl
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,16 @@ val(x) = get_value(x)
get_position(p::Point) = p
get_position(t::Transformation) = t.point

"""
get_delayed_position(obj::Object)

In principle this is similar to [`get_position`](@ref) however, unlike that
one it gets evaluated the first time is called after the rendering has started.
"""
function get_delayed_position(obj::AbstractObject)
DelayedPosition(obj, O, false)
end

"""
get_position(obj::Object)

Expand All @@ -32,13 +42,28 @@ function get_position(obj::Object)
return get_position(obj.result[1])
end

function get_position(p::DelayedPosition)
if CURRENTLY_RENDERING[1] && !p.called
p.called = true
p.position += get_position(p.obj)
end
return p.position
end

"""
pos(x)

`pos` is just a short-hand for [`get_position`](@ref)
"""
pos(x) = get_position(x)

"""
delayed_pos(x)

`delayed_pos` is just a short-hand for [`get_delayed_position`](@ref)
"""
delayed_pos(x) = get_delayed_position(x)

# As it is just the number tuple -> return it
get_scale(x::Tuple{<:Number,<:Number}) = Scale(x...)

Expand Down
30 changes: 21 additions & 9 deletions src/shorthands/JBox.jl
Original file line number Diff line number Diff line change
@@ -1,33 +1,45 @@
function _JBox(
cornerpoint1::Point,
cornerpoint2::Point,
cornerpoint1::Luxor.AbstractPoint,
cornerpoint2::Luxor.AbstractPoint,
color,
action::Symbol,
vertices::Bool,
)
sethue(color)
verts = box(cornerpoint1, cornerpoint2, action, vertices = vertices)
cp1 = get_position(cornerpoint1)
cp2 = get_position(cornerpoint2)
verts = box(cp1, cp2, action, vertices = vertices)
return verts[2]
end
function _JBox(points::Array, color, action::Symbol, vertices::Bool)
sethue(color)
points = [get_position(p) for p in points]
verts = box(points, action, vertices = vertices)
return verts[2]
end
function _JBox(pt::Point, width::Real, height::Real, color, action::Symbol, vertices::Bool)
function _JBox(
pt::Luxor.AbstractPoint,
width::Real,
height::Real,
color,
action::Symbol,
vertices::Bool,
)
sethue(color)
pt = get_position(pt)
box(pt, width, height, action, vertices = vertices)
return Point(pt.x - width / 2, pt.y + height / 2)
end
function _JBox(
pt::Point,
pt::Luxor.AbstractPoint,
width::Real,
height::Real,
cornerradius::Float64,
color,
action::Symbol,
)
sethue(color)
pt = get_position(pt)
box(pt, width, height, cornerradius, action)
return Point(pt.x - width / 2, pt.y + height / 2)
end
Expand All @@ -39,8 +51,8 @@ Create a box (rectangle) between two points and do an action.
Returns the top left corner point of the box.
"""
JBox(
cornerpoint1::Point,
cornerpoint2::Point;
cornerpoint1::Luxor.AbstractPoint,
cornerpoint2::Luxor.AbstractPoint;
color = "black",
action = :stroke,
vertices = false,
Expand Down Expand Up @@ -70,7 +82,7 @@ JBox(points::Array; color = "black", action = :stroke, vertices = false) =
Create a box/rectangle centered at point pt with width and height. Use vertices=true to return an array of the four corner points rather than draw the box.
"""
JBox(
pt::Point,
pt::Luxor.AbstractPoint,
width::Real,
height::Real;
color = "black",
Expand Down Expand Up @@ -101,7 +113,7 @@ JBox(x::Int64, y::Int64, width::Real, height::Real; color = "black", action = :s
Draw a box/rectangle centered at point pt with width and height and round each corner by cornerradius.
"""
JBox(
pt::Point,
pt::Luxor.AbstractPoint,
width::Real,
height::Real,
cornerradius::Float64;
Expand Down
15 changes: 11 additions & 4 deletions src/shorthands/JCircle.jl
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
function _JCircle(center, radius, color, linewidth, action)
sethue(color)
setline(linewidth)
circle(center, radius, action)
return center
thecenter = get_position(center)
circle(thecenter, radius, action)
return thecenter
end

"""
Expand All @@ -22,7 +23,13 @@ Draw a circle at `center` with the given `radius`

Returns the center of the circle
"""
JCircle(center::Point, radius::Real; color = "black", linewidth = 2, action = :stroke) =
JCircle(
center::Luxor.AbstractPoint,
radius::Real;
color = "black",
linewidth = 1,
action = :stroke,
) =
(
args...;
center = center,
Expand All @@ -35,7 +42,7 @@ JCircle(center::Point, radius::Real; color = "black", linewidth = 2, action = :s
JCircle(center_x::Real, center_y::Real, radius::Real; kwargs...) =
JCircle(Point(center_x, center_y), radius; kwargs...)

JCircle(p1::Point, p2::Point; kwargs...) =
JCircle(p1::Luxor.AbstractPoint, p2::Luxor.AbstractPoint; kwargs...) =
JCircle(midpoint(p1, p2), distance(p1, p2) / 2; kwargs...)

JCircle(radius::Real; kwargs...) = JCircle(O, radius; kwargs...)
Loading