paint

An alternative method of creating visuals & graphics on higher level access.
Instead of using in-game drawing systems, this uses an external version on a separated thread.


Optimizing

When you perform drawing objects, its good practice to localize & reuse data.
Try to minimize object creation if you can, paint.point.* may be harder to localize.
This is due to the idea of dynamically changing vectors, which can be a problem but do-able.

paint.add("example", function(w, h)
    -- don't do this, you are creating a new object every time
    local color = paint.color.rgba(255, 255, 255, 255)
end)

-- instead do this
local white = paint.color.rgba(255, 255, 255, 255)
paint.add("example", function(w, h)
    -- access white here, its now an upvalue
end)

-- if you need to change it at runtime
local dynamic = paint.color.rgba(255, 255, 255, 255)
paint.add("example", function(w, h)
    dynamic.r = (os.clock() % 1) * 255
    -- in here we are just changing the color object's "R" stat
end)

Interstate

Paint works everywhere no matter where you call it.
An example of this is by using linker's ability to attach to certain events.
This allows you to have a unique way of inline calls for accurate points.

linker.hook.add("Think", "paint!", function()
    -- perform paint operations here as well
end)

Types

Paint uses two different vector types.
Both types are recognized by their index characteristics.
Which means using internal game-software mechanics like "Vector", would also work.

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

paint.point.vector2(x: number, y: number): paint_point
  • Creates a 2D vector for paint to recognize

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

paint.point.vector3(x: number, y: number, z: number): paint_point
  • Creates a 3D vector for paint to recognize

Paint also uses its own color system due to the fact of interop with other frameworks.

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

paint.color.rgb(r: number, g: number, b: number): paint_color
  • Creates an RGB based color

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

paint.color.rgba(r: number, g: number, b: number, a?: number): paint_color
  • Creates an RGBA based color

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

paint.color.hex(hex: string): paint_color
  • Creates an RGBA based color from hex strings
  • Accepts format #RRGGBBAA or #RRGGBB

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

paint.color.hsv(h: number, s: number, v: number, a?: number): paint_color
  • Creates an RGBA based color from HSV

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

paint.color.hsl(h: number, s: number, l: number, a?: number): paint_color
  • Creates an RGBA based color from HSL

Functions

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)

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)
  • Creates a line between points A and B

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

paint.circle.filled(center: paint_point, color: paint_color, radius: number, outline?: paint_color = {0, 0, 0, 255}, outline_thickness?: number = 0)
  • Creates a circle around a center point that is filled

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)

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)
  • Creates a circle around a center point that is hollow

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)

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)
  • Creates a triangle from three points that is filled

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)

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)
  • Creates a triangle from three points that is hollow

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

paint.rect.filled(top_left: paint_point, size: paint_point, color: paint_color, outline?: paint_color = {0, 0, 0, 255}, outline_thickness?: number = 0)
  • Creates a rectangle that is filled

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)

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)
  • Creates a rectangle that is hollow

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)

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)
  • Creates a rectangle that is filled with rounding

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)

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)
  • Creates a rectangle that is hollow with rounding

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

paint.rect.gradient(top_left: paint_point, size: paint_point, up_left: paint_color, up_right: paint_color, bottom_left: color_paint, bottom_right: color_paint, outline?: paint_color = {0, 0, 0, 255}, outline_thickness?: number = 0)
  • Creates a rectangle using a gradient of colors

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)

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)
  • Creates a rectangle that is filled with rotation in degrees
  • These rectangles are centered

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)

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)
  • Creates a rectangle that is hollow with rotation in degrees
  • These rectangles are centered

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)

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)
  • Creates a quadrilateral that is filled

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)

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)
  • Creates a quadrilateral that is hollow

paint.text.left(point: paint_point, text: string, color: paint_color, outline?: paint_color = {0, 0, 0, 255})

paint.text.left(point: paint_point, text: string, color: paint_color, outline?: paint_color = {0, 0, 0, 255})
  • Creates text thats aligned to the left

paint.text.center(point: paint_point, text: string, color: paint_color, outline?: paint_color = {0, 0, 0, 255})

paint.text.center(point: paint_point, text: string, color: paint_color, outline?: paint_color = {0, 0, 0, 255})
  • Creates text thats aligned to the center

paint.text.right(point: paint_point, text: string, color: paint_color, outline?: paint_color = {0, 0, 0, 255})

