Draw module
Draw draws on the screen that Display opens: points, lines, shapes, text, images, sprites, tilemaps and scenes. It draws on the frame that Display.update() presents, in view coordinates: (0, 0) is the view's top left corner.

use Display
Display.open(320, 240)
var draw = Display.draw // the Draw module
draw.color = 0xFF0000
draw.circle(100, 100, 50)
You also have it with use Draw, as Draw.circle(…): it is the same module.
Colors
A color is an integer 0xRRGGBB: red, green and blue, from 00 to FF each. Shapes, lines and text are drawn with the color in draw.color.
| Color | Value | Example |
|---|---|---|
| Red | 0xFF0000 | draw.color = 0xFF0000 |
| Green | 0x00FF00 | draw.color = 0x00FF00 |
| Blue | 0x0000FF | draw.color = 0x0000FF |
| White | 0xFFFFFF | draw.color = 0xFFFFFF |
| Black | 0x000000 | draw.color = 0x000000 |
You can also build it with rgb(r, g, b) (see Standard library). On a board, the screen does not have all those colors: each one becomes the closest one it can show (Memory and limits).
Platforms
| Windows | ESP32 / ESP32-S3 | ESP32-P4 | ESP32-S31 | |
|---|---|---|---|---|
font | Changes nothing: text always comes out with the same font | Picks one of fonts 0 to 8 | The same | The same |
font errors | Throws the exception | Does not throw: writes the error to the serial monitor | The same | The same |
getPixel | The color of what was drawn | Approximate: in the double buffer, 256 colors | Approximate: 16 bits | Approximate: 16 bits |
cls | Clears the view | Clears the view; in direct mode, the screen | Clears the view | Clears the view |
line, circle, polygon and getPixel | In screen coordinates, not view coordinates | View coordinates | View coordinates | View coordinates |
bytes | Stretches the image over the whole window | At x, y | At x, y | At x, y |
The last two rows are bugs of the Windows VM: see Pitfalls.
Functions
| Function | What it does | Returns |
|---|---|---|
cls([color]) | Clears with a color | nil |
setPixel(x, y) | Draws a point | nil |
getPixel(x, y) | Reads the color of a point | The color |
line(x1, y1, x2, y2) | Draws a line | nil |
rect(x, y, width, height [, fill]) | Draws a rectangle | nil |
circle(x, y, radius [, fill]) | Draws a circle | nil |
ellipse(x, y, radiusX, radiusY [, fill]) | Draws an ellipse | nil |
polygon(vertices [, fill]) | Draws a polygon | true |
text(x, y, text [, color]) | Writes a text | nil |
font() / font(n) | Reads or changes the text font | The font / true |
bitmap(x, y, bitmap [, angle]) | Draws an image, with its transparent color | nil |
bitmapOpaque(x, y, bitmap [, angle]) | Draws an image without a transparent color | nil |
bitmapBatch(bitmap, positions) | Draws an image in many places | nil |
gridBitmap(grid, value, bitmap, x0, y0, stepX, stepY) | Draws an image on the cells of a GridMap that have a value | nil |
bytes(x, y, width, height, bytes, palette) | Draws a Bytes block with a palette | nil |
sprite(sprite) | Draws a sprite, or a list or an array of sprites | nil |
tilemap(grid [, tileset [, empty]]) | Draws a tilemap | nil |
scene(scene) | Draws a scene: its map and its sprites | nil |
With an argument of another type, the Draw functions do not throw an exception: they return an error value and draw nothing (see Errors and exceptions). The ones that do throw are tilemap, scene and, on Windows, font.
cls
Clears with a color, or with colorBG if you give it none.
draw.cls() // clears with colorBG
draw.cls(0x000000) // clears with black
In double buffer mode it is not needed: Display.update() already starts each frame by clearing the view with colorBG (autoClear). It clears only the view: what is outside, like a loadTFT frame, stays. On the ESP32 and the S3, in direct mode, it clears the whole screen. The color has to be an integer.
setPixel
Draws a point with the color in draw.color.
draw.setPixel(100, 50) // a point at (100, 50)
draw.setPixel([150, 75]) // also with a list [x, y]
getPixel
Reads the color of a point of the frame you are drawing, as an integer 0xRRGGBB.
draw.color = 0xFF0000
draw.setPixel(100, 50)
println(draw.getPixel(100, 50).toHex()) // FF0000
- It reads the frame that is not shown yet: the one the next
update()will present. Right after anupdate(), it reads the new frame, already cleared. - On Windows it gives the color of what was drawn. Images are stored in 16 bits, so the color of an image comes back approximate: a
0x123456reads as0x103452. - On a board, the color is approximate: the double buffer of the ESP32 and the S3 has 256 colors, and that of the P4 and the S31, 16 bits per pixel.
- On Windows it counts from the corner of the screen, not of the view (Pitfalls).
line
Draws a line between two points with the current color.
draw.line(x1, y1, x2, y2)
| Parameter | Description |
|---|---|
x1, y1 | The start point |
x2, y2 | The end point |
draw.color = 0xFFFFFF
draw.line(0, 0, 319, 239) // White diagonal
draw.line(160, 0, 160, 240) // Vertical line
rect
Draws a rectangle, outlined or filled, with the current color.
draw.rect(x, y, width, height [, fill])
| Parameter | Type | Description |
|---|---|---|
x, y | number | The top left corner |
width | number | The width |
height | number | The height |
fill | true / false | true for filled, false for outline (optional, false by default) |
draw.color = 0xFFFF00
draw.rect(50, 50, 100, 80) // Yellow outline
draw.rect(50, 50, 100, 80, true) // Yellow filled
Coordinates and sizes take decimals, which are truncated, in this function and in the other shapes. fill has to be true or false: with anything else, it draws nothing.
circle
Draws a circle, outlined or filled, with the current color.
draw.circle(x, y, radius [, fill])
| Parameter | Type | Description |
|---|---|---|
x, y | number | The center |
radius | number | The radius |
fill | true / false | true for filled, false for outline (optional, false by default) |
draw.color = 0x00FF00
draw.circle(160, 120, 50) // Green outline
draw.circle(160, 120, 50, true) // Green filled
ellipse
Draws an ellipse, outlined or filled, with the current color.
draw.ellipse(x, y, radiusX, radiusY [, fill])
| Parameter | Type | Description |
|---|---|---|
x, y | number | The center |
radiusX, radiusY | number | The horizontal and vertical radii |
fill | true / false | true for filled, false for outline (optional, false by default) |
draw.color = 0xFF00FF
draw.ellipse(160, 120, 80, 40) // Magenta outline
draw.ellipse(160, 120, 80, 40, true) // Magenta filled
polygon
Draws a polygon from a list of vertices, outlined or filled.
draw.polygon(vertices [, fill])
| Parameter | Type | Description |
|---|---|---|
vertices | list | The coordinates, in order: [x1, y1, x2, y2, x3, y3, ...] |
fill | true / false | true for filled, false for outline (optional, false by default) |
draw.color = 0xFFFFFF
// Outlined triangle
var vertices = [160, 50, 100, 150, 220, 150]
draw.polygon(vertices)
// Filled pentagon
draw.color = 0xFF8000
var penta = [160, 40, 200, 80, 185, 140, 135, 140, 120, 80]
draw.polygon(penta, true)
The vertices go in a list […], with at least three vertices (six numbers). With an odd number of elements it returns an error value and draws nothing. Filling also works with concave polygons. It is the only shape that returns true.
text
Writes a text on the screen.
draw.text(x, y, text [, color])
| Parameter | Description |
|---|---|
x, y | The top left corner of the text |
text | A string |
color | The text color (optional, uses draw.color if not specified) |
draw.text(10, 10, "Hello World", 0x00FF00)
draw.text(10, 30, "Score: 100") // with draw.color
var points = 250
draw.text(10, 50, "Points: " + points.toString())
texthas to be a string: with a number it writes nothing. Turn it into a string withtoString().- On a board, the font is the one in
draw.font. On Windows, text always comes out with the same font (font/consola.ttf, at size 13), whichever you pick. - It is the most expensive thing in Draw. On Windows, each call draws the text with the TrueType font from scratch: measured in a game on Windows, five short texts took 3.4 ms of a 4.8 ms frame. What changes every frame (points, lives, a clock), draw it with images: a sprite with one frame per digit, or
draw.bitmap. Keepdraw.textfor screens that do not move, such as the title. - With an empty string (
"") it draws nothing.
font
Reads or changes the text font.
// The current font (1, the default)
var f = draw.font()
// Another font
draw.font(2)
| Font | Description | Height | Characters |
|---|---|---|---|
0 | Small font (6x8 bitmap) | 8 px | Full ASCII |
1 | Default font (alias of 0) | 8 px | Full ASCII |
2 | Medium font | 16 px | 96 ASCII characters |
4 | Large font | 26 px | 96 ASCII characters |
6 | Clock font | 48 px | 0-9 : - . a p m |
7 | 7-segment font | 48 px | 0-9 : - . |
8 | Huge font | 75 px | 0-9 : - . |
The range is 0 to 8. Indices 3 and 5 exist for compatibility and are aliases of font 0. With another value, or with something that is not an integer, the font does not change: on Windows it throws ERR_INVALID_VALUE or ERR_TYPE_MISMATCH, and on a board it writes the error to the serial monitor and returns an error value.
They do not contain the alphabet: they are meant for clocks and scoreboards. Any character not in the list is drawn as a space, it does not raise an error. In font 6, the vertical bar | is a narrow space, useful to align the time.
The fonts belong to the boards. On Windows, draw.font changes what draw.font() returns, but text comes out the same with any of them.
bitmap
Draws a Bitmap image, with optional rotation. The image's transparent color is not drawn.
draw.bitmap(x, y, bitmap [, angle])
| Parameter | Description |
|---|---|
x, y | The top left corner |
bitmap | The image, from Bitmap.load |
angle | Degrees of rotation, around the center of the image (optional, 0 by default; decimals are truncated) |
use Display, Bitmap
Display.open(320, 240)
var draw = Display.draw
var img = Bitmap.load("Images/image.bmp")
draw.bitmap(100, 50, img) // Not rotated
draw.bitmap(100, 50, img, 45) // Rotated 45 degrees
The transparent color is the image's: black, unless you gave it another one when loading it with Bitmap.load or with tcolor.
bitmapOpaque
Draws an image like draw.bitmap, but with all its pixels: also those of the transparent color.
draw.bitmapOpaque(x, y, bitmap [, angle])
use Display, Bitmap
Display.open(320, 240)
var draw = Display.draw
var background = Bitmap.load("Images/background.bmp")
draw.bitmapOpaque(0, 0, background) // black is drawn too
It is meant for a background, or for an image with pure black that has to show, without changing its transparent color. The parameters are those of draw.bitmap. On Windows it costs more than draw.bitmap: it prepares the image on every call.
bitmapBatch
Draws the same image in many places with a single call. It is faster than calling draw.bitmap in a loop.
draw.bitmapBatch(bitmap, positions)
| Parameter | Description |
|---|---|
bitmap | The image |
positions | An array ({…}) with the corners: {x0, y0, x1, y1, ...}. With a list ([…]), or with an odd number of elements, it returns an error value and draws nothing |
use Display, Bitmap
Display.open(320, 240)
var draw = Display.draw
var coin = Bitmap.load("Images/coin.bmp")
var pos = {10, 20, 50, 20, 90, 20, 130, 20}
draw.bitmapBatch(coin, pos) // four coins at once
- It does not rotate the images. An element that is not a number is skipped.
- The transparent color is the image's (the one from
Bitmap.loadortcolor), as indraw.bitmap.
bitmapBatch is especially useful on ESP32: it reads the image's pixels only once for all the positions, and it skips those that fall outside the view.
gridBitmap
Draws an image on every cell of a GridMap that has a value. It is meant for drawing the loose things of a map, such as coins.
draw.gridBitmap(grid, value, bitmap, x0, y0, stepX, stepY)
| Parameter | Description |
|---|---|
grid | The GridMap |
value | An integer from 0 to 255: the value the cell must have |
bitmap | The image drawn on those cells |
x0, y0 | Where cell (0, 0) goes, in the view |
stepX, stepY | The distance in pixels from one cell to the next |
use Display, GridMap, Bitmap
Display.open(320, 240)
var draw = Display.draw
var map = GridMap.new(10, 10)
map.set(3, 2, 1)
var wall = Bitmap.load("Images/wall.bmp")
// The "wall" image on every cell with value 1
draw.gridBitmap(map, 1, wall, 0, 0, 16, 16)
It does not use the map's position (x, y): it uses x0 and y0. It goes through the whole map on every call. The transparent color is the image's, on a board too.
bytes
Draws a Bytes block as an image: each byte is a color of the palette.
draw.bytes(x, y, width, height, bytesObject, palette)
| Parameter | Description |
|---|---|
x, y | The top left corner |
width, height | The size of the image in pixels |
bytesObject | A Bytes object with one byte per pixel, row by row: the number of the color in the palette |
palette | An array ({…}) of up to 256 colors 0xRRGGBB |
use Display, Bytes
Display.open(320, 240)
var draw = Display.draw
var palette = {0x000000, 0xFF0000, 0x00FF00, 0x0000FF}
var data = Bytes.new(16) // 4 x 4 pixels
data.fill(1) // all of color 1, red
draw.bytes(100, 100, 4, 4, data, palette)
x,y,widthandheighthave to be integers.- Each byte has to be a color of the palette: a larger byte draws some random color.
- It has no transparent color: it draws every pixel.
- If the palette isn't an array, like a list
[…], it returns the error valueERR_TYPE_MISMATCH, and if the Bytes object has fewer thanwidth × heightbytes,ERR_INDEX_OUT_OF_RANGE. In both cases it draws nothing.
On Windows, draw.bytes does not use x or y: it stretches the image over the whole window. On a board it is drawn in its place and at its size.
sprite
Draws a sprite, or all the sprites of a list or an array.
draw.sprite(sprite)
draw.sprite(spriteList)
draw.sprite(spriteArray)
| Parameter | Description |
|---|---|
sprite | A sprite |
spriteList | A list or an array of sprites |
use Display, Bitmap, Sprite
Display.open(320, 240)
var draw = Display.draw
var s = Sprite.new(Bitmap.load("Images/frame1.bmp"))
s.addImage(Bitmap.load("Images/frame2.bmp"))
s.x = 100
s.y = 100
draw.sprite(s)
- The sprite goes with its pivot at
x,y, rotated (angle) and flipped (flipX,flipY). - It only draws the sprites with
active = trueand at least one frame. In a list, it skips what is not a sprite.
Each time it draws a sprite, draw.sprite advances its animation according to frameRate, loop and yoyo, and its automatic rotation, spin. A sprite that is not drawn is not animated. Draw each sprite only once per frame: the spin rotation is applied on every call.
tilemap
Draws a GridMap as a tilemap: each cell is the number of a tile in the tileset.
draw.tilemap(grid [, tileset [, empty]])
| Parameter | Description |
|---|---|
grid | The GridMap, with the tile size set (grid.tSize(width, height)) |
tileset | The image with the tiles (optional if the map has one in grid.tileset) |
empty | The tile that is not drawn (optional; by default, the one in grid.tEmpty(), 255) |
use Display, GridMap, Bitmap
Display.open(320, 240)
var draw = Display.draw
var map = GridMap.new(20, 15)
map.tSize(16, 16)
map.tileset = Bitmap.load("Images/tiles.bmp")
draw.tilemap(map) // with the map's tileset
draw.tilemap(map, map.tileset, 0) // tile 0 is not drawn either
- The tiles are counted in the tileset from left to right and from top to bottom, starting at 0, with the size in
tSize. The value of each cell is the tile drawn on it. Cells with 255 are never drawn. - The position of the map is
grid.xandgrid.y: where cell(0, 0)goes in the view. To scroll, change them (with negative values the map moves left and up). Only the tiles that fall in the view are drawn. - With
wrapXorwrapY, the map repeats on that axis. - It throws an exception if the tileset or the tile size is missing or the tileset is smaller than a tile (
ERR_INVALID_VALUE), or if an argument is of another type (ERR_TYPE_MISMATCH). - The transparent color is the tileset's (the one from
Bitmap.loadortcolor). - On Windows, the whole map is kept already drawn, and it is drawn again when a cell changes with
set.
With a Scene, draw.scene draws the scene's map with the camera applied: there is no need to move grid.x and grid.y by hand.
scene
Draws a whole scene: first its GridMap (if it has one, with setGridMap) and then its sprites, with the camera applied.
draw.scene(scene)
| Parameter | Description |
|---|---|
scene | A Scene object, from Scene.new() |
use Display, Scene
Display.open(320, 240)
Display.fps = 30
var draw = Display.draw
var level = Scene.new()
// ... the scene's sprites, map and camera
level.update()
draw.scene(level)
Display.update()
- The scene's map has to have its tileset in
grid.tileset: otherwise, it throwsERR_INVALID_VALUE. - It draws the active sprites the camera sees (with a 16 pixel margin), in the order of the scene's list. Those outside are not drawn, and their animation does not advance.
- With something that is not a scene, it throws
ERR_TYPE_MISMATCH.
The scene camera (the one you place with scene.setCamera(x, y) or that follows a sprite with scene.follow(sprite), and that you read with scene.getCamera()) is applied automatically to both the GridMap and all sprites. Only sprites marked as active and visible within the camera area are drawn.
Properties
| Property | Type | Default | Read / write |
|---|---|---|---|
color | integer 0xRRGGBB | 0xFFFFFF (white) | Both |
colorBG | integer 0xRRGGBB | 0x000000 (black) | Both |
Both have to be integers: a value of another type does not change them.
color
The color used to draw points, lines, shapes and text.
// Set color
draw.color = 0xFF0000
// Get current color
var c = draw.color
colorBG
The background color: the one cls() uses without arguments, and the one Display.update() uses to clear each frame (with autoClear).
// Set background color
draw.colorBG = 0x000080
// Get current background color
var bg = draw.colorBG
Complete example
A ball bounces for ten seconds in front of a floor and a star. Each frame starts cleared with colorBG.
use Display
Display.open(320, 240)
Display.fps = 30
var draw = Display.draw
draw.colorBG = 0x000020 // update() clears with this color
var ballX = 160.0
var ballY = 60.0
var vx = 90.0 // pixels per second
var vy = 60.0
var star = [160, 20, 168, 42, 192, 42, 173, 56, 180, 80, 160, 66, 140, 80, 147, 56, 128, 42, 152, 42]
for (var i = 0; i < 300; i++) // 10 seconds
draw.color = 0x228B22
draw.rect(0, 200, 320, 40, true) // the floor
draw.color = 0xFFD700
draw.polygon(star, true)
draw.color = 0xFF4040
draw.circle(ballX, ballY, 12, true) // the ball, filled
draw.color = 0xFFFFFF
draw.circle(ballX, ballY, 12) // and its outline
draw.line(0, 199, 319, 199)
draw.text(8, 8, "Draw", 0xFFFFFF)
Display.update()
ballX = ballX + vx * Display.dt
ballY = ballY + vy * Display.dt
if (ballX < 12 or ballX > 308) then vx = 0 - vx end
if (ballY < 12 or ballY > 188) then vy = 0 - vy end
end
Display.close()
Pitfalls and known issues
- On Windows,
line,circle,polygonandgetPixeldo not count from the view. They use screen coordinates. With a view smaller than the screen (Display.viewWidth), they come out shifted compared withrect,ellipse,textand images. Without a view, or with the view the size of the screen, it does not show. - On Windows,
draw.bytesstretches the image over the whole window (see bytes). - On Windows,
draw.tilemapdoes not see whatgrid.clear()changes. It keeps drawing the old map until the nextgrid.set(). After aclear, do asetof any cell. - The order of the sprites in
draw.scenechanges when one is removed. When a sprite leaves the scene, the last one of the list takes its slot: from then on it is drawn before others it used to cover. Layers, in the scene editor, explains it.
All the known issues of JARU, together, are in Known issues.