Images

Placing images

Luxor lets you place PNG and SVG images on the drawing. First, load an image:

  • for PNG images, use readpng(filename)
  • for SVG images, use readsvg(filename) or readsvg(string)

(JPEGs aren't supported.)

Then use placeimage() to place the image by its top left corner at point pt, or use the centered=true keyword to place the image's center point there. Access the image's dimensions with .width and .height. You can use the centered=true keyword.

img = readpng("assets/figures/julia-logo-mask.png")
w = img.width
h = img.height
rulers()
scale(0.3, 0.3)
rotate(π/4)
placeimage(img, Point(-w/2, -h/2), .5)
sethue("red")
circle(-w/2, -h/2, 15, :fill)

"Images"

PNG images can be placed with varying opacity/transparency/alpha.

readsvg() also lets you supply raw (or pure) SVG code in a string.

Luxor.readpngFunction
readpng(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("/tmp/test-image.png")
w = image.width
h = image.height
Luxor.readsvgFunction
readsvg(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("/tmp/mylogo.svg")
    placeimage(mycoollogo)
end

Placing SVG code:

# from https://github.com/edent/SuperTinyIcons
julialogocode = """<svg xmlns="http://www.w3.org/2000/svg"
    aria-label="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.placeimageFunction
placeimage(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.

placeimage(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.

placeimage(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.

placeimage(matrix::AbstractMatrix{UInt32}, pos=O;
    centered=false)

Place an image matrix on the drawing at pos.

Use keyword centered=true to place the center of the image at the position.

Placing an image matrix

You can also use placeimage() to put image matrices on a drawing.

using Luxor

mat = @imagematrix begin    
    juliacircles(6)
end 40 40

@draw begin
    for i in 1:500
        pos = rand(BoundingBox())
        @layer begin
            translate(pos)
            rotate(rand() * 2π)
            placeimage(mat, centered = rand(Bool))
        end
    end
end

place image matrix

Clipping images

You can clip images. The following script repeatedly places the image using a circle to define a clipping path:

"Images"

using Luxor

width, height = 4000, 4000
margin = 500

fname = "/tmp/test-image.pdf"
Drawing(width, height, fname)
origin()
background("grey25")

setline(5)
sethue("green")

image = readpng(dirname(@__FILE__) * "assets/figures/julia-logo-mask.png")

w = image.width
h = image.height

pagetiles = Tiler(width, height, 7, 9)
tw = pagetiles.tilewidth/2
for (pos, n) in pagetiles
    circle(pos, tw, :stroke)
    circle(pos, tw, :clip)
    gsave()
    translate(pos)
    scale(.95, .95)
    rotate(rand(0.0:π/8:2π))
    placeimage(image, O, centered=true)
    grestore()
    clipreset()
end

finish()

Transforming images

You can transform images by setting the current matrix, either with scale() and rotate() and similar, or by modifying it directly. This code scales and rotates an image made in an earlier chapter of this document around in a circle:

img = readpng(dirname(dirname(pathof(Luxor))) * "/docs/src/assets/figures/textoutlines.png")
w = img.width
h = img.height
for θ in 0:π/6:2π-π/6
    gsave()
    scale(.4, .4)
    rotate(θ)
    translate(300, 0)
    placeimage(img, -w/2, -h/2, 0.9)
    grestore()
end

transforming images

Drawing on images

You sometimes want to draw over images, for example to annotate them with text or vector graphics. The things to be aware of are mostly to do with coordinates and transforms.

In these examples, we'll annotate a PNG file.

image = readpng("assets/figures/julia-logo-mask.png")

w = image.width
h = image.height

# create a drawing surface of the same size

fname = "assets/figures/drawing_on_images.png"
Drawing(w, h, fname)

# place the image on the Drawing - it's positioned by its top/left corner

placeimage(image, 0, 0)

# now you can annotate the image. The (0/0) is at the top left.

sethue("red")
setline(1)
fontsize(16)
circle(Point(150, 50), 2, :fill)
label("(150/50)", :NE, Point(150, 50), leader=true, offset=25)

arrow(Point(w/2, 90), Point(0, 90))
arrow(Point(w/2, 90), Point(w, 90))
text("width $w", Point(w/2, 70), halign=:center)

# to divide up the image into rectangular areas and number them,
# temporarily position the axes at the center:

@layer begin
  setline(0.5)
  sethue("green")
  fontsize(12)
  translate(w/2, h/2)
  tiles = Tiler(w, h, 8, 8, margin=0)
  for (pos, n) in tiles
      box(pos, tiles.tilewidth, tiles.tileheight, :stroke)
      text(string(n-1), pos, halign=:center)
  end
end

drawing on images

Adding text to transformed images

The above approach works well, but suppose you want to locate the working origin at the lower left of the image, i.e. you want all coordinates to be relative to the bottom left corner of the image?

To do this, use translate() and transform() to modify the drawing space:

image = readpng("assets/figures/julia-logo-mask.png")
w = image.width
h = image.height
fname = "assets/figures/drawing_on_images_2.png"
Drawing(w, h, fname)
placeimage(image, 0, 0)

# Move the axes to the bottom:

translate(0, h)

# and reflect in the x-axis

transform([1 0 0 -1 0 0])

# now 0/0 is at the bottom left corner, and 100/100 is up and to the right.

sethue("blue")
arrow(Point(200, 300), Point(160, 300))

# However, don't draw text while flipped, because it will be reversed!

fontsize(20)
sethue("black")
text("Oh no!", Point(30, 250))

# To work around this, define a text function
# that flips the workspace over the x-axis just for the text:

function textoverlay(t, pos; kwargs...)
    @layer begin
        translate(pos)
        transform([1 0 0 -1 0 0])
        text(t, O; kwargs...)
    end
end

textoverlay("a tittle!", Point(200, 300), halign=:left, valign=:middle)
textoverlay("0/0", O)
arrow(Point(130, 400), Point(130, 340))

drawing on transformed images

Image compositing

You should be using Images.jl for most tasks involving image editing. But if you just need to composite images together, you can use the blending modes provided by setmode().

img = readpng("assets/figures/textoutlines.png")
w = img.width
h = img.height

placeimage(img, -w/2, -h/2, .5)
setmode("saturate")
translate(50, 0)
placeimage(img, -w/2, -h/2, .5)

transforming images