paint.text.right(point: paint_point, text: string, color: paint_color, outline?: paint_color = {0, 0, 0, 255})
  • Creates text thats aligned to the right

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

paint.text.bake(font_name: string, font_data: string, size: number): boolean
  • Bakes a font into memory, so that you can use it
  • Fonts already created under a name will simply return true as existing
local proggy_data = fs.read("proggy_clean.ttf")
if not paint.text.bake("Proggy", proggy_data, 14) then
    error("Couldn't load proggy font!")
end

local point = paint.point.vector2(40, 40)
local white = paint.color.rgb(255, 255, 255)
paint.add("example", function(w, h)
    if paint.text.isbaked("Proggy") then
        paint.text.font("Proggy") -- push the font, otherwise will use 'default'
   		paint.text.left(point, "hello world", white)
	end
end)

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

paint.text.unbake(font_name: string): boolean
  • De-allocates a font from memory, effectively unloading it

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

paint.text.isbaked(font_name: string): boolean
  • Check if a font has been added

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

paint.text.font(font_name?: string): string
  • Makes all future text renderers use a specific font
  • Returns the current font being used currently

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

paint.text.get_size(text: string): table
  • Returns the width and height (in pixels) of the given text using the default font.

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

paint.text.get_size(font_name: string, text: string): table
  • Returns the width and height (in pixels) of the given text using a specified font.

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

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

paint.image.bake(data: string): image | false paint.image.bake(width: number, height: number, rgba: string): image | false
  • Bakes an image into memory so you can draw it with paint.image.normal / paint.image.round
  • Images are managed by reference; when Lua no longer holds the value, it will eventually deallocate
  • Encoded form (bake(data)): data is a full image file in memory (PNG, JPEG, etc.)
  • Raw RGBA form (bake(width, height, rgba)): rgba is tightly packed RGBA8 pixels (width * height * 4 bytes)
-- From file (png/jpeg/…)
local data = fs.read("sample.jpg")
local image = paint.image.bake(data)
if not image then
    error("Couldn't load image!")
end

-- From raw RGBA8 pixels
local rgba = "\xFF\x00\x00\xFF" -- 1x1 red pixel
local pixel = paint.image.bake(1, 1, rgba)
if not pixel then
    error("Couldn't bake rgba!")
end

local point = paint.point.vector2(50, 50)
local size = paint.point.vector2(128, 128)
local white = paint.color.rgb(255, 255, 255)
paint.add("example", function(w, h)
    paint.image.normal(image, point, size, white)
end)

paint.image.normal(image: image, top_left: paint_point, size: paint_point, color: paint_color, outline?: paint_color = {0, 0, 0, 255})

paint.image.normal(image: image, top_left: paint_point, size: paint_point, color: paint_color, outline?: paint_color = {0, 0, 0, 255})
  • Creates a rectangle image

paint.image.round(image: image, rounding: number, top_left: paint_point, size: paint_point, color: paint_color, outline?: paint_color = {0, 0, 0, 255})

paint.image.round(image: image, rounding: number, top_left: paint_point, size: paint_point, color: paint_color, outline?: paint_color = {0, 0, 0, 255})
  • Creates a rectangle image with rounding

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

paint.poly.filled(vertices: table, color: paint_color, outline?: paint_color = {0, 0, 0, 255}, outline_thickness?: number = 0)
  • Creates a filled polygon from a table of vertices.
  • Requires at least 3 vertices

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

paint.poly.hollow(vertices: table, color: paint_color, thickness?: number = 1, outline?: paint_color = {0, 0, 0, 255}, outline_thickness?: number = 0)
  • Creates a hollow polygon from a table of vertices.
  • Requires at least 3 vertices
local point1 = paint.point.vector2(40, 40)
local point2 = paint.point.vector2(100, 40)
local point3 = paint.point.vector2(70, 100)

local white = paint.color.rgb(255, 255, 255)
local red   = paint.color.rgb(255, 0, 0)

paint.add("example", function(w, h)
    -- filled triangle
    paint.poly.filled({point1, point2, point3}, white)

    -- hollow triangle
    paint.poly.hollow({point1, point2, point3}, red, 2) 
end)

Events

Paint uses a slightly different type of Signal
You don't have to provide a name, instead its just an identifier.

-- instead of:
paint.listener.add("paint", "identity", function(w, h) end)
-- its:
paint.add("identity", function(w, h) end)

This is to make it simpler since paint really only has one event handler.