paint

Shapes, text and images drawn over the game for one frame. It lives at xiao.paint.


Listeners

Listeners run in the order they were first added. A listener that raises an error is removed and the error is printed.

xiao.paint.listener.add(id: any, callback: function(w: number, h: number))

xiao.paint.listener.add(id: any, callback: function(w: number, h: number))

  • Called every frame with the screen size.
  • Adding an existing id replaces its callback.

xiao.paint.listener.remove(id: any): boolean

xiao.paint.listener.remove(id: any): boolean

  • Returns true if the listener existed.

xiao.paint.listener.exists(id: any): boolean

xiao.paint.listener.exists(id: any): boolean

  • Returns a boolean.

xiao.paint.add(id: any, callback: function(w: number, h: number))

xiao.paint.add(id: any, callback: function(w: number, h: number))

  • Alias of xiao.paint.listener.add.

xiao.paint.remove(id: any): boolean

xiao.paint.remove(id: any): boolean

  • Alias of xiao.paint.listener.remove.

xiao.paint.screen(): number, number

xiao.paint.screen(): number, number

  • Returns w, h.

Points

Any table with numeric x and y fields is accepted. A point that also has a numeric z field is a world position: it is projected to the screen every time it is drawn, and the shape is skipped when the point is behind the camera.

Sizes (size arguments) always use x and y in pixels, even when the origin is a world point.

xiao.paint.point.vector2(x: number, y: number): paint_point

xiao.paint.point.vector2(x: number, y: number): paint_point

  • Returns a screen point {x, y} in pixels.

xiao.paint.point.vector3(x: number, y: number, z: number): paint_point

xiao.paint.point.vector3(x: number, y: number, z: number): paint_point

  • Returns a world point {x, y, z} in blocks.

Colors

Any table with numeric r, g, b fields (0 to 255) and an optional a (default 255) is accepted. Fields can be changed at runtime, for example color.r = 128.

xiao.paint.color.rgb(r: number, g: number, b: number): paint_color

xiao.paint.color.rgb(r: number, g: number, b: number): paint_color

  • Alpha is 255.

xiao.paint.color.rgba(r: number, g: number, b: number, a?: number = 255): paint_color

xiao.paint.color.rgba(r: number, g: number, b: number, a?: number = 255): paint_color

  • Alpha defaults to 255.

xiao.paint.color.hex(hex: string): paint_color

xiao.paint.color.hex(hex: string): paint_color

  • #RRGGBB or #RRGGBBAA, the # is optional.

xiao.paint.color.hsv(h: number, s: number, v: number, a?: number): paint_color

xiao.paint.color.hsv(h: number, s: number, v: number, a?: number): paint_color

  • h in degrees, s and v from 0 to 1, a from 0 to 255.

xiao.paint.color.hsl(h: number, s: number, l: number, a?: number): paint_color

xiao.paint.color.hsl(h: number, s: number, l: number, a?: number): paint_color

  • h in degrees, s and l from 0 to 1, a from 0 to 255.

Shapes

Most shapes end with outline?: paint_color = {0, 0, 0, 255} and outline_thickness?: number = 0. With the default thickness of 0 no outline is drawn.

xiao.paint.line(p1: paint_point, p2: paint_point, color: paint_color, thickness?: number = 1, outline?: paint_color = {0, 0, 0, 255}, outline_thickness?: number = 0)

xiao.paint.line(p1: paint_point, p2: paint_point, color: paint_color, thickness?: number = 1, outline?: paint_color = {0, 0, 0, 255}, outline_thickness?: number = 0)

  • Draws a line between two points.

xiao.paint.circle.filled(center: paint_point, color: paint_color, radius: number, outline?: paint_color = {0, 0, 0, 255}, outline_thickness?: number = 0)

xiao.paint.circle.filled(center: paint_point, color: paint_color, radius: number, outline?: paint_color = {0, 0, 0, 255}, outline_thickness?: number = 0)

  • Draws a filled circle.

xiao.paint.circle.hollow(center: paint_point, color: paint_color, radius: number, thickness?: number = 1, outline?: paint_color = {0, 0, 0, 255}, outline_thickness?: number = 0)

xiao.paint.circle.hollow(center: paint_point, color: paint_color, radius: number, thickness?: number = 1, outline?: paint_color = {0, 0, 0, 255}, outline_thickness?: number = 0)

  • Draws a hollow circle.

xiao.paint.tri.filled(p1: paint_point, p2: paint_point, p3: paint_point, color: paint_color, outline?: paint_color = {0, 0, 0, 255}, outline_thickness?: number = 0)

xiao.paint.tri.filled(p1: paint_point, p2: paint_point, p3: paint_point, color: paint_color, outline?: paint_color = {0, 0, 0, 255}, outline_thickness?: number = 0)

  • Draws a filled triangle.

xiao.paint.tri.hollow(p1: paint_point, p2: paint_point, p3: paint_point, color: paint_color, thickness?: number = 1, outline?: paint_color = {0, 0, 0, 255}, outline_thickness?: number = 0)

