Luxor is the lightest dusting of syntactic sugar on Julia's Cairo graphics package (which should also be installed). It provides some basic vector drawing commands, and a few utilities for working with polygons, clipping masks, and turtle graphics.
The idea of Luxor is that it's slightly easier to use than Cairo, with shorter names, fewer underscores, default contexts, and simplified functions. It's for when you just want to draw something without too much ceremony. For a much more powerful graphics environment, try Compose.jl. Also worth looking at is Andrew Cooke's Drawing.jl package.
Colors.jl provides excellent color definitions and is also required.
I've only tried this on MacOS X. It will need some changes to work on Windows (but I can't test it).
It's been updated for Julia version 0.4 and for the modified Colors.jl.
SVG rendering currently seems unreliable — text placement generates segmentation faults.
To install:
Pkg.clone(...)
using Luxor, Colors
Drawing(1000, 1000, "/tmp/hello-world.png")
origin()
sethue("red")
fontsize(50)
text("hello world")
finish()
preview()
using Luxor, Colors
Drawing(1200, 1400, "/tmp/basic-test.png") # or PDF/SVG filename for PDF or SVG
origin() # move 0/0 to center
background("purple")
setopacity(0.7) # opacity from 0 to 1
sethue(0.3,0.7,0.9) # sethue sets the color but doesn't change the opacity
setline(20) # line width
rect(-400,-400,800,800, :fill) # or :stroke, :fillstroke, :clip
randomhue()
circle(0, 0, 460, :stroke)
circle(0,-200,400,:clip) # set a circular mask above the x axis
sethue("gold")
setopacity(0.7)
setline(10)
for i in 0:pi/36:2*pi - pi/36
move(0, 0)
line(cos(i) * 600, sin(i) * 600 )
stroke()
end
clipreset() # finish clipping
fontsize(60)
setcolor("turquoise")
fontface("Optima-ExtraBlack") # a Mac OS X font
textwidth = textextents("Luxor")[5]
# move the text by half the width
text("Luxor", -textwidth/2, currentdrawing.height/2 - 400)
fontsize(18)
fontface("Avenir-Black")
# text on curve starting on an arc
textcurve("THIS IS TEXT ON A CURVE " ^ 14, 0, 0, 0, -10, 550)
finish()
preview() # on Mac OS X, opens in Preview
Some simple "turtle graphics" commands are included:
using Luxor, Colors
Drawing(1200, 1200, "/tmp/turtles.png")
origin()
background("black")
# let's have two turtles
raphael = Turtle(0, 0, true, 0, (1.0, 0.25, 0.25)) ; michaelangelo = Turtle(0, 0, true, 0, (1.0, 0.25, 0.25))
setopacity(0.95)
setline(6)
Pencolor(raphael, 1.0, 0.4, 0.2); Pencolor(michaelangelo, 0.2, 0.9, 1.0)
Reposition(raphael, 500, -200); Reposition(michaelangelo, 500, 200)
Message(raphael, "Raphael"); Message(michaelangelo, "Michaelangelo")
Reposition(raphael, 0, -200); Reposition(michaelangelo, 0, 200)
pace = 10
for i in 1:5:400
for turtle in [raphael, michaelangelo]
Circle(turtle, 3)
HueShift(turtle, rand())
Forward(turtle, pace)
Turn(turtle, 30 - rand())
Message(turtle, string(i))
pace += 1
end
end
finish()
preview()
using Luxor, Colors
function draw_triangle(points::Array{Point}, degree::Int64)
global triangle_count, cols
setcolor(cols[degree+1])
poly(points, :fill)
triangle_count += 1
end
get_midpoint(p1::Point, p2::Point) = Point((p1.x + p2.x) / 2, (p1.y + p2.y) / 2)
function sierpinski(points::Array{Point}, degree::Int64)
draw_triangle(points, degree)
if degree > 0
p1,p2,p3 = points
sierpinski([p1, get_midpoint(p1, p2),
get_midpoint(p1, p3)], degree-1)
sierpinski([p2, get_midpoint(p1, p2),
get_midpoint(p2, p3)], degree-1)
sierpinski([p3, get_midpoint(p3, p2),
get_midpoint(p1, p3)], degree-1)
end
end
@time begin
depth = 8 # 12 is ok, 20 is right out
cols = distinguishable_colors(depth+1)
Drawing(400, 400, "/tmp/sierpinski.svg")
origin()
setopacity(0.5)
triangle_count = 0
my_points = [Point(-100,-50), Point(0,100), Point(100.0,-50.0)]
sierpinski(my_points, depth)
println("drew $triangle_count triangles")
end
finish()
preview()
# drew 9841 triangles
# elapsed time: 1.738649452 seconds (118966484 bytes allocated, 2.20% gc time)
using Luxor, Colors
Drawing(1200, 1400)
origin()
cols = diverging_palette(60,120, 20) # hue 60 to hue 120
background(cols[1])
setopacity(0.7)
setline(2)
ngon(0, 0, 500, 8, 0, :clip)
for y in -500:50:500
for x in -500:50:500
setcolor(cols[rand(1:20)])
ngon(x, y, rand(20:25), rand(3:12), 0, :fill)
setcolor(cols[rand(1:20)])
ngon(x, y, rand(10:20), rand(3:12), 0, :stroke)
end
end
finish()
preview()
include("../examples/julia-logo.jl") # the julia logo coordinates
currentwidth = 500 # pts
currentheight = 500 # pts
Drawing(currentwidth, currentheight, "/tmp/clipping-tests.pdf")
function draw_logo_clip(x, y)
foregroundcolors = diverging_palette(rand(0:360), rand(0:360), 200, s = 0.99, b=0.8)
gsave()
translate(x-100, y)
julialogomask() # use julia logo as clipping mask
clip()
for i in 1:500
sethue(foregroundcolors[rand(1:end)])
circle(rand(-50:350), rand(0:300), 15, :fill)
end
grestore()
end
origin()
background("white")
setopacity(.4)
draw_logo_clip(0,0)
finish()
preview()
Using a text path as a clipping region - here filled with names of Julia functions.
using Luxor, Colors
currentwidth = 1250 # pts
currentheight = 800 # pts
Drawing(currentwidth, currentheight, "/tmp/text-path-clipping.png")
origin()
background("darkslategray3")
fontsize(600) # big fontsize to use for clipping
fontface("Agenda-Black")
str = "julia" # string to be clipped
w, h = textextents(str)[3:4] # get width and height
translate(-(currentwidth/2) + 50, -(currentheight/2) + h)
textpath(str) # make text into a path
setline(3)
setcolor("black")
fillpreserve() # fill but keep
clip() # clip
fontface("Monaco")
fontsize(10)
namelist = map(x->string(x), names(Base)) # list of names in Base.
x = -20
y = -h
while y < currentheight
sethue(rand(7:10)/10, rand(7:10)/10, rand(7:10)/10)
s = namelist[rand(1:end)]
text(s, x, y)
se = textextents(s)
x += se[5] # move to the right
if x > w
x = -20 # next row
y += 10
end
end
finish()
preview()
Drawing()
create a drawing, defaulting to PNG format, file called "/tmp/luxor-drawing.png", 800 pixels squareDrawing(300,300)
create a drawing 300 by 300 pixels, defaulting to PNG format, file called "/tmp/luxor-drawing.png",Drawing(300,300, "/tmp/my-drawing.pdf")
create a PDF drawing in the file "/tmp/my-drawing.pdf", 300 by 300 pixelsDrawing(800,800, "/tmp/my-drawing.svg")
create an SVG drawing in the file "/tmp/my-drawing.svg", 800 by 800 pixelsfinish()
finish the drawingpreview()
open the file in Preview (MacOS X only)
The global variable currentdrawing
keeps a few parameters:
julia> fieldnames(currentdrawing)
10-element Array{Symbol,1}:
:width
:height
:filename
:surface
:cr
:surfacetype
:redvalue
:greenvalue
:bluevalue
:alpha
The origin (0/0) is at the top left, x axis runs left to right, y axis runs top to bottom
origin()
move the 0/0 origin to the centre of the imageaxes()
draw axes at current 0/0background(color)
fill background with a colored rectangle
For these functions, the action argument can be :nothing
, :fill
, :stroke
, :fillstroke
, or :clip
, defaulting to :nothing
.
-
circle(x, y, r, action)
-
arc(xc, yc, radius, angle1, angle2, action)
add arc to current path centered atxc/yc
starting atangle1
and ending atangle2
drawing arc clockwise -
carc(xc, yc, radius, angle1, angle2, action)
add arc to current path centered atxc/yc
starting atangle1
and ending atangle2
, drawing arc counterclockwise. -
sector(innerradius, outerradius, startangle, endangle, action)
draw a sector/track
Angles are measured from the positive x-axis to the positive y-axis in radians, clockwise.
rect(xmin, ymin, w, h, action)
There is a 'current position':
-
move(x, y)
move to this position -
rmove(x, y)
move relative to current position byx
andy
-
line(x, y)
draw line from current position to thex/y
position -
rline(x, y)
draw line from current position byx
andy
-
curve(x1, y1, x2, y2, x3, y3)
a cubic Bézier spline, starting at the current position, finishing atx3/y3
, following two control pointsx1/y1
andx2/y2
There is a Point type (the only main type, apart from Drawing
):
Point(12.0, 13.0)
Polygons are arrays of points.
-
poly(list::Array, action = :nothing; close=false, reversepath=false)
draws a polygon using array of Points. For example:poly(randompointarray(0,0,200,200, 85), :stroke)
-
randompoint(lowx, lowy, highx, highy)
returns a random point -
randompointarray(lowx, lowy, highx, highy, n)
returns an array of random points. For example:poly(randompointarray(0,0,200,200, 85), :stroke)
Regular polygons, from triangles, pentagons, hexagons, septagons, heptagons, octagons, nonagons, decagons, and on-and-on-agons, with:
ngon(xc, yc, radius, sides, angle, action=:nothing)
draws asides
-sided polygon
Without an action, returns a poly (array of points) instead:
ngon(xc, yc, radius, sides, angle)
Compare:
ngon(0, 0, 4, 4, 0) # returns the polygon's points
4-element Array{Luxor.Point,1}:
Luxor.Point(2.4492935982947064e-16,4.0)
Luxor.Point(-4.0,4.898587196589413e-16)
Luxor.Point(-7.347880794884119e-16,-4.0)
Luxor.Point(4.0,-9.797174393178826e-16)
ngon(0, 0, 4, 4, 0, :close) # draws a polygon
Polygons can contain holes. The reversepath
keyword changes the direction of the polygon. This draws an hexagonal bolt shape:
ngon(0, 0, 60, 6, 0, :path)
newsubpath()
ngon(0, 0, 40, 6, 0, :path, reversepath=true)
fillstroke()
Polygons can be simplified using the Douglas-Peucker algorithm (non-recursive version):
-
simplify(polygon, tolerance)
to delete points from an array of Points within the tolerance provided -
isinside(point, polygon)
returns true if the point is inside the polygon
-
setline(n)
set line width -
setlinecap(s)
set line ends to "butt", "round", or "square" -
setlinejoin(s)
set line joins to "miter", "round", or "bevel" -
setdash(dashing)
set line dashing to "solid", "dotted", "dot", "dotdashed", "longdashed", "shortdashed", "dash", "dashed", "dotdotdashed", or "dotdotdotdashed" -
fillstroke()
fill and stroke the current path -
stroke()
stroke the current path -
fill()
fill the current path -
strokepreserve()
stroke the path but keep it current -
fillpreserve()
fill the path but keep it current
gsave()
and grestore()
should always be balanced in pairs. gsave()
saves a copy of the current graphics settings (current axis rotation, position, scale, line and text settings, and so on). When the next grestore()
is called, all changes you've made to the graphics settings will be discarded, and they'll return to how they were when you used gsave()
.
-
gsave()
save the graphics state -
grestore()
restore the graphics state
-
scale(sx, sy)
scale by sx and sy -
rotate(a)
rotate clockwise (positive x-axis to positive y-axis) bya
radians around current 0/0 -
translate(tx, ty)
translate by tx/ty
The current matrix is a six number array, perhaps like this:
[1, 0, 0, 1, 0, 0]
-
getmatrix()
gets the current matrix -
setmatrix(a)
sets the matrix to arraya
-
transform(a)
transform the current matrix by 'multiplying' it with matrixa
. For example, to skew by 45 degrees in x and move by 20 in y direction:transform([1, 0, tand(45), 1, 0, 20])
For color definitions and conversions, use Colors.jl.
The difference between the setcolor()
and sethue()
functions is that sethue()
is independent of alpha opacity, so you can change the hue without changing the current opacity value (like in Mathematica).
-
setcolor(color)
setcolor"gold"
(is a string macro, still experimental but might be quicker ...)setcolor("gold")
setcolor("darkturquoise")
setcolor(convert(Colors.HSV, Colors.RGB(0.5, 1, 1)))
-
setcolor(r, g, b, alpha)
eg:setcolor(.2, .3, .4, .5)
-
setcolor(r, g, b)
-
sethue(color)
likesetcolor
-
sethue(r, g, b)
likesetcolor()
but doesn't change opacity -
setopacity(alpha)
change the alpha opacity (alpha
is between 0 and 1) -
randomhue()
choose a random color without changing current alpha opacity -
randomcolor()
choose a random color
-
newpath()
-
newsubpath()
used for example to make holes in shapes -
closepath()
-
getpath()
get the current path as an array of element types and points -
getpathflat()
get the current path as an array of type/points with curves flattened to lines
-
clip()
turn the current path into a clipping region, masking any graphics outside the path -
clippreserve()
keep the current path, but also use it as a clipping region -
clipreset()
-
text(t, x, y)
draw stringt
atx
/y
, or at 0/0 ifx
/y
omitted -
textcentred(t, x, y)
draw stringt
centred atx
/y
or 0/0 -
textpath(t)
make the stringt
into a graphic path suitable forfill()
,stroke()
... -
textcurve(str, x, y, xc, yc, r)
draw stringstr
on a circular arc of radiusr
centered atxc/yc
starting on a line passing fromxc/yc
throughx/y
-
fontface(fontname)
choose fontfontname
-
fontsize(n)
set font size in points -
textextents(str)
get array of dimensions of the stringstr
, given current font:[xb, yb, width, height, xadvance, yadvance]