Luxor.@draw
— Macro@draw drawinginstructions [width] [height]
Preview an PNG drawing, optionally specifying width and height (the default is 600 by 600). The drawing is stored in memory, not in a file on disk.
Examples
@draw circle(O, 20, :fill)
@draw circle(O, 20, :fill) 400
@draw circle(O, 20, :fill) 400 1200
@draw begin
setline(10)
sethue("purple")
circle(O, 20, :fill)
end
@draw begin
setline(10)
sethue("purple")
circle(O, 20, :fill)
end 1200 1200
Luxor.@drawsvg
— Macro@drawsvg begin
body
end w h
Create and preview an SVG drawing. Like @draw
but using SVG format.
Unlike @draw
(PNG), there is no background, by default.
Luxor.@eps
— Macro@eps drawinginstructions [width] [height] [filename]
Create and preview an EPS drawing, optionally specifying width and height (the default is 600 by 600). The file is saved in the current working directory as filename
if supplied, or luxordrawing(timestamp).eps
.
On some platforms, EPS files are converted automatically to PDF when previewed.
Examples
@eps circle(O, 20, :fill)
@eps circle(O, 20, :fill) 400
@eps circle(O, 20, :fill) 400 1200
@eps circle(O, 20, :fill) 400 1200 "/tmp/A0version"
@eps circle(O, 20, :fill) 400 1200 "/tmp/A0version.eps"
@eps begin
setline(10)
sethue("purple")
circle(O, 20, :fill)
end
@eps begin
setline(10)
sethue("purple")
circle(O, 20, :fill)
end 1200 1200
Luxor.@imagematrix
— Macro@imagematrix drawinginstructions [width=256] [height=256]
Create a drawing and return a matrix of the image.
This macro returns a matrix of pixels that represent the drawing produced by the vector graphics instructions. It uses the image_as_matrix()
function.
The default drawing is 256 by 256 points.
You don't need finish()
(the macro calls it), and it's not previewed by preview()
.
m = @imagematrix begin
sethue("red")
box(O, 20, 20, :fill)
end 60 60
julia> m[1220:1224] > show
ARGB32[ARGB32(0.0N0f8,0.0N0f8,0.0N0f8,0.0N0f8),
ARGB32(1.0N0f8,0.0N0f8,0.0N0f8,1.0N0f8),
ARGB32(1.0N0f8,0.0N0f8,0.0N0f8,1.0N0f8),
ARGB32(1.0N0f8,0.0N0f8,0.0N0f8,1.0N0f8),
ARGB32(1.0N0f8,0.0N0f8,0.0N0f8,1.0N0f8)]
If, for some strange reason you want to draw the matrix as another Luxor drawing again, use code such as this:
m = @imagematrix begin
sethue("red")
box(O, 20, 20, :fill)
sethue("blue")
box(O, 10, 40, :fill)
end 60 60
function convertmatrixtocolors(m)
return convert.(Colors.RGBA, m)
end
function drawimagematrix(m)
d = Drawing(500, 500, "/tmp/temp.png")
origin()
w, h = size(m)
t = Tiler(500, 500, w, h)
mi = convertmatrixtocolors(m)
@show mi[30, 30]
for (pos, n) in t
c = mi[t.currentrow, t.currentcol]
setcolor(c)
box(pos, t.tilewidth 1, t.tileheight  1, :fill)
end
finish()
return d
end
drawimagematrix(m)
Transparency
The default value for the cells in an image matrix is transparent black. (Luxor's default color is opaque black.)
julia> @imagematrix begin
end 2 2
2×2 reinterpret(ARGB32, ::Array{UInt32,2}):
ARGB32(0.0,0.0,0.0,0.0) ARGB32(0.0,0.0,0.0,0.0)
ARGB32(0.0,0.0,0.0,0.0) ARGB32(0.0,0.0,0.0,0.0)
Setting the background to a partially or completely transparent value may give unexpected results:
julia> @imagematrix begin
background(1, 0.5, 0.0, 0.5) # semitransparent orange
end 2 2
2×2 reinterpret(ARGB32, ::Array{UInt32,2}):
ARGB32(0.502,0.251,0.0,0.502) ARGB32(0.502,0.251,0.0,0.502)
ARGB32(0.502,0.251,0.0,0.502) ARGB32(0.502,0.251,0.0,0.502)
here the semitransparent orange color has been partially applied to the transparent background.
julia> @imagematrix begin
sethue(1., 0.5, 0.0)
paint()
end 2 2
2×2 reinterpret(ARGB32, ::Array{UInt32,2}):
ARGB32(1.0,0.502,0.0,1.0) ARGB32(1.0,0.502,0.0,1.0)
ARGB32(1.0,0.502,0.0,1.0) ARGB32(1.0,0.502,0.0,1.0)
picks up the default alpha of 1.0.
Luxor.@imagematrix!
— Macro@imagematrix! buffer drawinginstructions [width=256] [height=256]
Like @imagematrix
, but use an existing UInt32 buffer.
w = 200
h = 150
buffer = zeros(UInt32, w, h)
m = @imagematrix! buffer juliacircles(40) 200 150;
Images.RGB.(m)
Luxor.@layer
— MacroThe `layer` macro is a shortcut for `gsave()` ... `grestore()`.
Luxor.@pdf
— Macro@pdf drawinginstructions [width] [height] [filename]
Create and preview an PDF drawing, optionally specifying width and height (the default is 600 by 600). The file is saved in the current working directory as filename
if supplied, or luxordrawing(timestamp).pdf
.
Examples
@pdf circle(O, 20, :fill)
@pdf circle(O, 20, :fill) 400
@pdf circle(O, 20, :fill) 400 1200
@pdf circle(O, 20, :fill) 400 1200 "/tmp/A0version"
@pdf circle(O, 20, :fill) 400 1200 "/tmp/A0version.pdf"
@pdf begin
setline(10)
sethue("purple")
circle(O, 20, :fill)
end
@pdf begin
setline(10)
sethue("purple")
circle(O, 20, :fill)
end 1200 1200
Luxor.@png
— Macro@png drawinginstructions [width] [height] [filename]
Create and preview an PNG drawing, optionally specifying width and height (the default is 600 by 600). The file is saved in the current working directory as filename
, if supplied, or luxordrawing(timestamp).png
.
Examples
@png circle(O, 20, :fill)
@png circle(O, 20, :fill) 400
@png circle(O, 20, :fill) 400 1200
@png circle(O, 20, :fill) 400 1200 "/tmp/round"
@png circle(O, 20, :fill) 400 1200 "/tmp/round.png"
@png begin
setline(10)
sethue("purple")
circle(O, 20, :fill)
end
@png begin
setline(10)
sethue("purple")
circle(O, 20, :fill)
end 1200 1200
Luxor.@polar
— Macro@polar (p)
Convert a tuple of two numbers to a Point of x, y Cartesian coordinates.
@polar (10, pi/4)
@polar [10, pi/4]
produces
Luxor.Point(7.0710678118654755, 7.071067811865475)
Luxor.@savesvg
— Macro@savesvg begin
body
end w h
Like @drawsvg
but returns the raw SVG code of the drawing in a string. Uses svgstring
.
Unlike @draw
(PNG), there is no background, by default.
Luxor.@setcolor_str
— MacroSet the current color to a string using a macro.
For example:
setcolor"red"
Luxor.@svg
— Macro@svg drawinginstructions [width] [height] [filename]
Create and preview an SVG drawing, optionally specifying width and height (the default is 600 by 600). The file is saved in the current working directory as filename
if supplied, or luxordrawing(timestamp).svg
.
Examples
@svg circle(O, 20, :fill)
@svg circle(O, 20, :fill) 400
@svg circle(O, 20, :fill) 400 1200
@svg circle(O, 20, :fill) 400 1200 "/tmp/test"
@svg circle(O, 20, :fill) 400 1200 "/tmp/test.svg"
@svg begin
setline(10)
sethue("purple")
circle(O, 20, :fill)
end
@svg begin
setline(10)
sethue("purple")
circle(O, 20, :fill)
end 1200 1200
Base.convert
— Methodconvert(Point, bbox::BoundingBox)
Convert a BoundingBox to a fourpoint clockwise polygon.
convert(Vector{Point}, BoundingBox())
Base.in
— Methodin(pt, bbox::BoundingBox)
Test whether pt
is inside bbox
.
Base.rand
— Methodrand(bbox::BoundingBox)
Return a random Point
that lies inside bbox
.
Luxor.Circle
— FunctionCircle(t::Turtle, radius=1.0)
Draw a filled circle centered at the current position with the given radius.
Luxor.Forward
— FunctionForward(t::Turtle, d=1)
Move the turtle forward by d
units. The stored position is updated.
Luxor.HueShift
— FunctionHueShift(t::Turtle, inc=1.0)
Shift the Hue of the turtle's pen forward by inc
. Hue values range between 0 and 360. (Don't start with black, otherwise the saturation and brightness values will be black.)
Luxor.Message
— MethodMessage(t::Turtle, txt)
Write some text at the current position.
Luxor.Orientation
— FunctionOrientation(t::Turtle, r=0.0)
Set the turtle's orientation to r
degrees. See also Turn
.
Luxor.Pen_opacity_random
— MethodPen_opacity_random(t::Turtle)
Change the opacity of the pen to some value at random.
Luxor.Pencolor
— MethodPencolor(t::Turtle, r, g, b)
Set the Red, Green, and Blue colors of the turtle.
Luxor.Pendown
— MethodPendown(t::Turtle)
Put that pen down and start drawing.
Luxor.Penup
— MethodPenup(t::Turtle)
Pick that pen up and stop drawing.
Luxor.Penwidth
— MethodPenwidth(t::Turtle, w)
Set the width of the line drawn.
Luxor.Pop
— MethodPop(t::Turtle)
Lift the turtle's position and orientation off a stack.
Luxor.Push
— MethodPush(t::Turtle)
Save the turtle's position and orientation on a stack.
Luxor.Randomize_saturation
— MethodRandomize_saturation(t::Turtle)
Randomize the saturation of the turtle's pen color.
Luxor.Rectangle
— FunctionRectangle(t::Turtle, width=10.0, height=10.0)
Draw a filled rectangle centered at the current position with the given radius.
Luxor.Reposition
— MethodReposition(t::Turtle, pos::Point)
Reposition(t::Turtle, x, y)
Reposition: pick the turtle up and place it at another position.
Luxor.Towards
— MethodTowards(t::Turtle, pos::Point)
Rotate the turtle to face towards a given point.
Luxor.Turn
— FunctionTurn(t::Turtle, r=5.0)
Increase the turtle's rotation by r
degrees. See also Orientation
.
Luxor._argb32_to_rgba
— Method_argb32_to_rgba(i)
Convert a 32bit ARGB Int to a four value array:
_argb32_to_rgba(0xFF800000)
4element Array{Float64,1}:
1.0
0.5019607843137255
0.0
0.0
Luxor._empty_neighbourhood
— Methodemptyneighbourhood(sample, w, h, cellsize, d, points, grid)
Uses entries in grid
to check whether the sample
point is more than d
units away from any other point in points
.
The region we're analyzing lies between the origin and Point(w, h)
`.
Luxor.add_mesh_patch
— Functionadd_mesh_patch(pattern::Mesh, bezierpath::BezierPath,
colors=Array{Colors.Colorant, 1})
Add a new patch to the mesh pattern in pattern
.
The first three or four elements of the supplied bezierpath
define the three or four sides of the mesh shape.
The colors
array define the color of each corner point. Colors are reused if necessary. At least one color should be supplied.
Use setmesh()
to select the mesh, which will be used to fill shapes.
Luxor.add_mesh_patch
— Functionadd_mesh_patch(pattern::Mesh, plist::Array{Point}, colors=Array{Colors.Colorant, 1})
Add a new patch to the mesh pattern in pattern
.
The first three or four sides of the supplied points
polygon define the three or four sides of the mesh shape.
The colors
array define the color of each corner point. Colors are reused if necessary. At least one color should be supplied.
Luxor.addstop
— Methodaddstop(b::Blend, offset, col)
addstop(b::Blend, offset, (r, g, b, a))
addstop(b::Blend, offset, string)
Add a color stop to a blend. The offset specifies the location along the blend's 'control vector', which varies between 0 (beginning of the blend) and 1 (end of the blend). For linear blends, the control vector is from the start point to the end point. For radial blends, the control vector is from any point on the start circle, to the corresponding point on the end circle.
Examples:
blendredblue = blend(Point(0, 0), 0, Point(0, 0), 1)
addstop(blendredblue, 0, setcolor(sethue("red")..., .2))
addstop(blendredblue, 1, setcolor(sethue("blue")..., .2))
addstop(blendredblue, 0.5, sethue(randomhue()...))
addstop(blendredblue, 0.5, setcolor(randomcolor()...))
Luxor.anglethreepoints
— Methodanglethreepoints(p1::Point, p2::Point, p3::Point)
Find the angle formed by two lines defined by three points.
If the angle is less than π, the line heads to the left.
Luxor.animate
— Methodanimate(movie::Movie, scene::Scene; creategif=false, framerate=30)
Create the movie defined in movie
by rendering the frames define in scene
.
Luxor.animate
— Methodanimate(movie::Movie, scenelist::Array{Scene, 1};
creategif=false,
framerate=30,
pathname="",
tempdirectory="",
usenewffmpeg=true)
Create the movie defined in movie
by rendering the frames define in the array of scenes in scenelist
.
If creategif
is true
, the function attempts to call the ffmpeg
utility on the resulting frames to build a GIF animation. This will be stored in pathname
(an existing file will be overwritten; use a ".gif" suffix), or in (movietitle).gif
in a temporary directory. ffmpeg
should be installed and available, of course, if this is to work.
In suitable environments, the resulting animation is displayed in the Plots window.
Example
animate(bang, [
Scene(bang, backdrop, 0:200),
Scene(bang, frame1, 0:200, easingfunction=easeinsine)],
creategif=true,
pathname="/tmp/animationtest.gif")
The usenewffmpeg
option, true
by default, uses singlepass palette generation and more complex filtering provided by recent versions of the ffmpeg
utility, mainly to cope with transparent backgrounds. If set to false
, the behavior is the same as in previous versions of Luxor.
If you prefer to use the FFMPEG package, use code such as this:
using FFMPEG
...
tempdirectory = "/tmp/temp/"
animate(movie, [
Scene(movie, frame, 1:50)
], creategif=false, tempdirectory=tempdirectory)
FFMPEG.ffmpeg_exe(`r 30 f image2 i $(tempdirectory)/%10d.png c:v libx264 r 30 pix_fmt yuv420p y /tmp/animation.mp4`)
Luxor.arc
— Methodarc(centerpoint::Point, radius, angle1, angle2; action=:none)
arc(centerpoint::Point, radius, angle1, angle2, action)
Add an arc to the current path from angle1
to angle2
going clockwise, centered at centerpoint
.
Angles are defined relative to the xaxis, positive clockwise.
Luxor.arc2r
— Method arc2r(c1::Point, p2::Point, p3::Point; action=:none)
arc2r(c1::Point, p2::Point, p3::Point, action)
Add a circular arc centered at c1
that starts at p2
and ends at p3
, going clockwise, to the current path.
c1
p2
really determines the radius. If p3
doesn't lie on the circular path, it will be used only as an indication of the arc's length, rather than its position.
Luxor.arc2sagitta
— Methodarc2sagitta(p1::Point, p2::Point, s;
action=:none)
Make a clockwise arc starting at p1
and ending at p2
that reaches a height of s
, the sagitta, at the middle, and add it to the current path.
Return tuple of the center point and the radius of the arc.
Luxor.arrow
— Functionarrow(start::Point, finish::Point, height::Vector, action=:stroke;
keyword arguments...)
Draw a Bézier arrow between start
and finish
, with control points defined to fit in an imaginary box defined by the two supplied height
values (see bezierfrompoints()
). If the height values are different signs, the arrow will change direction on its way.
Keyword arguments are the same as arrow(pt1, pt2, pt3, pt4)
.
Example
arrow(pts[1], pts[end], [15, 15],
decoration = 0.5,
decorate = () > text(string(pts[1])))
Luxor.arrow
— Functionarrow(start::Point, C1::Point, C2::Point, finish::Point, action=:stroke;
linewidth = 1.0,
arrowheadlength = 10,
arrowheadangle = pi/8,
startarrow = false,
finisharrow = true,
decoration = 0.5,
decorate = nothing
arrowheadfunction = nothing)
Draw a Bezier curved arrow, from start
to finish
, with control points C1
and C2
. Arrow heads can be added/hidden by changing startarrow
and finisharrow
options.
The decorate
keyword argument accepts a function that can execute code at one or more locations on the arrow's shaft. The inherited graphic environment is centered at each point on the shaft given by scalar or vector decoration
, and the xaxis is aligned with the direction of the curve at that point.
Example
This code draws an arrow head that's filled with orange and outlined in green.
function myarrowheadfunction(originalendpoint, newendpoint, shaftangle)
@layer begin
setline(5)
translate(newendpoint)
rotate(shaftangle)
sethue("orange")
ngon(O, 20, 3, 0, :fill)
sethue("green")
ngon(O, 20, 3, 0, :stroke)
end
end
@drawsvg begin
background("white")
arrow(O, 220, 0, π,
linewidth=10,
arrowheadlength=30,
arrowheadangle=π/7,
clockwise=true,
arrowheadfunction = myarrowheadfunction)
end
Luxor.arrow
— Methodarrow(centerpos::Point, radius, startangle, endangle;
linewidth = 1.0,
arrowheadlength = 10,
arrowheadangle = π/8,
decoration = 0.5,
decorate = nothing,
arrowheadfunction = nothing,
clockwise = true)
Draw a curved arrow, an arc centered at centerpos
starting at startangle
and ending at endangle
with an arrowhead at the end. Angles are measured clockwise from the positive xaxis.
Arrows don't use the current linewidth setting (setline()
); you can specify the linewidth.
The decorate
keyword argument accepts a zeroargument function that can execute code at one or more locations on the arrow's shaft. The inherited graphic environment is centered at points on the shaft between 0 and 1 given by scalar or vector decoration
, and the xaxis is aligned with the direction of the curve at that point.
A triangular arrowhead is drawn by default. But you can pass a function to the arrowheadfunction
keyword argument that accepts three arguments: the shaft end, the arrow head end, and the shaft angle. Thsi allows you to draw any shape arrowhead.
Luxor.arrow
— Methodarrow(startpoint::Point, endpoint::Point;
linewidth = 1.0,
arrowheadlength = 10,
arrowheadangle = pi/8,
decoration = 0.5 or range(),
decorate = nothing,
arrowheadfunction = nothing)
Draw a line between two points and add an arrowhead at the end. The arrowhead length will be the length of the side of the arrow's head, and the arrowhead angle is the angle between the sloping side of the arrowhead and the arrow's shaft.
Arrows don't use the current linewidth setting (setline()
), and defaults to 1, but you can specify another value. It doesn't need stroking/filling, the shaft is stroked and the head filled with the current color.
Decoration
The decorate
keyword argument accepts a function with zero arguments that can execute code at one or more locations on the arrow's shaft. The inherited graphic environment is centered at each point on the shaft between 0 and 1 given by scalar or vector decoration
, and the xaxis is aligned with the direction of the curve at that point.
Arrowheads
A triangular arrowhead is drawn by default. But you can pass a function to the arrowheadfunction
keyword argument that accepts three arguments: the shaft end, the arrow head end, and the shaft angle. Thsi allows you to draw any shape arrowhead.
Example
function redbluearrow(shaftendpoint, endpoint, shaftangle)
@layer begin
sethue("red")
sidept1 = shaftendpoint + polar(10, shaftangle + π/2 )
sidept2 = shaftendpoint  polar(10, shaftangle + π/2)
poly([sidept1, endpoint, sidept2], :fill)
sethue("blue")
poly([sidept1, endpoint, sidept2], :stroke, close=false)
end
end
@drawsvg begin
background("white")
arrow(O, O + (120, 120),
linewidth=4,
arrowheadlength=40,
arrowheadangle=π/7,
arrowheadfunction = redbluearrow)
arrow(O, 100, 3π/2, π,
linewidth=4,
arrowheadlength=20,
clockwise=false,arrowheadfunction=redbluearrow)
end 800 250
Luxor.arrowhead
— Functionarrowhead(target[, action=:fill];
shaftangle=0,
headlength=10,
headangle=pi/8)
Draw an arrow head. The arrowhead length will be the length of the side of the arrow's head, and the arrowhead angle is the angle between the sloping side of the arrowhead and the arrow's shaft.
This doesn't use the current linewidth setting (setline()
), and defaults to 1, but you can specify another value.
Luxor.background
— Methodbackground(color)
Fill the canvas with a single color. Returns the (red, green, blue, alpha) values.
Examples:
background("antiquewhite")
background(1, 0.0, 1.0)
background(1, 0.0, 1.0, .5)
If Colors.jl is installed:
background(RGB(0, 1, 0))
background(RGBA(0, 1, 0))
background(RGBA(0, 1, 0, .5))
background(Luv(20, 20, 30))
If you don't specify a background color for a PNG drawing, the background will be transparent. You can set a partly or completely transparent background for PNG files by passing a color with an alpha value, such as this 'transparent black':
background(RGBA(0, 0, 0, 0))
or
background(0, 0, 0, 0)
Returns a tuple (r, g, b, a)
of the color that was used to paint the background.
Luxor.barchart
— Methodbarchart(values;
boundingbox = BoundingBox(O + (250, 120), O + (250, 120)),
bargap=10,
margin = 5,
border=false,
labels=false,
labelfunction = (values, i, lowpos, highpos, barwidth, scaledvalue) > begin
label(string(values[i]), :n, highpos, offset=10)
end,
barfunction = (values, i, lowpos, highpos, barwidth, scaledvalue) > begin
@layer begin
setline(barwidth)
line(lowpos, highpos, :stroke)
end
end)
Draw a barchart where each bar is the height of a value in the values
array. The bars will be scaled to fit in a bounding box.
Text labels are drawn if the keyword labels=true
.
Extended help
The function returns a vector of points; each is the bottom center of a bar.
Draw a Fibonacci sequence as a barchart:
fib(n) = n > 2 ? fib(n  1) + fib(n  2) : 1
fibs = fib.(1:15)
@draw begin
fontsize(12)
barchart(fibs, labels=true)
end
To control the drawing of the text and bars, define functions that process the end points:
mybarfunction(values, i, lowpos, highpos, barwidth, scaledvalue)
mylabelfunction(values, i, lowpos, highpos, barwidth, scaledvalue)
and pass them like this:
barchart(vals, barfunction=mybarfunction)
barchart(vals, labelfunction=mylabelfunction)
function myprologfunction(values, basepoint, minbarrange, maxbarrange, barchartheight)
@layer begin
setline(0.2)
for i in 0:10:maximum(values)
rule(boxbottomcenter(basepoint) + (0, (rescale(i, minbarrange, maxbarrange) * barchartheight)))
end
end
end
Luxor.between
— Functionbetween(bb::BoundingBox, x)
Find a point between the two corners of a BoundingBox corresponding to x
, where x
is typically between 0 and 1.
Luxor.between
— Methodbetween(p1::Point, p2::Point, x)
between((p1::Point, p2::Point), x)
Find the point between point p1
and point p2
for x
, where x
is typically between 0 and 1. between(p1, p2, 0.5)
is equivalent to midpoint(p1, p2)
.
Luxor.bezier
— Methodbezier(t, A::Point, A1::Point, B1::Point, B::Point)
Return the result of evaluating the Bezier cubic curve function, t
going from 0 to 1, starting at A, finishing at B, control points A1 (controlling A), and B1 (controlling B).
Luxor.beziercurvature
— Methodbeziercurvature(t, A::Point, A1::Point, B1::Point, B::Point)
Return the curvature of the Bezier curve at t
([01]), given start and end points A and B, and control points A1 and B1. The value (kappa) will typically be a value between 0.001 and 0.001 for points with coordinates in the 100500 range.
κ(t) is the curvature of the curve at point t, which for a parametric planar curve is:
\[\begin{equation} \kappa = \frac{\mid \dot{x}\ddot{y}\dot{y}\ddot{x}\mid} {(\dot{x}^2 + \dot{y}^2)^{\frac{3}{2}}} \end{equation}\]
The radius of curvature, or the radius of an osculating circle at a point, is 1/κ(t). Values of 1/κ will typically be in the range 1000 to 1000 for points with coordinates in the 100500 range.
TODO Fix overshoot...
...The value of kappa can sometimes collapse near 0, returning NaN (and Inf for radius of curvature).
Luxor.bezierfrompoints
— Methodbezierfrompoints(startpoint::Point, pointonline1::Point,
pointonline2::Point, endpoint::Point)
Given four points, return the Bezier curve that passes through all four points, starting at startpoint
and ending at endpoint
. The two middle points of the returned BezierPathSegment are the two control points that make the curve pass through the two middle points supplied.
Luxor.bezierfrompoints
— Methodbezierfrompoints(ptslist::Array{Point, 1})
Given four points, return the Bezier curve that passes through all four points.
Luxor.bezierpathtopath
— Methodbezierpathtopath(bp::BezierPath)
Convert a Bezier path to a Path object.
@draw drawpath(polytopath(ngon(O, 145, 5, vertices=true)), action=:fill)
Luxor.bezierpathtopoly
— Methodbezierpathtopoly(bezierpath::BezierPath; steps=10)
Convert a Bezier path (an array of Bezier segments, where each segment is a tuple of four points: anchor1, control1, control2, anchor2) to a polygon.
To make a Bezier path, use makebezierpath()
on a polygon.
The steps
optional keyword determines how many line sections are used for each path.
Luxor.beziersegmentangles
— Methodbeziersegmentangles(pt1, pt2;
out = deg2rad(45),
in = deg2rad(135),
l = 100)
Return a BezierPathSegment joining pt1
and pt2
making the angles out
at the start and in
at the end.
It's similar to the tikZ (a) to [out=135, in=45] (b)
drawing instruction (but in radians obviously).
out
is the angle that a line from pt1
to the outgoing Bézier handle makes with the horizontal. in
is the angle that a line joining pt2
from the preceding Bézier handle makes with the horizontal.
The function finds the interesction point of two lines with the two angles and constructs a BezierPathSegment that fits.
See also the bezierfrompoints()
function that makes a BezierPathSegment that passes through four points.
Example
drawbezierpath(beziersegmentangles(O, O + (100, 0),
out = deg2rad(45),
in = 2π  deg2rad(45)),
:stroke)
Luxor.bezierstroke
— Functionbezierstroke(point1, point2, width=0.0)
Return a BezierPath, a stroked version of a straight line between two points.
It wil have 2 or 6 Bezier path segments that define a brush or pen shape. If width is 0, the brush shape starts and ends at a point. Otherwise the brush shape starts and ends with the thick end.
To draw it, use eg drawbezierpath(..., :fill)
.
Luxor.beziertopoly
— Methodbeziertopoly(bpseg::BezierPathSegment; steps=10)
Convert a Bezier segment to a polygon (an array of points).
Luxor.bezier′
— Methodbezier′(t, A::Point, A1::Point, B1::Point, B::Point)
Return the first derivative of the Bezier function.
Luxor.bezier′′
— Methodbezier′′(t, A::Point, A1::Point, B1::Point, B::Point)
Return the second derivative of Bezier function.
Luxor.blend
— Methodblend(centerpos1, rad1, centerpos2, rad2, color1, color2)
Create a radial blend.
Example:
redblue = blend(
pos, 0, # first circle center and radius
pos, tiles.tilewidth/2, # second circle center and radius
"red",
"blue"
)
Luxor.blend
— Methodblend(from::Point, startradius, to::Point, endradius)
Create an empty radial blend.
Radial blends are defined by two circles that define the start and stop locations. The first point is the center of the start circle, the first radius is the radius of the first circle.
A new blend is empty. To add colors, use addstop()
.
Luxor.blend
— Methodblend(pt1::Point, pt2::Point, color1, color2)
Create a linear blend.
Example:
redblue = blend(pos, pos, "red", "blue")
Luxor.blend
— Methodblend(from::Point, to::Point)
Create an empty linear blend.
A blend is a specification of how one color changes into another. Linear blends are defined by two points: parallel lines through these points define the start and stop locations of the blend. The blend is defined relative to the current axes origin. This means that you should be aware of the current axes when you define blends, and when you use them.
To add colors, use addstop()
.
Luxor.blendadjust
— Functionblendadjust(ablend, center::Point, xscale, yscale, rot=0)
Modify an existing blend by scaling, translating, and rotating it so that it will fill a shape properly even if the position of the shape is nowhere near the original location of the blend's definition.
For example, if your blend definition was this (notice the 1
)
blendgoldmagenta = blend(
Point(0, 0), 0, # first circle center and radius
Point(0, 0), 1, # second circle center and radius
"gold",
"magenta"
)
you can use it in a shape that's 100 units across and centered at pos
, by calling this:
blendadjust(blendgoldmagenta, Point(pos.x, pos.y), 100, 100)
then use setblend()
:
setblend(blendgoldmagenta)
Luxor.blendmatrix
— Methodblendmatrix(b::Blend, m)
Set the matrix of a blend.
To apply a sequence of matrix transforms to a blend:
A = [1 0 0 1 0 0]
Aj = cairotojuliamatrix(A)
Sj = scalingmatrix(2, .2) * Aj
Tj = translationmatrix(10, 0) * Sj
A1 = juliatocairomatrix(Tj)
blendmatrix(b, As)
Luxor.boundingboxesintersect
— Methodboundingboxesintersect(bbox1::BoundingBox, bbox2::BoundingBox)
boundingboxesintersect(acorner1::Point, acorner2::Point, bcorner1::Point, bcorner2::Point)
Return true if the two bounding boxes intersect.
Luxor.box
— Methodbox(points::Array; action=:none,
reversepath=reversepath,
vertices=vertices)
box(points::Array; action=:none,
reversepath=reversepath,
vertices=vertices)
Create a box/rectangle using the first two points of an array of Points to defined opposite corners, and add it to the current path. Then apply action
.
Use vertices=true
to return an array of the four corner points: bottom left, top left, top right, bottom right rather than execute action.
Luxor.box
— Methodbox(bbox::BoundingBox;
action = :none,
vertices = false)
box(bbox::BoundingBox, action::Symbol;
vertices=false)
Define a box using the bounds in bbox
.
Use vertices = true
to return an array of the four corner points: bottom left, top left, top right, bottom right.
Luxor.box
— Methodbox(tile::BoxmapTile, action::Symbol=:none; vertices=false)
box(tile::BoxmapTile, action=:none, vertices=false)
Use a Boxmaptile to make or draw a rectangular box. Use vertices=true
to obtain the coordinates.
Create boxmaps using boxmap()
.
Luxor.box
— Methodbox(pt, width, height, cornerradii::Array; action=:none)
box(pt, width, height, cornerradii::Array, action=:none)
Draw a box/rectangle centered at point pt
with width
and height
and round each corner by the corresponding value in the array cornerradii
.
The constructed path consists of arcs and straight lines.
The first corner is the one at the bottom left, the second at the top left, and so on.
Example
@draw begin
box(O, 120, 120, [0, 20, 40, 60], :fill)
end
Luxor.box
— Methodbox(pt, width, height, cornerradius, action=:none)
box(pt, width, height, cornerradius; action=:none)
Draw a box/rectangle centered at point pt
with width
and height
and round each corner by cornerradius
.
The constructed path consists of arcs and straight lines.
Luxor.box
— Methodbox(pt::Point, width, height; action=:none, vertices=false)
box(pt::Point, width, height, action=:none; 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 apply the action.
reversepath
reverses the direction of the path.
Luxor.box
— Methodbox(cornerpoint1, cornerpoint2; action=:none, vertices=false, reversepath=false)
box(cornerpoint1, cornerpoint2, action; vertices=false, reversepath=false)
Create a box (rectangle) between two points and add it to the current path. Then apply action
.
Use vertices=true
to return an array of the four corner points: bottom left, top left, top right, bottom right rather than execute action.
reversepath
reverses the direction of the path (and returns points in the order: bottom left, bottom right, top right, top left).
Luxor.box
— Methodbox(t::Table, cellnumber::Int, action::Symbol=:none; vertices=false)
box(t::Table, cellnumber::Int; action=:none, vertices=false)
Make box around cell cellnumber
in table t
.
Luxor.box
— Methodbox(t::Table, r::Integer, c::Integer, action::Symbol)
box(t::Table, r::Integer, c::Integer; action=:none)
Draw a box in table t
at row r
and column c
.
Luxor.box
— Methodbox(tiles::Tiler, n::Integer; action=:none, vertices=false, reversepath=false)
box(tiles::Tiler, n::Integer, action::Symbol=:none; vertices=false, reversepath=false)
Draw a box in tile n
of tiles tiles
.
Luxor.boxaspectratio
— Functionboxaspectratio(bb::BoundingBox=BoundingBox())
Return the aspect ratio (the height divided by the width) of bounding box bb
.
Luxor.boxbottomcenter
— Functionboxbottomcenter(bb::BoundingBox=BoundingBox())
Return the point at the bottom center of the BoundingBox bb
, defaulting to the drawing extent.
⋅ ⋅ ⋅
⋅ ⋅ ⋅
⋅ ■ ⋅
Luxor.boxbottomleft
— Functionboxbottomleft(bb::BoundingBox=BoundingBox())
Return the point at the bottom left of the BoundingBox bb
, defaulting to the drawing extent.
⋅ ⋅ ⋅
⋅ ⋅ ⋅
■ ⋅ ⋅
Luxor.boxbottomright
— Functionboxbottomright(bb::BoundingBox=BoundingBox())
Return the point at the bottom right of the BoundingBox bb
, defaulting to the drawing extent.
⋅ ⋅ ⋅
⋅ ⋅ ⋅
⋅ ⋅ ■
Luxor.boxdiagonal
— Functionboxdiagonal(bb::BoundingBox=BoundingBox())
Return the length of the diagonal of bounding box bb
.
Luxor.boxheight
— Functionboxheight(bb::BoundingBox=BoundingBox())
Return the height of bounding box bb
.
Luxor.boxmap
— Methodboxmap(A::Array, pt, w, h)
Build a box map of the values in A
with one corner at pt
and width w
and height h
. There are length(A)
boxes. The areas of the boxes are proportional to the original values, scaled as necessary.
The return value is an array of BoxmapTiles. For example:
[BoxmapTile(0.0, 0.0, 10.0, 20.0)
BoxmapTile(10.0, 0.0, 10.0, 13.3333)
BoxmapTile(10.0, 13.3333, 10.0, 6.66667)]
with each tile containing (x, y, w, h)
. box()
and BoundingBox()
can work with BoxmapTiles as well.
Example
using Luxor
@svg begin
fontsize(16)
fontface("HelveticaBold")
pt = Point(200, 200)
a = rand(10:200, 15)
tiles = boxmap(a, Point(200, 200), 400, 400)
for (n, t) in enumerate(tiles)
randomhue()
bb = BoundingBox(t)
box(bb  2, :stroke)
box(bb  5, :fill)
sethue("white")
text(string(n), midpoint(bb[1], bb[2]), halign=:center)
end
end 400 400 "boxmap.svg"
Luxor.boxmiddlecenter
— Functionboxmiddlecenter(bb::BoundingBox=BoundingBox())
Return the point at the center of the BoundingBox bb
, defaulting to the drawing extent.
⋅ ⋅ ⋅
⋅ ■ ⋅
⋅ ⋅ ⋅
Luxor.boxmiddleleft
— Functionboxmiddleleft(bb::BoundingBox=BoundingBox())
Return the point at the middle left of the BoundingBox bb
, defaulting to the drawing extent.
⋅ ⋅ ⋅
■ ⋅ ⋅
⋅ ⋅ ⋅
Luxor.boxmiddleright
— Functionboxmiddleright(bb::BoundingBox=BoundingBox())
Return the point at the midde right of the BoundingBox bb
, defaulting to the drawing extent.
⋅ ⋅ ⋅
⋅ ⋅ ■
⋅ ⋅ ⋅
Luxor.boxtopcenter
— Functionboxtopcenter(bb::BoundingBox=BoundingBox())
Return the point at the top center of the BoundingBox bb
, defaulting to the drawing extent.
⋅ ■ ⋅
⋅ ⋅ ⋅
⋅ ⋅ ⋅
Luxor.boxtopleft
— Functionboxtopleft(bb::BoundingBox=BoundingBox())
Return the point at the top left of the BoundingBox bb
, defaulting to the drawing extent.
■ ⋅ ⋅
⋅ ⋅ ⋅
⋅ ⋅ ⋅
Luxor.boxtopright
— Functionboxtopright(bb::BoundingBox=BoundingBox())
Return the point at the top right of the BoundingBox bb
, defaulting to the drawing extent.
⋅ ⋅ ■
⋅ ⋅ ⋅
⋅ ⋅ ⋅
Luxor.boxwidth
— Functionboxwidth(bb::BoundingBox=BoundingBox())
Return the width of bounding box bb
.
Luxor.brush
— Functionbrush(pt1, pt2, width=10;
strokes=10,
minwidth=0.01,
maxwidth=0.03,
twist = 1,
lowhandle = 0.3,
highhandle = 0.7,
randomopacity = true,
tidystart = false,
action = :fill,
strokefunction = (nbpb) > nbpb))
Draw a composite brush stroke made up of some randomized individual filled Bezier paths.
strokefunction
allows a function to process a BezierPathSegment or do other things before it's drawn.
There is a lot of randomness in this function. Results are unpredictable.
Luxor.buildcolumn
— Methodbuildcolumn(A, x, y, w, h)
Make a column of tiles from A.
Luxor.buildrow
— Methodbuildrow(A, x, y, w, h)
Make a row of tiles from A.
Luxor.cairotojuliamatrix
— Methodcairotojuliamatrix(c)
Return a 3x3 Julia matrix that's the equivalent of the sixelement matrix in c
.
Luxor.carc
— Methodcarc(centerpoint::Point, radius, angle1, angle2; action=:none)
carc(centerpoint::Point, radius, angle1, angle2, action)
Add an arc centered at centerpoint
to the current path from angle1
to angle2
, going counterclockwise.
Angles are defined relative to the xaxis, positive clockwise.
Luxor.carc2r
— Methodcarc2r(c1::Point, p2::Point, p3::Point; action=:none)
Add a circular arc centered at c1
that starts at p2
and ends at p3
, going counterclockwise, to the current path.
c1
p2
really determines the radius. If p3
doesn't lie on the circular path, it will be used only as an indication of the arc's length, rather than its position.
Luxor.carc2sagitta
— Methodcarc2sagitta(p1::Point, p2::Point, s;
action=:none)
Make a counterclockwise arc starting at p1
and ending at p2
that reaches a height of s
, the sagitta, at the middle, and add it to the current path.
Return tuple of center point and radius of arc.
Luxor.center3pts
— Methodcenter3pts(a::Point, b::Point, c::Point)
Find the radius and center point for three points lying on a circle.
returns (centerpoint, radius)
of a circle.
If there's no such circle, the function returns (Point(0, 0), 0)
.
If two of the points are the same, use circle(pt1, pt2)
instead.
Luxor.circle
— Methodcircle(pt1::Point, pt2::Point, pt3::Point; action=:none)
circle(pt1::Point, pt2::Point, pt3::Point, action)
Make a circle that passes through three points, and add it to the current path.
Luxor.circle
— Methodcircle(pt1::Point, pt2::Point; action=:none)
circle(pt1::Point, pt2::Point, action)
Make a circle that passes through two points that define the diameter, and add it to the current path.
Luxor.circle
— Methodcircle(centerpoint::Point, r; action=:none)
circle(centerpoint::Point, r, action)
Make a circle of radius r
centered at 'centerpoint', and add it to the current path.
action
is one of the actions applied by do_action
, defaulting to :none
.
Returns a tuple of two points, the corners of a bounding box that encloses the circle.
You can also use ellipse()
to draw circles and place them by their centerpoint.
Luxor.circlecircleinnertangents
— Methodcirclecircleinnertangents(circle1center::Point, circle1radius, circle2center::Point, circle2radius)
Find the inner tangents of two circles. These are tangent lines that cross as they skim past one circle and touch the other.
Returns the four points: tangentpoint1 on circle 1, tangentpoint1 on circle2, tangentpoint2 on circle 1, tangentpoint2 on circle2.
Returns (O, O, O, O)
if inner tangents can't be found (eg when the circles overlap).
Use circlecircleoutertangents()
to find the outer tangents.
Luxor.circlecircleoutertangents
— Methodcirclecircleoutertangents(cpt1::Point, r1, cpt2::Point, r2)
Return four points, p1
, p2,
p3,
p4, where a line through
p1and
p2, and a line through
p3and
p4, form the outer tangents to the circles defined by
cpt1/r1and
cpt2/r2`.
Returns four identical points (O
) if one of the circles lies inside the other.
Luxor.circlepath
— Methodcirclepath(center::Point, radius;
action=:none,
reversepath=false,
kappa = 0.5522847498307936)
circlepath(center::Point, radius, action;
reversepath=false,
kappa = 0.5522847498307936)
Make a circle using Bézier curves, and add it to the current path.
One benefit of using this rather than circle()
is that you can use the reversepath
option to draw the circle clockwise rather than circle
's counterclockwise.
The magic value, kappa
, is 4.0 * (sqrt(2.0)  1.0) / 3.0
.
Return two points, the corners of a bounding box.
Luxor.circlepointtangent
— Methodcirclepointtangent(through::Point, radius, targetcenter::Point, targetradius)
Find the centers of up to two circles of radius radius
that pass through point through
and are tangential to a circle that has radius targetradius
and center targetcenter
.
This function returns a tuple:
(0, O, O)  no circles exist
(1, pt1, O)  1 circle exists, centered at pt1
(2, pt1, pt2)  2 circles exist, with centers at pt1 and pt2
(The O are just dummy points so that three values are always returned.)
Luxor.circletangent2circles
— Methodcircletangent2circles(radius, circle1center::Point, circle1radius, circle2center::Point, circle2radius)
Find the centers of up to two circles of radius radius
that are tangent to the two circles defined by circle1...
and circle2...
. These two circles can overlap, but one can't be inside the other.
(0, O, O)  no such circles exist
(1, pt1, O)  1 circle exists, centered at pt1
(2, pt1, pt2)  2 circles exist, with centers at pt1 and pt2
(The O are just dummy points so that three values are always returned.)
Luxor.clip
— Methodclip()
Establish a new clipping region by intersecting the current clipping region with the current path and then clearing the current path.
An existing clipping region is enforced through and after a gsave()
grestore()
block, but a clipping region set inside a gsave()
grestore()
block is lost after grestore()
. [?]
Luxor.clippreserve
— Methodclippreserve()
Establish a new clipping region by intersecting the current clipping region with the current path, but keep the current path.
Luxor.clipreset
— Methodclipreset()
Reset the clipping region to the current drawing's extent.
Luxor.closepath
— Methodclosepath()
Close the current path. This is Cairo's close_path()
function.
Luxor.crescent
— Methodcrescent(cp1, r1, cp2, r2; action=nothing, vertices=false, reversepath=false)
Create a crescentshaped polygon, aligned with the current xaxis, by finding the intersection of two circles, and add it to the current path. The two center positions should be different.
See also crescent(point, innerradius, outeradius...)
.
Examples
Create a filled crescent shape from two circles.
crescent(O, 100, O + (60, 0), 150, :fill) # or
crescent(O, 100, O + (60, 0), 150, action=:fill)
Luxor.crescent
— Methodcrescent(pos, innerradius, outeradius;
action=nothing,
vertices=false,
reversepath=false,
steps = 30)
Create a crescentshaped polygon, aligned with the current xaxis, and add it to the current path. If the inner radius is 0, you'll get a semicircle.
See also crescent(pos1, innerradius, pos2, outeradius...)
.
Examples
Create a filled crescent shape with outer radius of 200, inner radius of 130.
crescent(O, 130, 200, :fill) # or
crescent(O, 130, 200, action=:fill)
Create a stroked crescent shape  the inner radius of 0 produces a semicircle  and add it to the current path.
crescent(O, 0, 200, :stroke) # or
crescent(O, 0, 200, action=:stroke)
Luxor.cropmarks
— Methodcropmarks(center, width, height)
Draw cropmarks (also known as trim marks). Use current color.
Luxor.crossproduct
— Methodcrossproduct(p1::Point, p2::Point)
This is the perp dot product, really, not the crossproduct proper (which is 3D):
Luxor.currentdrawing
— Methodcurrentdrawing()
Return the current Luxor drawing, if there currently is one.
Luxor.currentpoint
— Methodcurrentpoint()
Return the current point.
Luxor.curve
— Methodcurve(x1, y1, x2, y2, x3, y3)
curve(p1, p2, p3)
Add a Bézier curve to the current path..
The spline starts at the current position, finishing at x3/y3
(p3
), following two control points x1/y1
(p1
) and x2/y2
(p2
).
Luxor.determinant3
— Methoddeterminant3(p1::Point, p2::Point, p3::Point)
Find the determinant of the 3×3 matrix:
\[\begin{bmatrix} p1.x & p1.y & 1 \\ p2.x & p2.y & 1 \\ p3.x & p3.y & 1 \\ \end{bmatrix}\]
If the value is 0.0, the points are collinear.
Luxor.dimension
— Methoddimension(p1::Point, p2::Point;
format::Function = (d) > string(d), # process the measured value into a string
offset = 0.0, # left/right, parallel with x axis
fromextension = (10.0, 10.0), # length of extensions lines left and right
toextension = (10.0, 10.0), #
textverticaloffset = 0.0, # range 1.0 (top) to 1.0 (bottom)
texthorizontaloffset = 0.0, # range 1.0 (top) to 1.0 (bottom)
textgap = 5, # gap between start of each arrow (≈ fontsize?)
textrotation = 0.0,
arrowlinewidth = 1.0,
arrowheadlength = 10,
arrowheadangle = π/8)
Calculate and draw dimensioning graphics for the distance between p1
and p2
. The value can be formatted with function format
.
p1
is the lower on the page (ie probably the higher y value) point, p2
is the higher on the page (ie probably lower y) point.
offset
is to the left (x) when negative.
Dimension graphics will be rotated to align with a line between p1
and p2
.
In textverticaloffset
, "vertical" and "horizontal" are best understood by "looking" along the line from the first point to the second. textverticaloffset
ranges from 1 to 1, texthorizontaloffset
in default units.
toextension
[5 , 5]
<> <>
to
 +
^

50

v
 +
from
<> <>
[5 , 5]
fromextension
<>
offset
Returns the measured distance and the text.
Luxor.distance
— Methoddistance(p1::Point, p2::Point)
Find the distance between two points (two argument form).
Luxor.do_action
— Methoddo_action(action)
This is usually called by other graphics functions. Actions for graphics commands include :fill
, :stroke
, :clip
, :fillstroke
, :fillpreserve
, and :strokepreserve
.
The :path
action adds the graphics to the current path.
Luxor.dotproduct
— Methoddotproduct(a::Point, b::Point)
Return the scalar dot product of the two points.
Luxor.douglas_peucker
— MethodUse a nonrecursive DouglasPeucker algorithm to simplify a polygon. Used by simplify()
.
douglas_peucker(pointlist::Array, start_index, last_index, epsilon)
Luxor.drawbezierpath
— Functiondrawbezierpath(bps::BezierPathSegment, action=:none;
close=false)
Draw the Bézier path segment, and apply the action, such as :none
, :stroke
, :fill
, etc. By default the path is open.
TODO Return something more useful than a Boolean.
Luxor.drawbezierpath
— Methoddrawbezierpath(bezierpath::BezierPath, action=:none;
close=true)
drawbezierpath(bezierpath::BezierPath;
action=:none,
close=true)
Draw the Bézier path, and apply the action, such as :none
, :stroke
, :fill
, etc. By default the path is closed.
TODO Return something more useful than a Boolean.
Luxor.drawpath
— Methoddrawpath(cp::Path; action=:none, startnewpath=true)
drawpath(cp::Path, action; startnewpath=true)
Make the Luxor path stored in cp
and apply the action
.
To make paths, follow some path construction functions such as move()
, line()
, and curve()
with the storepath()
function.
By default, startnewpath=true
, which starts a new path, discarding any existing path contents.
TODO Return something more useful than a Boolean!
Luxor.easeincirc
— Methodeaseincirc(t, b, c, d)
circular easing in  accelerating from zero velocity
Luxor.easeincubic
— Methodeaseincubic(t, b, c, d)
cubic easing in  accelerating from zero velocity
Luxor.easeinexpo
— Methodeaseinexpo(t, b, c, d)
exponential easing in  accelerating from zero velocity
Luxor.easeinoutbezier
— Functioneaseinoutbezier(t, b, c, d, cpt1, cpt2)
This easing function takes six arguments, the usual t
, b
, c
, and d
, but also two points. These are the normalized control points of a Bezier curve drawn between Point(0, 0)
to Point(1.0, 1.0)
. The y
value of the Bezier is the eased value for t
.
In your frame()
generating function, if a Scene specifies the easeinoutbezier
easing function, you can use this:
...
lineareasing = rescale(framenumber, 1, scene.framerange.stop)
beziereasing = scene.easingfunction(lineareasing, 0, 1, 1,
Point(0.25, 0.25), Point(0.75, 0.75))
...
These two control points lie on the line between 0/0
and 1/1
, so it's equivalent to a linear easing (lineartween()
or easingflat
).
However, in the next example, the two control points define a wavelike curve that changes direction before changing back. When animating with this easing function, an object will 'go retrograde' for a while.
lineareasing = rescale(framenumber, 1, scene.framerange.stop)
beziereasing = scene.easingfunction(lineareasing, 0, 1, 1,
Point(0.01, 1.99), Point(0.99, 1.5))
Luxor.easeinoutcirc
— Methodeaseinoutcirc(t, b, c, d)
circular easing in/out  acceleration until halfway, then deceleration
Luxor.easeinoutcubic
— Methodeaseinoutcubic(t, b, c, d)
cubic easing in/out  acceleration until halfway, then deceleration
Luxor.easeinoutexpo
— Methodeaseinoutexpo(t, b, c, d)
exponential easing in/out  accelerating until halfway, then decelerating
Luxor.easeinoutinversequad
— Methodeaseinoutinversequad(t, b, c, d)
ease in, then slow down, then speed up, and ease out
Luxor.easeinoutquad
— Methodeaseinoutquad(t, b, c, d)
quadratic easing in/out  acceleration until halfway, then deceleration
Luxor.easeinoutquart
— Methodeaseinoutquart(t, b, c, d)
quartic easing in/out  acceleration until halfway, then deceleration
Luxor.easeinoutquint
— Methodeaseinoutquint(t, b, c, d)
quintic easing in/out  acceleration until halfway, then deceleration
Luxor.easeinoutsine
— Methodeaseinoutsine(t, b, c, d)
sinusoidal easing in/out  accelerating until halfway, then decelerating
Luxor.easeinquad
— Methodeaseinquad(t, b, c, d)
quadratic easing in  accelerating from zero velocity
Luxor.easeinquart
— Methodeaseinquart(t, b, c, d)
quartic easing in  accelerating from zero velocity
Luxor.easeinquint
— Methodeaseinquint(t, b, c, d)
quintic easing in  accelerating from zero velocity
Luxor.easeinsine
— Methodeaseinsine(t, b, c, d)
sinusoidal easing in  accelerating from zero velocity
Luxor.easeoutcirc
— Methodeaseoutcirc(t, b, c, d)
circular easing out  decelerating to zero velocity
Luxor.easeoutcubic
— Methodeaseoutcubic(t, b, c, d)
cubic easing out  decelerating to zero velocity
Luxor.easeoutexpo
— Methodeaseoutexpo(t, b, c, d)
exponential easing out  decelerating to zero velocity
Luxor.easeoutquad
— Methodeaseoutquad(t, b, c, d)
quadratic easing out  decelerating to zero velocity
Luxor.easeoutquart
— Methodeaseoutquart(t, b, c, d)
quartic easing out  decelerating to zero velocity
Luxor.easeoutquint
— Methodeaseoutquint(t, b, c, d)
quintic easing out  decelerating to zero velocity
Luxor.easeoutsine
— Methodeaseoutsine(t, b, c, d)
sinusoidal easing out  decelerating to zero velocity
Luxor.easingflat
— Methodeasingflat(t, b, c, d)
A flat easing function, same as lineartween()
.
For all easing functions, the four parameters are:
t
time, ie the current framenumberb
beginning position or bottom value of the rangec
total change in position or top value of the ranged
duration, ie a framecount
t/d
ort/=d
normalizest
to between 0 and 1... * c
scales up to the required range value... + b
adds the initial offset
Luxor.ellipse
— Methodellipse(focus1::Point, focus2::Point, k;
action=:none,
stepvalue=pi/100,
vertices=false,
reversepath=false)
Build a polygon approximation to an ellipse, given two points and a distance, k
, which is the sum of the distances to the focii of any points on the ellipse (or the shortest length of string required to go from one focus to the perimeter and on to the other focus), and add it to the current path.
Luxor.ellipse
— Methodellipse(focus1::Point, focus2::Point, pt::Point;
action=:none,
stepvalue=pi/100,
vertices=false,
reversepath=false)
Build a polygon approximation to an ellipse, given two points and a point somewhere on the ellipse.
Luxor.ellipse
— Methodellipse(centerpoint::Point, w, h; action=:none)
ellipse(centerpoint::Point, w, h; action)
Make an ellipse, centered at centerpoint
, with width w
, and height h
, and add it to the current path.
Returns a tuple of two points, the corners of a bounding box that encloses the ellipse.
Luxor.ellipseinquad
— Methodellipseinquad(qgon; action=:none)
ellipseinquad(qgon, action)
Calculate a Bézierbased ellipse that fits inside the quadrilateral qgon
, an array with at least four Points that form a convex polygon, and add it to the current path.
It returns ellipsecenter, ellipsesemimajor, ellipsesemiminor, ellipseangle
:
ellipsecenter
the ellipse centerellipsesemimajor
ellipse semimajor axisellipsesemiminor
ellipse semiminor axisellipseangle
ellipse rotation
The function returns O, 0, 0, 0
if a suitable ellipse can't be found. (The qgon is probably not a convex polygon.)
Examples
ellipseinquad(box(O, 130, 130); action=:stroke)
ellipseinquad(box(O, 140, 230), :stroke)
References
http://faculty.mae.carleton.ca/John_Hayes/Papers/InscribingEllipse.pdf
Luxor.epitrochoid
— Methodepitrochoid(R, r, d;
action=:none,
stepby=0.01,
period=0,
vertices=false)
epitrochoid(R, r, d, action;
stepby=0.01,
period=0,
vertices=false)
Make a epitrochoid with short line segments, and add it to the current path. (Like a Spirograph.) The curve is traced by a point attached to a circle of radius r
rolling around the outside of a fixed circle of radius R
, where the point is a distance d
from the center of the circle. Things get interesting if you supply nonintegral values.
stepby
, the angular step value, controls the amount of detail, ie the smoothness of the polygon.
If period
is not supplied, or 0, the lowest period is calculated for you.
The function can return a polygon (a list of points), or draw the points directly using the supplied action
. If the points are drawn, the function returns a tuple showing how many points were drawn and what the period was (as a multiple of pi
).
Luxor.fillpath
— Methodfillpath()
Fill the current path according to the current settings. The current path is then cleared.
Luxor.fillpreserve
— Methodfillpreserve()
Fill the current path with current settings, but then keep the path current.
Luxor.fillstroke
— Methodfillstroke()
Fill and stroke the current path.
Luxor.findbeziercontrolpoints
— Methodfindbeziercontrolpoints(previouspt::Point,
pt1::Point,
pt2::Point,
nextpt::Point;
smooth_value=0.5)
Find the Bézier control points for the line between pt1
and pt2
, where the point before pt1
is previouspt
and the next point after pt2
is nextpt
.
Luxor.finish
— Methodfinish()
Finish the drawing, and close the file. You may be able to open it in an external viewer application with preview()
.
Luxor.fontface
— Methodfontface(fontname)
Select a font to use. (Toy API)
Luxor.fontsize
— Methodfontsize(n)
Set the font size to n
units. The default size is 10 units. (Toy API)
Luxor.get_current_color
— Methodgetcurrentcolor()
As set by eg setcolor()
. Return an RGBA colorant.
Luxor.get_current_hue
— Methodgetcurrenthue()
As set by eg sethue()
. Return an RGB colorant.
Luxor.get_fontsize
— Methodget_fontsize()
Return the font size set by fontsize
or. more precisely. the yscale of the Cairo font matrix if Cairo.set_font_matrix
is used directly. (Toy API)
This only works if Cairo is at least at v1.0.5.
Luxor.getmatrix
— Methodgetmatrix()
Get the current matrix. Returns an array of six float64 numbers:
xx component of the affine transformation
yx component of the affine transformation
xy component of the affine transformation
yy component of the affine transformation
x0 translation component of the affine transformation
y0 translation component of the affine transformation
Some basic matrix transforms:
 translate
transform([1, 0, 0, 1, dx, dy])
shifts by dx
, dy
 scale
transform([fx 0 0 fy 0 0])
scales by fx
and fy
 rotate
transform([cos(a), sin(a), sin(a), cos(a), 0, 0])
rotates around to a
radians
rotate around O: [c s s c 0 0]
 shear
transform([1 0 a 1 0 0])
shears in x direction by a
shear in y direction by a
: [1 a 0 1 0 0]
 xskew
transform([1, 0, tan(a), 1, 0, 0])
skews in x by a
 yskew
transform([1, tan(a), 0, 1, 0, 0])
skews in y by a
 flip
transform([fx, 0, 0, fy, centerx * (1  fx), centery * (fy1)])
flips with center at centerx
/centery
 reflect
transform([1 0 0 1 0 0])
reflects in xaxis
transform([1 0 0 1 0 0])
reflects in yaxis
When a drawing is first created, the matrix looks like this:
getmatrix() = [1.0, 0.0, 0.0, 1.0, 0.0, 0.0]
When the origin is moved to 400/400, it looks like this:
getmatrix() = [1.0, 0.0, 0.0, 1.0, 400.0, 400.0]
To reset the matrix to the original:
setmatrix([1.0, 0.0, 0.0, 1.0, 0.0, 0.0])
Luxor.getmode
— Methodgetmode()
Return the current compositing/blending mode as a string.
Luxor.getnearestpointonline
— Methodgetnearestpointonline(pt1::Point, pt2::Point, startpt::Point)
Given a line from pt1
to pt2
, and startpt
is the start of a perpendicular heading to meet the line, at what point does it hit the line?
Luxor.getpath
— Methodgetpath()
Get the current path and return a CairoPath object, which is an array of element_type
and points
objects. With the results you can step through and examine each entry:
o = getpath()
x, y = currentpoint()
for e in o
if e.element_type == Cairo.CAIRO_PATH_MOVE_TO
(x, y) = e.points
move(x, y)
elseif e.element_type == Cairo.CAIRO_PATH_LINE_TO
(x, y) = e.points
# straight lines
line(x, y)
strokepath()
circle(x, y, 1, :stroke)
elseif e.element_type == Cairo.CAIRO_PATH_CURVE_TO
(x1, y1, x2, y2, x3, y3) = e.points
# Bezier control lines
circle(x1, y1, 1, :stroke)
circle(x2, y2, 1, :stroke)
circle(x3, y3, 1, :stroke)
move(x, y)
curve(x1, y1, x2, y2, x3, y3)
strokepath()
(x, y) = (x3, y3) # update current point
elseif e.element_type == Cairo.CAIRO_PATH_CLOSE_PATH
closepath()
else
error("unknown CairoPathEntry " * repr(e.element_type))
error("unknown CairoPathEntry " * repr(e.points))
end
end
Luxor.getpathflat
— Methodgetpathflat()
Get the current path, like getpath()
but flattened so that there are no Bèzier curves.
Returns a CairoPath which is an array of element_type
and points
objects.
Luxor.getrotation
— Methodgetrotation(R::Matrix)
getrotation()
Get the rotation of a Julia 3x3 matrix, or the current Luxor rotation.
\[\begin{bmatrix} a & b & tx \\ c & d & ty \\ 0 & 0 & 1 \\ \end{bmatrix}\]
The rotation angle is atan(b, a)
or atan(c, d)
.
Luxor.getscale
— Methodgetscale(R::Matrix)
getscale()
Get the current scale of a Julia 3x3 matrix, or the current Luxor scale.
Returns a tuple of x and y values.
Luxor.gettranslation
— Methodgettranslation(R::Matrix)
gettranslation()
Get the current translation of a Julia 3x3 matrix, or the current Luxor translation.
Returns a tuple of x and y values.
Luxor.getworldposition
— Functiongetworldposition(pt::Point = O;
centered=true)
Return the world coordinates of pt
.
The default coordinate system for Luxor/Cairo is that the top left corner is 0/0. If you use origin()
, everything moves to the center of the drawing, and this function with the default centered
option assumes an origin()
function. If you choose centered=false
, the returned coordinates will be relative to the top left corner of the drawing.
origin()
translate(120, 120)
@show currentpoint() # => Point(0.0, 0.0)
@show getworldposition() # => Point(120.0, 120.0)
Luxor.grestore
— Methodgrestore()
Replace the current graphics state with the one on top of the stack.
Luxor.gsave
— Methodgsave()
Save the current color settings on the stack.
Luxor.hascurrentpoint
— Methodhascurrentpoint()
Return true if there is a current point. Obtain the current point with currentpoint()
.
Luxor.highestaspectratio
— Methodhighestaspectratio()
Find the highest aspect ratio of a list of rectangles, given the length of the side along which they are to be laid out.
Luxor.highlightcells
— Functionhighlightcells(t::Table, cellnumbers, action::Symbol=:stroke;
color::Colorant=colorant"red",
offset = 0)
Highlight (draw or fill) one or more cells of table t
. cellnumbers
is a range, array, or an array of row/column tuples.
highlightcells(t, 1:10, :fill, color=colorant"blue")
highlightcells(t, vcat(1:5, 150), :stroke, color=colorant"magenta")
highlightcells(t, [(4, 5), (3, 6)])
Luxor.hypotrochoid
— Methodhypotrochoid(R, r, d;
action=:none,
stepby=0.01,
period=0.0,
vertices=false)
hypotrochoid(R, r, d, action;
stepby=0.01,
period=0.0,
vertices=false)
Make a hypotrochoid with short line segments, and add it to the current path. (Like a Spirograph.) The curve is traced by a point attached to a circle of radius r
rolling around the inside of a fixed circle of radius R
, where the point is a distance d
from the center of the interior circle. Things get interesting if you supply nonintegral values.
Special cases include the hypocycloid, if d
= r
, and an ellipse, if R
= 2r
.
stepby
, the angular step value, controls the amount of detail, ie the smoothness of the polygon,
If period
is not supplied, or 0, the lowest period is calculated for you.
The function can return a polygon (a list of points), or draw the points directly using the supplied action
. If the points are drawn, the function returns a tuple showing how many points were drawn and what the period was (as a multiple of pi
).
Luxor.image_as_matrix!
— Methodimage_as_matrix!(buffer)
Like image_as_matrix()
, but use an existing UInt32 buffer.
buffer
is a buffer of UInt32.
w = 200
h = 150
buffer = zeros(UInt32, w, h)
Drawing(w, h, :image)
origin()
juliacircles(50)
m = image_as_matrix!(buffer)
finish()
# collect(m)) is Array{ARGB32,2}
Images.RGB.(m)
Luxor.image_as_matrix
— Methodimage_as_matrix()
Return an Array of the current state of the picture as an array of ARGB32.
A matrix 50 wide and 30 high => a table 30 rows by 50 cols
using Luxor, Images
Drawing(50, 50, :png)
origin()
background(randomhue()...)
sethue("white")
fontsize(40)
fontface("Georgia")
text("42", halign=:center, valign=:middle)
mat = image_as_matrix()
finish()
Luxor.initnoise
— Methodinitnoise(seed::Int)
initnoise()
Initialize the noise generation code.
julia> initnoise(); noise(1)
0.7453148982810598
julia> initnoise(); noise(1)
0.7027617067916981
If you provide an integer seed, it will be used to seed Random.seed!()
` when the noise code is initialized:
julia> initnoise(41); noise(1) # yesterday
0.7134000046640385
julia> initnoise(41); noise(1) # today
0.7134000046640385
If you need to control which type of random number generator is used, you can provide your own and it will be used instead of the default Julia implementation.
julia> rng = MersenneTwister(1234) # any AbstractRNG
julia> initnoise(rng)
Luxor.insertvertices!
— Methodinsertvertices!(pgon;
ratio=0.5)
Insert a new vertex into each edge of a polygon pgon
. The default ratio
of 0.5 divides the original edge of the polygon into half.
Luxor.intersectboundingboxes
— Methodintersectboundingboxes(bb1::BoundingBox, bb2::BoundingBox)
Return a BoundingBox that's an intersection of the two bounding boxes.
Luxor.intersection2circles
— Methodintersection2circles(pt1, r1, pt2, r2)
Find the area of intersection between two circles, the first centered at pt1
with radius r1
, the second centered at pt2
with radius r2
.
If one circle is entirely within another, that circle's area is returned.
Luxor.intersectioncirclecircle
— Methodintersectioncirclecircle(cp1, r1, cp2, r2)
Find the two points where two circles intersect, if they do. The first circle is centered at cp1
with radius r1
, and the second is centered at cp1
with radius r1
.
Returns
(flag, ip1, ip2)
where flag
is a Boolean true
if the circles intersect at the points ip1
and ip2
. If the circles don't intersect at all, or one is completely inside the other, flag
is false
and the points are both Point(0, 0).
Use intersection2circles()
to find the area of two overlapping circles.
In the pure world of maths, it must be possible that two circles 'kissing' only have a single intersection point. At present, this unromantic function reports that two kissing circles have no intersection points.
Luxor.intersectionlinecircle
— Methodintersectionlinecircle(p1::Point, p2::Point, cpoint::Point, r)
Find the intersection points of a line (extended through points p1
and p2
) and a circle.
Return a tuple of (n, pt1, pt2)
where
n
is the number of intersections,0
,1
, or2
pt1
is first intersection point, orPoint(0, 0)
if nonept2
is the second intersection point, orPoint(0, 0)
if none
The calculated intersection points won't necessarily lie on the line segment between p1
and p2
.
Luxor.intersectionlines
— Methodintersectionlines(p0, p1, p2, p3,
crossingonly=false)
Find point where two lines intersect.
If crossingonly == true
the point of intersection must lie on both lines.
If crossingonly == false
the point of intersection can be where the lines meet if extended almost to 'infinity'.
Accordng to this function, collinear, overlapping, and parallel lines never intersect. Ie, the line segments might be collinear but have no points in common, or the lines segments might be collinear and have many points in common, or the line segments might be collinear and one is entirely contained within the other.
If the lines are collinear and share a point in common, that is the intersection point.
Luxor.intersectlinepoly
— Methodintersectlinepoly(pt1::Point, pt2::Point, C)
Return an array of the points where a line between pt1 and pt2 crosses polygon C.
Luxor.isarcclockwise
— Methodisarcclockwise(c::Point, A::Point, B::Point)
Return true
if an arc centered at c
going from A
to B
is clockwise.
If c
, A
, and B
are collinear, then a hemispherical arc could be either clockwise or not.
Luxor.isinside
— Methodisinside(p::Point, bb:BoundingBox)
Returns true
if pt
is inside bounding box bb
.
Luxor.isinside
— Methodisinside(p, pol; allowonedge=false)
Is a point p
inside a polygon pol
? Returns true if it does, or false.
This is an implementation of the HormannAgathos (2001) Point in Polygon algorithm.
The classification of points lying on the edges of the target polygon, or coincident with its vertices is not clearly defined, due to rounding errors or arithmetical inadequacy. By default these will generate errors, but you can suppress these by setting allowonedge
to true
.
Luxor.ispointonline
— Methodispointonline(pt::Point, pt1::Point, pt2::Point;
extended = false,
atol = 10E5)
Return true
if the point pt
lies on a straight line between pt1
and pt2
.
If extended
is false (the default) the point must lie on the line segment between pt1
and pt2
. If extended
is true, the point lies on the line if extended in either direction.
Luxor.ispointonpoly
— Methodispointonpoly(pt::Point, pgon;
atol=10E5)
Return true
if pt
lies on the polygon pgon.
Luxor.ispolyclockwise
— Methodispolyclockwise(pgon)
Returns true if polygon is clockwise. WHEN VIEWED IN A LUXOR DRAWING...?
TODO This code is still experimental...
Luxor.ispolyconvex
— Methodispolyconvex(pts)
Return true if polygon is convex. This tests that every interior angle is less than or equal to 180°.
Luxor.juliacircles
— Functionjuliacircles(radius=100;
outercircleratio=0.75,
innercircleratio=0.65,
action=:fill)
Draw the three Julia circles ("dots") in color centered at the origin.
The distance of the centers of each circle from the origin is radius
.
The optional keyword argument outercircleratio
(default 0.75) determines the radius of each circle relative to the main radius. So the default is to draw circles of radius 75 points around a larger circle of radius 100.
Return the three centerpoints.
The innercircleratio
(default 0.65) no longer does anything useful (it used to draw the smaller circles) and will be deprecated.
Luxor.julialogo
— Methodjulialogo(;
action=:fill,
color=true,
bodycolor=colorant"black",
centered=false)
Draw the Julia logo. The default action is to fill the logo and use the colors:
julialogo()
If color
is false
, the bodycolor
color is used for the logo.
The function uses the current drawing state (position, scale, etc).
The centered
keyword lets you center the logo at its mathematical center, but the optical center might lie somewhere else  it's difficult to position well due to its asymmetric design.
To use the logo as a clipping mask:
julialogo(action=:clip)
(In this case the color
setting is automatically ignored.)
To obtain a stroked (outlined) version:
julialogo(action=:path)
sethue("red")
strokepath()
TODO Return something more useful than a Boolean.
Luxor.juliatocairomatrix
— Methodjuliatocairomatrix(c)
Return a sixelement matrix that's the equivalent of the 3x3 Julia matrix in c
.
Luxor.label
— Functionlabel(txt::AbstractString, alignment::Symbol=:N, pos::Point=O;
offset=5,
leader=false,
leaderoffsets=[0.0, 1.0])
Add a text label at a point, positioned relative to that point, for example, :N
signifies North and places the text directly above that point.
Use one of :N
, :S
, :E
, :W
, :NE
, :SE
, :SW
, :NW
to position the label relative to that point.
label("text") # positions text at North (above), relative to the origin
label("text", :S) # positions text at South (below), relative to the origin
label("text", :S, pt) # positions text South of pt
label("text", :N, pt, offset=20) # positions text North of pt, offset by 20
The default offset is 5 units.
If leader
is true, draw a line as well.
leaderoffsts
uses normalized fractions (see between()
) to specify the gap between the designated points and the start and end of the lines.
TODO: Negative offsets don't give good results.
Luxor.label
— Functionlabel(txt::AbstractString, rotation::Float64, pos::Point=O;
offset=5,
leader=false,
leaderoffsets=[0.0, 1.0])
Add a text label at a point, positioned relative to that point, for example, 0.0
is East, pi
is West.
label("text", pi) # positions text to the left of the origin
Luxor.layout
— Methodlayout(A, x, y, w, h)
From A, make a row of tiles (if wider than tall) or a column of tiles (if taller than wide).
Luxor.layout_spring
— Methodlayout_spring(adjmatrix::Array{T,2} where T;
densityconstant = 2.0,
maxiterations = 100,
initialtemperature = 2.0,
boundingbox=BoundingBox(O  (250, 250), O + (250, 250)))
Luxor.layout_tree
— Methodlayout_tree(adjlist)
adj_list
must not be cyclic, otherwise welcome to stack overflow...
Luxor.layoutgraph
— Methodlayoutgraph(adj_matrix::Array{T,2}; kwargs...) <
layoutgraph(adj_list::AbstractVector; kwargs...) not yet implemented
Run one of two graph layout algorithms.
1: Layout the graph in adjacency matrix adj_matrix
, using a springbased method. Returns a tuple of ([xcoords], [ycoords])
, which are scaled according to the supplied BoundingBox.
Keyword arguments
Lay out the graph in adjmatrix
using the spring/repulsion model of Fruchterman and Reingold (1991). Returns a tuple of ([xcoords], [ycoords])
.
Arguments
am
An adjacency matrix of some type. Nonzero of the eltype of the matrix is used to determine if a link exists, but currently no sense of magnitude
densityconstant
Constant to adjust the density of resulting layout. The default value is 2.0.
maxiterations
how many iterations for applying the forces
initialtemperature
the initial temperature controls movement per iteration. The default value is 2.0. Each iteration uses a lower temperature (TEMP = initialtemperature / iter). The idea is that the displacements of vertices are limited to some maximum temperature value, and this decreases over time. As the layout becomes better, the amount of adjustment becomes smaller.
locs_x
the starting x values, between 1 and 1. The default values are randomly selected.
locs_y
the starting y values, between 1 and 1. The default values are randomly selected.
boundingbox
The Luxor BoundingBox into which the graph's coordinates will fit. Defaults to 500 × 500.
Luxor.line
— Methodline(pt)
Draw a line from the current position to the pt
.
Luxor.line
— Methodline(pt1::Point, pt2::Point; action=:path)
line(pt1::Point, pt2::Point, action=:path)
Make a line between two points, pt1
and pt2
and do an action.
Luxor.lineartween
— Methoddefault linear transition  no easing, no acceleration
Luxor.makebezierpath
— Methodmakebezierpath(pgon::Array{Point, 1}; smoothing=1.0)
Return a Bézier path (a BezierPath) that represents a polygon (an array of points). The Bézier path is an array of segments (tuples of 4 points); each segment contains the four points that make up a section of the entire Bézier path. smoothing
determines how closely the curve follows the polygon. A value of 0 returns a straightsided path; as values move above 1 the paths deviate further from the original polygon's edges.
Luxor.mask
— Methodmask(point::Point, focus::Point, width, height)
max = 1.0,
min = 0.0,
easingfunction = easingflat)
Calculate a value between 0 and 1 for a point
relative to a rectangular area defined by focus
, width
, and height
. The value will approach max
(1.0) at the center, and min
(0.0) at the edges.
Luxor.mask
— Methodmask(point::Point, focus::Point, radius)
max = 1.0,
min = 0.0,
easingfunction = easingflat)
Calculate a value between 0 and 1 for a point
relative to a circular area defined by focus
and radius
. The value will approach max
(1.0) at the center of the circular area, and min
(0.0) at the circumference.
Luxor.mesh
— Functionmesh(points::Array{Point},
colors=Array{Colors.Colorant, 1})
Create a mesh.
The first three or four sides of the supplied points
polygon define the three or four sides of the mesh shape.
The colors
array define the color of each corner point. Colors are reused if necessary. At least one color should be supplied.
Example
@svg begin
pl = ngon(O, 250, 3, pi/6, vertices=true)
mesh1 = mesh(pl, [
"purple",
Colors.RGBA(0.0, 1.0, 0.5, 0.5),
"yellow"
])
setmesh(mesh1)
setline(180)
ngon(O, 250, 3, pi/6, :strokepreserve)
setline(5)
sethue("black")
strokepath()
end
Luxor.mesh
— Functionmesh(bezierpath::BezierPath,
colors=Array{Colors.Colorant, 1})
Create a mesh. The first three or four elements of the supplied bezierpath
define the three or four sides of the mesh shape.
The colors
array define the color of each corner point. Colors are reused if necessary. At least one color should be supplied.
Use setmesh()
to select the mesh, which will be used to fill shapes.
Example
@svg begin
bp = makebezierpath(ngon(O, 50, 4, 0, vertices=true))
mesh1 = mesh(bp, [
"red",
Colors.RGB(0, 1, 0),
Colors.RGB(0, 1, 1),
Colors.RGB(1, 0, 1)
])
setmesh(mesh1)
box(O, 500, 500, :fill)
end
Luxor.midpoint
— Functionmidpoint(bb::BoundingBox=BoundingBox())
Returns the point midway between the two points of the BoundingBox. This should also be the center, unless I've been very stupid...
Luxor.midpoint
— Methodmidpoint(a)
Find midpoint between the first two elements of an array of points.
Luxor.midpoint
— Methodmidpoint(p1, p2)
Find the midpoint between two points.
Luxor.move
— Methodmove(pt)
Move to a point.
Luxor.nearestindex
— Methodnearestindex(polydistancearray, value)
Return a tuple of the index of the largest value in polydistancearray
less than value
, and the difference value. Array is assumed to be sorted.
(Designed for use with polydistances()
).
Luxor.newpath
— Methodnewpath()
Create a new path, after clearing the current path. After this there's no path and no current point.
Luxor.newsubpath
— Methodnewsubpath()
Start a new subpath, keeping the current path. After this there's no current point.
Luxor.nextgridpoint
— Methodnextgridpoint(g::GridHex)
Returns the next available grid point of a hexagonal grid.
Luxor.nextgridpoint
— Methodnextgridpoint(g::GridRect)
Returns the next available (or even the first) grid point of a grid.
Luxor.ngon
— Functionngon(centerpos, radius, sides=5, orientation=0;
action=:none,
vertices=false,
reversepath=false)
Draw a regular polygon centered at point centerpos
.
Find the vertices of a regular nsided polygon centered at x
, y
with circumradius radius
.
The polygon is constructed counterclockwise, starting with the first vertex drawn below the positive xaxis.
If you just want the raw points, use keyword argument vertices=true
, which returns the array of points. Compare:
ngon(0, 0, 4, 4, 0, vertices=true) # returns the polygon's points:
4element Array{Luxor.Point, 1}:
Luxor.Point(2.4492935982947064e16, 4.0)
Luxor.Point(4.0, 4.898587196589413e16)
Luxor.Point(7.347880794884119e16, 4.0)
Luxor.Point(4.0, 9.797174393178826e16)
whereas
ngon(0, 0, 4, 4, 0, :close) # draws a polygon
Luxor.ngon
— Functionngon(x, y, radius, sides=5, orientation=0;
action = :none,
vertices = false,
reversepath = false)
Draw a regular polygon centered at point centerpos
.
Luxor.ngonside
— Functionngonside(centerpoint::Point, sidelength::Real, sides::Int=5, orientation=0;
action=:none,
vertices=false,
reversepath=false)
ngonside(centerpoint::Point, sidelength::Real, sides::Int, orientation, action;
vertices=false,
reversepath=false)
Draw a regular polygon centered at centerpoint
with sides
sides of length sidelength
.
Luxor.noise
— Methodnoise(x) ; detail = 1, persistence = 1.0) # 1D
noise(x, y) ; detail = 1, persistence = 1.0) # 2D
noise(x, y, z) ; detail = 1, persistence = 1.0) # 3D
noise(x, y, z, w) ; detail = 1, persistence = 1.0) # 4D
Generate a noise value between 0.0 and 1.0 corresponding to the x
, y
, z
, and w
values. An x
value on its own produces 1D noise, x
and y
make 2D noise, and so on.
The detail
value is an integer (>= 1) specifying how many octaves of noise you want.
The persistence
value, typically between 0.0 and 1.0, controls how quickly the amplitude diminishes for each successive octave for values of detail
greater than 1.
Luxor.offsetlinesegment
— Methodoffsetlinesegment(p1, p2, p3, d1, d2)
Given three points, find another 3 points that are offset by d1 at the start and d2 at the end.
Negative d values put the offset on the left.
Used by offsetpoly()
.
Luxor.offsetpoly
— Methodoffsetpoly(plist, shape::Function)
Return a closed polygon that is offset from and encloses an polyline.
The incoming set of points plist
is treated as an polyline, and another set of points is created, which form a closed polygon offset from the source poly.
There must be at least 4 points in the polyline.
This method for offsetpoly()
treats the list of points as n
vertices connected with n  1
lines. (The other method offsetpoly(plist, d)
treats the list of points as n
vertices connected with n
lines.)
The supplied function determines the width of the line. f(0, θ)
gives the width at the start (the slope of the curve at that point is supplied in θ), f(1, θ)
provides the width at the end, and f(n, θ)
is the width of point n/l
.
Examples
This example draws a tilde, with the ends starting at 20 (10 + 10) units wide, swelling to 50 (10 + 10 + 15 + 15) in the middle, as f(0.5) = 25.
f(x, θ) = 10 + 15sin(x * π)
sinecurve = [Point(50x, 50sin(x)) for x in π:π/24:π]
pgon = offsetpoly(sinecurve, f)
poly(pgon, :fill)
This example enhances the vertical part of the curve, and thins the horizontal parts.
g(x, θ) = rescale(abs(sin(θ)), 0, 1, 0.1, 30)
sinecurve = [Point(50x, 50sin(x)) for x in π:π/24:π]
pgon = offsetpoly(sinecurve, g)
poly(pgon, :fill)
TODO  rewrite it!
Luxor.offsetpoly
— Methodoffsetpoly(plist;
startoffset = 10,
endoffset = 10,
easingfunction = lineartween)
Return a closed polygon that is offset from and encloses an open polygon.
The incoming set of points plist
is treated as an open polygon, and another set of points is created, which form a polygon lying ...offset
units away from the source poly.
This method for offsetpoly()
treats the list of points as n
vertices connected with n  1
lines. It allows you to vary the offset from the start of the line to the end.
The other method offsetpoly(plist, d)
treats the list of points as n
vertices connected with n
lines.
Extended help
This function accepts a keyword argument that allows you to control the offset using a function, using the easing functionality built in to Luxor. By default the function is lineartween()
, so the offset changes linearly between the startoffset
and the endoffset
. The function:
f(a, b, c, d) = 2sin((a * π))
runs from 0 to 2 and back as a
runs from 0 to 1. The offsets are scaled by this amount.
Luxor.offsetpoly
— Methodoffsetpoly(plist::Array{Point, 1}, d) where T<:Number
Return a polygon that is offset from a polygon by d
units.
The incoming set of points plist
is treated as a polygon, and another set of points is created, which form a polygon lying d
units away from the source poly.
Polygon offsetting is a topic on which people have written PhD theses and published academic papers, so this short braindead routine will give good results for simple polygons up to a point (!). There are a number of issues to be aware of:
very short lines tend to make the algorithm 'flip' and produce larger lines
small polygons that are counterclockwise and larger offsets may make the new
polygon appear the wrong side of the original
very sharp vertices will produce even sharper offsets, as the calculated intersection point veers off to infinity
duplicated adjacent points might cause the routine to scratch its head and wonder how to draw a line parallel to them
Luxor.origin
— Methodorigin(pt:Point)
Reset the current matrix, then move the 0/0
position to pt
.
Luxor.origin
— Methodorigin()
Reset the current matrix, and then set the 0/0 origin to the center of the drawing (otherwise it will stay at the top left corner, the default).
You can refer to the 0/0 point as O
. (O = Point(0, 0)
),
Luxor.paint
— Methodpaint()
Paint the current clip region with the current settings.
Luxor.pathtobezierpaths
— Methodpathtobezierpaths(
; flat=true)
Convert the current path (which may consist of one or more paths) to an array of Bezier paths. Each Bezier path is, in turn, an array of path segments. Each path segment is a tuple of four points. A straight line is converted to a Bezier segment in which the control points are set to be the same as the end points.
If flat
is true, use getpathflat()
rather than getpath()
.
Example
This code draws the Bezier segments and shows the control points as "handles", like a vectorediting program might.
@svg begin
fontface("MyanmarMNBold")
st = "goo"
thefontsize = 100
fontsize(thefontsize)
sethue("red")
fontsize(thefontsize)
textpath(st)
nbps = pathtobezierpaths()
for nbp in nbps
setline(.15)
sethue("grey50")
drawbezierpath(nbp, :stroke)
for p in nbp
sethue("red")
circle(p[2], 0.16, :fill)
circle(p[3], 0.16, :fill)
line(p[2], p[1], :stroke)
line(p[3], p[4], :stroke)
if p[1] != p[4]
sethue("black")
circle(p[1], 0.26, :fill)
circle(p[4], 0.26, :fill)
end
end
end
end
Luxor.pathtopoly
— Methodpathtopoly()
Convert the current path to an array of polygons.
Returns an array of polygons, corresponding to the paths and subpaths of the original path.
Luxor.perpendicular
— Methodperpendicular(p1, p2, k)
Return a point p3
that is k
units away from p1
, such that a line p1 p3
is perpendicular to p1 p2
.
Convention? to the right?
Luxor.perpendicular
— Methodperpendicular(p1, p2)
Return two points p3
and p4
such that a line from p3
to p4
is perpendicular to a line from p1
to p2
, the same length, and the lines intersect at their midpoints.
Luxor.perpendicular
— Methodperpendicular(p::Point)
Returns point Point(p.y, p.x)
.
Luxor.pie
— Methodpie(x, y, radius, startangle, endangle; action=:none)
pie(centerpoint, radius, startangle, endangle; action=:none)
Make a pie shape centered at x
/y
. Angles start at the positive xaxis and are measured clockwise, and add it to the current path.
TODO  return something more useful than a Boolean
Luxor.pie
— Methodpie(centerpoint::Point, radius::Real, startangle::Real, endangle::Real, action::Symbol)
Luxor.pie
— Methodpie(radius, startangle, endangle;
action=:none)
Make a pie shape centered at the origin, and add it to the current path.
Luxor.placeimage
— Functionplaceimage(svgimg, pos=O; centered=false)
Place an SVG image stored in svgimg
on the drawing at pos
. Use readsvg()
to read an SVG image from file, or from SVG code.
Use keyword centered=true
to place the center of the image at the position.
Luxor.placeimage
— Functionplaceimage(matrix::AbstractMatrix{UInt32}, pos=O;
alpha=1, centered=false)
Place an image matrix on the drawing at pos
with opacity/transparency alpha
.
Use keyword centered=true
to place the center of the image at the position.
Luxor.placeimage
— Methodplaceimage(img, pt::Point=O, alpha; centered=false)
placeimage(pngimg, xpos, ypos, alpha; centered=false)
Place a PNG image pngimg
on the drawing at pt
or Point(xpos, ypos)
with opacity/transparency alpha
. The image has been previously loaded using readpng()
.
Use keyword centered=true
to place the center of the image at the position.
Luxor.placeimage
— Methodplaceimage(pngimg, pos=O; centered=false)
placeimage(pngimg, xpos, ypos; centered=false)
Place the PNG image on the drawing at pos
, or (xpos
/ypos
). The image img
has been previously read using readpng()
.
Use keyword centered=true
to place the center of the image at the position.
Luxor.pointcircletangent
— Methodpointcircletangent(point::Point, circlecenter::Point, circleradius)
Find the two points on a circle that lie on tangent lines passing through an external point.
If both points are O, the external point is inside the circle, and the result is (O, O)
.
Luxor.pointcrossesboundingbox
— Methodpointcrossesboundingbox(pt, bbox::BoundingBox)
Find and return the point where a line from the center of bounding box bbox
to point pt
would, if continued, cross the edges of the box.
Luxor.pointinverse
— Methodpointinverse(A::Point, centerpoint::Point, rad)
Find A′
, the inverse of a point A with respect to a circle centerpoint
/rad
, such that:
distance(centerpoint, A) * distance(centerpoint, A′) == rad^2
Return (true, A′) or (false, A).
Luxor.pointlinedistance
— Methodpointlinedistance(p::Point, a::Point, b::Point)
Find the distance between a point p
and a line between two points a
and b
.
Luxor.polar
— Methodpolar(r, theta)
Convert point in polar form (radius and angle) to a Point.
polar(10, pi/4)
produces
Luxor.Point(7.071067811865475, 7.0710678118654755)
Luxor.poly
— Functionpoly(bbox::BoundingBox, :action; kwargs...)
Make a polygon around the BoundingBox in bbox
.
Luxor.poly
— MethodDraw a polygon.
poly(pointlist::Array{Point, 1}, action = :none;
close=false,
reversepath=false)
A polygon is an Array of Points. By default poly()
doesn't close or fill the polygon, to allow for clipping.
Luxor.polyarea
— Methodpolyarea(p::Array)
Find the area of a simple polygon. It works only for polygons that don't selfintersect. See also polyorientation()
.
Luxor.polycentroid
— MethodFind the centroid of simple polygon.
polycentroid(pointlist)
Returns a point. This only works for simple (nonintersecting) polygons.
You could test the point using isinside()
.
Luxor.polycross
— Functionpolycross(pt::Point, radius, npoints::Int, ratio=0.5, orientation=0.0;
action = :none,
splay = 0.5,
vertices = false,
reversepath = false)
polycross(pt::Point, radius, npoints::Int, ratio=0.5, orientation=0.0, action;
splay = 0.5,
vertices = false,
reversepath = false)
Make a crossshaped polygon with npoints
arms to fit inside a circle of radius radius
centered at pt
.
ratio
specifies the ratio of the two sides of each arm. splay
makes the arms ... splayed.
Use vertices=true
to return the vertices of the shape instead of executing the action.
(Adapted from Compose.jl.xgon()))
Examples
polycross(O, 100, 5,
action = :fill,
splay = 0.5)
polycross(O, 120, 5, 0.5, 0.0, :stroke,
splay = 0.5)
Luxor.polydistances
— Methodpolydistances(p::Array{Point, 1}; closed=true)
Return an array of the cumulative lengths of a polygon.
Luxor.polyfit
— Functionpolyfit(plist::Array, npoints=30)
Build a polygon that constructs a Bspine approximation to it. The resulting list of points makes a smooth path that runs between the first and last points.
Luxor.polyhull
— Methodpolyhull(pts)
Find all points in pts
that form a convex hull around the points in pts
, and return them.
This uses the Graham Scan algorithm.
TODO : there might be bugs
Luxor.polyintersect
— Methodpolyintersect(p1::AbstractArray{Point, 1}, p2::AbstractArray{Point, 1};
closed=true)
TODO: Fix/test/improve this experimental polygon intersection routine.
Return the points where polygon p1 and polygon p2 cross.
If closed
is false, the intersection points must lie on the first n  1
lines of each polygon.
Luxor.polyintersections
— Methodpolyintersections(S::Array{Point, 1}, C::Array{Point, 1})
Return an array of the points in polygon S plus the points where polygon S crosses polygon C. Calls intersectlinepoly()
.
TODO This code is experimental...
Luxor.polymove!
— Methodpolymove!(pgon, frompoint::Point, topoint::Point)
Move (permanently) a polygon from frompoint
to topoints
.
Luxor.polyorientation
— Methodpolyorientation(pgon)
Returns a number which is positive if the polygon is clockwise in Luxor...
TODO This code is still experimental...
Luxor.polyperimeter
— Methodpolyperimeter(p::Array{Point, 1}; closed=true)
Find the total length of the sides of polygon p
.
Luxor.polyportion
— Functionpolyportion(p::Array{Point, 1}, portion=0.5; closed=true, pdist=[])
Return a portion of a polygon, starting at a value between 0.0 (the beginning) and 1.0 (the end). 0.5 returns the first half of the polygon, 0.25 the first quarter, 0.75 the first three quarters, and so on.
Use closed=false
to exclude the line joining the final point to the first point from the calculations.
If you already have a list of the distances between each point in the polygon (the "polydistances"), you can pass them in pdist
, otherwise they'll be calculated afresh, using polydistances(p, closed=closed)
.
Use the complementary polyremainder()
function to return the other part.
Luxor.polyreflect!
— Functionpolyreflect!(pgon, pt1 = O, pt2 = O + (0, 100)
Reflect (permanently) a polygon in a line (default to the yaxis) joining two points.
Luxor.polyremainder
— Functionpolyremainder(p::Array{Point, 1}, portion=0.5; closed=true, pdist=[])
Return the rest of a polygon, starting at a value between 0.0 (the beginning) and 1.0 (the end). 0.5 returns the last half of the polygon, 0.25 the last three quarters, 0.75 the last quarter, and so on.
Use closed=false
to exclude the line joining the final point to the first point from the calculations.
If you already have a list of the distances between each point in the polygon (the "polydistances"), you can pass them in pdist
, otherwise they'll be calculated afresh, using polydistances(p, closed=closed)
.
Use the complementary polyportion()
function to return the other part.
Luxor.polyremovecollinearpoints
— Methodpolyremovecollinearpoints(pgon::Array{Point, 1})
Return copy of polygon with no collinear points.
Caution: may return an empty polygon... !
TODO This code is still experimental...
Luxor.polyrotate!
— Methodpolyrotate!(pgon, θ;
center=O)
Rotate (permanently) a polygon around center
by θ
radians.
Luxor.polysample
— Methodpolysample(p::Array{Point, 1}, npoints::T where T <: Integer;
closed=true)
Sample the polygon p
, returning a polygon with npoints
to represent it. The first sampled point is:
1/`npoints` * `perimeter of p`
away from the original first point of p
.
If npoints
is the same as length(p)
the returned polygon is the same as the original, but the first point finishes up at the end (so new=circshift(old, 1)
).
If closed
is true, the entire polygon (including the edge joining the last point to the first point) is sampled.
If include_first
is true, the first point of plist
is included in the result.
If the resulting polygon's first and end points are the same, the end point is discarded.
Luxor.polyscale!
— Methodpolyscale!(pgon, sh, sv;
center=O)
Scale (permanently) a polygon by sh
horizontally and sv
vertically, relative to center
.
Luxor.polyscale!
— Methodpolyscale!(pgon, s;
center=O)
Scale (permanently) a polygon by s
, relative to center
.
Luxor.polysmooth
— Methodpolysmooth(points, radius, action=:action; debug=false)
polysmooth(points, radius; action=:none, debug=false)
Make a closed path from the points
and round the corners by making them arcs with the given radius. Execute the action when finished.
The arcs are sometimes different sizes: if the given radius is bigger than the length of the shortest side, the arc can't be drawn at its full radius and is therefore drawn as large as possible (as large as the shortest side allows).
The debug
option also draws the construction circles at each corner.
TODO Return something more useful than a Boolean.
Luxor.polysortbyangle
— FunctionSort the points of a polygon into order. Points are sorted according to the angle they make with a specified point.
polysortbyangle(pointlist::Array, refpoint=minimum(pointlist))
The refpoint
can be chosen, but the minimum point is usually OK too:
polysortbyangle(parray, polycentroid(parray))
Luxor.polysortbydistance
— MethodSort a polygon by finding the nearest point to the starting point, then the nearest point to that, and so on.
polysortbydistance(p, starting::Point)
You can end up with convex (selfintersecting) polygons, unfortunately.
Luxor.polysplit
— Methodpolysplit(p, p1, p2)
Split a polygon into two where it intersects with a line. It returns two polygons:
(poly1, poly2)
This doesn't always work, of course. For example, a polygon the shape of the letter "E" might end up being divided into more than two parts.
Luxor.polytopath
— Methodpolytopath(ptlist)
Convert a polygon to a Path object.
@draw drawpath(polytopath(ngon(O, 145, 5, vertices=true)), action=:fill)
Luxor.polytriangulate
— Methodpolytriangulate(plist::Array{Point,1}; epsilon = 0.01)
Triangulate the polygon in plist
.
This uses the Bowyer–Watson/Delaunay algorithm to make triangles. It returns an array of triangular polygons.
TODO: This experimental polygon function is not very efficient, because it first copies the list of points (to avoid modifying the original), and sorts it, before making triangles.
Luxor.prettypoly
— Functionprettypoly(points::Array{Point, 1}, vertexfunction = () > circle(O, 2, :stroke);
action=:none,
close=false,
reversepath=false,
vertexlabels = (n, l) > ()
)
Draw the polygon defined by points
, possibly closing and reversing it, using the current parameters, and then evaluate the vertexfunction
function at every vertex of the polygon.
The default vertexfunction draws a 2 pt radius circle.
To mark each vertex of a polygon with a randomly colored filled circle:
p = star(O, 70, 7, 0.6, 0, vertices=true)
prettypoly(p, action=:fill, () >
begin
randomhue()
circle(O, 10, :fill)
end,
close=true)
The optional keyword argument vertexlabels
lets you supply a function with two arguments that can access the current vertex number and the total number of vertices at each vertex. For example, you can label the vertices of a triangle "1 of 3", "2 of 3", and "3 of 3" using:
prettypoly(triangle, action=:stroke,
vertexlabels = (n, l) > (text(string(n, " of ", l))))
Luxor.prettypoly
— Functionprettypoly(bbox::BoundingBox, action; kwargs...)
Make a decorated polygon around the BoundingBox in bbox
. The vertices are in the order: bottom left, top left, top right, and bottom right.
Luxor.preview
— Methodpreview()
If working in a notebook (eg Jupyter/IJulia), display a PNG or SVG file in the notebook.
If working in Juno, display a PNG or SVG file in the Plot pane.
Drawings of type :image should be converted to a matrix with image_as_matrix()
before calling finish()
.
Otherwise:
 on macOS, open the file in the default application, which is probably the Preview.app for PNG and PDF, and Safari for SVG
 on Unix, open the file with
xdgopen
 on Windows, refer to
COMSPEC
.
Luxor.randomcolor
— Methodrandomcolor()
Set a random color. This may change the current alpha opacity too.
Luxor.randomhue
— Methodrandomhue()
Set a random hue, without changing the current alpha opacity.
Luxor.randompoint
— Methodrandompoint(lowx, lowy, highx, highy)
Return a random point somewhere inside a rectangle defined by the four values.
Luxor.randompoint
— Methodrandompoint(lowpt, highpt)
Return a random point somewhere inside the rectangle defined by the two points.
Luxor.randompointarray
— Methodrandompointarray(lowx, lowy, highx, highy, n)
Return an array of n
random points somewhere inside the rectangle defined by the four coordinates.
Luxor.randompointarray
— Methodrandompointarray(w, h, d; attempts=20)
Return an array of randomly positioned points inside the rectangle defined by the current origin (0/0) and the width
and height
. d
determines the minimum distance between each point. Increase attempts
if you want the function to try harder to fill empty spaces; decrease it if it's taking too long to look for samples that work.
This uses Bridson's Poisson Disk Sampling algorithm: https://www.cs.ubc.ca/~rbridson/docs/bridsonsiggraph07poissondisk.pdf
Example
for pt in randompointarray(BoundingBox(), 20)
randomhue()
circle(pt, 10, :fill)
end
Luxor.randompointarray
— Methodrandompointarray(bbox::BoundingBox, d; attempts=20)
Return an array of randomly positioned points inside the bounding box d
units apart.
Luxor.randompointarray
— Methodrandompointarray(lowpt, highpt, n)
Return an array of n
random points somewhere inside the rectangle defined by two points.
Luxor.readpng
— Methodreadpng(pathname)
Read a PNG file.
This returns a image object suitable for placing on the current drawing with placeimage()
. You can access its width
and height
fields:
image = readpng("testimage.png")
w = image.width
h = image.height
Luxor.readsvg
— Methodreadsvg(str)
Read an SVG image. str
is either pathname or pure SVG code. This returns an SVG image object suitable for placing on the current drawing with placeimage()
.
Placing an SVG file:
@draw begin
mycoollogo = readsvg("mylogo.svg")
placeimage(mycoollogo)
end
Placing SVG code:
# from https://github.com/edent/SuperTinyIcons
julialogocode = """<svg xmlns="http://www.w3.org/2000/svg"
arialabel="Julia" role="img"
viewBox="0 0 512 512">
<rect width="512" height="512" rx="15%" fill="#fff"/>
<circle fill="#389826" cx="256" cy="137" r="83"/>
<circle fill="#cb3c33" cx="145" cy="329" r="83"/>
<circle fill="#9558b2" cx="367" cy="329" r="83"/>
</svg>"""
@draw begin
julia_logo = readsvg(julialogocode)
placeimage(julia_logo, centered=true)
end
Luxor.rect
— Methodrect(xmin, ymin, w, h; action=:none)
rect(xmin, ymin, w, h, action)
Create a rectangle with one corner at (xmin
/ymin
) with width w
and height h
, and add it to the current path. Then apply action
.
Returns a tuple of two points, the corners of a bounding box that encloses the rectangle.
See box()
for more ways to do similar things, such as supplying two opposite corners, placing by centerpoint and dimensions.
Luxor.rect
— Methodrect(cornerpoint, w, h; action = none, reversepath=false,
vertices=false)
rect(cornerpoint, w, h, action; reversepath=false,
vertices=false)
Create a rectangle with one corner at cornerpoint
with width w
and height h
, and add it to the current path. Then apply action
.
Use vertices=true
to return an array of the four corner points: bottom left, top left, top right, bottom right.
reversepath
reverses the direction of the path (and returns points in the order: bottom left, bottom right, top right, top left).
Returns the four corner vertices.
Luxor.rescale
— Functionrescale(x, from_min, from_max, to_min=0.0, to_max=1.0)
Convert x
from one linear scale (from_min
to from_max
) to another (to_min
to to_max
).
The scales can also be supplied in tuple form:
rescale(x, (from_min, from_max), (to_min, to_max))
using Luxor
julia> rescale(15, 0, 100, 0, 1)
0.15
julia> rescale(15, (0, 100), (0, 1))
0.15
julia> rescale(pi/20, 0, 2pi, 0, 1)
0.025
julia> rescale(pi/20, (0, 2pi), (0, 1))
0.025
julia> rescale(25, 0, 1, 0, 1.609344)
40.2336
julia> rescale(15, (0, 100), (1000, 0))
850.0
Luxor.rline
— Methodrline(pt)
Draw a line relative to the current position to the pt
.
Luxor.rmove
— Methodrmove(pt)
Move relative to current position by the pt
's x and y:
Luxor.rotate
— Methodrotate(a::Float64)
Rotate workspace by a
radians clockwise (from positive xaxis to positive yaxis).
Luxor.rotate_point_around_point
— Methodrotate_point_around_point(targetpt, pt, angle)
Rotate a target point around another point by an angle specified in radians. Returns the new point.
Luxor.rotationmatrix
— Methodrotationmatrix(a)
Return a 3x3 Julia matrix that will apply a rotation through a
radians.
Luxor.rule
— Functionrule(pos, theta;
boundingbox=BoundingBox(),
vertices=false)
Draw a straight line through pos
at an angle theta
from the x axis.
By default, the line spans the entire drawing, but you can supply a BoundingBox to change the extent of the line.
rule(O) # draws an x axis
rule(O, pi/2) # draws a y axis
The function:
rule(O, pi/2, boundingbox=BoundingBox()/2)
draws a line that spans a bounding box half the width and height of the drawing, and returns a Set of end points. If you just want the vertices and don't want to draw anything, use vertices=true
.
Luxor.rulers
— Methodrulers()
Draw and label two rulers starting at O
, the current 0/0, and continuing out along the current positive x and y axes.
Luxor.scale
— Methodscale(x, y)
Scale workspace by x
and y
.
Example:
scale(0.2, 0.3)
Luxor.scale
— Methodscale(f)
Scale workspace by f
in both x
and y
.
Luxor.scalingmatrix
— Methodscalingmatrix(sx, sy)
Return a 3x3 Julia matrix that will apply a scaling by sx
and sy
.
Luxor.sector
— Methodsector(innerradius::Real, outerradius::Real, startangle::Real, endangle::Real;
action=:none)
Make an annular sector centered at the origin, and add it to the current path.
Luxor.sector
— Methodsector(centerpoint::Point, innerradius, outerradius,
startangle, endangle, cornerradius;
action:none)
Make an annular sector with rounded corners, basically a bent sausage shape, centered at centerpoint
, and add it to the current path.
TODO: The results aren't 100% accurate at the moment. There are small discontinuities where the curves join.
TODO  return something more useful than a Boolean
The cornerradius is reduced from the supplied value if neceesary to prevent overshoots.
Luxor.sector
— Methodsector(centerpoint::Point, innerradius, outerradius, startangle, endangle;
action=:none)
Make an annular sector centered at centerpoint
, and add it to the current path.
TODO  return something more useful than a Boolean
Luxor.sector
— Methodsector(innerradius::Real, outerradius::Real, startangle::Real, endangle::Real,
cornerradius::Real, action)
Make an annular sector with rounded corners, centered at the current origin, and add it to the current path.
Luxor.setantialias
— Methodsetantialias(n)
Set the current antialiasing to a value between 0 and 6:
antialias_default = 0, the default antialiasing for the subsystem and target device
antialias_none = 1, use a bilevel alpha mask
antialias_gray = 2, use singlecolor antialiasing (using shades of gray for black text on a white background, for example)
antialias_subpixel = 3, take advantage of the order of subpixel elements on devices such as LCD panels
antialias_fast = 4, perform some antialiasing but prefer speed over quality
antialias_good = 5, balance quality against performance
antialias_best = 6, render at the highest quality, sacrificing speed if necessary
This affects subsequent graphics, but not text, and it doesn't apply to all types of output file.
Luxor.setbezierhandles
— Methodsetbezierhandles(bps::BezierPathSegment;
angles = [0.05, 0.1],
handles = [0.3, 0.3])
Return a new Bezier path segment with new locations for the Bezier control points in the Bezier path segment bps
.
angles
are the two angles that the "handles" make with the line direciton.
handles
are the lengths of the "handles". 0.3 is a typical value.
Luxor.setbezierhandles
— Methodsetbezierhandles(bezpath::BezierPath;
angles=[0 .05, 0.1],
handles=[0.3, 0.3])
Return a new Bezierpath with new locations for the Bezier control points in every Bezier path segment of the BezierPath in bezpath
.
angles
are the two angles that the "handles" make with the line direciton.
handles
are the lengths of the "handles". 0.3 is a typical value.
Luxor.setblend
— Methodsetblend(blend)
Start using the named blend for filling graphics.
This aligns the original coordinates of the blend definition with the current axes.
Luxor.setblendextend
— Methodsetblendextend(blend::Blend, mode)
Specify how color blend patterns are repeated/extended. Supply the blend and one of the following strings:
"repeat": the pattern is repeated
"reflect": the pattern is reflected (repeated in reverse)
"pad": outside the pattern, use the closest color
"none": outside of the pattern, use transparent pixels
Luxor.setcolor
— Methodsetcolor("gold")
setcolor("darkturquoise")
Set the current color to a named color. This use the definitions in Colors.jl to convert a string to RGBA eg setcolor("gold")
or "green", "darkturquoise", "lavender", etc. The list is at Colors.color_names
.
Use sethue()
for changing colors without changing current opacity level.
sethue()
and setcolor()
return the three or four values that were used:
julia> setcolor(sethue("red")..., .8)
(1.0, 0.0, 0.0, 0.8)
julia> sethue(setcolor("red")[1:3]...)
(1.0, 0.0, 0.0)
You can also do:
using Colors
sethue(colorant"red")
See also setcolor
.
Luxor.setcolor
— Methodsetcolor(r, g, b)
setcolor(r, g, b, alpha)
setcolor(color)
setcolor(col::Colors.Colorant)
setcolor(sethue("red")..., .2)
Set the current color.
Examples:
setcolor(convert(Colors.HSV, Colors.RGB(0.5, 1, 1)))
setcolor(.2, .3, .4, .5)
setcolor(convert(Colors.HSV, Colors.RGB(0.5, 1, 1)))
for i in 1:15:360
setcolor(convert(Colors.RGB, Colors.HSV(i, 1, 1)))
...
end
See also sethue
.
Luxor.setcolor
— Methodsetcolor((r, g, b, a))
Set the color to the tuple's values.
Luxor.setcolor
— Methodsetcolor((r, g, b))
Set the color to the tuple's values.
Luxor.setdash
— Functionsetdash(dashes::Vector, offset=0.0)
Set the dash pattern to the values in dashes
. The first number is the length of the ink, the second the gap, and so on.
The offset
specifies an offset into the pattern at which the stroke begins. So an offset of 10 means that the stroke starts at dashes[1] + 10
into the pattern.
Or use setdash("dot")
etc.
Luxor.setdash
— Methodsetdash("dot")
Set the dash pattern to one of: "solid", "dotted", "dot", "dotdashed", "longdashed", "shortdashed", "dash", "dashed", "dotdotdashed", "dotdotdotdashed".
Use setdash(dashes::Vector)
to specify the pattern numerically.
Luxor.setfont
— Methodsetfont(family, fontsize)
Select a font and specify the size.
Example:
setfont("Helvetica", 24)
settext("Hello in Helvetica 24 using the Pro API", Point(0, 10))
Luxor.setgray
— Methodsetgray(n)
setgrey(n)
Set the color to a gray level of n
, where n
is between 0 and 1.
Luxor.sethue
— Methodsethue("black")
sethue(0.3, 0.7, 0.9)
setcolor(sethue("red")..., .2)
Set the color without changing opacity.
sethue()
is like setcolor()
, but we sometimes want to change the current color without changing alpha/opacity. Using sethue()
rather than setcolor()
doesn't change the current alpha opacity.
See also setcolor
.
Luxor.sethue
— Methodsethue(0.3, 0.7, 0.9)
Set the color's r
, g
, b
values. Use setcolor(r, g, b, a)
to set transparent colors.
Luxor.sethue
— Methodsethue(col::Colors.Colorant)
Set the color without changing the current alpha/opacity:
Luxor.sethue
— Methodsethue((r, g, b, a))
Set the color to the tuple's values.
Luxor.sethue
— Methodsethue((r, g, b))
Set the color to the tuple's values.
Luxor.setline
— Methodsetline(n)
Set the line width, in points.
Luxor.setlinecap
— Functionsetlinecap(s)
Set the line ends. s
can be "butt" or :butt
(the default), "square" or :square
, or "round" or :round
.
Luxor.setlinejoin
— Functionsetlinejoin("miter")
setlinejoin("round")
setlinejoin("bevel")
Set the line join style, or how to render the junction of two lines when stroking.
Luxor.setmatrix
— Methodsetmatrix(m::Array)
Change the current matrix to matrix m
. Use getmatrix()
to get the current matrix.
Luxor.setmesh
— Methodsetmesh(mesh::Mesh)
Select a mesh, previously created with mesh()
, for filling and stroking subsequent graphics.
Luxor.setmode
— Methodsetmode(mode::AbstractString)
Set the compositing/blending mode. mode
can be one of:
"clear"
Where the second object is drawn, the first is completely removed."source"
The second object is drawn as if nothing else were below."over"
The default mode: like two transparent slides overlapping."in"
The first object is removed completely, the second is only drawn where the first was."out"
The second object is drawn only where the first one wasn't."atop"
The first object is mostly intact, but mixes both objects in the overlapping area. The second object object is not drawn elsewhere."dest"
Discard the second object completely."dest_over"
Like "over" but draw second object below the first"dest_in"
Keep the first object whereever the second one overlaps."dest_out"
The second object is used to reduce the visibility of the first where they overlap."dest_atop"
Like "over" but draw second object below the first."xor"
XOR where the objects overlap"add"
Add the overlapping areas together"saturate"
Increase Saturation where objects overlap"multiply"
Multiply where objects overlap"screen"
Input colors are complemented and multiplied, the product is complemented again. The result is at least as light as the lighter of the input colors."overlay"
Multiplies or screens colors, depending on the lightness of the destination color."darken"
Selects the darker of the color values in each component."lighten"
Selects the lighter of the color values in each component.
See the Cairo documentation for details.
Luxor.setopacity
— Methodsetopacity(alpha)
Set the current opacity to a value between 0 and 1. This modifies the alpha value of the current color.
Luxor.setstrokescale
— Methodsetstrokescale(state::Bool)
Enable/disable stroke scaling for the current drawing.
Luxor.setstrokescale
— Methodsetstrokescale()
Return the current stroke scaling setting.
Luxor.settext
— Methodsettext(text, pos;
halign = "left",
valign = "bottom",
angle = 0, # degrees!
markup = false)
settext(text;
kwargs)
Draw the text
at pos
(if omitted defaults to 0/0
). If no font is specified, on macOS the default font is Times Roman.
To align the text, use halign
, one of "left", "center", or "right", and valign
, one of "top", "center", or "bottom".
angle
is the rotation  in counterclockwise degrees, rather than Luxor's default clockwise (+xaxis to +yaxis) radians.
If markup
is true
, then the string can contain some HTMLstyle markup. Supported tags include:
<b>, <i>, <s>, <sub>, <sup>, <small>, <big>, <u>, <tt>, and <span>
The <span>
tag can contains things like this:
<span font='26' background='green' foreground='red'>unreadable text</span>
Luxor.shiftbezierhandles
— Methodshiftbezierhandles(bps::BezierPathSegment;
angles=[0.1, 0.1], handles=[1.1, 1.1])
Return a new BezierPathSegment that modifies the Bezier path in bps
by moving the control handles. The values in angles
increase the angle of the handles; the values in handles
modifies the lengths: 1 preserves the length, 0.5 halves the length of the handles, 2 doubles them.
Luxor.simplify
— FunctionSimplify a polygon:
simplify(pointlist::Array, detail=0.1)
detail
is the maximum approximation error of simplified polygon.
Luxor.slope
— Methodslope(pointA::Point, pointB::Point)
Find angle of a line starting at pointA
and ending at pointB
.
Return a value between 0 and 2pi. Value will be relative to the current axes.
slope(O, Point(0, 100)) > rad2deg # y is positive down the page
90.0
slope(Point(0, 100), O) > rad2deg
270.0
The slope isn't the same as the gradient. A vertical line going up has a slope of 3π/2.
Luxor.snapshot
— Methodsnapshot(;
fname = :png,
cb = missing,
scalefactor = 1.0)
snapshot(fname, cb, scalefactor)
> finished snapshot drawing, for display
Take a snapshot and save to 'fname' name and suffix. This requires that the current drawing is a recording surface. You can continue drawing on the same recording surface.
Arguments
fname
the file name or symbol, see Drawing
cb
crop box::BoundingBox  what's inside is copied to snapshot
scalefactor
snapshot width/crop box width. Same for height.
Examples
snapshot()
snapshot(fname = "temp.png")
snaphot(fname = :svg)
cb = BoundingBox(Point(0, 0), Point(102.4, 96))
snapshot(cb = cb)
pngdrawing = snapshot(fname = "temp.png", cb = cb, scalefactor = 10)
The last example would return and also write a png drawing with 1024 x 960 pixels to storage.
Luxor.spiral
— Methodspiral(a, b;
action = :none,
stepby = 0.01,
period = 4pi,
vertices = false,
log =false)
spiral(a, b, action;
stepby = 0.01,
period = 4pi,
vertices = false,
log =false)
Make a spiral, and add it to the current path. The two primary parameters a
and b
determine the start radius, and the tightness.
For linear spirals (log=false
), b
values are:
lituus: 2
hyperbolic spiral: 1
Archimedes' spiral: 1
Fermat's spiral: 2
For logarithmic spirals (log=true
):
golden spiral: b = ln(phi)/ (pi/2) (about 0.30)
Values of b
around 0.1 produce tighter, staircaselike spirals.
Luxor.splittext
— Methodsplittext(s)
Split the text in string s
into an array, but keep all the separators attached to the preceding word.
Luxor.squircle
— Methodsquircle(center::Point, hradius, vradius;
action=:none,
rt = 0.5, stepby = pi/40, vertices=false)
squircle(center::Point, hradius, vradius, action;
rt = 0.5, stepby = pi/40, vertices=false)
Make a squircle or superellipse (basically a rectangle with rounded corners), and add it to the current path. Specify the center position, horizontal radius (distance from center to a side), and vertical radius (distance from center to top or bottom):
The root (rt
) option defaults to 0.5, and gives an intermediate shape. Values less than 0.5 make the shape more rectangular. Values above make the shape more round. The horizontal and vertical radii can be different.
Luxor.star
— Functionstar(center, radius, npoints, ratio=0.5, orientation, action=:none;
vertices = false, reversepath=false)
star(center, radius, npoints, ratio=0.5, orientation=0.0;
action=:none, vertices = false, reversepath=false)
Make a star centered at center
with npoints
sections oriented by orientation
. ratio
specifies the height of the smaller radius of the star relative to the larger.
Returns the vertices of the star.
Use vertices=true
to only return the vertices of a star instead of making it.
Examples
star(O, 120, 5, 0.5, 0.0, :fill,
vertices = false,
reversepath=false)
star(O, 220, 5, 0.5;
action=:stroke,
vertices = false,
reversepath=false)
Luxor.storepath
— Methodstorepath()
Obtain the current Cairo path and make a Luxor Path object, which is an array of PathElements.
Returns the Path object.
See also getpath() and getpathflat().
Luxor.strokepath
— Methodstrokepath()
Stroke the current path with the current line width, line join, line cap, dash, and stroke scaling settings. The current path is then cleared.
Luxor.strokepreserve
— Methodstrokepreserve()
Stroke the current path with current line width, line join, line cap, dash, and stroke scaling settings, but then keep the path current.
Luxor.svgstring
— Methodsvgstring()
Return the current and recently completed SVG drawing as a string of SVG commands.
Returns ""
if there is no SVG information available.
To display the SVG string as a graphic, try the HTML()
function in Base.
...
HTML(svgstring())
In a Pluto notebook, you can also display the SVG using:
# using PlutoUI
...
PlutoUI.Show(MIME"image/svg+xml"(), svgstring())
(This lets you rightclick to save the SVG.)
Example
This example manipulates the raw SVG code representing the Julia logo:
Drawing(500, 500, :svg)
origin()
julialogo()
finish()
s = svgstring()
eachmatch(r"rgb.*?;", s) > collect
6element Vector{RegexMatch}:
RegexMatch("rgb(100%,100%,100%);")
RegexMatch("rgb(0%,0%,0%);")
RegexMatch("rgb(79.6%,23.5%,20%);")
RegexMatch("rgb(25.1%,38.8%,84.7%);")
RegexMatch("rgb(58.4%,34.5%,69.8%);")
RegexMatch("rgb(22%,59.6%,14.9%);")
@drawsvg begin
background("midnightblue")
fontface("JuliaMonoRegular")
fontsize(20)
sethue("gold")
text("JuliaMono: a monospaced font ", halign=:center)
text("with reasonable Unicode support", O + (0, 22), halign=:center)
end 500 150
write("txt.svg", svgstring())
# minimize SVG
run(`svgo txt.svg o txtmin.svg`)
Luxor.text
— Methodtext(str)
text(str, pos)
text(str, pos, angle = pi/2)
text(str, x, y)
text(str, pos, halign = :left)
text(str, valign = :baseline)
text(str, valign = :baseline, halign = :left)
text(str, pos, valign = :baseline, halign = :left)
text(latexstr, pos, valign = :baseline, halign = :left, rotationfixed = false, angle = 0)
Draw the text in the string str
at x
/y
or pt
, placing the start of the string at the point. If you omit the point, it's placed at the current 0/0
.
angle
specifies the rotation of the text relative to the current xaxis.
Horizontal alignment halign
can be :left
, :center
, (also :centre
) or :right
. Vertical alignment valign
can be :baseline
, :top
, :middle
, or :bottom
.
The default alignment is :left
, :baseline
.
This uses textextents()
to query the dimensions of the text. This returns values of the built in to the font. You can't find
This uses Cairo's Toy text API.
Luxor.textbox
— Functiontextbox(s::AbstractString, pos::Point=O;
leading = 12,
linefunc::Function = (linenumber, linetext, startpos, height) > (),
alignment=:left)
Luxor.textbox
— Functiontextbox(lines::Array, pos::Point=O;
leading = 12,
linefunc::Function = (linenumber, linetext, startpos, height) > (),
alignment=:left)
Draw the strings in the array lines
vertically downwards. leading
controls the spacing between each line (default 12), and alignment
determines the horizontal alignment (default :left
).
Optionally, before each line, execute the function linefunc(linenumber, linetext, startpos, height)
.
Returns the position of what would have been the next line.
See also textwrap()
, which modifies the text so that the lines fit into a specified width.
Luxor.textcurve
— Functiontextcurve(the_text, start_angle, start_radius, x_pos = 0, y_pos = 0;
# optional keyword arguments:
spiral_ring_step = 0, # step out or in by this amount
letter_spacing = 0, # tracking/space between chars, tighter is (), looser is (+)
spiral_in_out_shift = 0, # + values go outwards,  values spiral inwards
clockwise = true
)
Place a string of text on a curve. It can spiral in or out.
start_angle
is relative to +ve xaxis, arc/circle is centered on (x_pos,y_pos)
with radius start_radius
.
Luxor.textcurvecentered
— Methodtextcurvecentered(the_text, the_angle, the_radius, center::Point;
clockwise = true,
letter_spacing = 0,
baselineshift = 0
This version of the textcurve()
function is designed for shorter text strings that need positioning around a circle. (A cheesy effect much beloved of hipster brands and retronauts.)
letter_spacing
adjusts the tracking/space between chars, tighter is (), looser is (+)). baselineshift
moves the text up or down away from the baseline.
textcurvecentred
(UK spelling) is a synonym.
Luxor.textextents
— Methodtextextents(str)
Return an array of six Float64s containing the measurements of the string str
when set using the current font settings (Toy API):
1 x_bearing
2 y_bearing
3 width
4 height
5 x_advance
6 y_advance
The x and y bearings are the displacement from the reference point to the upperleft corner of the bounding box. It is often zero or a small positive value for x displacement, but can be negative x for characters like "j"; it's almost always a negative value for y displacement.
The width and height then describe the size of the bounding box. The advance takes you to the suggested reference point for the next letter. Note that bounding boxes for subsequent blocks of text can overlap if the bearing is negative, or the advance is smaller than the width would suggest.
Example:
textextents("R")
returns
[1.18652; 9.68335; 8.04199; 9.68335; 9.74927; 0.0]
Luxor.textfit
— Functiontextfit(str, bbox::BoundingBox, maxfontsize = 800;
horizontalmargin=12,
leading=100)
Fit the string str
into the bounding box bbox
by adjusting the font size and line breaks.
Instead of using the current font size, a suitable value will be calculated. You can specify the largest size in maxfontsize
, otherwise the largest possible value below 200 will be used.
horizontalmargin
is applied to each side.
Optionally, leading
can be supplied, and it will be interpreted as a percentage of the final calculated font size. The default value is 100, so no extra leading is used.
The function returns a named tuple with information about the calculated values:
(fontsize = 37.6, linecount = 5, finalpos = Point(117.43, 92.60)
This function is in need of improvement. It's not Adobe InDesign... :)
Luxor.textlines
— Methodtextlines(s::AbstractString, width::Real;
rightgutter=5)
Split the text in s
into lines up to width
units wide (in the current font).
Returns an array of strings. Use textwrap
to draw an array of strings.
TODO: A rightgutter
optional keyword adds some padding to the right hand side of the column. This appears to be needed sometimes — perhaps the algorithm needs improving to take account of the interaction of textextents
and spaces?
Luxor.textonpoly
— Methodtextonpoly(str, pgon;
tracking = 0,
startoffset = 0.0,
baselineshift = 0.0,
closed = false)
Draw the text in str
along the route of the polygon in pgon
.
The closed
option determines whether the final edge of the polygon (joining the last point to the first) is included or not. Eg if you want to draw a string around all three sides of a triangle, you'd use closed=true
:
textonpoly("mèdeis ageômetrètos eisitô mou tèn
stegèn  let no one ignorant of geometry come under my roof
",
ngon(O, 100, 3, vertices=true),
closed=true)
If false
, only two sides are considered.
Increase tracking
from 0 to add space between the glyphs.
The startoffset
value is a normalized percentage that specifies the start position. So, to start drawing the text halfway along the polygon, specify a start offset value of 0.5.
Positive values for baselineshift
move the characters upwards from the baseline.
Returns a tuple with the number of characters drawn, and the final value of the index, between 0.0 and 1.0. If the returned index value is less than 1, this means that the text supplied ran out before the end of the polygon was reached.
Luxor.textoutlines
— Methodtextoutlines(s::AbstractString, pos::Point=O;
action=:none,
halign=:left,
valign=:baseline,
startnewpath=true)
Convert text to polygons and apply action
.
By default this function discards any current path, unless you use startnewpath=false
See also textpath()
.
TODO Return something more useful than a Boolean.
Luxor.textpath
— Methodtextpath(s::AbstractString, pos::Point;
action=:none,
halign=:left,
valign=:baseline,
startnewpath=true)
Convert the text in string s
to paths and apply the action.
TODO Return something more useful than a Boolean.
Luxor.textpath
— Methodtextpath(t)
Convert the text in string t
to paths, adding them to the current path, for subsequent filling/stroking etc...
You can use pathtopoly()
or getpath()
or getpathflat()
to convert the paths to polygons.
See also textoutlines()
.
Luxor.textplace
— Methodtextplace(txt::AbstractString, pos::Point, params::Vector)
A lowlevel function that places text characters one by one according to the parameters in params
. First character uses the first tuple, second character uses the second, and so on. Returns the next text position.
A tuple of parameters is:
(face = "TimesRoman", size = 12, kern = 0, shift = 0, advance = true)
where
face
is fontface "string" # stickysize
is fontsize # pts # stickykern
amount (pixels) shifted to the right # resets after each charshift
= baseline shifted vertically # resets after each charadvance
 whether to advance # resets after each char
Font face and size parameters are sticky, and stay set until reset. Kern/shift/advance are reset for each character.
Example
Draw the Hogwarts Express Platform number 9 and 3/4
txtpos = textplace("93—4!", O  (200, 0), [
(size=120, face="BodoniPoster", ), # format for 9
(size=60, kern = 5, shift = 60, advance=false,), # format for 3
( kern = 0, shift = 25, advance=false,), # format for 
( kern = 5, shift = 20, advance=true), # format for 4
(size=120, kern = 20,),
])
Luxor.texttrack
— Methodtexttrack(txt, pos, tracking;
action=:fill,
halign=:left,
valign=:baseline,
startnewpath=true)
texttrack(txt, pos, tracking, fontsize;
action=:fill,
halign=:left,
valign=:baseline,
startnewpath=true)
Place the text in txt
at pos
, leftjustified, and letter space ('track') the text using the value in tracking
.
The tracking units depend on the current font size. In a 12‑point font, 1 em equals 12 points. A point is about 0.35mm, 1em is about 4.2mm, and a 1000 units of tracking are about 4.2mm. So a tracking value of 1000 for a 12 point font places about 4mm between each character.
A negative value tightens the letter spacing noticeably.
The text drawing action applied to each character defaults to textoutlines(... :fill)
.
If startnewpath
is true, each character is acted on separately. To clip and track text, specify the clip action and avoid resetting the clipping path for each character.
newpath()
texttrack(t, O + (0, 80), 200, action=:clip, startnewpath=false)
...
clipreset()
TODO Is it possible to fix strings with combining characters such as "̈"?
Luxor.textwrap
— Methodtextwrap(s::T where T<:AbstractString, width::Real, pos::Point;
rightgutter=5,
leading=0)
textwrap(s::T where T<:AbstractString, width::Real, pos::Point, linefunc::Function;
rightgutter=5,
leading=0)
Draw the string in s
by splitting it at whitespace characters into lines, so that each line is no longer than width
units. The text starts at pos
such that the first line of text is drawn entirely below a line drawn horizontally through that position. Each line is aligned on the left side, below pos
.
See also textbox()
.
Optionally, before each line, execute the function linefunc(linenumber, linetext, startpos, leading)
.
If you don't supply a value for leading
, the font's builtin extents are used.
Text with no whitespace characters won't wrap. You can write a simple chunking function to split a string or array into chunks:
chunk(x, n) = [x[i:min(i+n1,length(x))] for i in 1:n:length(x)]
For example:
textwrap(the_text, 300, boxtopleft(BoundingBox()) + 20,
(ln, lt, sp, ht) > begin
c = count(t > occursin(r"[[:punct:]]", t), split(lt, ""))
@layer begin
fontface("Menlo")
sethue("darkred")
text(string("[", c, "]"), sp + (310, 0))
end
end)
puts a count of the number of punctuation characters in each line at the end of the line.
Returns the position of what would have been the next line.
Luxor.tickline
— Methodtickline(startpos, finishpos;
startnumber = 0,
finishnumber = 1,
major = 1,
minor = 0,
major_tick_function = nothing,
minor_tick_function = nothing,
rounding = 2,
axis = true, # draw the line?
log = false,
vertices = false # just return the points
)
Draw a line with ticks. major
is the number of ticks required between the start and finish point. So 1
divides the line in half. minor
is the number of ticks between each major tick.
Examples
tickline(Point(0, 0), Point(100, 0))
tickline(Point(0, 0), Point(100, 0), major = 4)
majorticks, minorticks = tickline(Point(0, 0), Point(100, 0), axis=false)
Custom ticks
Supply functions to make custom ticks. Custom tick functions should have arguments as follows:
function mtick(n, pos;
startnumber = 0,
finishnumber = 1,
nticks = 1)
...
and
function mntick(n, pos;
startnumber = 0,
finishnumber = 1,
nticks = 1,
majorticklocations = [])
...
For example:
tickline(O  (300, 0), Point(300, 0),
startnumber = 10,
finishnumber = 10,
minor = 0,
major = 4,
axis = false,
major_tick_function = (n, pos;
startnumber=30, finishnumber=40, nticks=10) > begin
@layer begin
translate(pos)
ticklength = get_fontsize()
line(O, O + polar(ticklength, 3π/2), :stroke)
k = rescale(n, 0, nticks  1, startnumber, finishnumber)
ticklength = get_fontsize() * 1.3
text("$(round(k, digits=2))",
O + (0, ticklength),
halign=:center,
valign=:middle,
angle = getrotation())
end
end)
Luxor.tidysvg
— Methodtidysvg(fname)
Read the SVG image in fname
and write it to a file fnametidy.svg
with modified glyph names.
Return the name of the modified file.
SVG images use named defs for text, which cause errors problem when used in a notebook. See for example.
A kludgy workround is to rename the elements...
Luxor.transform
— Methodtransform(a::Array)
Modify the current matrix by multiplying it by matrix a
.
For example, to skew the current state by 45 degrees in x and move by 20 in y direction:
transform([1, 0, tand(45), 1, 0, 20])
Use getmatrix()
to get the current matrix.
Luxor.translate
— Methodtranslate(point)
translate(x::Real, y::Real)
Translate the workspace to x
and y
or to pt
.
Luxor.translationmatrix
— Methodtranslationmatrix(x, y)
Return a 3x3 Julia matrix that will apply a translation in x
and y
.
Luxor.trianglecenter
— Methodtrianglecenter(pt1::Point, pt2::Point, pt3::Point)
Return the centroid of the triangle defined by pt1
, pt2
, and pt3
.
Luxor.trianglecircumcenter
— Methodtrianglecircumcenter(pt1::Point, pt2::Point, pt3::Point)
Return the circumcenter of the triangle defined by pt1
, pt2
, and pt3
. The circumcenter is the center of a circle that passes through the vertices of the triangle.
Luxor.triangleincenter
— Methodtriangleincenter(pt1::Point, pt2::Point, pt3::Point)
Return the incenter of the triangle defined by pt1
, pt2
, and pt3
. The incenter is the center of a circle inscribed inside the triangle.
Luxor.triangleorthocenter
— Methodtriangleorthocenter(pt1::Point, pt2::Point, pt3::Point)
Return the orthocenter of the triangle defined by pt1
, pt2
, and pt3
.
Luxor.unpremultiplyalpha
— Methodunpremultiplyalpha(a)
Given an array of UInt32 values, divide each value by the alpha value. See alphadivide or reversing premultiplied alpha values.
Returns an array of arrays, where each array has four Float64 values.
In a premultiplied image array, a 50% transparent red pixel is stored as 0x80800000, rather than not 0x80ff0000. This function reverses the process, dividing each RGB value by the alpha value.
The highest two digits of each incoming element is interpreted as the alpha value.
unpremultiplyalpha([0x80800000])
1element Array{Array{Float64,1},1}:
[1.0, 0.0, 0.0, 0.5019607843137255]
Notice the arithmetic errors introduced as 0x80 gets converted to 0.5019.
Luxor.AnimatedGif
— TypeWraps the location of an animated gif so that it can be displayed
Luxor.BezierPath
— TypeBezierPath is an array of BezierPathSegments. segments
is Vector{BezierPathSegment}
.
Luxor.BezierPathSegment
— TypeBezierPathSegment is an array of four points:
p1
 start point cp1
 control point for start point cp2
 control point for finishpoint p2
 finish point
Luxor.BoundingBox
— MethodBoundingBox(str::AbstractString)
Return a BoundingBox that just encloses a text string, given the current font selection. Uses the Toy text API (ie text()
and textextents()
).
Text is assumed to be placed at the origin (0/0
).
Luxor.BoundingBox
— MethodBoundingBox(tile::BoxmapTile)
Return a BoundingBox of a BoxmapTile (as created with boxmap()
).
Luxor.BoundingBox
— MethodBoundingBox(path::Path)
Find bounding box of a stored Path (made with storepath()
).
Luxor.BoundingBox
— MethodBoundingBox(pointlist::Array)
Return the BoundingBox of a polygon (array of points).
Luxor.BoundingBox
— MethodBoundingBox()
with no arguments returns a BoundingBox that includes the current drawing.
The default BoundingBox(;centered=true)
returns a BoundingBox the same size and position as the current drawing, and assumes the origin (0, 0) is at the center.
If the centered
option is false
, the function assumes that the origin is at the top left of the drawing. So this function doesn't really work if the current matrix has been modified (by translate()
, scale()
, rotate()
etc.)
An instance of the BoundingBox type holds two Points, corner1
and corner2
.
BoundingBox(;centered = true) # the bounding box of the Drawing
BoundingBox(s::AbstractString) # the bounding box of a text string at the origin
BoundingBox(pt::Array) # the bounding box of a polygon
BoundingBox(circle(O, 100)) # the bounding box of a path added by circle()
BoundingBox(path::Path) # the bounding box of a Path
You can use BoundingBox()
with the functions that add graphic shapes to the current path (eg box()
, circle()
, star()
, ngon()
). But note that eg BoundingBox(box(O, 100, 100))
adds a shape to the current path as well as returning a bounding box.
Luxor.Drawing
— TypeCreate a new drawing, and optionally specify file type (PNG, PDF, SVG, EPS), filebased or inmemory, and dimensions.
Drawing(width=600, height=600, file="luxordrawing.png")
Extended help
Drawing()
creates a drawing, defaulting to PNG format, default filename "luxordrawing.png", default size 800 pixels square.
You can specify dimensions, and assume the default output filename:
Drawing(400, 300)
creates a drawing 400 pixels wide by 300 pixels high, defaulting to PNG format, default filename "luxordrawing.png".
Drawing(400, 300, "mydrawing.pdf")
creates a PDF drawing in the file "mydrawing.pdf", 400 by 300 pixels.
Drawing(1200, 800, "mydrawing.svg")
creates an SVG drawing in the file "mydrawing.svg", 1200 by 800 pixels.
Drawing(width, height, surfacetype  filename)
creates a new drawing of the given surface type (e.g. :svg, :png), storing the picture only in memory if no filename is provided.
Drawing(1200, 1200/Base.Mathconstants.golden, "mydrawing.eps")
creates an EPS drawing in the file "mydrawing.eps", 1200 wide by 741.8 pixels (= 1200 ÷ ϕ) high. Only for PNG files must the dimensions be integers.
Drawing("A4", "mydrawing.pdf")
creates a drawing in ISO A4 size (595 wide by 842 high) in the file "mydrawing.pdf". Other sizes available are: "A0", "A1", "A2", "A3", "A4", "A5", "A6", "Letter", "Legal", "A", "B", "C", "D", "E". Append "landscape" to get the landscape version.
Drawing("A4landscape")
creates the drawing A4 landscape size.
PDF files default to a white background, but PNG defaults to transparent, unless you specify one using background()
.
Drawing(width, height, :image)
creates the drawing in an image buffer in memory. You can obtain the data as a matrix with image_as_matrix()
.
Drawing(width, height, :rec)
creates the drawing in a recording surface in memory. snapshot(fname, ...)
to any file format and bounding box, or render as pixels with image_as_matrix()
.
Drawing(width, height, strokescale=true)
creates the drawing and enables stroke scaling (strokes will be scaled according to the current transformation). (Stroke scaling is disabled by default.)
Luxor.GridHex
— TypeGridHex(startpoint, radius, width=1200.0, height=1200.0)
Define a hexagonal grid, to start at startpoint
and proceed along the xaxis and then along the yaxis, radius
is the radius of a circle that encloses each hexagon. The distance in x
between the centers of successive hexagons is:
$\frac{\sqrt{(3)} radius}{2}$
To get the next point from the grid, use nextgridpoint(g::Grid)
.
When you run out of grid points, you'll wrap round and start again.
Luxor.GridRect
— TypeGridRect(startpoint, xspacing, yspacing, width, height)
Define a rectangular grid, to start at startpoint
and proceed along the xaxis in steps of xspacing
, then along the yaxis in steps of yspacing
.
GridRect(startpoint, xspacing=100.0, yspacing=100.0, width=1200.0, height=1200.0)
For a column, set the xspacing
to 0:
grid = GridRect(O, 0, 40)
To get points from the grid, use nextgridpoint(g::Grid)
.
julia> grid = GridRect(O, 0, 40);
julia> nextgridpoint(grid)
Luxor.Point(0.0, 0.0)
julia> nextgridpoint(grid)
Luxor.Point(0.0, 40.0)
When you run out of grid points, you'll wrap round and start again.
Luxor.Movie
— TypeThe Movie
and Scene
types and the animate()
function are designed to help you create the frames that can be used to make an animated GIF or movie.
1 Provide width, height, title, and optionally a frame range to the Movie constructor:
demo = Movie(400, 400, "test", 1:500)
2 Define one or more scenes and scenedrawing functions.
3 Run the animate()
function, calling those scenes.
Example
bang = Movie(400, 100, "bang")
backdrop(scene, framenumber) = background("black")
function frame1(scene, framenumber)
background("white")
sethue("black")
eased_n = scene.easingfunction(framenumber, 0, 1, scene.framerange.stop)
circle(O, 40 * eased_n, :fill)
end
animate(bang, [
Scene(bang, backdrop, 0:200),
Scene(bang, frame1, 0:200, easingfunction=easeinsine)],
creategif=true,
pathname="/tmp/animationtest.gif")
Luxor.Movie
— MethodMovie(width, height, movietitle)
Define a movie, specifying the width, height, and a title. The title will be used to make the output file name. The range defaults to 1:250
.
Luxor.Partition
— Typep = Partition(areawidth, areaheight, tilewidth, tileheight)
A Partition is an iterator that, for each iteration, returns a tuple of:
 the
x
/y
point of the center of each tile in a set of tiles that divide up a
rectangular space such as a page into rows and columns (relative to current 0/0)
 the number of the tile
areawidth
and areaheight
are the dimensions of the area to be tiled, tilewidth
/tileheight
are the dimensions of the tiles.
Tiler and Partition are similar:
Partition lets you specify the width and height of a cell
Tiler lets you specify how many rows and columns of cells you want, and a margin
tiles = Partition(1200, 1200, 30, 30)
for (pos, n) in tiles
# the point pos is the center of the tile
end
You can access the calculated tile width and height like this:
tiles = Partition(1200, 1200, 30, 30)
for (pos, n) in tiles
ellipse(pos.x, pos.y, tiles.tilewidth, tiles.tileheight, :fill)
end
It's sometimes useful to know which row and column you're currently on:
tiles.currentrow
tiles.currentcol
should have that information for you.
Unless the tilewidth and tileheight are exact multiples of the area width and height, you'll see a border at the right and bottom where the tiles won't fit.
Luxor.Path
— TypeA Path object contains, in the .path
field, a vector of PathElement
s (PathCurve
, PathMove
, PathLine
, PathClose
) that describe a Cairo path. Use drawpath()
to draw it.
Path([PathMove(Point(2.0, 90.5625)),
PathCurve(Point(4.08203, 68.16015), Point(11.28, 45.28), Point(24.8828, 26.40234)),
PathLine(Point(2.0, 90.5625)),
PathClose()
])
Luxor.Path
— MethodPath(ptlist::Vector{Point}; close=false))
Create a Path from the points in ptlist
.
Luxor.Point
— TypeThe Point type holds two coordinates. It's immutable, you can't change the values of the x and y values directly.
Luxor.Scene
— TypeThe Scene type defines a function to be used to render a range of frames in a movie.
 the
movie
created by Movie()  the
framefunction
is a function taking two arguments: the scene and the framenumber.  the
framerange
determines which frames are processed by the function. Defaults to the entire movie.  the optional
easingfunction
can be accessed by the framefunction to vary the transition speed  the optional
opts
which is a single argument of an abstract type which can be accessed within the framefunction
Luxor.Scene
— MethodScene(movie, function, range;
easingfunction=easinoutquad,
optarg=nothing)
Use the Scene() constructor function to create a scene. Supply a movie, a function to generate the scene, and a range of frames. Optionally you can supply an easing function, and other information, in optarg
, which can be accessed as scene.opts
.
Example
function initial(scene, framenumber)
balls = scene.opts
...
end
animate(poolmovie, [
Scene(poolmovie, initial, optarg=balls, 1:20),
...
])
To use an easing function inside the framegenerating function, you can create a normalized value with, for example:
eased_n = scene.easingfunction(framenumber, 0, 1, scene.framerange.stop)
Or, if the scene doesn't start at frame 1, calculate normalized easing function like this:
eased_n = scene.easingfunction(framenumber  scene.framerange.start,
0, 1, scene.framerange.stop  scene.framerange.start)
Luxor.Table
— Typet = Table(nrows, ncols)
t = Table(nrows, ncols, colwidth, rowheight)
t = Table(rowheights, columnwidths)
Tables are centered at O
, but you can supply a point after the specifications.
t = Table(nrows, ncols, centerpoint)
t = Table(nrows, ncols, colwidth, rowheight, centerpoint)
t = Table(rowheights, columnwidths, centerpoint)
Examples
Simple tables
t = Table(4, 3) # 4 rows and 3 cols, default is 100w, 50 h
t = Table(4, 3, 80, 30) # 4 rows of 30pts high, 3 cols of 80pts wide
t = Table(4, 3, (80, 30)) # same
t = Table((4, 3), (80, 30)) # same
Specify row heights and column widths instead of quantities:
t = Table([60, 40, 100], 50) # 3 different height rows, 1 column 50 wide
t = Table([60, 40, 100], [100, 60, 40]) # 3 rows, 3 columns
t = Table(fill(30, (10)), [50, 50, 50]) # 10 rows 30 high, 3 columns 10 wide
t = Table(50, [60, 60, 60]) # just 1 row (50 high), 3 columns 60 wide
t = Table([50], [50]) # just 1 row, 1 column, both 50 units wide
t = Table(50, 50, 10, 5) # 50 rows, 50 columns, 10 units wide, 5 units high
t = Table([6, 11, 16, 21, 26, 31, 36, 41, 46], [6, 11, 16, 21, 26, 31, 36, 41, 46])
t = Table(15:5:55, vcat(5:2:15, 15:2:5))
# table has 108 cells, with:
# row heights: 15 20 25 30 35 40 45 50 55
# col widths: 5 7 9 11 13 15 15 13 11 9 7 5
t = Table(vcat(5:10:60, 60:10:5), vcat(5:10:60, 60:10:5))
t = Table(vcat(5:10:60, 60:10:5), 50) # 1 column 50 units wide
t = Table(vcat(5:10:60, 60:10:5), 1:5:50)
A Table is an iterator that, for each iteration, returns a tuple of:
the
x
/y
point of the center of cells arranged in rows and columns (relative to current 0/0)the number of the cell (left to right, then top to bottom)
nrows
/ncols
are the number of rows and columns required.
It's sometimes useful to know which row and column you're currently on while iterating:
t.currentrow
t.currentcol
and row heights and column widths are available in:
t.rowheights
t.colwidths
box(t::Table, r, c)
can be used to fill table cells:
@svg begin
for (pt, n) in (t = Table(8, 3, 30, 15))
randomhue()
box(t, t.currentrow, t.currentcol, :fill)
sethue("white")
text(string(n), pt)
end
end
or without iteration, using cellnumber:
@svg begin
t = Table(8, 3, 30, 15)
for n in eachindex(t)
randomhue()
box(t, n, :fill)
sethue("white")
text(string(n), t[n])
end
end
To use a Table to make grid points:
julia> first.(collect(Table(10, 6)))
60element Array{Luxor.Point,1}:
Luxor.Point(10.0, 18.0)
Luxor.Point(6.0, 18.0)
Luxor.Point(2.0, 18.0)
⋮
Luxor.Point(2.0, 18.0)
Luxor.Point(6.0, 18.0)
Luxor.Point(10.0, 18.0)
which returns an array of points that are the center points of the cells in the table.
Luxor.Tiler
— Typetiles = Tiler(areawidth, areaheight, nrows, ncols, margin=20)
A Tiler is an iterator that, for each iteration, returns a tuple of:
the
x
/y
point of the center of each tile in a set of tiles that divide up a rectangular space such as a page into rows and columns (relative to current 0/0)the number of the tile
areawidth
and areaheight
are the dimensions of the area to be tiled, nrows
/ncols
are the number of rows and columns required, and margin
is applied to all four edges of the area before the function calculates the tile sizes required.
Tiler and Partition are similar:
Partition lets you specify the width and height of a cell
Tiler lets you specify how many rows and columns of cells you want, and a margin:
tiles = Tiler(1000, 800, 4, 5, margin=20)
for (pos, n) in tiles
# the point pos is the center of the tile
end
You can access the calculated tile width and height like this:
tiles = Tiler(1000, 800, 4, 5, margin=20)
for (pos, n) in tiles
ellipse(pos.x, pos.y, tiles.tilewidth, tiles.tileheight, :fill)
end
It's sometimes useful to know which row and column you're currently on. tiles.currentrow
and tiles.currentcol
should have that information for you.
To use a Tiler to make grid points:
first.(collect(Tiler(800, 800, 4, 4)))
which returns an array of points that are the center points of the grid.
Luxor.Turtle
— TypeTurtle()
Turtle(O)
Turtle(0, 0)
Turtle(O, pendown=true, orientation=0, pencolor=(1.0, 0.25, 0.25))
Create a Turtle. You can command a turtle to move and draw "turtle graphics".
The commands (unusually for Julia) start with a capital letter, and angles are specified in degrees.
Basic commands are Forward()
, Turn()
, Pendown()
, Penup()
, Pencolor()
, Penwidth()
, Circle()
, Orientation()
, Rectangle()
, and Reposition()
.
Others include Push()
, Pop()
, Message()
, HueShift()
, Randomize_saturation()
, Reposition()
, and Pen_opacity_random()
.
Luxor.O
— ConstantO is a shortcut for the current origin, 0/0
Luxor.paper_sizes
— Constantpaper_sizes
The paper_sizes
Dictionary holds a few paper sizes, width is first, so default is Portrait:
"A0" => (2384, 3370),
"A1" => (1684, 2384),
"A2" => (1191, 1684),
"A3" => (842, 1191),
"A4" => (595, 842),
"A5" => (420, 595),
"A6" => (298, 420),
"A" => (612, 792),
"Letter" => (612, 792),
"Legal" => (612, 1008),
"Ledger" => (792, 1224),
"B" => (612, 1008),
"C" => (1584, 1224),
"D" => (2448, 1584),
"E" => (3168, 2448))