Skip to content

Drawing

All drawing goes through the gfx table and lands on a canvas the size of the panel. The canvas is copied to the LEDs after _draw returns.

Call What it draws
gfx.cls(r, g, b) Fills the canvas. gfx.cls() with no arguments fills with black
gfx.pset(x, y, r, g, b) One pixel
gfx.rectfill(x, y, w, h, r, g, b) A filled rectangle with its top-left corner at x, y
gfx.line(x0, y0, x1, y1, r, g, b) A line between two points, one pixel wide
gfx.width(), gfx.height() The panel size in pixels
  • Coordinates count from the top-left pixel, (0, 0). x grows to the right and y grows downwards. The bottom-right pixel is (gfx.width() - 1, gfx.height() - 1).
  • Colours are three numbers from 0 to 255: red, green, blue. Values outside that range are clamped.
  • Anything off the canvas is ignored. You can draw a rectangle that hangs over the edge, or a pixel at (-5, 200), without an error.
  • Coordinates are whole pixels. Pass math.floor(x) when your position is a fraction, so a moving shape does not shimmer.

Later calls draw over earlier ones. Clear first, then draw back to front:

function _draw()
gfx.cls(10, 10, 30) -- sky
gfx.rectfill(0, 48, 64, 16, 20, 90, 30) -- ground
gfx.rectfill(20, 30, 12, 18, 150, 80, 40) -- house
gfx.line(20, 30, 26, 22, 200, 60, 60) -- roof
gfx.line(26, 22, 32, 30, 200, 60, 60)
end

There is no sprite call; a sprite is a loop over a table of rows. Strings make compact rows:

local SHIP = {
"..#..",
".###.",
"#####",
"#.#.#",
}
local function sprite(rows, left, top, r, g, b)
for row, line in ipairs(rows) do
for column = 1, #line do
if line:sub(column, column) == "#" then
gfx.pset(left + column - 1, top + row - 1, r, g, b)
end
end
end
end
function _draw()
gfx.cls()
sprite(SHIP, 30, 50, 120, 220, 255)
end

If you set the same colour many times, pack it once and use gfx.pset565:

local white = gfx.rgb(255, 255, 255) -- one number, in the panel's own format
function _draw()
gfx.cls()
for x = 0, gfx.width() - 1, 2 do
gfx.pset565(x, 10, white)
end
end

gfx.rgb(r, g, b) returns the colour as a 16-bit number (5 bits of red, 6 of green, 5 of blue). That is also the precision of the canvas: neighbouring shades that differ by less than 8 in red or blue, or 4 in green, come out the same.

Setting every pixel with gfx.pset in a Lua loop works, and for simple patterns it is fast enough:

local t = 0
function _update(dt)
t = t + dt
end
function _draw()
local shift = math.floor(t * 40)
for y = 0, gfx.height() - 1 do
for x = 0, gfx.width() - 1 do
gfx.pset(x, y, (x * 4 + shift) & 255, (y * 4) & 255, 128)
end
end
end

When every pixel needs real math, a fragment shader is the better tool, and a compiled one is much faster than this loop.

Next: Animation and uniforms.