  The canvas functions
  Alpha blending
  Using clips
  Affine transformations

The canvas functions

The canvas is a rectangular region of pixels that can be displayed in the viewport. Using the following functions, Lua scripts have total control over what the canvas looks like.

  blend copy create cursor delete ellipse fill |  | flood font getpixel getsize getxy line linewidth |  | load optimize paste position replace resize rgba |  | save scale setpixel sound target text textalign |  | textback transform update

The examples given below assume a script has started with these lines:

  local glu = glu()
  local cv = canvas()
  local cp = require "cplus"

blend(i)
  Set alpha blending off (i = 0), full (i = 1) or fast (i = 2) and return the previous blend state. The fast mode should only be used when the target pixels are opaque. The functions affected by alpha blending are: ellipse, fill, flood, line, paste, scale and setpixel. Note that setting alpha blending to full or fast also forces ellipses and lines to be antialiased.

  Example: local oldblend = cv.blend(1)

copy(x, y, wd, ht, clipname)
  Copy the pixels in the given rectangle from the current render target into the named clip for later use in a function like paste or scale. If the given width or height is less than 1 then it is treated as an inset from the render target's current width or height (this makes it easy to copy the entire render target with a call like copy(0,0,0,0,"all")). Any parts of the rectangle outside the render target are filled with transparent pixels. The clip name can be any non-empty string.

  Example: cv.copy(0, 0, 100, 200, "tempbox")

create(wd, ht, clipname)
  Create a clip with the given name, width and height. The clip name can be any non-empty string. If no clipname is supplied then create a canvas with the given width and height. If the given width or height is less than 1 then it is treated as an inset from the viewport's width or height. If no arguments are supplied then create a canvas with the size of the viewport. All pixels are initially transparent (their RGBA values are set to 0,0,0,0). When creating a canvas the following are also set:

  The initial RGBA values used by later drawing functions are set to 255,255,255,255 (opaque white). The canvas's initial position is set to "topleft", the cursor is set to the standard arrow, the transform values are set to 1,0,0,1 (identity) and alpha blending is off. The initial font for drawing text is the default system font at a size of 10pt. The text alignment for multi-line text is set to "left" and the text background is set to transparent (0,0,0,0). The initial line width (used by the ellipse and line functions) is set to 1. The render target is set to be the canvas.

  Examples:
  cv.create() -- create a canvas with size of viewport
  cv.create(-2, 0) -- ditto but 2 pixels narrower
  cv.create(400, 300) -- create a 400x300 canvas
  cv.create(400, 300, "myclip") -- create a 400x300 clip
  cv.create(0, 0, "myclip") -- create a clip with size of viewport

cursor(name)
  Specify which cursor to use when the mouse moves over a non-transparent pixel within the canvas. The valid cursor names are:

  arrow |  | – standard arrow
  cross |  | – crosshairs
  hand |  | – hand
  hidden |  | – invisible cursor
  pencil |  | – pencil
  pick |  | – color picker
  wait |  | – a lengthy task is in progress
  zoomin |  | – magnifying glass with "+"
  zoomout |  | – magnifying glass with "-"

  The previous cursor name is returned.

  Example: local oldcursor = cv.cursor("pencil")

delete(clipname)
  Delete the canvas if no clip name is supplied, otherwise delete the named clip. Note that it is an error to delete a clip that is the current render target. Deleting the canvas will stop any sound that is playing.

  Examples:
  cv.delete("myclip") -- delete the given clip
  cv.delete() -- delete the canvas

ellipse(x, y, wd, ht)
  Draw an ellipse inside the given rectangle using the current rgba values onto the render target. Any parts of the ellipse outside the render target are automatically clipped. A width or height less than 1 is relative to the render target's width or height. If the width equals the height (and both are positive) then the result is a circle. If alpha blending is turned on then the edges of the ellipse are antialiased. The linewidth function determines the thickness of the ellipse.

  Example: cv.ellipse(10, 10, 60, 40)

fill(x, y, wd, ht)
  Fill the given rectangle in the render target with the current rgba values. Any parts of the rectangle outside the render target are automatically clipped. If no rectangle is given then the entire render target is filled. A width or height less than 1 is relative to the render target's width or height, so a call like cv.fill(1,1,-2,-2) would fill all of the render target except for a 1 pixel border around the edges.

  Examples:
  cv.fill(10, 10, 30, 50)
  cv.fill() -- equivalent to cv.fill(0,0,0,0)

