CanvasDraw - A powerful pixel-based graphics engine (Draw pixels, lines, triangles, read image data, and much more!)


Version 3.4.1 - Works in-game
Version 4.1.0.b - Studio only EditableImage beta


Current featured project using CanvasDraw:

Entirely made with CanvasDraw. No third-party resources or http services used!


Resources

Introduction

Ever wanted a way to easily create a canvas for drawing pixels, lines, dynamic shapes, or even manipulate or draw PNG image data?

Or are you software engineer or game engine designer that wants to make or port game engines into roblox with ease?

Well now you can with CanvasDraw!

CanvasDraw is currently the largest and most performant custom graphics API for games and projects in roblox.

Here's some media of other featured games/projects made with CanvasDraw

Ball Collision Physics

Painting system

Video/GIF Player

3D .OBJ Renderer | Made by @HeroShiner46

Orthographic 3D Textured Level Editor for Tilemap Engines

Simple Maze Generator

Fully Textured Raycaster Engine

Falling Sand/Powder Game

CanvasDraw is a super fast and an efficient module which allows you to create pixel-based canvases with desired resolutions that can then be used to draw pixels, lines, and other shapes on the canvas and much more with the provided drawing methods! e.g.

  • SetPixel()
  • DrawLine()
  • DrawCircle()
  • DrawRectange()
  • DrawTriangle()
  • And much more!

This module is great for pixel-based games and projects where a canvas or reading/importing pixel data is necessary.

You can do much more than drawing pixels and shapes as there are a many other useful and advanced graphics methods to use! e.g.

  • GetPixel()
  • ImageData:GetPixel()
  • DrawImage()
  • DrawTexturedTriangle()
  • GetImageDataFromTextureId()
  • and more much!

And yes, this module has the ability to get RGBA values from pixels from an image too! Check out the tutorials to learn how to use this feature.


All these methods allow for use in much more than just simple drawing or static images. Fun fact, this module is designed for use in basic computer graphics for things like rendering or creating game engines inside of roblox! e.g:

  • Raycaster Engines
  • Raytracers
  • 3D Rendering Engines
  • 2D Physics Engines
  • Sandbox games
  • Custom video/GIF players
  • Drawing programs
  • Algorithm Visualisers
  • And much more!

Features

  • Easy to create super fast and performant canvas that scales dynamically and runs real-time
  • Heaps of simple and advanced drawing methods
  • Variable resolution (up to 1024x1024)
  • Image file reading, processing, sampling and drawing
  • Can run on both server and client
  • GUI, SurfaceGUI and BillboardGui support

No Limitations!

Ever since CanvasDraw 4.0, the only real limitations here now are what EditableImages are limited to. This includes things like roblox’s image asset texture resolution of 1024x1024 or performance from software code execution and luau.

Get CanvasDraw

You can get a model of the module here:

Latest Release v4.0 [Studio-only EditableImage beta]


Legacy Release v3.4 [Works in-game]


And here’s the CanvasDraw Tools plugin which contains an image importer that let’s you create SaveObjects from PNG image images, and an image editor that can let you edit said images and SaveObjects.

(Use this plugin to work with PNG pixel data)

How to use CanvasDraw

Using CanvasDraw is pretty straight forward and easy to understand. Especially if you have had experience with knowledge or working with pixel grids from other libraries or engines.

I highly recommend checking out the API reference manual for this module so you can find out and understand what some of the functions, methods and properties do and how you can use them to your advantage:


Creating a canvas

Before we start drawing, we actually need a canvas to draw on. You can easily create a canvas of any kind with CanvasDraw by calling CanvasDraw.new() and providing a GUI with a frame to contain the canvas.

-- Local Script
local Gui = script.Parent
local Frame = Gui.Frame

local CanvasDraw = require(Gui.CanvasDraw)

local Canvas = CanvasDraw.new(Frame)

By default, CanvasDraw.new() creates a canvas being coloured of what your frame’s BackgroundColor3 is with a resolution at 100x100 creating a grid of pixels on an EditableImage.