xiao.paint.tri.hollow(p1: paint_point, p2: paint_point, p3: paint_point, color: paint_color, thickness?: number = 1, outline?: paint_color = {0, 0, 0, 255}, outline_thickness?: number = 0)

  • Draws a hollow triangle.

xiao.paint.rect.filled(top_left: paint_point, size: paint_point, color: paint_color, outline?: paint_color = {0, 0, 0, 255}, outline_thickness?: number = 0)

xiao.paint.rect.filled(top_left: paint_point, size: paint_point, color: paint_color, outline?: paint_color = {0, 0, 0, 255}, outline_thickness?: number = 0)

  • Draws a filled rectangle.

xiao.paint.rect.hollow(top_left: paint_point, size: paint_point, color: paint_color, thickness?: number = 1, outline?: paint_color = {0, 0, 0, 255}, outline_thickness?: number = 0)

xiao.paint.rect.hollow(top_left: paint_point, size: paint_point, color: paint_color, thickness?: number = 1, outline?: paint_color = {0, 0, 0, 255}, outline_thickness?: number = 0)

  • Draws a hollow rectangle.

xiao.paint.rect.rfilled(rounding: number, top_left: paint_point, size: paint_point, color: paint_color, outline?: paint_color = {0, 0, 0, 255}, outline_thickness?: number = 0)

xiao.paint.rect.rfilled(rounding: number, top_left: paint_point, size: paint_point, color: paint_color, outline?: paint_color = {0, 0, 0, 255}, outline_thickness?: number = 0)

  • Filled rectangle with rounded corners.

xiao.paint.rect.rhollow(rounding: number, top_left: paint_point, size: paint_point, color: paint_color, thickness?: number = 1, outline?: paint_color = {0, 0, 0, 255}, outline_thickness?: number = 0)

xiao.paint.rect.rhollow(rounding: number, top_left: paint_point, size: paint_point, color: paint_color, thickness?: number = 1, outline?: paint_color = {0, 0, 0, 255}, outline_thickness?: number = 0)

  • Hollow rectangle with rounded corners.

xiao.paint.rect.gradient(top_left: paint_point, size: paint_point, up_left: paint_color, up_right: paint_color, bottom_left: paint_color, bottom_right: paint_color, outline?: paint_color = {0, 0, 0, 255}, outline_thickness?: number = 0)

xiao.paint.rect.gradient(top_left: paint_point, size: paint_point, up_left: paint_color, up_right: paint_color, bottom_left: paint_color, bottom_right: paint_color, outline?: paint_color = {0, 0, 0, 255}, outline_thickness?: number = 0)

  • One color per corner.

xiao.paint.qrot.filled(center: paint_point, size: paint_point, color: paint_color, rotation: number, outline?: paint_color = {0, 0, 0, 255}, outline_thickness?: number = 0)

xiao.paint.qrot.filled(center: paint_point, size: paint_point, color: paint_color, rotation: number, outline?: paint_color = {0, 0, 0, 255}, outline_thickness?: number = 0)

  • Centered rectangle, rotation in degrees.

xiao.paint.qrot.hollow(center: paint_point, size: paint_point, color: paint_color, rotation: number, thickness?: number = 1, outline?: paint_color = {0, 0, 0, 255}, outline_thickness?: number = 0)

xiao.paint.qrot.hollow(center: paint_point, size: paint_point, color: paint_color, rotation: number, thickness?: number = 1, outline?: paint_color = {0, 0, 0, 255}, outline_thickness?: number = 0)

  • Centered rectangle, rotation in degrees.

xiao.paint.quad.filled(p1: paint_point, p2: paint_point, p3: paint_point, p4: paint_point, color: paint_color, outline?: paint_color = {0, 0, 0, 255}, outline_thickness?: number = 0)

xiao.paint.quad.filled(p1: paint_point, p2: paint_point, p3: paint_point, p4: paint_point, color: paint_color, outline?: paint_color = {0, 0, 0, 255}, outline_thickness?: number = 0)

  • Draws a filled quad.

xiao.paint.quad.hollow(p1: paint_point, p2: paint_point, p3: paint_point, p4: paint_point, color: paint_color, thickness?: number = 1, outline?: paint_color = {0, 0, 0, 255}, outline_thickness?: number = 0)

xiao.paint.quad.hollow(p1: paint_point, p2: paint_point, p3: paint_point, p4: paint_point, color: paint_color, thickness?: number = 1, outline?: paint_color = {0, 0, 0, 255}, outline_thickness?: number = 0)

  • Draws a hollow quad.

xiao.paint.poly.filled(vertices: paint_point[], color: paint_color, outline?: paint_color = {0, 0, 0, 255}, outline_thickness?: number = 0)

xiao.paint.poly.filled(vertices: paint_point[], color: paint_color, outline?: paint_color = {0, 0, 0, 255}, outline_thickness?: number = 0)

  • 3 to 4096 vertices, concave shapes allowed.