flood(x, y)
  Flood a connected region of pixels in the render target that match the given starting pixel with the current rgba values.

  Example: cv.flood(10, 20)

font(fontname, fontsize)
  Set the font used by later text calls. If the font name is "" then only the font size will be changed. The font size must be from 1 to 1000. The valid font names (apart from an empty string) are:

  default |  | – standard system font
  default-bold |  | – bold system font
  default-italic |  | – italic system font
  mono |  | – mono-spaced font
  mono-bold |  | – bold mono-spaced font
  mono-italic |  | – italic mono-spaced font
  roman |  | – roman font
  roman-bold |  | – bold roman font
  roman-italic |  | – italic roman font

  The previous font is returned as a string and an integer.

  Examples:
  local oldfont, oldsize = cv.font("roman-bold", 15)
  local oldfont, oldsize = cv.font("", 20) -- change the size of the current font

getpixel(x, y)
  Return the RGBA values of the given pixel in the current render target. If the pixel is inside the render target then all values are from 0 to 255, otherwise -1 is returned for each component.

  Example: local R,G,B,A = cv.getpixel(10, 20)

getsize(name)
  Return the width and height of a clip if the given name is a non-empty string, or the canvas if the name is empty, or the current render target if no name is supplied.

  Examples:
  local clipwd, clipht = cv.getsize("myclip")
  local canvaswd, canvasht = cv.getsize("")
  local targetwd, targetht = cv.getsize()

getxy()
  Return the x,y pixel coordinates of the current cursor position within the canvas. If the cursor is outside the canvas then -1,-1 is returned. The top left pixel in the canvas is 0,0; x values increase to the right and y values increase downwards.

  Example: local x, y = cv.getxy()

line(x1, y1, x2, y2, ... xn, yn)
  Draw one or more connected lines between the supplied coordinates x1,y1 to xn,yn on the render target using the current rgba values. Any pixels outside the render target are automatically clipped. If alpha blending is turned on then the lines are antialiased. The linewidth function sets the thickness of the lines.

  Example:
  -- draw triangle with vertices at (0,0), (100,0) and (50,100)
  cv.line(0, 0, 100, 0, 50, 100, 0, 0)

linewidth(value)
  Set the line width used by the ellipse and line functions. The given value must be an integer from 1 to 1000. The previous line width is returned.

  Example: local oldwidth = cv.linewidth(3)

load(file, clipname)
  Load the given BMP/GIF/PNG/TIFF file into the named clip (a non-empty string). Returns the size of the image as 2 integers: width, height.

  Example: local imgwd, imght = cv.load("foo.png", "imgclip")

optimize(clipname)
  Optimize the named clip for improved paste performance when using alpha blending. Returns the bounding box of all the non-zero alpha pixels within the clip as 4 integers: x, y, width, height. A clip that is modified after optimization must be optimized again before use to get the performance benefit. Especially effective for text rendering and used automatically by the cp.maketext function.

  Example: local x, y, w, h = cv.optimize("textclip")

paste(clipname, x, y)
  Paste the named clip (created by an earlier create, copy, load or text call) into the render target at the given location. Any pixels outside the render target are automatically clipped.

  Example: cv.paste("tempbox", 10, 20)

position(pos)
  Specify where the canvas is to be displayed within the viewport. The valid positions are:

  topleft |  | – top left corner
  topright |  | – top right corner
  bottomright |  | – bottom right corner
  bottomleft |  | – bottom left corner
  middle |  | – middle of viewport
  middletop |  | – middle of top edge
  middleright |  | – middle of right edge
  middlebottom |  | – middle of bottom edge
  middleleft |  | – middle of left edge

  Example: cv.position("middle")

