diff --git a/src/Javis.jl b/src/Javis.jl index ddb840d0b..d02086071 100644 --- a/src/Javis.jl +++ b/src/Javis.jl @@ -1,7 +1,7 @@ module Javis using Animations -import Cairo: CairoImageSurface, image +import Cairo: CairoImageSurface, CairoSurfaceBase, image using FFMPEG using Gtk using GtkReactive @@ -726,6 +726,7 @@ include("shorthands/JEllipse.jl") include("shorthands/JStar.jl") include("shorthands/JPoly.jl") include("shorthands/JShape.jl") +include("shorthands/JImage.jl") export render, latex export Video, Object, Background, Action, RFrames, GFrames @@ -740,7 +741,7 @@ export act! export anim_translate, anim_rotate, anim_rotate_around, anim_scale export @Frames, prev_start, prev_end, startof, endof -export JBox, JCircle, JEllipse, JLine, JPoly, JRect, JStar, @JShape +export JBox, JCircle, JEllipse, JImage, JLine, JPoly, JRect, JStar, @JShape # custom override of luxor extensions export setline, setopacity, fontsize, get_fontsize, scale, text diff --git a/src/shorthands/JBox.jl b/src/shorthands/JBox.jl index 27c002b4f..1b3200b1e 100644 --- a/src/shorthands/JBox.jl +++ b/src/shorthands/JBox.jl @@ -117,3 +117,73 @@ JBox( color = color, action = action, ) -> _JBox(pt, width, height, cornerradius, color, action) + +""" +TODO: Add documentation +""" +JBox( + pt::Point, + width::Real, + height::Real, + color = "", + action = :stroke, + vertices::Bool = false, + image_path::String = "", + scale_factor::Symbol = :inset, +) = + ( + args...; + pt = pt, + width = width, + height = height, + color = color, + action = action, + vertices = vertices, + image_path = image_path, + scale_factor = scale_factor, + ) -> _JBox(pt, width, height, color, action, vertices, image_path, scale_factor) + +function _JBox( + pt::Point, + width::Real, + height::Real, + color, + action::Symbol, + vertices::Bool, + image_path::String, + scale_factor::Symbol, +) + bbox = BoundingBox(box(pt, width, height, :path, vertices = vertices)) + + if !isnothing(color) + sethue(color) + box(pt, width, height, :fill, vertices = vertices) + end + + img = readpng(image_path) + + if scale_factor == :inset + boxside = max(boxwidth(bbox), boxheight(bbox)) + imageside = max(img.width, img.height) + + scaling = boxside / imageside + + box(bbox, :clip) + + translate(boxmiddlecenter(bbox)) + scale(scaling) + elseif scale_factor == :clip + boxside = min(boxwidth(bbox), boxheight(bbox)) + imageside = min(img.width, img.height) + + box(bbox, :clip) + + scaling = boxside / imageside + + translate(boxmiddlecenter(bbox)) + scale(scaling) + end + + placeimage(img, pt, centered = true) + return Point(pt.x - width / 2, pt.y + height / 2) +end diff --git a/src/shorthands/JCircle.jl b/src/shorthands/JCircle.jl index 638b8c686..fd7488715 100644 --- a/src/shorthands/JCircle.jl +++ b/src/shorthands/JCircle.jl @@ -39,3 +39,58 @@ JCircle(p1::Point, p2::Point; kwargs...) = JCircle(midpoint(p1, p2), distance(p1, p2) / 2; kwargs...) JCircle(radius::Real; kwargs...) = JCircle(O, radius; kwargs...) + +""" +TODO: Add Documentation! +""" +JCircle( + center::Point, + radius::Real, + color = "black", + linewidth = 1, + action = :stroke, + image_path = "", + scale_factor = :inset, +) = + ( + args...; + center = center, + radius = radius, + color = color, + linewidth = linewidth, + action = action, + image_path = image_path, + scale_factor = scale_factor, + ) -> _JCircle(center, radius, color, linewidth, action, image_path, scale_factor) + +function _JCircle(center, radius, color, linewidth, action, image_path, scale_factor) + + bbox = BoundingBox(circle(center, radius, :path)) + + if !isnothing(color) + sethue(color) + circle(center, radius, :fill) + end + + img = readpng(image_path) + + if scale_factor == :inset + side = sqrt((radius * 2)^2 / 2) + bbox = BoundingBox(box(O, side, side, action = :path)) + boxside = max(boxwidth(bbox), boxheight(bbox)) + imageside = max(img.width, img.height) + box(O, side, side, action = :clip) + elseif scale_factor == :clip + bbox = BoundingBox(circle(O, radius, action = :path)) + boxside = min(boxwidth(bbox), boxheight(bbox)) + imageside = min(img.width, img.height) + circle(O, radius, action = :clip) + end + + scalefactor = boxside / imageside + + translate(boxmiddlecenter(bbox)) + scale(scalefactor) + placeimage(img, centered = true) + return center +end diff --git a/src/shorthands/JEllipse.jl b/src/shorthands/JEllipse.jl index 894eb348a..d144d665c 100644 --- a/src/shorthands/JEllipse.jl +++ b/src/shorthands/JEllipse.jl @@ -95,3 +95,70 @@ JEllipse( linewidth = linewidth, action = action, ) -> _JEllipse(focus1, focus2, pt, color, linewidth, action, stepvalue, reversepath) + + +""" +TODO: Add documentation +""" +JEllipse( + cpt::Point, + w::Real, + h::Real, + color = "black", + linewidth = 1, + action = :stroke, + image_path::String = "", + scale_factor::Symbol = :inset, +) = + ( + args...; + cpt = cpt, + w = w, + h = h, + color = color, + linewidth = linewidth, + action = action, + image_path = image_path, + scale_factor = scale_factor, + ) -> _JEllipse(cpt, w, h, color, linewidth, action, image_path, scale_factor) + +function _JEllipse( + cpt::Point, + w::Real, + h::Real, + color, + linewidth, + action::Symbol, + image_path::String = "", + scale_factor = :inset, +) + + bbox = BoundingBox(ellipse(O, w, h, :path)) + + if !isnothing(color) + sethue(color) + ellipse(O, w, h, :fill) + end + + img = readpng(image_path) + + if scale_factor == :inset + inset_radius = minimum([w, h]) / 2 + bbox = BoundingBox(circle(O, inset_radius, :path)) + boxside = max(boxwidth(bbox), boxheight(bbox)) + imageside = max(img.width, img.height) + circle(O, inset_radius, :clip) + elseif scale_factor == :clip + boxside = min(boxwidth(bbox), boxheight(bbox)) + imageside = min(img.width, img.height) + ellipse(O, 200, 100, :clip) + end + + scalefactor = boxside / imageside + + translate(boxmiddlecenter(bbox)) + scale(scalefactor) + placeimage(img, centered = true) + return cpt +end + diff --git a/src/shorthands/JImage.jl b/src/shorthands/JImage.jl new file mode 100644 index 000000000..e7e1df13e --- /dev/null +++ b/src/shorthands/JImage.jl @@ -0,0 +1,137 @@ +# TODO: Add scaling logic for each shape; see https://github.com/JuliaAnimators/Javis.jl/pull/399#issuecomment-1003549900 for details on what to do + +function _JImage(pos, img, centering, shapeargs, shape, scaleargs) + if !isnothing(shape) + bbox = BoundingBox(shape(shapeargs...)) + end + + if scaleargs == :inset + if Symbol(shape) == :box + boxside = max(boxwidth(bbox), boxheight(bbox)) + imageside = max(img.width, img.height) + + scalefactor = boxside / imageside + + box(bbox, :clip) + + translate(boxmiddlecenter(bbox)) + scale(scalefactor) + elseif Symbol(shape) == :star + bbox = BoundingBox(circle(shapeargs.center, shapeargs.radius * shapeargs.ratio)) + boxside = max(boxwidth(bbox), boxheight(bbox)) + imageside = max(img.width, img.height) + circle(shapeargs.center, shapeargs.radius * shapeargs.ratio, action = :clip) + + scalefactor = boxside / imageside + + translate(boxmiddlecenter(bbox)) + scale(scalefactor) + end + elseif scaleargs == :clip + if Symbol(shape) == :box + boxside = min(boxwidth(bbox), boxheight(bbox)) + imageside = min(img.width, img.height) + + box(bbox, :clip) + + scalefactor = boxside / imageside + + translate(boxmiddlecenter(bbox)) + scale(scalefactor) + elseif Symbol(shape) == :star + boxside = min(boxwidth(bbox), boxheight(bbox)) + imageside = min(img.width, img.height) + + star( + shapeargs.center, + shapeargs.radius, + shapeargs.npoints, + shapeargs.ratio, + shapeargs.orientation, + action = :clip, + ) + + scalefactor = boxside / imageside + + translate(boxmiddlecenter(bbox)) + scale(scalefactor) + end + else + scale(scaleargs) + end + + placeimage(img, pos, centered = centering) + return pos +end + +""" + JImage(pos::Point, img::CairoSurfaceBase{UInt32}, centering = true; shapeargs = (), shape = nothing, scaleargs = 1) + +Place a given image at a given location as a `Javis` object. +Images can be cropped to different shapes and scaled to different sizes while being placed. + +# Arguments +- `pos::Point`: Where to place the image inside a shape. +- `img::CairoSurfaceBase{UInt32}`: Expects a CairoSurfaceBase object via `readpng("your_image.png")` +- `centering::Bool`: Centers the object at `pos` +- `shapeargs`: Arguments to be passed to a given shape type +- `shape`: A Luxor shape function such as `circle`, `box`, etc. +- `scaleargs`: The arguments used for scaling the image used on the shape + +# Return + +Returns the position of the image location, `pos`. +""" +JImage( + pos::Point, + img::CairoSurfaceBase{UInt32}, + centering::Bool = true; + shapeargs = (), + shape = nothing, + scaleargs = :inset, +) = + ( + args...; + pos = pos, + img = img, + centering = centering, + shapeargs = shapeargs, + shape = shape, + scaleargs = scaleargs, + ) -> _JImage(pos, img, centering, shapeargs, shape, scaleargs) + +""" + JImage(pos::Point, img::String, centering = true; shapeargs = (), shape = nothing, scaleargs = 1) + +Place a given image at a given location as a `Javis` object. +Images can be cropped to different shapes and scaled to different sizes while being placed. + +# Arguments +- `pos::Point`: Where to place the image inside a shape +- `img::String`: Expects the path to an image +- `centering::Bool`: Centers the object at `pos` +- `shapeargs`: Arguments to be passed to a given shape type +- `shape`: A Luxor shape function such as `circle`, `box`, etc. +- `scaleargs`: The arguments used for scaling the image used on the shape + +# Return + +Returns the position of the image location, `pos`. +""" +JImage( + pos::Point, + img::String, + centering::Bool = true; + shapeargs = (), + shape = nothing, + scaleargs = :inset, +) = + ( + args...; + pos = pos, + img = readpng(img), + centering = centering, + shapeargs = shapeargs, + shape = shape, + scaleargs = scaleargs, + ) -> _JImage(pos, img, centering, shapeargs, shape, scaleargs) diff --git a/src/shorthands/JPoly.jl b/src/shorthands/JPoly.jl index dd2df48ef..cf5b4a2e5 100644 --- a/src/shorthands/JPoly.jl +++ b/src/shorthands/JPoly.jl @@ -13,7 +13,7 @@ Draw a polygon around points in the pointlist. - `color` specifies the color of the outline or the fill of it (depends on action) - `linewidth` linewidth of the outline - `action` can be `:stroke`, `:fill` or other symbols (check the Luxor documentation for details) (default: :stroke) -- `close` whether the polygon should be closed or not (default: closed) +- `close` whether the polygon should be closed or not (default: closed) - `reversepath` can be set to `true` to reverse the path and create a polygon hole """ JPoly( @@ -31,3 +31,71 @@ JPoly( linewidth = linewidth, action = action, ) -> _JPoly(pointlist, color, linewidth, action, close, reversepath) + +""" +TODO: Add documentation +""" +JPoly( + pointlist, + color = "white", + linewidth = 1, + action = :stroke, + close = true, + reversepath = false, + image_path = "", + scale_factor = :inset, +) = + ( + args...; + pointlist = pointlist, + color = color, + linewidth = linewidth, + action = action, + image_path = image_path, + scale_factor = scale_factor, + ) -> _JPoly( + pointlist, + color, + linewidth, + action, + close, + reversepath, + image_path, + scale_factor, + ) + +function _JPoly( + pointlist, + color, + linewidth, + action, + close, + reversepath, + image_path, + scale_factor, +) + img = readpng(image_path) + bbox = BoundingBox(poly(pointlist, :path)) + + if !isnothing(color) + sethue(color) + setline(linewidth) + poly(pointlist, action; close = close, reversepath = reversepath) + end + + if scale_factor == :inset + boxside = max(boxwidth(bbox), boxheight(bbox)) + imageside = max(img.width, img.height) + elseif scale_factor == :clip + boxside = min(boxwidth(bbox), boxheight(bbox)) + imageside = min(img.width, img.height) + end + + poly(pointlist, :clip) + scalefactor = boxside / imageside + + translate(boxmiddlecenter(bbox)) + scale(scalefactor) + placeimage(img, centered = true) + +end diff --git a/src/shorthands/JRect.jl b/src/shorthands/JRect.jl index bd76be7cb..4ab98df53 100644 --- a/src/shorthands/JRect.jl +++ b/src/shorthands/JRect.jl @@ -45,3 +45,67 @@ JRect( JRect(xmin::Int64, ymin::Int64, w::Real, h::Real; kwargs...) = JRect(Point(xmin, ymin), w, h; kwargs...) + + +""" +TODO: Add Documentation +""" +JRect( + cornerpoint::Point, + w::Real, + h::Real, + color = "black", + linewidth = 1, + action = :stroke, + image_path = "", + scale_factor = :inset +) = + ( + args...; + cornerpoint = cornerpoint, + w = w, + h = h, + color = color, + linewidth = linewidth, + action = action, + image_path = image_path, + scale_factor = scale_factor + ) -> _JRect(cornerpoint, w, h, color, linewidth, action, image_path, scale_factor) + +function _JRect( + cornerpoint::Point, + w::Real, + h::Real, + color, + linewidth::Real, + action::Symbol, + image_path, + scale_factor +) + bbox = BoundingBox(box(cornerpoint, w, h, :path)) + + if !isnothing(color) + sethue(color) + box(cornerpoint, w, h, :fill) + end + + img = readpng(image_path) + + if scale_factor == :inset + side = minimum([w, h]) + bbox = BoundingBox(box(cornerpoint, side, side, action = :path)) + boxside = max(boxwidth(bbox), boxheight(bbox)) + imageside = max(img.width, img.height) + box(cornerpoint, side, side, action = :clip) + elseif scale_factor == :clip + bbox = BoundingBox(rect(O, w, h, action = :path)) + boxside = min(boxwidth(bbox), boxheight(bbox)) + imageside = min(img.width, img.height) + rect(O, w, h, action = :clip) + end + + translate(boxmiddlecenter(bbox)) + scale(boxside / imageside) + placeimage(img, centered = true) + return cornerpoint +end diff --git a/src/shorthands/JStar.jl b/src/shorthands/JStar.jl index 306e5cbcb..a0dcc498a 100644 --- a/src/shorthands/JStar.jl +++ b/src/shorthands/JStar.jl @@ -20,7 +20,7 @@ end 2. JStar(xcenter, ycenter, radius; kwargs...) - same as 1. with `center = Point(xcenter, ycenter)` -Draw a star centered at a position. +Draw a star centered at a position. Return the center of the star. # Keywords for all @@ -65,3 +65,95 @@ JStar( JStar(xcenter, ycenter, radius; kwargs...) = JStar(Point(xcenter, ycenter), radius; kwargs...) + +""" +TODO: Add documentation docstring +""" +JStar( + center::Point, + radius, + color = "black", + linewidth = 1, + npoints = 5, + ratio = 0.5, + orientation = 0, + action = :stroke, + reversepath = false, + image_path = "", + scale_factor = :inset, +) = + ( + args...; + center = center, + radius = radius, + color = color, + linewidth = linewidth, + orientation = orientation, + action = action, + npoints = npoints, + image_path = image_path, + scale_factor = scale_factor, + ) -> _JStar( + center, + radius, + color, + linewidth, + npoints, + ratio, + orientation, + action, + reversepath, + image_path, + scale_factor, + ) + +function _JStar( + center, + radius, + color, + linewidth, + npoints, + ratio, + orientation, + action, + reversepath, + image_path, + scale_factor, +) + img = readpng(image_path) + bbox = BoundingBox( + star(center, radius, npoints, ratio, orientation, :path, reversepath = reversepath), + ) + if !isnothing(color) + sethue(color) + star(center, radius, npoints, ratio, orientation, :fill, reversepath = reversepath) + end + if scale_factor == :inset + bbox = BoundingBox(circle(center, radius * ratio)) + boxside = max(boxwidth(bbox), boxheight(bbox)) + imageside = max(img.width, img.height) + circle(center, radius * ratio, action = :clip) + + scalefactor = boxside / imageside + + translate(boxmiddlecenter(bbox)) + scale(scalefactor) + elseif scale_factor == :clip + boxside = min(boxwidth(bbox), boxheight(bbox)) + imageside = min(img.width, img.height) + + star(center, radius, npoints, ratio, orientation, action = :clip) + + scalefactor = boxside / imageside + + translate(boxmiddlecenter(bbox)) + scale(scalefactor) + end + + sethue(color) + setline(linewidth) + + placeimage(img, center, centered = true) + + return center +end diff --git a/test/refs/dispatch.png b/test/refs/dispatch.png new file mode 100644 index 000000000..4ebf9d5cd Binary files /dev/null and b/test/refs/dispatch.png differ diff --git a/test/runtests.jl b/test/runtests.jl index 849f1dfd3..0ff074ca8 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -35,28 +35,28 @@ function circle_with_color(position, radius, action, color) end @testset "Javis" begin - @testset "Unit" begin - include("unit.jl") - end - @testset "SVG LaTeX tests" begin - include("svg.jl") - end - @testset "Animations" begin - include("animations.jl") - end - @testset "Morphing" begin - include("morphing.jl") - end - @testset "Javis Viewer" begin - include("viewer.jl") - end + # @testset "Unit" begin + # include("unit.jl") + # end + # @testset "SVG LaTeX tests" begin + # include("svg.jl") + # end + # @testset "Animations" begin + # include("animations.jl") + # end + # @testset "Morphing" begin + # include("morphing.jl") + # end + # @testset "Javis Viewer" begin + # include("viewer.jl") + # end @testset "Shorthands" begin include("shorthands.jl") end - @testset "Layers" begin - include("layers.jl") - end - @testset "Postprocessing" begin - include("postprocessing.jl") - end + # @testset "Layers" begin + # include("layers.jl") + # end + # @testset "Postprocessing" begin + # include("postprocessing.jl") + # end end diff --git a/test/shorthands.jl b/test/shorthands.jl index ac236cf0b..ac0f4c4e9 100644 --- a/test/shorthands.jl +++ b/test/shorthands.jl @@ -147,3 +147,103 @@ video = Video(800, 800) rm("images/palette.png") rm("shorthands.gif") end + +video = Video(400, 400) +origin(Point(200, 200)) +@testset "JImage for J-Objects" begin + function ground(args...) + background("white") + sethue("black") + end + + Background(1:30, ground) + + circle_img = Object( + 1:5, + JImage( + O, + readpng("refs/dispatch.png"), + true; + shape = circle, + shapeargs = (pt = O, r = 40, action = :clip), + scaleargs = 1, + ), + ) + poly_img = Object( + 6:10, + JImage( + O, + readpng("refs/dispatch.png"), + true; + shape = poly, + shapeargs = ( + pointlist = [ + Point(-100, 0), + Point(0, -100), + Point(100, 0), + Point(80, 100), + Point(-80, 100), + ], + action = :clip, + ), + scaleargs = 1, + ), + ) + box_img = Object( + 11:15, + JImage( + O, + readpng("refs/dispatch.png"), + true; + shape = box, + shapeargs = (points = [O, Point(-100, -100)], action = :clip), + scaleargs = 1, + ), + ) + star_img = Object( + 16:20, + JImage( + O, + readpng("refs/dispatch.png"), + true; + shape = star, + shapeargs = ( + center = O, + radius = 100, + npoints = 5, + ratio = 0.5, + orientation = 0, + action = :clip, + ), + scaleargs = 1, + ), + ) + ellipse_img = Object( + 21:25, + JImage( + O, + readpng("refs/dispatch.png"), + true; + shape = ellipse, + shapeargs = (cpt = O, w = 200, h = 100, action = :clip), + scaleargs = 1, + ), + ) + + render(video; tempdirectory = "images", pathname = "shorthands.gif") + + @test_reference "refs/jimage01.png" load("images/0000000001.png") + @test_reference "refs/jimage06.png" load("images/0000000006.png") + @test_reference "refs/jimage11.png" load("images/0000000011.png") + @test_reference "refs/jimage16.png" load("images/0000000016.png") + @test_reference "refs/jimage21.png" load("images/0000000021.png") + + # TODO: Add tests for scaling + # for i in 1:90 + # rm("images/$(lpad(i, 10, "0")).png") + # end + + rm("images/palette.png") + rm("shorthands.gif") + +end