CanvasDraw 4.0 Tutorial

This thread contains a bunch of tutorials for CanvasDraw v4.0

There is also a handy API reference for this module which contains documentation of every property and functions that CanvasDraw has to offer which you should also check out:


The Basics

Using the Canvas

Before we can actually start drawing, we actually need something for CanvasDraw to draw on. That’s where the canvas comes in handy!

The canvas is a 2D grid of pixels that can be drawn on.

image

To create a canvas, we firstly need to create a GUI with a frame for our canvas.

Next, we need the actual CanvasDraw module and a local script in the GUI so we can start coding.

image

Now we are ready to actually start using CanvasDraw. So firstly, let’s create a canvas so we can draw something on it.

Local Script:

local GUI = script.Parent
local Frame = GUI.Frame

local CanvasDraw = require(GUI.CanvasDraw) -- Require the module

-- Create the canvas
local Canvas = CanvasDraw.new(
	Frame, -- The frame we want the canvas to be on
	Vector2.new(200, 100) -- The resolution of the canvas
)

Now that we have created a canvas, we can do what ever we want to the canvas!
Lets draw a circle in the middle of the canvas.

-- Draw a blue circle
Canvas:DrawCircle(
	Vector2.new(100, 50), -- Draw at point (100, 50) on the canvas (which is the middle)
	10, -- Circle radius
	Color3.new(0, 0, 1), -- Circle colour (blue)
	true -- Fill the circle
)

Now if we run the game, this should be your result:


A white canvas with a blue filled circle.

Complete Code
local GUI = script.Parent
local Frame = GUI.Frame

local CanvasDraw = require(GUI.CanvasDraw) -- Require the module

-- Create the canvas
local Canvas = CanvasDraw.new(
	Frame, -- The frame we want the canvas to be on
	Vector2.new(200, 100) -- The resolution of the canvas
)

-- Draw a blue circle
Canvas:DrawCircle(
	Vector2.new(100, 50), -- Draw at point (100, 50) on the canvas (which is the middle)
	10, -- Circle radius
	Color3.new(0, 0, 1), -- Circle colour (blue)
	true -- Fill the circle
)

Creating Games & Programs

Creating a Drawing/Paint Program

Alright, let’s actually make something interactable with CanvasDraw!

After you have set up your GUI with it’s frame and script, set up your script and create a canvas.

I will be using a square canvas with a resolution of 200x200, but you can use any aspect ratio and resolution you’d like.

We will also need to get the players mouse and set up some basic input functions.

local Mouse = game.Players.LocalPlayer:GetMouse() -- Get the player's mouse

local Gui = script.Parent
local Frame = Gui:WaitForChild("Frame")

local CanvasDraw = require(Gui:WaitForChild("CanvasDraw"))
local Canvas = CanvasDraw.new(Frame, Vector2.new(200, 200))

-- Inputs

local ButtonDown = false

local function MouseButtonDown()
	ButtonDown = true
end

local function MouseButtonUp()
	ButtonDown = false
end

Mouse.Button1Down:Connect(MouseButtonDown)
Mouse.Button1Up:Connect(MouseButtonUp)

Alright, now that we have all that, we need to check whenever the mouse moves and get the canvas point from the user’s mouse.

Thankfully doing this is really easy with CanvasDraw as all you have to do is call the GetMousePoint() canvas method which will return a Vector2 value if the mouse is within the canvas.

Mouse.Move:Connect(function()
    -- Get the canvas point from the mouse. Returns either a point, or nil
	local MousePoint = Canvas:GetMousePoint()
	
    -- Check if the mouse is within the canvas and the button is being held
	if MousePoint and ButtonDown then
		Canvas:DrawPixel(MousePoint, Color3.new(0, 0, 0))
	end
end)

And now we have a prototype of a basic drawing program! All though there seems to be one issue.

When you move your mouse quickly, the lines have large gaps leaving nothing but single pixels. This issue is caused by the fact that the mouse is actually snapping to places on your screen due to the framerate of your computer.

To fix this issue, all we have to do is draw a line between the last mouse point on the previous frame, and the current mouse point.