replace("red green blue alpha")
  Replace pixels that match the given color components in the render target with the current rgba values. The given string must contain 4 components separated by single spaces. Returns the number of pixels replaced.

  Example:
  -- replace opaque black pixels with semi-transparent blue pixels in clip named 'myclip'
  local oldtarget = cv.target("myclip")
  cv.rgba(0, 0, 255, 128)
  local replaced = cv.replace("0 0 0 255")
  cv.target(oldtarget)

  There are two special characters that can be used in the color specification for more advanced matches: ! and *. A color component may be specified as the wildcard * which means match any value for that component.

  Example:
  -- replace any opaque pixel with opaque blue
  cv.rgba(cp.blue)
  local replaced = cv.replace("* * * 255")

  If the match specification is prefixed with ! then pixels that don't match the specification are replaced.

  Example:
  -- replace any pixel that isn't white with yellow
  cv.rgba(cp.yellow)
  local replaced = cv.replace("!255 255 255 255")

  Alternatively if the alpha specification is prefixed with ! then pixels that don't have the specified alpha value are replaced.

  Example:
  -- replace any non-opaque pixel with opaque blue
  cv.rgba(cp.blue)
  local replaced = cv.replace("* * * !255")

  Normally when a match is found the clip pixel is replaced with the current rgba values. This can be overridden by postfixing one or more color components with r, g, b, a or #. Each color component in the clip pixel can either be left unchanged (in the case of #) or replaced with the specified color component from the clip pixel.

  Examples:
  -- make transparent pixels opaque (r g b 0 -> r g b 255)
  cv.rgba(0, 0, 0, 255)
  local replaced = cv.replace("*# *# *# 0")

  -- swap the red and green components (r g b a -> g r b a)
  local replaced = cv.replace("*g *r *# *#")

  -- set the pixels to opaque gray based on the alpha level (r g b a -> a a a 255)
  cv.rgba(0, 0, 0, 255)
  local replaced = cv.replace("*a *a *a *")

  If the postfix character is followed by - then the component value is inverted.

  Examples:
  -- invert the r g b components but leave alpha unchanged (r g b a -> 255-r 255-g 255-b a)
  local replaced = cv.replace("*#- *#- *#- *#")

  -- make transparent pixels opaque and vice versa (r g b a -> r g b 255-a)
  local replaced = cv.replace("*# *# *# *#-")

  Color components can also be adjusted by using -- to decrement, ++ to increment, -value to subtract a constant or +value to add a constant. Component values are clamped to the range 0 to 255.

  Examples:
  -- increase the brightness of all pixels
  local replaced = cv.replace("*#+50 *#+50 *#+50 *#")

  -- fade all pixels to black
  while cv.replace("*#-- *#-- *#-- *#") > 0 do cv.update() end

  The postfixes can also be used without a target component. In this case the current rgba values are adjusted.

  Example:
  -- replace green pixels with the current drawing color minus 32 alpha (0 255 0 255 -> 255 0 0 96)
  cv.rgba(255, 0, 0, 128)
  local replaced = cv.replace("0 255 0 255-32")

resize(wd, ht, clipname)
  Resize the named clip to the given width and height. The clip name can be any non-empty string. If no clipname is supplied then resize the current canvas to the given width and height. A width or height less than 1 is treated as an inset from the viewport's width or height. If no arguments are supplied then resize the canvas to the size of the viewport. The previous width and height are returned. This function is typically used if a window resize is detected and you wish to keep the canvas covering the entire viewport. Note that all pixels in the resized canvas/clip become transparent (their RGBA values are reset to 0,0,0,0).

  Examples:
  local oldwd, oldht = cv.resize()
  local oldwd, oldht = cv.resize(1000, 800)
  local oldwd, oldht = cv.resize(300, 240, "myclip")
  local oldwd, oldht = cv.resize(0, 0, "myclip")

rgba(red, green, blue, alpha) or
rgba{red, green, blue, alpha}
  Set the current RGBA values used by later drawing functions and return the old values in a table. The RGBA values can be supplied as 4 integers, or as a table with 4 integers. All values must be from 0 to 255. The functions that use the current RGBA values are: ellipse, fill, flood, line, replace, setpixel and text.

  Example: local oldrgba = cv.rgba(255, 0, 0, 255)

save(x, y, wd, ht, file)
  Save the pixels in the given rectangle of the render target in the given PNG file. An error will occur if any part of the rectangle is outside the render target. If the given width or height is less than 1 then it is treated as an inset from the render target's current width or height (this makes it easy to save the entire render target with a call like save(0,0,0,0,"canvas.png")).

  Example: cv.save(0, 0, 100, 80, glu.getdir("temp").."foo.png")

