Skip to main content

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.

JARU Draw module screenshot for drawing text, lines, rectangles, circles, bitmaps, and sprites

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.

ColorValueExample
Red0xFF0000draw.color = 0xFF0000
Green0x00FF00draw.color = 0x00FF00
Blue0x0000FFdraw.color = 0x0000FF
White0xFFFFFFdraw.color = 0xFFFFFF
Black0x000000draw.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​

WindowsESP32 / ESP32-S3ESP32-P4ESP32-S31
fontChanges nothing: text always comes out with the same fontPicks one of fonts 0 to 8The sameThe same
font errorsThrows the exceptionDoes not throw: writes the error to the serial monitorThe sameThe same
getPixelThe color of what was drawnApproximate: in the double buffer, 256 colorsApproximate: 16 bitsApproximate: 16 bits
clsClears the viewClears the view; in direct mode, the screenClears the viewClears the view
line, circle, polygon and getPixelIn screen coordinates, not view coordinatesView coordinatesView coordinatesView coordinates
bytesStretches the image over the whole windowAt x, yAt x, yAt x, y

The last two rows are bugs of the Windows VM: see Pitfalls.

Functions​

FunctionWhat it doesReturns
cls([color])Clears with a colornil
setPixel(x, y)Draws a pointnil
getPixel(x, y)Reads the color of a pointThe color
line(x1, y1, x2, y2)Draws a linenil
rect(x, y, width, height [, fill])Draws a rectanglenil
circle(x, y, radius [, fill])Draws a circlenil
ellipse(x, y, radiusX, radiusY [, fill])Draws an ellipsenil
polygon(vertices [, fill])Draws a polygontrue
text(x, y, text [, color])Writes a textnil
font() / font(n)Reads or changes the text fontThe font / true
bitmap(x, y, bitmap [, angle])Draws an image, with its transparent colornil
bitmapOpaque(x, y, bitmap [, angle])Draws an image without a transparent colornil
bitmapBatch(bitmap, positions)Draws an image in many placesnil
gridBitmap(grid, value, bitmap, x0, y0, stepX, stepY)Draws an image on the cells of a GridMap that have a valuenil
bytes(x, y, width, height, bytes, palette)Draws a Bytes block with a palettenil
sprite(sprite)Draws a sprite, or a list or an array of spritesnil
tilemap(grid [, tileset [, empty]])Draws a tilemapnil
scene(scene)Draws a scene: its map and its spritesnil

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 an update(), 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 0x123456 reads as 0x103452.
  • 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)
ParameterDescription
x1, y1The start point
x2, y2The 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])
ParameterTypeDescription
x, ynumberThe top left corner
widthnumberThe width
heightnumberThe height
filltrue / falsetrue 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])
ParameterTypeDescription
x, ynumberThe center
radiusnumberThe radius
filltrue / falsetrue 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])
ParameterTypeDescription
x, ynumberThe center
radiusX, radiusYnumberThe horizontal and vertical radii
filltrue / falsetrue 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])
ParameterTypeDescription
verticeslistThe coordinates, in order: [x1, y1, x2, y2, x3, y3, ...]
filltrue / falsetrue 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)
info

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])
ParameterDescription
x, yThe top left corner of the text
textA string
colorThe 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())
  • text has to be a string: with a number it writes nothing. Turn it into a string with toString().
  • 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. Keep draw.text for 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)
FontDescriptionHeightCharacters
0Small font (6x8 bitmap)8 pxFull ASCII
1Default font (alias of 0)8 pxFull ASCII
2Medium font16 px96 ASCII characters
4Large font26 px96 ASCII characters
6Clock font48 px0-9 : - . a p m
77-segment font48 px0-9 : - .
8Huge font75 px0-9 : - .
Valid range

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.

Fonts 6, 7 and 8

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])
ParameterDescription
x, yThe top left corner
bitmapThe image, from Bitmap.load
angleDegrees 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)
ParameterDescription
bitmapThe image
positionsAn 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.load or tcolor), as in draw.bitmap.
Performance

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)
ParameterDescription
gridThe GridMap
valueAn integer from 0 to 255: the value the cell must have
bitmapThe image drawn on those cells
x0, y0Where cell (0, 0) goes, in the view
stepX, stepYThe 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)
ParameterDescription
x, yThe top left corner
width, heightThe size of the image in pixels
bytesObjectA Bytes object with one byte per pixel, row by row: the number of the color in the palette
paletteAn 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, width and height have 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 value ERR_TYPE_MISMATCH, and if the Bytes object has fewer than width × height bytes, ERR_INDEX_OUT_OF_RANGE. In both cases it draws nothing.
On Windows, the image fills the whole window

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)
ParameterDescription
spriteA sprite
spriteListA 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 = true and at least one frame. In a list, it skips what is not a sprite.
Animations

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]])
ParameterDescription
gridThe GridMap, with the tile size set (grid.tSize(width, height))
tilesetThe image with the tiles (optional if the map has one in grid.tileset)
emptyThe 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.x and grid.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 wrapX or wrapY, 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.load or tcolor).
  • On Windows, the whole map is kept already drawn, and it is drawn again when a cell changes with set.
Scrolling

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)
ParameterDescription
sceneA 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 throws ERR_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.
tip

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​

PropertyTypeDefaultRead / write
colorinteger 0xRRGGBB0xFFFFFF (white)Both
colorBGinteger 0xRRGGBB0x000000 (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, polygon and getPixel do not count from the view. They use screen coordinates. With a view smaller than the screen (Display.viewWidth), they come out shifted compared with rect, ellipse, text and images. Without a view, or with the view the size of the screen, it does not show.
  • On Windows, draw.bytes stretches the image over the whole window (see bytes).
  • On Windows, draw.tilemap does not see what grid.clear() changes. It keeps drawing the old map until the next grid.set(). After a clear, do a set of any cell.
  • The order of the sprites in draw.scene changes 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.