diff --git a/.gitignore b/.gitignore index 2ef99b508..1dc58d7cc 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,5 @@ Manifest.toml .vscode/ test.png -test/current/ \ No newline at end of file +test/current/ +.commit \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d2fb7208..ffb0ca727 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,8 @@ # Javis.jl - Changelog ## Unreleased v0.2 +- Ability to use [Animations.jl](https://github.com/jkrumbiegel/Animations.jl) + - for Transformations and `appear` and `disappear` - Show progress of rendering using [ProgressMeter.jl](https://github.com/timholy/ProgressMeter.jl) - Use [VideoIO](https://github.com/JuliaIO/VideoIO.jl) for faster rendering without temporary images diff --git a/Project.toml b/Project.toml index 50a3c640f..df099d40a 100644 --- a/Project.toml +++ b/Project.toml @@ -4,6 +4,7 @@ authors = ["Ole Kröger and Jacob Zelko circle(O, 50, :fill); subactions = [ + SubAction(1:20, sineio(), appear(:fade)), + SubAction(81:100, disappear(:fade)) + ]) +]) +``` + +# Arguments +- `frames`: A list of frames for which the function should be called. + - The frame numbers are relative to the parent [`Action`](@ref). +- `easing::Union{ReversedEasing, Easing}`: The easing function for `args...` +- `args...`: Either a function like [`appear`](@ref) or a Transformation + like [`Translation`](@ref) +""" +SubAction(frames, easing::Union{ReversedEasing,Easing}, args...) = + SubAction(frames, easing_to_animation(easing), args...) + +SubAction(frames, anim::Animation, transition::Transition...) = + SubAction(frames, anim, (args...) -> 1, transition...) + +""" + SubAction(frames, func::Function) A `SubAction` can be defined with frames and a function inside the `subactions` kwarg of an [`Action`](@ref). @@ -174,7 +227,7 @@ javis(demo, [ - For [`appear`](@ref) and [`disappear`](@ref) a closure exists, such that `appear(:fade)` works. """ -SubAction(frames, func::Function) = SubAction(frames, func, [], []) +SubAction(frames, func::Function) = SubAction(frames, easing_to_animation(linear()), func) """ SubAction(frames, trans::Transition...) @@ -207,7 +260,11 @@ javis(demo, [ - `trans::Transition...`: A list of transitions that shall be performed. """ SubAction(frames, trans::Transition...) = - SubAction(frames, (args...) -> 1, collect(trans), []) + SubAction(frames, easing_to_animation(linear()), (args...) -> 1, trans...) + + +SubAction(frames, anim::Animation, func::Function, transitions::Transition...) = + SubAction(frames, anim, func::Function, collect(transitions), []) """ ActionSetting @@ -270,6 +327,7 @@ Defines what is drawn in a defined frame range. - `id::Union{Nothing, Symbol}`: An id which can be used to save the result of `func` - `func::Function`: The drawing function which draws something on the canvas. It gets called with the arguments `video, action, frame` +- `anim::Animation`: defines the interpolation function for the transitions - `transitions::Vector{Transition}` a list of transitions which can be performed before the function gets called. - `internal_transitions::Vector{InternalTransition}`: @@ -282,6 +340,7 @@ mutable struct Action <: AbstractAction frames::Frames id::Union{Nothing,Symbol} func::Function + anim::Animation transitions::Vector{Transition} internal_transitions::Vector{InternalTransition} subactions::Vector{SubAction} @@ -340,6 +399,9 @@ The current action can be accessed using CURRENT_ACTION[1] """ const CURRENT_ACTION = Array{Action,1}() +easing_to_animation(easing) = Animation(0.0, 0.0, easing, 1.0, 1.0) +easing_to_animation(rev_easing::ReversedEasing) = + Animation(0.0, 1.0, rev_easing.easing, 1.0, 0.0) """ Action(frames, func::Function, args...) @@ -375,6 +437,52 @@ Similar to the above but uses the same as frames as the action above. Action(func::Function, args...; kwargs...) = Action(:same, nothing, func, args...; kwargs...) +""" + Action(frames, id::Union{Nothing,Symbol}, func::Function, + transitions::Transition...; kwargs...) + +Fallback constructor for an Action which doesn't define an animation. +A linear animation is assumed. +""" +function Action( + frames, + id::Union{Nothing,Symbol}, + func::Function, + transitions::Transition...; + kwargs..., +) + + Action(frames, id, func, easing_to_animation(linear()), transitions...; kwargs...) +end + +""" + Action(frames, id::Union{Nothing,Symbol}, func::Function, easing::Union{ReversedEasing, Easing}, + args...; kwargs...) + +Fallback constructor for an Action which does define an animation using an easing function. + +# Example +``` +javis( + video, [ + BackgroundAction(1:100, ground), + Action((args...)->t(), sineio(), Translation(250, 0)) + ] +) +``` +""" +function Action( + frames, + id::Union{Nothing,Symbol}, + func::Function, + easing::Union{ReversedEasing,Easing}, + args...; + kwargs..., +) + + Action(frames, id, func, easing_to_animation(easing), args...; kwargs...) +end + """ Action(frames, id::Union{Nothing,Symbol}, func::Function, transitions::Transition...; kwargs...) @@ -393,6 +501,7 @@ function Action( frames, id::Union{Nothing,Symbol}, func::Function, + anim::Animation, transitions::Transition...; kwargs..., ) @@ -403,7 +512,17 @@ function Action( subactions = opts[:subactions] delete!(opts, :subactions) end - Action(frames, id, func, collect(transitions), [], subactions, ActionSetting(), opts) + Action( + frames, + id, + func, + anim, + collect(transitions), + [], + subactions, + ActionSetting(), + opts, + ) end """ @@ -714,9 +833,7 @@ function compute_transition!( action::AbstractAction, frame, ) - t = (frame - first(get_frames(action))) / (length(get_frames(action)) - 1) - # makes sense to only allow 0 ≤ t ≤ 1 - t = min(1.0, t) + t = get_interpolation(action, frame) from, to, center = rotation.from, rotation.to, rotation.center center isa Symbol && (center = pos(center)) @@ -743,9 +860,7 @@ function compute_transition!( action::AbstractAction, frame, ) - t = (frame - first(get_frames(action))) / (length(get_frames(action)) - 1) - # makes sense to only allow 0 ≤ t ≤ 1 - t = min(1.0, t) + t = get_interpolation(action, frame) from, to = translation.from, translation.to from isa Symbol && (from = pos(from)) @@ -1253,7 +1368,9 @@ export Video, Action, BackgroundAction, SubAction, Rel export Line, Translation, Rotation, Transformation, Scaling export val, pos, ang, get_value, get_position, get_angle export projection, morph -export appear, disappear +export appear, disappear, rotate_around +export rev +export scaleto # custom override of luxor extensions export setline, setopacity, fontsize, get_fontsize, scale diff --git a/src/luxor_overrides.jl b/src/luxor_overrides.jl index 9dad313fa..fcf3615f3 100644 --- a/src/luxor_overrides.jl +++ b/src/luxor_overrides.jl @@ -125,6 +125,8 @@ function scale(scl_x, scl_y) # println("cs.current_scale: $(cs.current_scale)") end +scaleto(xy) = scaleto(xy, xy) + """ scaleto(x, y) diff --git a/src/subaction_animations.jl b/src/subaction_animations.jl index 462e7dc07..280b91d13 100644 --- a/src/subaction_animations.jl +++ b/src/subaction_animations.jl @@ -27,20 +27,17 @@ function appear(s::Symbol) end function _appear(video, action, subaction, rel_frame, symbol::Val{:fade_line_width}) - # t is between 0 and 1 - t = (rel_frame - first(get_frames(subaction))) / (length(get_frames(subaction)) - 1) + t = get_interpolation(subaction, rel_frame) action.current_setting.mul_line_width = t end function _appear(video, action, subaction, rel_frame, symbol::Val{:fade}) - # t is between 0 and 1 - t = (rel_frame - first(get_frames(subaction))) / (length(get_frames(subaction)) - 1) + t = get_interpolation(subaction, rel_frame) action.current_setting.mul_opacity = t end function _appear(video, action, subaction, rel_frame, symbol::Val{:scale}) - # t is between 0 and 1 - t = (rel_frame - first(get_frames(subaction))) / (length(get_frames(subaction)) - 1) + t = get_interpolation(subaction, rel_frame) action.current_setting.mul_scale = t end @@ -73,19 +70,190 @@ function disappear(s::Symbol) end function _disappear(video, action, subaction, rel_frame, symbol::Val{:fade_line_width}) - # t is between 0 and 1 - t = (rel_frame - first(get_frames(subaction))) / (length(get_frames(subaction)) - 1) + t = get_interpolation(subaction, rel_frame) action.current_setting.mul_line_width = 1 - t end function _disappear(video, action, subaction, rel_frame, symbol::Val{:fade}) - # t is between 0 and 1 - t = (rel_frame - first(get_frames(subaction))) / (length(get_frames(subaction)) - 1) + t = get_interpolation(subaction, rel_frame) action.current_setting.mul_opacity = 1 - t end function _disappear(video, action, subaction, rel_frame, symbol::Val{:scale}) - # t is between 0 and 1 - t = (rel_frame - first(get_frames(subaction))) / (length(get_frames(subaction)) - 1) + t = get_interpolation(subaction, rel_frame) action.current_setting.mul_scale = 1 - t end + +""" + translate() + +Translate a function defined inside a [`SubAction`](@ref) using an Animation defined +with Animations.jl. + +If you're used to working with Animations.jl this should feel quite natural. +Instead of defining each movement in its own subaction it's possible to define it in one +by using an Animation. + +# Example +``` +using Javis, Animations + +function ground(args...) + background("black") + sethue("white") +end + +video = Video(500, 500) +circle_anim = Animation( + [0.0, 0.3, 0.6, 1.0], # must go from 0 to 1 + # the circle will move from the origin to `Point(150, 0)` then `Point(150, 150)` + # and back to the origin `O`. + [O, Point(150, 0), Point(150, 150), O], + [sineio(), polyin(5), expin(8)], +) +javis( + video, [ + BackgroundAction(1:150, ground), + Action((args...)->circle(O, 25, :fill); subactions=[ + SubAction(1:150, circle_anim, translate()) + ]) + ], pathname="moving_a_circle.gif" +) +``` + +This notation uses the Animations.jl library very explicitly. It's also possible to do the +same with: + +``` +javis( + video, + [ + BackgroundAction(1:150, ground), + Action((args...)->circle(O, 25, :fill); subactions = [ + SubAction(1:50, sineio(), Translation(150, 0)), + SubAction(51:100, polyin(2), Translation(0, 150)), + SubAction(101:150, expin(8), Translation(-150, -150)) + ]) + ], + pathname = "moving_a_circle_javis.gif", +) +``` + +which uses the `SubAction` syntax three times and only uses easing functions instead of +specifying the `Animation` directly. + +Here `circle_anim` defines the movement of the circle. The most important part is that the +time in animations has to be from `0.0` to `1.0`. +""" +function Luxor.translate() + (video, action, subaction, rel_frame) -> _translate(video, action, subaction, rel_frame) +end + +function _translate(video, action, subaction, rel_frame) + p = get_interpolation(subaction, rel_frame) + Luxor.translate(p) +end + +""" + rotate() + +Rotate a function defined inside a [`SubAction`](@ref) using an Animation defined +with Animations.jl. + +If you're used to working with Animations.jl this should feel quite natural. +Instead of defining each movement in its own subaction it's possible to define it in one +by using an Animation. + +# Example +``` +using Javis, Animations + +video = Video(500, 500) +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()], +) + +rotate_anim = Animation( + [0, 1], # must go from 0 to 1 + [0, 2π], + [linear()], +) + +javis( + video, + [ + BackgroundAction(1:150, ground), + Action( + (args...) -> circle(O, 25, :fill); + subactions = [ + SubAction(1:10, sineio(), scale()), + SubAction(11:50, translate_anim, translate()), + SubAction(51:100, rotate_anim, rotate_around(Point(-150, 0))), + SubAction(101:140, translate_back_anim, translate()), + SubAction(141:150, rev(sineio()), scale()) + ], + ), + ], + pathname = "animation.gif", +) +``` + +which uses the `SubAction` syntax five times with both easing functions directly and animation objects. +The `rev(sineio())` creates an `Animation` which goes from `1.0` to `0.0`. +""" +function Luxor.rotate() + (video, action, subaction, rel_frame) -> _rotate(video, action, subaction, rel_frame) +end + +function _rotate(video, action, subaction, rel_frame) + p = get_interpolation(subaction, rel_frame) + Luxor.rotate(p) +end + +""" + rotate_around(p::Point) + +Rotate a function defined inside a [`SubAction`](@ref) using an Animation defined +with Animations.jl around the point `p`. + +An example can be seen in [`rotate`](@ref). + +# Arguments +- `p::Point`: the point to rotate around +""" +function rotate_around(p::Point) + (video, action, subaction, rel_frame) -> + _rotate_around(video, action, subaction, rel_frame, p) +end + +function _rotate_around(video, action, subaction, rel_frame, p) + i = get_interpolation(subaction, rel_frame) + Luxor.translate(p) + Luxor.rotate(i) + Luxor.translate(-p) +end + +""" + scale() + +Scale a function defined inside a [`SubAction`](@ref) using an Animation defined +with Animations.jl around the point `p`. + +An example can be seen in [`rotate`](@ref). +""" +function scale() + (video, action, subaction, rel_frame) -> _scale(video, action, subaction, rel_frame) +end + +function _scale(video, action, subaction, rel_frame) + p = get_interpolation(subaction, rel_frame) + scale(p) +end diff --git a/src/util.jl b/src/util.jl index ddf055e61..dc1dc87be 100644 --- a/src/util.jl +++ b/src/util.jl @@ -26,3 +26,28 @@ function get_current_setting() action = CURRENT_ACTION[1] return action.current_setting end + +""" + get_interpolation(frames::UnitRange, frame) + +Return a value between 0 and 1 which represents the relative `frame` inside `frames`. +""" +function get_interpolation(frames::UnitRange, frame) + t = (frame - first(frames)) / (length(frames) - 1) + # makes sense to only allow 0 ≤ t ≤ 1 + t = min(1.0, t) +end + +""" + get_interpolation(action::AbstractAction, frame) + +Return the value of the `action.anim` Animation based on the relative frame given by +`get_interpolation(get_frames(action), frame)` +""" +function get_interpolation(action::AbstractAction, frame) + t = get_interpolation(get_frames(action), frame) + if !(action.anim.frames[end].t ≈ 1) + @warn "Animations should be defined from 0.0 to 1.0" + end + return at(action.anim, t) +end diff --git a/test/Project.toml b/test/Project.toml index 1a1e1b28d..fc7a5caba 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -1,4 +1,5 @@ [deps] +Animations = "27a7e980-b3e6-11e9-2bcd-0b925532e340" ImageIO = "82e4d734-157c-48bb-816b-45c225c6df19" ImageMagick = "6218d12a-5da1-5696-b52f-db25d2ecc6d1" Images = "916415d5-f1e6-5110-898d-aaa5f9f070e0" @@ -8,5 +9,5 @@ Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" VideoIO = "d6d074c3-1acf-5d4c-9a43-ef38773959a2" [compat] -VideoIO = "0.8.1" LaTeXStrings = "1.1" +VideoIO = "0.8.1" diff --git a/test/animations.jl b/test/animations.jl index 5a18ffe6e..6b23ca094 100644 --- a/test/animations.jl +++ b/test/animations.jl @@ -415,8 +415,8 @@ end 5:50, (args...) -> square_opacity(Point(-100, 0), 60); subactions = [ - SubAction(1:15, appear(:fade)), - SubAction(Rel(20), Translation(100, 50)), + SubAction(1:15, linear(), appear(:fade)), + SubAction(Rel(20), linear(), Translation(100, 50)), SubAction(Rel(5), disappear(:fade)), # for global frames 46-50 it should still be disappeared ], @@ -426,9 +426,9 @@ end pathname = "", ) - @test_reference "refs/circlerSquare07opacity.png" load("images/0000000007.png") - @test_reference "refs/circlerSquare25opacity.png" load("images/0000000025.png") - @test_reference "refs/circlerSquare42opacity.png" load("images/0000000042.png") + @test_reference "refs/circleSquare07opacity.png" load("images/0000000007.png") + @test_reference "refs/circleSquare25opacity.png" load("images/0000000025.png") + @test_reference "refs/circleSquare42opacity.png" load("images/0000000042.png") # test that the last frame is completely white @test sum(load("images/0000000050.png")) == RGB{Float64}(500 * 500, 500 * 500, 500 * 500) @@ -437,6 +437,130 @@ end end end +@testset "Animations.jl translate()" begin + video = Video(500, 500) + circle_anim = Animation( + [0, 0.3, 0.6, 1], # must go from 0 to 1 + [O, Point(150, 0), Point(150, 150), O], + [sineio(), polyin(5), expin(8)], + ) + javis( + video, + [ + BackgroundAction(1:150, ground), + Action( + (args...) -> circle(O, 25, :fill); + subactions = [SubAction(1:150, circle_anim, translate())], + ), + ], + tempdirectory = "images", + pathname = "", + ) + @test_reference "refs/anim_circle020.png" load("images/0000000020.png") + @test_reference "refs/anim_circle075.png" load("images/0000000075.png") + @test_reference "refs/anim_circle142.png" load("images/0000000142.png") + for i in 1:150 + rm("images/$(lpad(i, 10, "0")).png") + end +end + +@testset "Animations.jl @warn" begin + video = Video(500, 500) + circle_anim = Animation( + [0, 0.3, 0.6, 1.2], # must go from 0 to 1 + [O, Point(150, 0), Point(150, 150), O], + [sineio(), polyin(5), expin(8)], + ) + # warning as animation goes to 1.2 but should go to 1.0 + @test_logs (:warn,) (:warn,) javis( + video, + [ + BackgroundAction(1:2, ground), + Action( + (args...) -> circle(O, 25, :fill); + subactions = [SubAction(1:2, circle_anim, translate())], + ), + ], + pathname = "", + ) +end + +@testset "Animations.jl rotate, scale, translate" begin + video = Video(500, 500) + 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()], + ) + + rotate_anim = Animation( + [0, 1], # must go from 0 to 1 + [0, 2π], + [linear()], + ) + + javis( + video, + [ + BackgroundAction(1:150, ground), + Action( + (args...) -> circle(O, 25, :fill); + subactions = [ + SubAction(1:10, sineio(), scale()), + SubAction(11:50, translate_anim, translate()), + SubAction(51:100, rotate_anim, rotate_around(Point(-150, 0))), + SubAction(101:140, translate_back_anim, translate()), + SubAction(141:150, rev(sineio()), scale()), + ], + ), + ], + tempdirectory = "images", + pathname = "", + ) + + @test_reference "refs/animations_all_05.png" load("images/0000000005.png") + @test_reference "refs/animations_all_25.png" load("images/0000000025.png") + @test_reference "refs/animations_all_65.png" load("images/0000000065.png") + @test_reference "refs/animations_all_125.png" load("images/0000000125.png") + @test_reference "refs/animations_all_145.png" load("images/0000000145.png") + + for i in 1:150 + rm("images/$(lpad(i, 10, "0")).png") + end +end + +@testset "Rotate around center animation" begin + rotate_anim = Animation([0.0, 1.0], [0.0, 2π], [sineio()]) + + video = Video(500, 500) + javis( + video, + [ + BackgroundAction(1:50, ground), + BackgroundAction(1:50, (args...) -> scaleto(2)), + Action( + (args...) -> circ(Point(75, 0)), + subactions = [SubAction(1:50, rotate_anim, rotate())], + ), + ], + tempdirectory = "images", + pathname = "", + ) + + @test_reference "refs/rotate_center25.png" load("images/0000000025.png") + @test_reference "refs/rotate_center45.png" load("images/0000000045.png") + + for i in 1:50 + rm("images/$(lpad(i, 10, "0")).png") + end +end + @testset "Scaling circle" begin video = Video(500, 500) javis( diff --git a/test/refs/anim_circle020.png b/test/refs/anim_circle020.png new file mode 100644 index 000000000..2888e4d6a Binary files /dev/null and b/test/refs/anim_circle020.png differ diff --git a/test/refs/anim_circle075.png b/test/refs/anim_circle075.png new file mode 100644 index 000000000..3388c15b8 Binary files /dev/null and b/test/refs/anim_circle075.png differ diff --git a/test/refs/anim_circle142.png b/test/refs/anim_circle142.png new file mode 100644 index 000000000..5b14bd74a Binary files /dev/null and b/test/refs/anim_circle142.png differ diff --git a/test/refs/animations_all_05.png b/test/refs/animations_all_05.png new file mode 100644 index 000000000..8079209d1 Binary files /dev/null and b/test/refs/animations_all_05.png differ diff --git a/test/refs/animations_all_125.png b/test/refs/animations_all_125.png new file mode 100644 index 000000000..475b774d7 Binary files /dev/null and b/test/refs/animations_all_125.png differ diff --git a/test/refs/animations_all_145.png b/test/refs/animations_all_145.png new file mode 100644 index 000000000..3f07b9220 Binary files /dev/null and b/test/refs/animations_all_145.png differ diff --git a/test/refs/animations_all_25.png b/test/refs/animations_all_25.png new file mode 100644 index 000000000..6d890b8a9 Binary files /dev/null and b/test/refs/animations_all_25.png differ diff --git a/test/refs/animations_all_65.png b/test/refs/animations_all_65.png new file mode 100644 index 000000000..ec229f825 Binary files /dev/null and b/test/refs/animations_all_65.png differ diff --git a/test/refs/circlerSquare07opacity.png b/test/refs/circleSquare07opacity.png similarity index 100% rename from test/refs/circlerSquare07opacity.png rename to test/refs/circleSquare07opacity.png diff --git a/test/refs/circlerSquare25opacity.png b/test/refs/circleSquare25opacity.png similarity index 100% rename from test/refs/circlerSquare25opacity.png rename to test/refs/circleSquare25opacity.png diff --git a/test/refs/circlerSquare42opacity.png b/test/refs/circleSquare42opacity.png similarity index 100% rename from test/refs/circlerSquare42opacity.png rename to test/refs/circleSquare42opacity.png diff --git a/test/refs/rotate_center25.png b/test/refs/rotate_center25.png new file mode 100644 index 000000000..87acb328d Binary files /dev/null and b/test/refs/rotate_center25.png differ diff --git a/test/refs/rotate_center45.png b/test/refs/rotate_center45.png new file mode 100644 index 000000000..69bf8bb5a Binary files /dev/null and b/test/refs/rotate_center45.png differ diff --git a/test/runtests.jl b/test/runtests.jl index 353d8d476..6bec0e36c 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -1,3 +1,4 @@ +using Animations using Images using Javis using LaTeXStrings diff --git a/test/unit.jl b/test/unit.jl index 5cc94900d..3e8e3aca4 100644 --- a/test/unit.jl +++ b/test/unit.jl @@ -20,6 +20,173 @@ @test action.internal_transitions[1].by == Point(50, 50) Javis.compute_transformation!(action, video, 100) @test action.internal_transitions[1].by == Point(100, 100) + + # with easing function + video = Video(500, 500) + # dummy action doesn't need a real function + action = Action(1:100, () -> 1, sineio(), Translation(Point(1, 1), Point(100, 100))) + anim = Animation([0.0, 1.0], [1.0, 100.0], [sineio()]) + m = 49 / 99 + # needs internal translation as well + push!(action.internal_transitions, Javis.InternalTranslation(O)) + Javis.compute_transformation!(action, video, 1) + @test action.internal_transitions[1].by == Point(1, 1) + Javis.compute_transformation!(action, video, 50) + @test action.internal_transitions[1].by == Point(at(anim, m), at(anim, m)) + Javis.compute_transformation!(action, video, 100) + @test action.internal_transitions[1].by == Point(100, 100) + + # with animation function + anim_01 = Animation([0.0, 1.0], [0.0, 1.0], [sineio()]) + video = Video(500, 500) + # dummy action doesn't need a real function + action = Action(1:100, () -> 1, anim_01, Translation(Point(1, 1), Point(100, 100))) + anim = Animation([0.0, 1.0], [1.0, 100.0], [sineio()]) + m = 49 / 99 + # needs internal translation as well + push!(action.internal_transitions, Javis.InternalTranslation(O)) + Javis.compute_transformation!(action, video, 1) + @test action.internal_transitions[1].by == Point(1, 1) + Javis.compute_transformation!(action, video, 50) + @test action.internal_transitions[1].by == Point(at(anim, m), at(anim, m)) + Javis.compute_transformation!(action, video, 100) + @test action.internal_transitions[1].by == Point(100, 100) + end + + @testset "translation subaction" begin + video = Video(500, 500) + # dummy action doesn't need a real function + action = Action( + 1:100, + () -> 1; + subactions = [SubAction(1:100, Translation(Point(1, 1), Point(100, 100)))], + ) + # needs internal translation as well + push!(action.subactions[1].internal_transitions, Javis.InternalTranslation(O)) + Javis.compute_transformation!(action.subactions[1], video, 1) + @test action.subactions[1].internal_transitions[1].by == Point(1, 1) + Javis.compute_transformation!(action.subactions[1], video, 50) + @test action.subactions[1].internal_transitions[1].by == Point(50, 50) + Javis.compute_transformation!(action.subactions[1], video, 100) + @test action.subactions[1].internal_transitions[1].by == Point(100, 100) + + ######## testing with easing function ############## + video = Video(500, 500) + # dummy action doesn't need a real function + action = Action( + 1:100, + () -> 1; + subactions = [SubAction( + 1:100, + polyout(10), + Translation(Point(1, 1), Point(100, 100)), + )], + ) + anim = Animation([0.0, 1.0], [1.0, 100.0], [polyout(10)]) + m = 49 / 99 + # needs internal translation as well + push!(action.subactions[1].internal_transitions, Javis.InternalTranslation(O)) + Javis.compute_transformation!(action.subactions[1], video, 1) + @test action.subactions[1].internal_transitions[1].by == Point(1, 1) + Javis.compute_transformation!(action.subactions[1], video, 50) + @test action.subactions[1].internal_transitions[1].by == + Point(at(anim, m), at(anim, m)) + Javis.compute_transformation!(action.subactions[1], video, 100) + @test action.subactions[1].internal_transitions[1].by == Point(100, 100) + + ######## testing with animation ############## + video = Video(500, 500) + anim_01 = Animation([0.0, 1.0], [0.0, 1.0], [polyin(10)]) + # dummy action doesn't need a real function + action = Action( + 1:100, + () -> 1; + subactions = [SubAction( + 1:100, + anim_01, + Translation(Point(1, 1), Point(100, 100)), + )], + ) + anim = Animation([0.0, 1.0], [1.0, 100.0], [polyin(10)]) + m = 49 / 99 + # needs internal translation as well + push!(action.subactions[1].internal_transitions, Javis.InternalTranslation(O)) + Javis.compute_transformation!(action.subactions[1], video, 1) + @test action.subactions[1].internal_transitions[1].by == Point(1, 1) + Javis.compute_transformation!(action.subactions[1], video, 50) + @test action.subactions[1].internal_transitions[1].by == + Point(at(anim, m), at(anim, m)) + Javis.compute_transformation!(action.subactions[1], video, 100) + @test action.subactions[1].internal_transitions[1].by == Point(100, 100) + end + + @testset "Relative frames" begin + video = Video(500, 500) + action = Action(Rel(10), (args...) -> 1, Translation(Point(1, 1), Point(100, 100))) + # dummy action doesn't need a real function + test_file = javis( + video, + [ + Action(1:100, (args...) -> 1, Translation(Point(1, 1), Point(100, 100))), + action, + ], + ) + @test Javis.get_frames(action) == 101:110 + rm(test_file) + end + + @testset "translation from origin" begin + video = Video(500, 500) + # dummy action doesn't need a real function + action = Action(1:100, () -> 1, Translation(Point(99, 99))) + # needs internal translation as well + push!(action.internal_transitions, Javis.InternalTranslation(O)) + + Javis.compute_transformation!(action, video, 1) + @test action.internal_transitions[1].by == O + Javis.compute_transformation!(action, video, 50) + @test action.internal_transitions[1].by == Point(49, 49) + Javis.compute_transformation!(action, video, 100) + @test action.internal_transitions[1].by == Point(99, 99) + + video = Video(500, 500) + # dummy action doesn't need a real function + action = Action(1:100, () -> 1, Translation(99, 99)) + # needs internal translation as well + push!(action.internal_transitions, Javis.InternalTranslation(O)) + + Javis.compute_transformation!(action, video, 1) + @test action.internal_transitions[1].by == O + Javis.compute_transformation!(action, video, 50) + @test action.internal_transitions[1].by == Point(49, 49) + Javis.compute_transformation!(action, video, 100) + @test action.internal_transitions[1].by == Point(99, 99) + end + + @testset "rotations" begin + video = Video(500, 500) + # dummy action doesn't need a real function + action = Action(1:100, () -> 1, Rotation(2π)) + # needs internal translation as well + push!(action.internal_transitions, Javis.InternalRotation(0.0, O)) + + Javis.compute_transformation!(action, video, 1) + @test action.internal_transitions[1].angle == 0.0 + Javis.compute_transformation!(action, video, 100) + @test action.internal_transitions[1].angle == 2π + + video = Video(500, 500) + # dummy action doesn't need a real function + action = Action(1:100, () -> 1, Rotation(2π, Point(2.0, 5.0))) + # needs internal translation as well + push!(action.internal_transitions, Javis.InternalRotation(0.0, O)) + + Javis.compute_transformation!(action, video, 1) + @test action.internal_transitions[1].angle == 0.0 + @test action.internal_transitions[1].center == Point(2.0, 5.0) + Javis.compute_transformation!(action, video, 100) + @test action.internal_transitions[1].angle == 2π + @test action.internal_transitions[1].center == Point(2.0, 5.0) end @testset "scaling" begin