If you wish to create a different canvas with different resolutions and settings, then you can fill out some of the parameters of the CanvasDraw.new() function. For example;

local Canvas = CanvasDraw.new(
    Frame, -- The frame for the canvas to be parented to
    Vector2.new(320, 240), -- Our desired resolution
    Color3.new(0.5, 0.5, 1), -- Light blue for the default canvas colour
    true -- Blurs the canvas instead having sharp pixels
)

This will create a nice light blue canvas with a resolution of 320x240.

Drawing on the canvas

As mentioned before, CanvasDraw provides a bunch of fast and efficient pre-coded pixel-based drawing methods! So don’t worry about trying to create your own algorithms to draw things like lines or filled shapes between points, because I took the pain and frustration for you guys and implemented all the commonly used algorithms for you guys!

Here’s some sample code of drawing a pixel to the canvas:

local CanvasDraw = require(Gui.CanvasDraw)

-- Create a 150x100 canvas
local Canvas = CanvasDraw.new(Frame, Vector2.new(150, 100))

-- Draw a red pixel at point 75, 50 (the middle) with a Vector2 and Color3
Canvas:DrawPixel(Vector2.new(75, 50), Color3.new(1, 0, 0))

-- Alternatively, you can use a much faster method with pure X, Y and RGB values
Canvas:SetRGB(150, 100, 0, 0, 1) -- Draws a blue pixel at the bottom left corner

Here’s a list of some other very simple methods you can use to draw some simple scenes:

  • DrawRectangle()
  • DrawCircle()
  • DrawTriangle()
  • DrawLine()
  • DrawImage()

There are much more and graphically capable methods and functions than these that you can use. Learn more about in the API Reference for this module

You can also learn more about using CanvasDraw to create projects and games with these simple step-by-step tutorials here: CanvasDraw 3.0 Tutorial

By using CanvasDraw and its functions and methods, you can pretty well create what ever you wish! From drawing programs, to 2D and 3D games, to literally anything that you can do in computer graphics!

Future plans

There aren’t a whole lot of plans for this module aside from things you guys could suggest or contribute to. So please reply a suggestion or make a contribution!


Hope you guys find good use with this module. I will also be accepting any kind of feedback.

Enjoy!

370 Likes

I don’t appreciate the way you have to flex your superiority and intelligence over the rest of us!

All jokes aside this is astronomically amazing, in my opinion the getPixel function and 3D rendering capability will be the most useful.

36 Likes

Well, I believe it’s a fantastic concept. What inspired you to do it?

11 Likes

Ever since I got the idea of creating game engines within roblox on a GUI, I have always been fascinated withcreating real-time 3D renderers, raycasters, and raytracing in roblox.

And ever since I made those, I have always wanted an easy way to just create a canvas with one command and then draw graphics with other simple functions. And now we have this module!

15 Likes

Incredible work, thank you for making this awesome module! Can’t wait to experiment with this & see the new updates to come.

9 Likes

MAJOR UPDATE - v2.0.0

This update contains a lot of new features and an overhaul to the canvas system and how pixels are represented. Now there’s quite a bit to get through here, so let’s start from the biggest update here;

The canvas and it’s major optimisation

The canvas has changed completely. This new version of the canvas uses @boatbomber’s Greedy Canvas system to reduce frame count by A LOT! No longer do you have to suffer with tens of thousands of gui objects just for a decent resolution canvas! With the Greedy Canvas system in place, you can now instantly create a really high quality canvas up to 256x256 pixels with no lag from creating or destroying the canvas what so ever!

There’s a lot more that has changed to the canvas, so here’s a list of all changes related to the canvas:

  • A pixel is no longer represented as a physical frame object. Instead, a pixel is now represented as a Color3 value. This means all the pixel fetch methods will return colour values instead of frames.

  • The canvas’s resolution now uses a Vector2 value as you can now create canvases with different aspect ratios! For example, you can have a resolution of 150 x 75! No longer are you restricted to square canvases!

  • Two arguments have been removed from the CreateCanvas() function which were; PixelBorderSize, and LoadLineByLine.
    LoadLineByLine has been removed because creating a canvas can now be instant with no lag or slow downs at all!
    PixelBorderSize has also been removed due the frame compression used. You will have to come up with your own way to create pixel borders.

  • A limit has been addressed to how large a canvas can be. This limit is 256 by 256 pixels. This limit has been created because creating too many frames can cause some weird visual issues that causes roblox’s GUI renderering to fail. And also having super high resolution canvases will cause performance issues anyways.

  • The canvas now has an Update() function and a AutoUpdate property that allows you to control when to update the canvas. These features may be useful for engines, programs, or games as you can control when the canvas updates after you have drawn every pixel.

