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

Your work is awesome! Also, why not store the fonts in a “proper” bitmap format (using lua binary numerals?

Your format:

How I store characters:


Preferably, each character could be indexed automatically with it’s ASCII code

Now, each font could have it’s own bitmap data array (in ascii order), and return other functions along with that can return/find the relative character data, etc.

2 Likes

Well, the main reason why i did this is so i didn’t have to rewrite my DrawText() function when I added the fonts.

The other reason is simplicity. This is easy to modify and can be arranged in a way so you can somewhat visualise each character’s pixels too.

Also I didn’t really see how binary numerals can give me an advantage, so I tend to not use them often. I’d love to know why this might be better though!

How would one achieve this? Because I have realised that some characters cannot be used to index my character sheet, meaning i cant use most special characters

This might be a stupid question, I’m trying to make a drawing system with this module but I’m quite confused on how to make an Undo and Redo function? I appreciate if you could help me.

2 Likes

You could probably store the last 10 frames or so, and then revert to the last one, and store a new one every time you draw/erase.

And then for redo, when undoing still store the frames you were undoing, and then remove them the next time you draw/erase.

Somewhat how I would do it. Hope you figure it out! :+1:

(Sorry if it’s a little hard to follow. Tried my best lol)

Yeah exactly what i was trying to do but i don’t know how to get the last stroke of frames the player did with their mouse, I looked over the module to get the frames but still couldn’t find anything

1 Like

Using binary numerals would be as intuitive as using nested tables, but more efficient:
Instead of having {{0,0,1,0,1},{0,1,0,0,1}}, you’d have {0b00101, 0b01001}

You can then get the pixel value by using bitwise functions (I’d me more than glad to help over discord, but here’s how I did it on a 5x8 20x4 LCD with the original hitachi bitmap: Dot Matrix LCD in Roblox! (with hd44780 bitmaps))

Creating an undo system is relatively easy to do. One way to do this is to store multiple copies of the canvas as you draw your image (i.e, upon every mouse button click, save the current state of our image)

You can do this by using a table

local UndoFrames = {}
local MaxUndoFrames = 32

local function AddToUndoHistory()
	-- Get all current pixels on the canvas
	local Pixels = {}
	
	for Y = 1, Resolution.Y do
		for X = 1, Resolution.X do
			table.insert(Pixels, Canvas:GetPixelXY(X, Y))
		end
	end
	
    -- Avoid excessive memory consumption
	if #UndoFrames >= MaxUndoFrames then
		table.remove(UndoFrames, 1)
	end

	table.insert(UndoFrames, Pixels)
end

local function Undo()
    -- Draw and remove the previous state of the canvas
	local LastFrame = UndoFrames[#UndoFrames]

	if LastFrame then
		for X = 1, Resolution.X do
			for Y = 1, Resolution.Y do
				Canvas:SetPixel(X, Y, LastFrame[X + (Y - 1) * Resolution.X]) -- Formula for getting a 1D position from 2D coordinates
			end
		end

		table.remove(UndoFrames, #UndoFrames)
	end
end

In this example, I am saving to an array of a size of 32 images max every time I press the mouse button, and pressing Z to undo:

Full code
local Players = game:GetService("Players")
local LocalPlr = Players.LocalPlayer

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

local CanvasDraw = require(Gui:WaitForChild("CanvasDraw"))

local Mouse = LocalPlr:GetMouse()

local DrawColour = Color3.new(0, 0, 0)
local Radius = 1

-- Main

local MouseDown = false
local LastMousePoint

local Resolution = Vector2.new(158, 128)

local Canvas = CanvasDraw.new(Frame, Resolution)

local UndoFrames = {}
local MaxUndoFrames = 32

local function AddToUndoHistory()
	-- Get all current pixels on the canvas
	local Pixels = {}
	
	for Y = 1, Resolution.Y do
		for X = 1, Resolution.X do
			table.insert(Pixels, Canvas:GetPixelXY(X, Y))
		end
	end
	
	if #UndoFrames >= MaxUndoFrames then
		table.remove(UndoFrames, 1)
	end

	table.insert(UndoFrames, Pixels)
end

local function Undo()
	local LastFrame = UndoFrames[#UndoFrames]

	if LastFrame then
		for X = 1, Resolution.X do
			for Y = 1, Resolution.Y do
				Canvas:SetPixel(X, Y, LastFrame[X + (Y - 1) * Resolution.X])
			end
		end

		table.remove(UndoFrames, #UndoFrames)
	end
end

Mouse.Move:Connect(function()
	local MousePoint = Canvas:GetMousePoint()
	
	-- Check if we have a valid point on the canvas and the mouse button is being held down
	if MousePoint and MouseDown then 
		--MousePoint += Vector2.new(25, 25)
		
		-- Draw a line between the last mouse point and the current mouse point to avoid gaps from dragging the mouse too quickly
		if LastMousePoint then
			Canvas:DrawLine(LastMousePoint, MousePoint, DrawColour, Radius)
		end
		
		LastMousePoint = MousePoint
	end
end)

Mouse.Button1Down:Connect(function()
	MouseDown = true
	
	AddToUndoHistory()
	
	local MousePoint = Canvas:GetMousePoint()
	
	-- For those who like dots
	if MousePoint then
		LastMousePoint = MousePoint
		Canvas:DrawCircle(MousePoint, Radius, DrawColour)
	end
end)

Mouse.Button2Down:Connect(function()
	AddToUndoHistory()
	
	local MousePoint = Canvas:GetMousePoint()

	-- For those who like dots
	if MousePoint then
		Canvas:FloodFillXY(MousePoint.X, MousePoint.Y, DrawColour)
	end
end)


Mouse.Button1Up:Connect(function()
	LastMousePoint = nil
	MouseDown = false
end)

-- Undo
game.UserInputService.InputBegan:Connect(function(Inp)
	if Inp.KeyCode == Enum.KeyCode.Z then
		Undo()
	end
end)

AddToUndoHistory() -- Initalise
3 Likes

Thank you very much didn’t know it was that simple.

2 Likes

Thanks! I think this might be more efficient, I’ll give it a try!

1 Like

Module Update v4.6.0

Hey all. We have some new and very useful additions that a couple of you guys have suggested in the past.


Canvas:Resize()

Yes, that’s right! We can now dynamically resize the canvas without having to destroy and create a new one.

NOTE: Currently, contents of the canvas do not rescale, and instead clears.
You will have to redraw and render the image after clearing for a clean transition.


Canvas:SetGrid() / Canvas:GetGrid()

These new methods were honestly quite overdue. These are very useful for when you want to do per pixel processing or copying/pasting of pixel data


Full list of changes:

  • Added Canvas:SetGrid()

  • Added Canvas:GetGrid()

  • Added Canvas:Resize()

  • Optimised font storage and rendering for Canvas:DrawText() and Canvas:DrawTextXY()

  • Fixed Canvas:GetPixels() not working correctly on aspect ratios that aren’t 1:1


Special thanks to: @bluebxrrybot and @Lxi099 for contributing some of these ideas


CanvasDraw4.6.0.b.rbxm (50.9 KB)

5 Likes

Here’s a port of the ModernDOS (GitHub - susam/pcface: Bitmap arrays for rendering CP437 glyphs using IBM PC OEM fonts) font to your CanvasDraw Bitmap format:


ModernDOS.lua (27.5 KB)

Demo string containing all characters:

☺☻♥♦♣♠•◘○◙♂♀♪♫☼►◄↕‼¶§▬↨↑↓→←∟↔▲▼ !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~⌂ÇüéâäàåçêëèïîìÄÅÉæÆôöòûùÿÖÜ¢£¥₧ƒáíóúñѪº¿⌐¬½¼¡«»░▒▓│┤╡╢╖╕╣║╗╝╜╛┐└┴┬├─┼╞╟╚╔╩╦╠═╬╧╨╤╥╙╘╒╓╫╪┘┌█▄▌▐▀αßΓπΣσµτΦΘΩδ∞φε∩≡±≥≤⌠⌡÷≈°∙·√ⁿ²■ 

EDIT:
I noticed most of the characters weren’t being displayed. This is due to the string.split(TextLine, "") in the Characters definition on the DrawTextXY script. You have to replace Characters with this; which adds support for multi-byte utf8 graphemes (like the smiley faces, greek letters and blocks)

local Characters = {}
for _, c in utf8.codes(TextLine) do
	table.insert(Characters, utf8.char(c))
end
2 Likes

I have been looking for this for a very long time, I was wondering how you could also “Redo” after doing “Undo”? Been trying for the past 3 hours but just couldn’t do it

Oh, thank you so much for fixing this. I’ve been trying to figure out why I haven’t been able to use ascii characters for ages.

I think one way to do this is like so:

Whenever you undo, instead of removing the latest saved frame from the table, we instead keep it and use a number value that goes up and down when ever we undo and redo, and just index the saved frame with that number to draw it

Module Patch / Small Update - v4.6.1

CanvasDraw fonts can now support multi-byte UTF-8 characters (such as greek letters, ascii characters, blocks, etc)

The Codepage and CodepageLarge fonts have been updated and now support 254 characters

These changes affect Canvas:DrawText() and Canvas:DrawTextXY()


Special thanks to @Lxi099!


CanvasDraw4.6.1.b.rbxm (63.2 KB)

4 Likes

That’l be good, especially for any games using CanvasDraw that want an easy way to incorporate Graphics Levels* for performance on different devices.

*(Not 100% even needed since CanvasDraw is so optimized!)

1 Like

CanvasDraw Tools Plugin - Image Editor Temporarily Broken

Hey all, we currently have an EditableImage rendering issue with the plugin, which causes the Image Editor to no longer show anything while drawing/importing an image.

This issue should be fixed sometime on Monday


EDIT: The issue has now been resolved

1 Like

Small Optimisation Update - v4.6.2

Hey all, this update is one of the few rare optimisation updates where I somehow make CanvasDraw even faster than it already is!


Here is the list of changes:

  • Improved performance by 20% for textured triangles. This affects the following methods:

    • Canvas:DrawTexturedTriangle() and Canvas:DrawTexturedTriangleXY()
    • Canvas:DrawDistortedImage() and Canvas:DrawDistortedImageXY()
    • Canvas:DrawRotatedImage() and Canvas:DrawRotatedImageXY()
    • Canvas:DrawImageRect() and Canvas:DrawImageRectXY()
  • Improved performance for Canvas:DrawTriangle() and Canvas:DrawTriangleXY() by 22%

  • Fixed and increased performance for Canvas:ClearPixels() and Canvas:FillPixels() by over 200%


CanvasDraw4.6.2.b.rbxm (63.6 KB)

1 Like

Optimisation Update - v4.7.0


Hey all, roblox has recently released a new API for EditableImages that allows us to use buffers efficiently with them.

CanvasDraw now uses 8-bit integer buffers instead of arrays of doubles for the pixel grid management.

Doing this has not only improved memory usage, but also has improved framerate as well!


From a 1024x1024 Canvas:SetRGB() with clearing for every heartbeat test, I have acheived the following numbers:

  • CanvasDraw v4.6.2 (Previous Version):

    • 13 FPS
  • CanvasDraw v4.7.0 (Newest Version):

    • 18 FPS

This update has also reduced ImageData size by about 33%

Prior to this update, an uncompressed 1024x1024 ImageData would take up 0.3 GB of memory, and now with this update the same image only takes up 0.1 GB of memory!


CanvasDraw4.7.0.b.rbxm (64.2 KB)

3 Likes

Yay! More Mandelbrot frames per recording! But to be fair, I’m happier with the FPS improvements! Thanks, Roblox and my beloved Ethan.

2 Likes