setpixel(x, y)
  Set the specified pixel in the render target to the current rgba values. A pixel outside the render target is silently ignored.

  Example: cv.setpixel(10, 20)

scale(clipname, x, y, wd, ht, quality)
  Paste the named clip into the current render target, scaling it so it fits into the specified rectangle. Any pixels outside the render target are automatically clipped. The given quality must be one of these strings:

  best |  | – use a high quality, but slow, scaling algorithm
  fast |  | – use a fast, but lower quality, scaling algorithm

  Example: cv.scale("imgclip", 10, 10, 100, 200, "best")

sound(command, soundfile, level)
  Controls playback of audio files. If present the soundfile argument must point to a WAV or OGG format file containing the sound to be played. If the volume level is supplied it must be a number from 0.0 (silent) to 1.0 (maximum). Multiple sounds can be played simultaneously.

  If the function is invoked with no arguments then it returns an integer indicating whether sound is available:

  0 |  | – sound support is not available
  1 |  | – sound support is available but failed to initialize
  2 |  | – sound support is available and ready to use

  There are seven sound commands:

  play soundfile (level)
  Play the named soundfile at volume level asynchronously and return immediately. The volume is set to maximum level (1.0) if not specified.
  loop soundfile (level)
  Play the named soundfile at volume level asynchronously and loop until the stop command is used.
  stop (soundfile)
  Stop all sound playback or just the specified soundfile.
  pause (soundfile) |
  Pause all sound playback or just the specified soundfile.
  resume (soundfile)
  Resume all sound playback or just the specified soundfile.
  volume soundfile level
  Set the named soundfile volume level from 0.0 (silent) to 1.0 (maximum). This is typically used to change the volume of a sound that is already playing.
  state (soundfile)
  Returns the playback state of "playing" if any sound (or the specified sound) is playing, "paused" if the specified sound is paused or "stopped" otherwise.

  Notes:

  - Deleting the canvas will stop any sounds that are playing.
  - The state command will return "unknown" if the specified sound is not found.
  - If sound support is not available or failed to initialize then the sound commands will silently do nothing.

  Examples:
  cv.sound("play", "beep.wav", 0.5) -- play beep.wav at half volume
  cv.sound("play", "beep.wav") -- play beep.wav at full volume
  cv.sound("loop", "background.ogg") -- play background.ogg in a loop at full volume
  cv.sound("volume", "background.ogg", 0.7) -- set background.ogg volume to 0.7
  cv.sound("pause", "background.ogg") -- pause playback of background.ogg
  cv.sound("resume") -- resume playback of all paused sounds
  cv.sound("stop") -- stop all sounds playing

target (clipname)
  Set the render target to the named clip or to the canvas if no clip is specified. All subsequent drawing functions will use the new render target. Returns the previous target as a string (an empty string if it was the canvas). It is an error to attempt to delete a clip while it is the render target. The functions that use the render target are: copy (as the source), ellipse, fill, flood, getpixel (as the source), line, paste, replace, save (as the source), scale and setpixel.

  Example: local oldtarget = cv.target("myclip")

text(clipname, "one or more lines of text ...")
  Create a named clip containing the given text in the current font (as set by the most recent font call). Return the dimensions of the clip as 3 integers: width, height, descent. The descent is the number of pixels under the baseline (useful if you want to place text with different sized fonts on the same line).

  The text's color depends on the most recent rgba call. The text background color can be set with the textback function. By default the text has a transparent background, so you'll probably need to set blend to 1 before drawing the text with paste. If you specify an opaque background then there's no need to turn on alpha blending when doing the paste, so drawing such text will be significantly faster. You can also specify the alignment of multi-line text with the textalign function.

  Example: local w, h, d = cv.text("tempclip", "First line.\nSecond line.")

textalign(option)
  Set the alignment of multi-line text for future text calls and return the previous option. The valid alignment options are "left", "right" or "center". The create function initializes the alignment to "left".

  Example: local oldalign = cv.textalign("right")

textback(red, green, blue, alpha) or
textback{red, green, blue, alpha}
  Set the text background for future text calls. The RGBA values can be supplied as 4 integers, or as a table with 4 integers. All values must be from 0 to 255. The previous background is returned as a table with 4 integers. The create function initializes the text background to be transparent (0,0,0,0).

  Example: local oldbg = cv.textback(255, 0, 0, 255)