Image importing, saving and loading

Another major part of this update introduces new methods to create, import, save, and load image data!
For example, if you have a drawing program you can save and load drawings! Or if you have a tile based game, you can create a floor texture at 16x16p, and then you can save that image as data for your game, and then use it to create your map for your game!

With this part of the update, there are also two new classes/objects that CanvasDraw can create and use:

  • ImageData
    ImageData is a table that contains three parameters of information for images created by CanvasDraw (ImageColours, ImageAlphas, ImageResolution). ImageData can be stored in code as a variable and then you load the ImageData into the canvas or grab pixel colours from the data when ever you want!

  • SaveObject
    SaveObject is a folder instance that can created or read by CanvasDraw to create permanent image saves for tools or plugins, or for textures to be loaded into a game.
    image
    This instance uses attributes to store ImageData, which allows CanvasDraw to easily read the data.
    NOTE: This object only supports ImageData with a limited resolution of 128x128. This limit is caused by roblox’s string values being limited to 200000 characters, which CanvasDraw relies on to write data.

These classes/objects can be manipulated by 6 of the new functions brought in this update:

  • CreateImageDataFromCanvas(PointA: Vector2, PointB: Vector2, InstantCreate: boolean)
  • CreateSaveObject(ImageData: Table)
  • GetPixelFromImageData(ImageData: Table, Point: Vector2)
  • DrawImageFromImageData(Point: Vector2, ImageData: Table, TransparencyEnabled: boolean)
  • GetImageDataFromSaveObject(SaveObject: Folder)

You can learn more on how these functions work in the API reference manual for this module.

And to make to creating textures and images easier, I have created a brand new simple plugin built for CanvasDraw that allows you to create a SaveObject from a given PNG file on your computer. This allows you to easily create textures and use them on CanvasDraw without having to manually create the textures as you can just use any PNG image file! (As long as the image being imported is under 128 x 128 pixels)

https://www.roblox.com/library/8580432843/CanvasDraw-Image-Creator-v1-0-0


New drawing functions: FloodFill() and FillCanvas()

FloodFill:
FloodFill is a new function which allows you colour an area from a point of a specific colour. This works pretty well the same way how MS Paint’s paint bucket tool does.

CanvasDraw.FloodFill(Point: Vector2, Colour: Color3)

FillCanvas:
FillCanvas is a simple function to make replacing all colours on the canvas super easy. There’s not much to say about this function as all you do is specify a colour in the parameter and then every single pixel colour on your canvas will get replaced.

CanvasDraw.FillCanvas(Colour: Color3)

Renamed the function DrawPolygon() to DrawTriangle()

This change was made because the name of this function was misleading. A “polygon” is a shape that has ‘at least three corners’. And this function only allows you to create a triangle (which is a shape with three points).

I do plan to add a proper DrawPolygon() function which allows you to create what ever polygon of any shape you want with as much points given as possible!


Special thanks to @repgainer3 for the idea for frame compression

9 Likes

Wow this is crazy good stuff,may i know how you did that 3d rendering of a chess piece? that looked op

6 Likes

Well, all i had to do was read some .OBJ file data, and then store the vertices into an array, and from there you can project the vertices onto your screen just like any other 3D engine. As for drawing the polygons, I just used the DrawTriangle() function.

Just be thankful that .OBJ files are in a human friendly readable format.

I learnt all this from a small tutorial series I found on youtube. The videos are super easy to follow and understand. Highly recommend checking them out:

14 Likes

Literally watched that video a while back xd, great channel!

