diff --git a/CHANGELOG.md b/CHANGELOG.md index 5eee3c8bf..413e77d8d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ##for PR - change font scaling in svg2luxor from 1/2 to 425/1000 - change default line width to 2.0 like luxor +- add `get_delayed_position` and `delayed_pos` ## v0.8.0 (1st of February 2022) - Allow Luxor v3.0 diff --git a/docs/src/howto.md b/docs/src/howto.md index 202a16931..8e3d6a0d4 100644 --- a/docs/src/howto.md +++ b/docs/src/howto.md @@ -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. @@ -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)) @@ -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)) @@ -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)) @@ -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))) @@ -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 @@ -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? @@ -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") @@ -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 @@ -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) +``` + +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) +``` diff --git a/images/.keep b/images/.keep new file mode 100644 index 000000000..e69de29bb diff --git a/src/Javis.jl b/src/Javis.jl index ffe095a60..9b296dac9 100644 --- a/src/Javis.jl +++ b/src/Javis.jl @@ -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") @@ -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) + Point(get_position(pos).x - width / 2, get_position(pos).y - height / 2) +end + """ preprocess_frames!(video::Video) @@ -204,6 +216,9 @@ end # finally objects flatten!(objects::Array{AbstractObject}, object::Object) = push!(objects, object) + +const CURRENTLY_RENDERING = [false] + """ render( video::Video; @@ -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) @@ -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 @@ -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) @@ -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 diff --git a/src/object_values.jl b/src/object_values.jl index f09d7ce5f..155f80c0a 100644 --- a/src/object_values.jl +++ b/src/object_values.jl @@ -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) @@ -32,6 +42,14 @@ 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) @@ -39,6 +57,13 @@ end """ 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...) diff --git a/src/shorthands/JBox.jl b/src/shorthands/JBox.jl index 27c002b4f..839383dee 100644 --- a/src/shorthands/JBox.jl +++ b/src/shorthands/JBox.jl @@ -1,26 +1,37 @@ 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, @@ -28,6 +39,7 @@ function _JBox( 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 @@ -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, @@ -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", @@ -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; diff --git a/src/shorthands/JCircle.jl b/src/shorthands/JCircle.jl index 787c00f02..8384d0e37 100644 --- a/src/shorthands/JCircle.jl +++ b/src/shorthands/JCircle.jl @@ -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 """ @@ -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, @@ -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...) diff --git a/src/shorthands/JEllipse.jl b/src/shorthands/JEllipse.jl index 2d195c674..2271e0617 100644 --- a/src/shorthands/JEllipse.jl +++ b/src/shorthands/JEllipse.jl @@ -1,13 +1,21 @@ -function _JEllipse(cpt::Point, w::Real, h::Real, color, linewidth, action::Symbol) +function _JEllipse( + cpt::Luxor.AbstractPoint, + w::Real, + h::Real, + color, + linewidth, + action::Symbol, +) sethue(color) setline(linewidth) + cpt = get_position(cpt) ellipse(cpt, w, h, action) return cpt end function _JEllipse( - focus1::Point, - focus2::Point, + focus1::Luxor.AbstractPoint, + focus2::Luxor.AbstractPoint, k::Union{Real,Point}, color, linewidth, @@ -17,6 +25,8 @@ function _JEllipse( ) sethue(color) setline(linewidth) + focus1 = get_position(focus1) + focus2 = get_position(focus2) ellipse(focus1, focus2, k, action, stepvalue = stepvalue, reversepath = reversepath) return focus1, focus2 end @@ -33,7 +43,14 @@ Returns the center of the ellipse. - `linewidth` = 2 - `action::Symbol` :stroke by default can be `:fill` or other actions explained in the Luxor documentation. """ -JEllipse(cpt::Point, w::Real, h::Real; color = "black", linewidth = 2, action = :stroke) = +JEllipse( + cpt::Luxor.AbstractPoint, + w::Real, + h::Real; + color = "black", + linewidth = 2, + action = :stroke, +) = ( args...; cpt = cpt, @@ -54,8 +71,8 @@ Build a polygon approximation to an ellipse, given two points and a distance, k, required to go from one focus to the perimeter and on to the other focus). """ JEllipse( - focus1::Point, - focus2::Point, + focus1::Luxor.AbstractPoint, + focus2::Luxor.AbstractPoint, k::Real; color = "black", linewidth = 2, @@ -78,8 +95,8 @@ JEllipse( Build a polygon approximation to an ellipse, given two points and a point somewhere on the ellipse. """ JEllipse( - focus1::Point, - focus2::Point, + focus1::Luxor.AbstractPoint, + focus2::Luxor.AbstractPoint, pt::Point; color = "black", linewidth = 2, diff --git a/src/shorthands/JLine.jl b/src/shorthands/JLine.jl index 7afb83224..9791302bd 100644 --- a/src/shorthands/JLine.jl +++ b/src/shorthands/JLine.jl @@ -1,6 +1,8 @@ function _JLine(p1, p2, linewidth, color) sethue(color) setline(linewidth) + p1 = get_position(p1) + p2 = get_position(p2) line(p1, p2, :stroke) return p2 end @@ -17,8 +19,8 @@ end Draw a line between the points pt1 and pt2. Returns the final point of the line """ -JLine(pt1::Point, pt2::Point; linewidth = 2, color = "black") = +JLine(pt1::Luxor.AbstractPoint, pt2::Luxor.AbstractPoint; linewidth = 1, color = "black") = (args...; color = color, linewidth = linewidth, pt1 = pt1, pt2 = pt2) -> _JLine(pt1, pt2, linewidth, color) -JLine(pt::Point; kwargs...) = JLine(O, pt; kwargs...) +JLine(pt::Luxor.AbstractPoint; kwargs...) = JLine(O, pt; kwargs...) diff --git a/src/shorthands/JPoly.jl b/src/shorthands/JPoly.jl index 03ac283aa..932262f29 100644 --- a/src/shorthands/JPoly.jl +++ b/src/shorthands/JPoly.jl @@ -1,6 +1,7 @@ function _JPoly(pointlist, color, linewidth, action, close, reversepath) sethue(color) setline(linewidth) + pointlist = [get_position(p) for p in pointlist] poly(pointlist, action; close = close, reversepath = reversepath) end @@ -17,13 +18,13 @@ Draw a polygon around points in the pointlist. - `reversepath` can be set to `true` to reverse the path and create a polygon hole """ JPoly( - pointlist::Vector{Point}; + pointlist::Vector{T}; color = "black", linewidth = 2, action = :stroke, close = true, reversepath = false, -) = +) where {T<:Luxor.AbstractPoint} = ( args...; pointlist = pointlist, diff --git a/src/shorthands/JRect.jl b/src/shorthands/JRect.jl index eb494ea4c..64cd3aa63 100644 --- a/src/shorthands/JRect.jl +++ b/src/shorthands/JRect.jl @@ -1,5 +1,5 @@ function _JRect( - cornerpoint::Point, + cornerpoint::Luxor.AbstractPoint, w::Real, h::Real, color, @@ -8,6 +8,7 @@ function _JRect( ) sethue(color) setline(linewidth) + cornerpoint = get_position(cornerpoint) rect(cornerpoint, w, h, action) return cornerpoint end @@ -26,7 +27,7 @@ You can specify the `linewidth` and the `color` of the rectangle. - `action` Defines whether the rectangle should be outlined (`:stroke`) or filled (`:fill`) """ JRect( - cornerpoint::Point, + cornerpoint::Luxor.AbstractPoint, w::Real, h::Real; color = "black", diff --git a/src/shorthands/JStar.jl b/src/shorthands/JStar.jl index cc8657aba..a15909120 100644 --- a/src/shorthands/JStar.jl +++ b/src/shorthands/JStar.jl @@ -11,6 +11,7 @@ function _JStar( ) sethue(color) setline(linewidth) + center = get_position(center) star(center, radius, npoints, ratio, orientation, action, reversepath = reversepath) return center end @@ -33,7 +34,7 @@ Return the center of the star. - `reversepath` if true it reverses the path and therefore creates a hole (default: true) """ JStar( - center::Point, + center::Luxor.AbstractPoint, radius; color = "black", linewidth = 2, diff --git a/src/structs/Delayed.jl b/src/structs/Delayed.jl new file mode 100644 index 000000000..7f0a40730 --- /dev/null +++ b/src/structs/Delayed.jl @@ -0,0 +1,30 @@ +""" + DelayedPosition(obj, position, called) + +This struct is used in place of a `Point` as a hook that will let you access the +position of an object at the starting frame of an `Action`. One should not need to call +this directly, rather check [`delayed_pos`](@ref). +""" +mutable struct DelayedPosition <: Luxor.AbstractPoint + obj::AbstractObject + position::Point + called::Bool +end + +import Base: +, -, *, / +import Luxor: distance + +*(k::Number, dp::DelayedPosition) = DelayedPosition(dp.obj, k * get_position(dp), dp.called) +*(dp::DelayedPosition, k::Number) = *(k, dp) +/(dp::DelayedPosition, k::Number) = DelayedPosition(dp.obj, get_position(dp) / k, dp.called) +-(dp::DelayedPosition) = DelayedPosition(dp.obj, -get_position(dp), dp.called) +-(dp::DelayedPosition, p::Point) = DelayedPosition(dp.obj, get_position(dp) - p, dp.called) +-(p::Point, dp::DelayedPosition) = DelayedPosition(dp.obj, p - get_position(dp), dp.called) ++(dp::DelayedPosition, p::Point) = DelayedPosition(dp.obj, get_position(dp) + p, dp.called) ++(p::Point, dp::DelayedPosition) = +(dp, p) + +translate(pos::DelayedPosition) = translate(get_position(pos)) + +function distance(p1::T1, p2::T2) where {T1<:Luxor.AbstractPoint,T2<:Luxor.AbstractPoint} + return distance(get_position(p1), get_position(p2)) +end diff --git a/src/structs/Layer.jl b/src/structs/Layer.jl index 116a41d61..9b4b0df26 100644 --- a/src/structs/Layer.jl +++ b/src/structs/Layer.jl @@ -18,7 +18,7 @@ mutable struct Layer <: AbstractObject frames::Frames width::Int height::Int - position::Point + position::Luxor.AbstractPoint layer_objects::Vector{AbstractObject} actions::Vector{AbstractAction} current_setting::LayerSetting @@ -40,7 +40,7 @@ function Layer( frames, width, height, - position::Point; + position::Luxor.AbstractPoint; layer_objects::Vector{AbstractObject} = AbstractObject[], actions::Vector{AbstractAction} = AbstractAction[], setting::LayerSetting = LayerSetting(), diff --git a/src/structs/Transitions.jl b/src/structs/Transitions.jl index 3cb2e7a2a..11b06f763 100644 --- a/src/structs/Transitions.jl +++ b/src/structs/Transitions.jl @@ -5,8 +5,8 @@ =# struct Translation <: AbstractTransition - from::Union{Object,Point} - to::Union{Object,Point} + from::Union{Object,Luxor.AbstractPoint} + to::Union{Object,Luxor.AbstractPoint} end """ @@ -29,12 +29,15 @@ act!(obj, Action(1:50, anim_translate(10, 10))) """ anim_translate(x::Real, y::Real) = anim_translate(Point(x, y)) anim_translate(tp::Point) = Translation(O, tp) -anim_translate(fp::Union{Object,Point}, tp::Union{Object,Point}) = Translation(fp, tp) +anim_translate( + fp::Union{Object,Luxor.AbstractPoint}, + tp::Union{Object,Luxor.AbstractPoint}, +) = Translation(fp, tp) struct Rotation{T<:Real} <: AbstractTransition from::T to::T - center::Union{Nothing,Point,AbstractObject} + center::Union{Nothing,Point,AbstractObject,DelayedPosition} end Rotation(from::Real, to::Real, center) = Rotation(promote(from, to)..., center) diff --git a/test/animations.jl b/test/animations.jl index 2d77473d8..fabbf7863 100644 --- a/test/animations.jl +++ b/test/animations.jl @@ -916,5 +916,4 @@ end end rm("images/with_layer/", recursive = true) rm("images/without_layer/", recursive = true) - end diff --git a/test/delayed.jl b/test/delayed.jl new file mode 100644 index 000000000..e64dc4871 --- /dev/null +++ b/test/delayed.jl @@ -0,0 +1,204 @@ +@testset "delayed operations" begin + + + testvideo = Video(300, 300) + Background(1:31, (args...) -> begin + background("white") + sethue("black") + end) + ball1 = Object(1:31, JCircle(O, 10, color = "black", action = :fill), Point(-45, 0)) + ball2 = Object(1:31, JCircle(O, 10, color = "blue", action = :fill), Point(0, 0)) + act!(ball1, Action(1:31, anim_translate(Point(-45, 0), Point(45, 0)))) + act!(ball2, Action(16:31, anim_translate(Point(25, 0), Point(0, -45)))) + mkdir("images/not_delayed/") + render(testvideo, tempdirectory = "images/not_delayed", pathname = "") + + testvideo = Video(300, 300) + Background(1:31, (args...) -> begin + background("white") + sethue("black") + end) + ball1 = Object(1:31, JCircle(O, 10, color = "black", action = :fill), Point(-45, 0)) + ball2 = Object(1:31, JCircle(O, 10, color = "blue", action = :fill), Point(0, 0)) + act!(ball1, Action(1:31, anim_translate(Point(-45, 0), Point(45, 0)))) + act!( + ball2, + Action( + 16:31, + anim_translate(Javis.delayed_pos(ball1) + Point(25, 0), Point(0, -45)), + ), + ) + mkdir("images/delayed/") + render(testvideo, tempdirectory = "images/delayed/", pathname = "") + + for frame in [1, 5, 14, 24, 30] + png_name = lpad(frame, 10, "0") + @test_reference "images/delayed/$(png_name).png" load( + "images/not_delayed/$(png_name).png", + ) + end + + rm("images/delayed", recursive = true) + + testvideo = Video(300, 300) + Background(1:31, (args...) -> begin + background("white") + sethue("black") + end) + ball1 = Object(1:31, JCircle(O, 10, color = "black", action = :fill), Point(-45, 0)) + ball2 = Object(1:31, JCircle(O, 10, color = "blue", action = :fill), Point(0, 0)) + act!(ball1, Action(1:31, anim_translate(Point(-45, 0), Point(45, 0)))) + act!( + ball2, + Action( + 16:31, + anim_translate(Javis.delayed_pos(ball1) - Point(-25, 0), Point(0, -45)), + ), + ) + mkdir("images/delayed/") + render(testvideo, tempdirectory = "images/delayed/", pathname = "") + + for frame in [1, 5, 14, 24, 30] + png_name = lpad(frame, 10, "0") + @test_reference "images/delayed/$(png_name).png" load( + "images/not_delayed/$(png_name).png", + ) + end + + rm("images/delayed", recursive = true) + + testvideo = Video(300, 300) + Background(1:31, (args...) -> begin + background("white") + sethue("black") + end) + ball1 = Object(1:31, JCircle(O, 10, color = "black", action = :fill), Point(-45, 0)) + ball2 = Object(1:31, JCircle(O, 10, color = "blue", action = :fill), Point(0, 0)) + act!(ball1, Action(1:31, anim_translate(Point(-45, 0), Point(45, 0)))) + act!( + ball2, + Action( + 16:31, + anim_translate(Javis.delayed_pos(ball1) - Point(-25, 0), Point(0, -45)), + ), + ) + mkdir("images/delayed/") + render(testvideo, tempdirectory = "images/delayed/", pathname = "") + + + for frame in [1, 5, 14, 24, 30] + png_name = lpad(frame, 10, "0") + @test_reference "images/delayed/$(png_name).png" load( + "images/not_delayed/$(png_name).png", + ) + end + + rm("images/delayed", recursive = true) + rm("images/not_delayed", recursive = true) + + @test Javis.CURRENTLY_RENDERING[1] == false +end + + + +@testset "delayedposition Translation" begin + testvideo = Video(300, 300) + Background(1:31, (args...) -> begin + background("white") + sethue("black") + end) + ball1 = Object(1:31, JCircle(O, 10, color = "black", action = :fill), Point(-45, 0)) + act!(ball1, Action(1:31, anim_translate(Point(-45, 0), Point(45, 0)))) + act!(ball1, Action(17:31, anim_translate(Javis.delayed_pos(ball1), Point(0, -45)))) + render(testvideo, tempdirectory = "images", pathname = "") + + for frame in [1, 7, 15, 24, 30] + png_name = lpad(string(frame), 10, "0") + @test_reference "refs/delayed_translation_$(frame).png" load("images/$png_name.png") + end + + for i in readdir("images", join = true) + endswith(i, ".png") && rm(i) + end +end + +@testset "delayedposition rotation" begin + testvideo = Video(300, 300) + Background(1:31, (args...) -> begin + background("white") + sethue("black") + end) + + ball1 = Object(1:31, JCircle(O, 10, color = "black", action = :fill), Point(-45, 0)) + ball2 = Object(1:31, JCircle(O, 10, color = "red", action = :fill), Point(0, -25)) + + act!(ball1, Action(1:17, anim_translate(Point(-45, 0), O))) + act!(ball2, Action(17:31, anim_rotate_around(-π, O))) + render(testvideo, tempdirectory = "images", pathname = "") + + for frame in [1, 7, 15, 24, 30] + png_name = lpad(string(frame), 10, "0") + @test_reference "refs/delayed_rotation_$(frame).png" load("images/$png_name.png") + end + + for i in readdir("images", join = true) + if endswith(i, ".png") + rm(i) + end + end +end + + +@testset "delayed_shorthands" begin + vid = Video(500, 500) + Background(1:200, (args...) -> background("black")) + o1 = Object(JCircle(O, 30, color = "red", action = :fill), Point(50, 0)) + act!(o1, Action(anim_rotate_around(2π, Point(0, 0)))) + o2 = Object(50:200, JStar(Javis.delayed_pos(o1), 50, color = "blue", action = :fill)) + l1 = @JLayer 100:200 200 200 Javis.delayed_pos(o2) begin + lo1 = Object(JStar(O, 50, color = "gray", action = :fill)) + end + + act!(o2, Action(anim_rotate_around(2π, Point(100, 0)))) + act!(l1, Action(1:100, anim_rotate_around(2π, Point(-100, 0)))) + + o4 = Object( + 100:200, + JEllipse(Javis.delayed_pos(o2), 10, 20, color = "green", action = :fill), + ) + o5 = Object( + 100:200, + JLine(Javis.delayed_pos(o2), Javis.delayed_pos(o1), color = "white"), + ) + o6 = Object( + 100:200, + JPoly( + Luxor.AbstractPoint[O, Javis.delayed_pos(o1), Javis.delayed_pos(o2)], + color = "navyblue", + action = :stroke, + linewidth = 10, + ), + ) + o3 = Object( + 100:200, + JBox(Javis.delayed_pos(o2), 10, 10, color = "orange", action = :fill), + ) + act!( + o3, + Action( + anim_translate(Javis.delayed_pos(o2), Javis.delayed_pos(o1) + Point(10, 10)), + ), + ) + render(vid, pathname = "", tempdirectory = "images/") + + for frame in [1, 2, 41, 42, 101, 102, 151, 152, 200] + png_name = lpad(string(frame), 10, "0") + @test_reference "refs/delayed_shorthands_$(png_name).png" load( + "images/$png_name.png", + ) + end + + for i in readdir("images", join = true) + endswith(i, ".png") && rm(i) + end +end diff --git a/test/postprocessing.jl b/test/postprocessing.jl index 22b523a40..8e399f74e 100644 --- a/test/postprocessing.jl +++ b/test/postprocessing.jl @@ -131,8 +131,8 @@ end @test_reference "images/$png_no_pad.png" load("images/$png_pad.png") end - for i in 1:n_frames - rm("images/$(lpad(i, 10, "0")).png") + for i in readdir("images", join = true) + endswith(i, ".png") && rm(i) end function postprocess_crop(frame_image, idx, frames) @@ -158,8 +158,8 @@ end @test_reference "images/$png_no_crop.png" load("images/$png_crop.png") end - for i in 1:n_frames - rm("images/$(lpad(i, 10, "0")).png") + for i in readdir("images", join = true) + endswith(i, ".png") && rm(i) end end diff --git a/test/refs/delayed_rotation_1.png b/test/refs/delayed_rotation_1.png new file mode 100644 index 000000000..bddd3bf72 Binary files /dev/null and b/test/refs/delayed_rotation_1.png differ diff --git a/test/refs/delayed_rotation_15.png b/test/refs/delayed_rotation_15.png new file mode 100644 index 000000000..fe1a14709 Binary files /dev/null and b/test/refs/delayed_rotation_15.png differ diff --git a/test/refs/delayed_rotation_24.png b/test/refs/delayed_rotation_24.png new file mode 100644 index 000000000..fea20846d Binary files /dev/null and b/test/refs/delayed_rotation_24.png differ diff --git a/test/refs/delayed_rotation_30.png b/test/refs/delayed_rotation_30.png new file mode 100644 index 000000000..9e157e88e Binary files /dev/null and b/test/refs/delayed_rotation_30.png differ diff --git a/test/refs/delayed_rotation_7.png b/test/refs/delayed_rotation_7.png new file mode 100644 index 000000000..6fe489254 Binary files /dev/null and b/test/refs/delayed_rotation_7.png differ diff --git a/test/refs/delayed_shorthands_0000000001.png b/test/refs/delayed_shorthands_0000000001.png new file mode 100644 index 000000000..49241579d Binary files /dev/null and b/test/refs/delayed_shorthands_0000000001.png differ diff --git a/test/refs/delayed_shorthands_0000000002.png b/test/refs/delayed_shorthands_0000000002.png new file mode 100644 index 000000000..5b78704eb Binary files /dev/null and b/test/refs/delayed_shorthands_0000000002.png differ diff --git a/test/refs/delayed_shorthands_0000000041.png b/test/refs/delayed_shorthands_0000000041.png new file mode 100644 index 000000000..535363491 Binary files /dev/null and b/test/refs/delayed_shorthands_0000000041.png differ diff --git a/test/refs/delayed_shorthands_0000000042.png b/test/refs/delayed_shorthands_0000000042.png new file mode 100644 index 000000000..eb1c18673 Binary files /dev/null and b/test/refs/delayed_shorthands_0000000042.png differ diff --git a/test/refs/delayed_shorthands_0000000101.png b/test/refs/delayed_shorthands_0000000101.png new file mode 100644 index 000000000..c4474c8cb Binary files /dev/null and b/test/refs/delayed_shorthands_0000000101.png differ diff --git a/test/refs/delayed_shorthands_0000000102.png b/test/refs/delayed_shorthands_0000000102.png new file mode 100644 index 000000000..bc70ca6fe Binary files /dev/null and b/test/refs/delayed_shorthands_0000000102.png differ diff --git a/test/refs/delayed_shorthands_0000000151.png b/test/refs/delayed_shorthands_0000000151.png new file mode 100644 index 000000000..06db71c30 Binary files /dev/null and b/test/refs/delayed_shorthands_0000000151.png differ diff --git a/test/refs/delayed_shorthands_0000000152.png b/test/refs/delayed_shorthands_0000000152.png new file mode 100644 index 000000000..c72aab396 Binary files /dev/null and b/test/refs/delayed_shorthands_0000000152.png differ diff --git a/test/refs/delayed_shorthands_0000000200.png b/test/refs/delayed_shorthands_0000000200.png new file mode 100644 index 000000000..dc5b0b91c Binary files /dev/null and b/test/refs/delayed_shorthands_0000000200.png differ diff --git a/test/refs/delayed_translation_1.png b/test/refs/delayed_translation_1.png new file mode 100644 index 000000000..4a8d27349 Binary files /dev/null and b/test/refs/delayed_translation_1.png differ diff --git a/test/refs/delayed_translation_15.png b/test/refs/delayed_translation_15.png new file mode 100644 index 000000000..36b1ed929 Binary files /dev/null and b/test/refs/delayed_translation_15.png differ diff --git a/test/refs/delayed_translation_24.png b/test/refs/delayed_translation_24.png new file mode 100644 index 000000000..070664c34 Binary files /dev/null and b/test/refs/delayed_translation_24.png differ diff --git a/test/refs/delayed_translation_30.png b/test/refs/delayed_translation_30.png new file mode 100644 index 000000000..c5ad4adaa Binary files /dev/null and b/test/refs/delayed_translation_30.png differ diff --git a/test/refs/delayed_translation_7.png b/test/refs/delayed_translation_7.png new file mode 100644 index 000000000..805bb848d Binary files /dev/null and b/test/refs/delayed_translation_7.png differ diff --git a/test/runtests.jl b/test/runtests.jl index d9e2eaf52..44cfa559e 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -59,4 +59,7 @@ end @testset "Javis Viewer" begin include("viewer.jl") end + @testset "Delayed" begin + include("delayed.jl") + end end diff --git a/test/stream_local.gif b/test/stream_local.gif index 094a9e6db..c6d62af4f 100644 Binary files a/test/stream_local.gif and b/test/stream_local.gif differ diff --git a/test/unit.jl b/test/unit.jl index 227624bb0..1b0ffdd53 100644 --- a/test/unit.jl +++ b/test/unit.jl @@ -295,6 +295,10 @@ act!(a, Action(RFrames(1:3), appear(:fade))) # 1:3 render(video; tempdirectory = "images", pathname = "") @test Javis.get_frames(a.actions[1]) == 1:3 + + for i in readdir("images", join = true) + endswith(i, ".png") && rm(i) + end end @testset "anim_" begin