xiao.paint.poly.hollow(vertices: paint_point[], color: paint_color, thickness?: number = 1, outline?: paint_color = {0, 0, 0, 255}, outline_thickness?: number = 0)

xiao.paint.poly.hollow(vertices: paint_point[], color: paint_color, thickness?: number = 1, outline?: paint_color = {0, 0, 0, 255}, outline_thickness?: number = 0)

  • 3 to 4096 vertices.

Text

Text is outlined only when the outline argument is given. Text longer than 1024 bytes is cut.

xiao.paint.text.left(point: paint_point, text: string, color: paint_color, outline?: paint_color)

xiao.paint.text.left(point: paint_point, text: string, color: paint_color, outline?: paint_color)

  • point is the left edge of the text.

xiao.paint.text.center(point: paint_point, text: string, color: paint_color, outline?: paint_color)

xiao.paint.text.center(point: paint_point, text: string, color: paint_color, outline?: paint_color)

  • point is the horizontal center.

xiao.paint.text.right(point: paint_point, text: string, color: paint_color, outline?: paint_color)

xiao.paint.text.right(point: paint_point, text: string, color: paint_color, outline?: paint_color)

  • point is the right edge.

xiao.paint.text.bake(font_name: string, font_data: string, size: number): boolean

xiao.paint.text.bake(font_name: string, font_data: string, size: number): boolean

  • Loads a TTF or OTF font from memory, returns a boolean.
  • A name already baked returns true.

xiao.paint.text.unbake(font_name: string): boolean

xiao.paint.text.unbake(font_name: string): boolean

  • Frees a baked font, returns true if it existed.

xiao.paint.text.isbaked(font_name: string): boolean

xiao.paint.text.isbaked(font_name: string): boolean

  • Returns a boolean, "default" is always baked.

xiao.paint.text.font(font_name?: string): string

xiao.paint.text.font(font_name?: string): string

  • Selects the font used by the next text calls, returns the current font name.

xiao.paint.text.get_size(text: string): table

xiao.paint.text.get_size(text: string): table

  • Returns {width, height} in pixels with the current font.

xiao.paint.text.get_size(font_name: string, text: string): table

xiao.paint.text.get_size(font_name: string, text: string): table

  • Same as above with a given font.
local data = xiao.fs.read("lua/fonts/consola.ttf")
if not xiao.paint.text.bake("Consolas", data, 16) then
    error("could not load the font")
end

local point = xiao.paint.point.vector2(40, 40)
local white = xiao.paint.color.rgb(255, 255, 255)
xiao.paint.add("hello", function(w, h)
    xiao.paint.text.font("Consolas")
    xiao.paint.text.left(point, "hello world", white)
    xiao.paint.text.font("default")
end)

See xiao.fs for xiao.fs.read.


Images

An image is freed automatically once Lua no longer references it, and on Clear VM.

xiao.paint.image.bake(data: string): image | false

xiao.paint.image.bake(data: string): image | false

  • data is a full image file in memory (PNG, JPEG, BMP...).
  • Returns an image or false.

xiao.paint.image.bake(width: number, height: number, rgba: string): image | false

xiao.paint.image.bake(width: number, height: number, rgba: string): image | false

  • rgba holds width * height * 4 bytes of RGBA8 pixels, up to 8192 x 8192.
  • Returns an image or false.

xiao.paint.image.normal(image: image, top_left: paint_point, size: paint_point, color: paint_color, outline?: paint_color)

xiao.paint.image.normal(image: image, top_left: paint_point, size: paint_point, color: paint_color, outline?: paint_color)

  • color tints the image, use white to keep it as is.

xiao.paint.image.round(image: image, rounding: number, top_left: paint_point, size: paint_point, color: paint_color, outline?: paint_color)

xiao.paint.image.round(image: image, rounding: number, top_left: paint_point, size: paint_point, color: paint_color, outline?: paint_color)

  • Image with rounded corners.
local pixel = xiao.paint.image.bake(1, 1, "\255\0\0\255")
local origin = xiao.paint.point.vector2(50, 50)
local size = xiao.paint.point.vector2(64, 64)
local white = xiao.paint.color.rgb(255, 255, 255)
xiao.paint.add("square", function(w, h)
    xiao.paint.image.normal(pixel, origin, size, white)
end)

Types

paint_point

Field Type Description
x number x coordinate (pixels, or blocks for a world point)
y number y coordinate (pixels, or blocks for a world point)
z number optional, makes the point a world position projected to the screen at each draw

paint_color

Field Type Description
r number red, 0 to 255
g number green, 0 to 255
b number blue, 0 to 255
a number optional alpha, default 255

Good practice

Create colors and points once outside the listener and reuse them. Change their fields when they must move.

local paint = xiao.paint
local color = paint.color.rgba(255, 255, 255, 255)
local center = paint.point.vector2(0, 0)
paint.add("pulse", function(w, h)
    color.r = (os.clock() % 1) * 255
    center.x, center.y = w / 2, h / 2
    paint.circle.filled(center, color, 8, paint.color.rgb(0, 0, 0), 1)
end)