5 Likes

Small Update - v2.1.0

This update consists of a few new features, bug fixes, and some small changes to this thread page. The API reference for this update also now has a bunch of example codes. So hopefully it should it make easier to learn functions and such from the module. So let’s now start form the top with all the changes to the module:

Fixed a horrible precision bug for canvases with non-square aspect ratios

  • This bug was an issue as all pixels were stored in a table and relied on converting points to the index for said pixel. Turns out that the math behind was incorrect and causes pixels to end up being draw in the wrong place on the canvas. Thankfully that is now fixed!

Added a simple event to CanvasDraw: CanvasDraw.Heartbeat

  • This event is exactly the same as RunService.Heartbeat. This event was added as there are so many situations where I use RunService once and it’s only for the heartbeat event so I can manually draw and then update the canvas afterwards. This event just makes simple code even simpler and more tidy.
    CanvasDraw also relies on the Heartbeat event for updating the canvas every frame when CanvasDraw.AutoUpdate is set to true
CanvasDraw.Heartbeat:Connect(SomeFunction)

Added a Fill parameter to the functions; DrawRectange, DrawCircle, and DrawTriangle

  • There are many situations where you want an empty shape that isn’t filled. Well this update gives all these functions a new parameter that lets you choose if you wish to fill your shapes with colour or not.
  • If the parameter is left empty, then filling will be enabled by default.
    Also having this parameter set to false saves heaps of performance for high resolution canvases and renderers.
CanvasDraw.DrawCircle(
   Vector2.new(25, 25), -- Place the circle on the canvas at 25, 25
   10, -- Give the circle a pixel radius of 10
   Color3.new(1, 0, 0), -- Colour the circle
   false -- Don't fill the circle
)

Added a new drawing function: DrawDistortedImage()

  • This function works similarly to DrawImage(), all though difference being is that there are heaps of parameters that let you customise how the image is drawn to the screen. This function lets you do lots to the image. These new parameters allow for:

    • Skewing
    • Stretching
    • And tinting the image colour
  • This function was intended for use in pseudo 3D renderering or for situations where you may want to upscale or downscale or stretch images.

CanvasDraw.DrawDistortedImage(
    ImageData, -- The image data we want to load
    Vector2.new(50, 50), -- Place the image at point 50, 50
    Vector2.new(2, 2), -- Double the size of the image
    Vector2.new(45, 45), -- Skew the image in a 45 degree angle for both directions.
    true, -- Enable alpha transparency
    Color3.new(1, 0, 0), -- Tint the image red
    0.5 -- Only tint the image red by 50%
)
5 Likes

Sorry for stupid quastion. It’s possible create up and down rotate camera on CanvasDraw?

5 Likes

What do you mean by this?
Do you mean having a camera and rendering a scene according to it’s position and orientation? Because all that’s possible with CanvasDraw.
CanvasDraw is pretty well built for this sort of stuff.

It also depends on how you render your scene and what that scene is. I find it easier to render top-down 2D maps and project them to the screen with pseudo 3D methods.

But you do need some knowledge on this sort of stuff. There heaps of videos and tutorials online that are easy to follow and learn for basic computer graphics by using nothing but a canvas system.

CanvasDraw also has a lot of functions that lets you create scenes much easier.

For example;

  • DrawImage (can be used in a 2D game)
  • DrawTriangle (can be used in 3D rendering)
  • GetPixelFromImage (sampling texture data)
  • And much more!
10 Likes

Small Update - v2.2.0

This update consists of some optimisations to the drawing side of things and some new functions!

New function: SetPixel() - The performant version of DrawPixel()

  • This function behaves similarly to DrawPixel() , all though main difference being is that this a much more performant method for colouring a pixel to the screen as it avoids constructing a vector and avoids checks to the canvas. So this function assumes your X and Y values are already whole numbers and are within the canvas.

  • If you are not doing high-resolution real-time rendering, then the DrawPixel() function will work just fine in most cases, and is in-fact, much more recommended as it it already pretty performant and consists of safe checks to ensure you don’t have problems when colouring a pixel on canvas.

  • This function is only recommended if you are having performance issues from creating lots and lots of pixels with high resolutions, or you have guaranteed coordinates for pixels and checks are never necessary for the canvas.