transform(axx, axy, ayx, ayy) or
transform{axx, axy, ayx, ayy}
  Set the affine transformation values used by later paste calls and return the old values in a table with 4 integers. The transform values can be supplied as 4 integers, or as a table with 4 integers. The allowed values are 0, 1 or -1.

  Example: local oldt = cv.transform(0, -1, 1, 0)

update()
  Tell Glu to update the canvas without drawing the viewport's background. This will be faster than calling glu.update() but should only be used in cases where the canvas covers the entire viewport and all pixels in the canvas are opaque.

  Example: cv.update()

Alpha blending

Alpha blending is a way of drawing translucent pixels on top of existing pixels to get nicer looking results. When a canvas is created, alpha blending is initially turned off. To turn it on a script needs to call blend(1) for blending with any target, or blend(2) for faster blending when you know the target pixels are opaque. Consider this simple script which draws a translucent blue square on top of a translucent red square (both on top of an opaque white background):

  local glu = glu() local cv = canvas() cv.create(100, 80) cv.fill() cv.blend(1) cv.rgba(255, 0, 0, 128) cv.fill(10, 10, 50, 50) cv.rgba(0, 0, 255, 128) cv.fill(30, 20, 50, 50) |  |
  blend(1)
  blend(0)

The left image shows the resulting canvas. The right image shows the canvas produced by the same script but with the cv.blend(1) line removed (note that the square colors will depend on the viewport background color — dark gray in the above example).

Drawing with alpha blending on is significantly slower than drawing with it off, so it's a good idea to turn it on only when necessary. The canvas functions that can do alpha blending are: ellipse, fill, flood, line, paste, scale, setpixel.

Using clips

Scripts can use multiple "clipboards" called clips. A clip is simply a chunk of memory containing a rectangular area of pixels. Each clip has a unique name which can be any non-empty string of characters. Clips are created using these functions: create, copy, load or text. Clips are drawn using paste or scale. Clips can be the render target for any of the drawing functions.

The following script illustrates the use of clips. It creates a tiled background pattern on which some translucent text is drawn. The image on the right shows the resulting canvas.

  local glu = glu() local cv = canvas() cv.create(199, 177) cv.rgba(255, 255, 0, 255) cv.fill() -- create the tile cv.rgba(0, 0, 0, 255) cv.line(10, 0, 0, 20) cv.line(10, 0, 20, 20) cv.line(0, 20, 20, 20) cv.rgba(200, 200, 255, 255) cv.flood(10, 10) cv.copy(0, 0, 21, 21, "mytile") -- tile the canvas for y = 0, 177, 21 do for x = 0, 199, 21 do cv.paste("mytile", x, y) end end -- create translucent red text cv.rgba(255, 0, 0, 180) cv.font("roman-bold", 80) cv.text("mytext", "Glu") cv.blend(1) cv.paste("mytext", 10, 10) |  |

Affine transformations

The transform function allows a limited set of affine transformations, namely rotation by multiples of 90 degrees and reflection about the x, y or diagonal axes. Only the paste function is affected by the current transform settings.

The following script shows how text can be rotated or reflected. Note that the cv.transform calls use pre-defined tables from the cplus module. The image on the right shows the resulting canvas.

  local glu = glu() local cv = canvas() local cp = require "cplus" cv.create(200, 380) cv.fill() cv.blend(1) cv.font("default-bold", 12) -- do rotations cv.rgba(0, 0, 255, 255) cv.text("temp", "ROTATE") cv.setpixel(100, 100) cv.paste("temp", 100, 100) cv.transform(cp.rcw) cv.paste("temp", 100, 100) cv.transform(cp.racw) cv.paste("temp", 100, 100) cv.transform(cp.r180) cv.paste("temp", 100, 100) -- do reflections cv.rgba(255, 0, 0, 255) cv.transform(cp.identity) cv.text("temp", "-- REFLECT") cv.setpixel(100, 270) cv.paste("temp", 100, 270) cv.transform(cp.flip_x) cv.paste("temp", 100, 270) cv.transform(cp.flip_y) cv.paste("temp", 100, 270) cv.transform(cp.swap_xy) cv.paste("temp", 100, 270) cv.transform(cp.swap_xy_flip) cv.paste("temp", 100, 270) |  |