local PreviousMousePoint = nil

local function MouseButtonDown()
	ButtonDown = true
	
	-- Declare initial previous mouse point so we can start our line
	if not PreviousMousePoint then
		PreviousMousePoint = Canvas:GetMousePoint()
	end
end

local function MouseButtonUp()
	ButtonDown = false
	PreviousMousePoint = nil
end

Mouse.Button1Down:Connect(MouseButtonDown)
Mouse.Button1Up:Connect(MouseButtonUp)

Mouse.Move:Connect(function()
	local MousePoint = Canvas:GetMousePoint()
	
	if MousePoint and ButtonDown then
		-- Draw a line between the last mouse point, and the current mouse point
		Canvas:DrawLine(PreviousMousePoint, MousePoint, Color3.new(0, 0, 0))
		
		-- Declare the previous mouse point for the next frame
		PreviousMousePoint = MousePoint
	end
end)

And now we have a fully functional simple drawing program!

Complete Code
local Mouse = game.Players.LocalPlayer:GetMouse() -- Get the player's mouse

local Gui = script.Parent
local Frame = Gui:WaitForChild("Frame")

local CanvasDraw = require(Gui:WaitForChild("CanvasDraw"))
local Canvas = CanvasDraw.new(Frame, Vector2.new(200, 200))

local ButtonDown = false

local PreviousMousePoint = nil

local function MouseButtonDown()
	ButtonDown = true
	
	-- Declare initial previous mouse point so we can start our line
	if not PreviousMousePoint then
		PreviousMousePoint = Canvas:GetMousePoint()
	end
end

local function MouseButtonUp()
	ButtonDown = false
	PreviousMousePoint = nil
end

Mouse.Button1Down:Connect(MouseButtonDown)
Mouse.Button1Up:Connect(MouseButtonUp)

Mouse.Move:Connect(function()
	local MousePoint = Canvas:GetMousePoint() -- Returns either a point, or nil
	
	if MousePoint and ButtonDown then -- Check if the mouse is within the canvas
		-- Draw a line between the last mouse point, and the current mouse point
		Canvas:DrawLine(PreviousMousePoint, MousePoint, Color3.new(0, 0, 0))
		
		-- Declare the previous mouse point for the next frame
		PreviousMousePoint = MousePoint
	end
end)

Importing & Reading PNG Image File Data

Loading a PNG image as an instance Into Roblox for CanvasDraw From Your PC

In order for CanvasDraw to manipulate image data, we need to import an instance that I call a SaveObject.

These SaveObjects are instances that can easily be imported by the CanvasDraw Image Importer plugin:
https://www.roblox.com/library/8580432843/CanvasDraw-Image-Importer

These instances store your PNG pixel data in the instance’s attributes as compressed strings.

Now that we know what SaveObjects are, lets import some. To start, I have created a GUI with an empty local script, a frame for my canvas and the CanvasDraw module.

TIP: CanvasDraw wont care what size your selected frame for the canvas is. Canvases will automatically scale and fit within your frame at any resolution!

Now before we code we actually need a PNG image to import. I am going to be using this image of a Doge downscaled to 100x100:

Doge.png

NOTE: CanvasDraw cannot use PNG Images larger than 1024x1024 from SaveObjects, so be sure that any PNG images you use is under or equal to that resolution limit!

Once your have your PNG Image, make sure that you have installed the CanvasDraw Image Importer plugin into Roblox Studio.

Once you have done that, we can now import a SaveObject! To do so, select what instance in the explorer you want your image SaveObject to be parented to. I have selected the main GUI.

image

Now go to the Plugins tab at the top and click on the button in the CanvasDraw Tools category, then browse to your Image, select it and click Open in the pop-up window.

image

After opening your Image, CanvasDraw will convert it into a SaveObject folder instance and parent it what ever you selected before:

image

Now that we have our Instance, lets load it into CanvasDraw and draw it to the canvas!

local Gui = script.Parent
local Frame = Gui:WaitForChild("Frame")

local CanvasDraw = require(Gui.CanvasDraw)