-- Creates a red pixel at point 25, 25
CanvasDraw.SetPixel(25, 25, Color3.new(1, 0, 0))

New function: GetPointFromMouse()

  • This function allows you get a point on the canvas from the client’s mouse!

  • This functions gives you heaps of possibilities for cool things such as drawing programs!

  • NOTE: This function only works on the client or locally. If you are using this function from the server, it will throw a warning in the console/output.

This function will also only work for GUIs. Currently, this will not work for SurfaceGuis as this function assumes everything is in screen space.

-- Get the mouses point on the canvas and draws a pixel.
local Point = CanvasDraw.GetPointFromMouse()

if Point then
    CanvasDraw.DrawPixel(Point, Color3.new(1, 0, 0))
    print("Red pixel drawn!")
else
    print("The mouse is not the within the canvas!")
end

Optimised some of the pixel functions

Some of the drawing and fetch functions for CanvasDraw has been optimised to further allow maximum performance.

Here’s a list of them of the functions that have been optimised:

  • GetPixel()
  • GetPixels()
  • DrawPixel()
  • DrawImage()
  • DrawDistortedImage()
  • GetPixelFromImage()
5 Likes

You can make it work for surface guis pretty easily using some basic projection math

4 Likes

GetPointFromMouse? SurfaceGuis? Damn i would love to add that, but i have already gotten sick of trying to get the transformations for the mouse coordinates to work, let alone project the thing.

But if you have ideas on how i can do this simply and efficiently, please let me know. Cause i have no clue how to do this

3 Likes

This transforms a 2d position on a surface gui to a 3d point

function guiPointToWorld(gui, point)
	local adornee = assert(gui.Adornee)

	local normalizedPoint = point/gui.AbsoluteSize
	local dir = Vector3.FromNormalId(gui.Face)

	local projY = math.abs(dir.z + dir.x)
	local projZ = dir.x + math.abs(dir.y)

	local origin = Vector3.new(
		dir.x - dir.y - dir.z,
		dir.y + projY,
		dir.z + projZ)*0.5
	
	local surfX = Vector3.new(dir.z, 0, -projZ)*normalizedPoint.x
	local surfY = Vector3.new(dir.y, -projY, 0)*normalizedPoint.y

	local pos = origin + surfX + surfY

	return adornee.CFrame*(adornee.Size*pos)
end

Do that conversion for the 4 corners, a,b,c,d, and using those push 4 more relative to the canvas’s normal. You essentially have a cube now and then from there you just do a point-in-box test for the mouses hit. If it is inside you can then just transform the coordinate space by mapping it from world space to basis vectors AB and AC (look up lookAt matrices). Your point should now be a 2d point with values from 0 to 1

5 Likes

Plugin update

The CanvasDraw Image Importer plugin has been updated. You can now mass import images from your computer. This means you can now select and import more than one image when the plugin prompts images from your computer to create multiple image save objects at once!

This can be very handy in situations where you need to store heaps of images for things such as frames for videos and GIFs


6 Likes

Wait, can we actually scroll through each frame that we mass imported into this plugin?

Can we configure where the imported files go?

3 Likes

Yep. You sure can.

I have managed to easily create GIFs thanks to the mass import update:

Yeah, when you import the images, just select an instance or a folder or something in the explorer of where you want all the SaveObjects to be parented to.

(These save objects uses attributes to store the pixel data of the images)

3 Likes

hol up, wait a minute. Did this dude just made a brand new way to efficiently make a GIF/Video in roblox? Thats just insane, CanvasDraw at this point is the most useful module for rendering 3D models, Images and Videos in roblox to date. Even I am too impressed that i am also very excited to use this module in most of my projects.

But btw, Isnt it better to bypass the rendering problem of too many gui objects by simply using RichText? With as you can actually put in multiple pixel colors in 1 textlabel doing so.

This will probs allow for higher resolutions like 512x512 or even 720x1280.

5 Likes