local Canvas = CanvasDraw.new(Frame, Vector2.new(200, 100))

-- The SaveObject Instance
local DogeImage = Gui["Doge.png"]

-- Pre-load the ImageData from the SaveObject (Doge.png)
local ImageData = CanvasDraw.GetImageData(DogeImage)

-- Draw the Doge image to the canvas
Canvas:DrawImage(ImageData, Vector2.new(50, 1))

After running the game, you should have your nice image on the canvas.

TIP: By default, CanvasDraw automatically supports transparency blending in PNG Images. If you would like to disable transparency, set the Transparency parameter in the DrawImage() function to false

Canvas:DrawImage(
    ImageData, 
    Vector2.new(50, 1),
    false -- Disable transparency blending
)

ImageObjects can also be very useful for custom 2D or 3D game graphics. For example, you want a textured floor tile in your game.

I have this 16x16 brick texture that I have imported and I can easily tile this with some simple loops:

Brick-1.png

Sample Code
local Gui = script.Parent
local Frame = Gui:WaitForChild("Frame")

local CanvasDraw = require(Gui.CanvasDraw)

local GridSize = 128 -- Resolution X and Y
local TextureSize = 16 -- For both X and Y

local Canvas = CanvasDraw.new(Frame, Vector2.new(GridSize, GridSize))

-- Get our brick image data
local Texture = Gui.Brick
local ImageData = CanvasDraw.GetImageData(Texture)

-- Loop length
local TileLength = (GridSize - TextureSize) / TextureSize 

for X = 0, TileLength do
	for Y = 0, TileLength do
		Canvas:DrawImage(ImageData, Vector2.new(X * TextureSize + 1, Y * TextureSize + 1))
	end
end


Reading and using ImageData

Reading ImageData is quite simple. First, you must acquire ImageData. There are two different types of way to get ImageData:

-- Physical stored SaveObject (can instantly load)
local ImageData = CanvasDraw.GetImageData(workspace.Textures["Doge.png"])

-- OR

-- Roblox online image asset (Yields your code)
local ImageData = CanvasDraw.GetImageDataFromTextureId("rbxassetid://12518348649")

ImageData is stored in memory as an object with colour and transparency information along with many helpful related methods.

Fetching a Color3 and Alpha value, or RGBA numbers is very similar to doing it on the canvas:

-- Get middle pixel coordinates
local MidX = math.floor(ImageData.Width / 2)
local MidY = math.floor(ImageData.Height / 2)

-- Returns a Color3 and Alpha number value. Good for simple image processing
local PixelColor3, Alpha = ImageData:GetPixelXY(MidX, MidY)

-- A much faster alternative which uses raw RGBA number values
-- Use this for game engines or renderers
local R, G, B, A = ImageData:GetRGBA(MidX, MidY)

Tips and Misc

Framebuffers/buffers

The concept of buffers in graphics and image processing are quite simple. Essentially, a buffer is just an image, layer or bitmap stored in memory that can be combined or used to generate a final picture. CanvasDraw has it’s own simple version of a buffer.

Using the Canvas:

If you didn’t know, a canvas is it’s own buffer!! It stores pixel information just like an ImageData object without rendering to the screen.

There’s no need for extra buffers or ImageData objects if you are only writing pixel information once!

local Canvas = CanvasDraw.new(Gui.Frame, Vector2.new(320, 240))
Canvas.AutoRender = false -- We don't want to automatically render the final image every frame

Canvas:SetRGB(20, 20, 1, 0, 0) -- Draw a red pixel at (20, 20)

Canvas:Render() -- Draw final the image to the canvas

Using Blank ImageData Objects:

If you need multiple buffers or layers, you can create and use blank ImageData objects as buffers as you can read and write pixels to them. Example:

local DepthBuffer = CanvasDraw.CreateBlankImageData(240, 170) -- Creates a 240x170 blank ImageData object
DepthBuffer:SetRGB(20, 20, 1, 0, 0) -- Set pixel at (20, 20) to red

-- To draw a buffer to the canvas, you can just do:
Canvas:DrawImage(DepthBuffer)
7 Likes