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

Hi there, um the image import just sometimes fails to import images, i noticed that when you try to import multiple frames, it will error on frames that are dark.

Okay so, i tried importing an animation that has multiple fade in and fade out effects. Then i tried to import, only to go through some errors. I realized that the module will struggle agaisnt frames that have a dark lighting or are completely black.

2 Likes

Oh really? Thats odd. Do you mind giving the png images that causes the error? I will fix the issues asap

lemme find a way to send you the folder, it has 300 pngs to upload, this one is prone tto the same error.

alright this link will lead you to a website called WeTransfer, it will allow u to download all of the png files:

I have made a temporary fix. There for some reason appears to be something wrong with the actual pixel data in the PNG file.

You should be able to import the images now though without any issues. If something does happen with other PNG images, please let me know.


Also, I have actually released a small secret update to the CanvasDraw module which heavily optimises the DrawImage() and DrawImageXY() function. Now your videos should be able to run much better at higher resolutions.

TIP: For maximum performance, use DrawImageXY for each frame in your video/GIF


Enjoy!


A nice crispy 30 FPS at 256x157

Could actually get a much higher framerate if i had something better than Boatbomber’s GreedyCanvas module to draw the pixels

4 Likes

Hi, I have a question about your 3D render engine. I’ve watched the youtube video that you have linked and I got to the end of the tutorial. The cube does show up on the CanvasDraw screen but the cube is squashed. I’m not sure if you actually based your 3D render engine code on the code of the video that you linked but if you did then maybe you know what I did wrong. Here’s my code and explorer:

--!strict

local ScreenWidth : number = 256
local ScreenHeight : number = 240 --// In tutorial 240

-- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --

local Screen = script.Parent
local CanvasFrame = Screen.CanvasFrame

local CanvasDraw = require(Screen.CanvasDraw)
local VectorModule = require(Screen.VectorModule)

local RunService = game:GetService("RunService")

type vec3d = {["X"] : number, ["Y"] : number, ["Z"] : number, ["Magnitude"] : number}
type pArray = {[number] : vec3d}
type triangle = {["p"] : pArray}

type triangleArray = {[number?] : triangle}
type mesh = {["tris"] : triangleArray}

type matRow = {[number] : number}
type mat4x4 = {[number] : matRow}

--// Construct
local function Construct_vec3d(...) : vec3d
	if #{...} == 0 then return VectorModule.zero end
	return VectorModule.new(unpack{...})
end

local function Construct_triangle(...) : triangle
	local Params = {...}
	if #Params == 0 then return {p = {Construct_vec3d(), Construct_vec3d(), Construct_vec3d()}} end
	return {p = {Params[1] or Construct_vec3d(), Params[2] or Construct_vec3d(), Params[3] or Construct_vec3d()}}
end

local function Construct_mesh(...) : mesh
	local Params = {...}
	if #Params == 0 then return {tris = {}} end
	return {tris = Params}
end

local function Construct_mat4x4(x : number?) : mat4x4
	--// x is a default
	local d = 0
	if type(x) == "number" then d = x end
	local NewMatrix = {
		{d, d, d, d};	
		{d, d, d, d};	
		{d, d, d, d};	
		{d, d, d, d};	
	}
	return NewMatrix
end

--// Methods
local function MultiplyMatrixVector(i : vec3d, m : mat4x4) : vec3d
	local o : vec3d = Construct_vec3d()
	
	o.X = i.X * m[1][1] + i.Y * m[2][1] + i.Z * m[3][1] + m[4][1]
	o.Y = i.X * m[1][2] + i.Y * m[2][2] + i.Z * m[3][1] + m[4][2]
	o.Z = i.X * m[1][3] + i.Y * m[2][3] + i.Z * m[3][3] + m[4][3]
	local w : number = i.X * m[1][4] + i.Y * m[2][4] + i.Z * m[3][4] + m[4][4]
	
	if w ~= 0 then
		o.X /= w
		o.Y /= w
		o.Z /= w
	end
	
	return o
end

local function CopyTable(Target : any) : any
	local Result : any = {}
	for Key, Value in Target do
		Result[Key] = Value
	end
	return Result
end

local meshCube = Construct_mesh()
local matProj : mat4x4 = Construct_mat4x4()

local function Initialize() : nil --// main
	local Resolution = Vector2.new(ScreenWidth, ScreenHeight)
	CanvasDraw.CreateCanvas(CanvasFrame, Resolution, Color3.fromRGB(0,0,0), false)
	meshCube.tris = {
		--// South
		Construct_triangle(Construct_vec3d(0, 0, 0), Construct_vec3d(0, 1, 0), Construct_vec3d(1, 1, 0));
		Construct_triangle(Construct_vec3d(0, 0, 0), Construct_vec3d(1, 1, 0), Construct_vec3d(1, 0, 0));

		--// East
		Construct_triangle(Construct_vec3d(1, 0, 0), Construct_vec3d(1, 1, 0), Construct_vec3d(1, 1, 1));
		Construct_triangle(Construct_vec3d(1, 0, 0), Construct_vec3d(1, 1, 1), Construct_vec3d(1, 0, 1));

		--// North
		Construct_triangle(Construct_vec3d(1, 0, 1), Construct_vec3d(1, 1, 1), Construct_vec3d(0, 1, 1));
		Construct_triangle(Construct_vec3d(1, 0, 1), Construct_vec3d(0, 1, 1), Construct_vec3d(0, 0, 1));

		--// West
		Construct_triangle(Construct_vec3d(0, 0, 1), Construct_vec3d(0, 1, 1), Construct_vec3d(0, 1, 0));
		Construct_triangle(Construct_vec3d(0, 0, 1), Construct_vec3d(0, 1, 0), Construct_vec3d(0, 0, 0));

		--// Top
		Construct_triangle(Construct_vec3d(0, 1, 0), Construct_vec3d(0, 1, 1), Construct_vec3d(1, 1, 1));
		Construct_triangle(Construct_vec3d(0, 1, 0), Construct_vec3d(1, 1, 1), Construct_vec3d(1, 1, 0));

		--// Bottom
		Construct_triangle(Construct_vec3d(1, 0, 1), Construct_vec3d(0, 0, 1), Construct_vec3d(0, 0, 0));
		Construct_triangle(Construct_vec3d(1, 0, 1), Construct_vec3d(0, 0, 0), Construct_vec3d(1, 0, 0));
	}
	return
end

--// OnUserCreate
Initialize()
local fNear = 0.1
local fFar = 1000
local fFov = 90
local fAspectRartio = ScreenHeight / ScreenWidth
local fFovRad = 1 / math.tan(math.rad(fFov * 0.5))

local fTheta = 0

matProj[1][1] = fAspectRartio * fFovRad
matProj[2][2] = fFovRad
matProj[3][3] = fFar / (fFar - fNear)
matProj[4][3] = (-fFar * fNear) / (fFar - fNear)
matProj[3][4] = 1
matProj[4][4] = 0

RunService.RenderStepped:Connect(function() --// OnUserUpdate
	CanvasDraw.ClearCanvas()
	
	local matRotZ : mat4x4, matRotX : mat4x4 = Construct_mat4x4(), Construct_mat4x4()
	fTheta += 0.01
	
	--// Rotation Z
	matRotZ[1][1] = math.cos(fTheta)
	matRotZ[1][2] = math.sin(fTheta)
	matRotZ[2][1] = -math.sin(fTheta)
	matRotZ[2][2] = math.cos(fTheta)
	matRotZ[3][3] = 1
	matRotZ[4][4] = 1
	
	--// Rotation X
	matRotX[1][1] = 1
	matRotX[2][2] = math.cos(fTheta * 0.5)
	matRotX[2][3] = math.sin(fTheta * 0.5)
	matRotX[3][2] = -math.sin(fTheta * 0.5)
	matRotX[3][3] = math.cos(fTheta * 0.5)
	matRotX[4][4] = 1
	
	--// Draw Triangles
	for _, tri : triangle in meshCube.tris do
		local triProjected : triangle = Construct_triangle()
		local triRotatedZ : triangle = Construct_triangle()
		local triRotatedZX : triangle = Construct_triangle()
		
		triRotatedZ.p[1] = MultiplyMatrixVector(tri.p[1], matRotZ)
		triRotatedZ.p[2] = MultiplyMatrixVector(tri.p[2], matRotZ)
		triRotatedZ.p[3] = MultiplyMatrixVector(tri.p[3], matRotZ)
		
		triRotatedZX.p[1] = MultiplyMatrixVector(triRotatedZ.p[1], matRotX)
		triRotatedZX.p[2] = MultiplyMatrixVector(triRotatedZ.p[2], matRotX)
		triRotatedZX.p[3] = MultiplyMatrixVector(triRotatedZ.p[3], matRotX)
		
		local triTranslated : triangle = Construct_triangle(
			Construct_vec3d(triRotatedZX.p[1].X, triRotatedZX.p[1].Y, triRotatedZX.p[1].Z),
			Construct_vec3d(triRotatedZX.p[2].X, triRotatedZX.p[2].Y, triRotatedZX.p[2].Z), 
			Construct_vec3d(triRotatedZX.p[3].X, triRotatedZX.p[3].Y, triRotatedZX.p[3].Z)
		)
		
		triTranslated.p[1].Z = triRotatedZX.p[1].Z + 3
		triTranslated.p[2].Z = triRotatedZX.p[2].Z + 3
		triTranslated.p[3].Z = triRotatedZX.p[3].Z + 3
		
		triProjected.p[1] = MultiplyMatrixVector(triTranslated.p[1], matProj)
		triProjected.p[2] = MultiplyMatrixVector(triTranslated.p[2], matProj)
		triProjected.p[3] = MultiplyMatrixVector(triTranslated.p[3], matProj)
		
		--// Scale into view
		triProjected.p[1].X += 1; triProjected.p[1].Y += 1
		triProjected.p[2].X += 1; triProjected.p[2].Y += 1
		triProjected.p[3].X += 1; triProjected.p[3].Y += 1
		
		triProjected.p[1].X *= 0.5 * ScreenWidth; triProjected.p[1].Y *= 0.5 * ScreenHeight
		triProjected.p[2].X *= 0.5 * ScreenWidth; triProjected.p[2].Y *= 0.5 * ScreenHeight
		triProjected.p[3].X *= 0.5 * ScreenWidth; triProjected.p[3].Y *= 0.5 * ScreenHeight
		
		CanvasDraw.DrawTriangle(Vector2.new(triProjected.p[1].X , triProjected.p[1].Y ), -- You have to shift everything by 1 on both axes because the canvas ...
			-- actually starts on 1, 1 and not 0, 0 :(
			Vector2.new(triProjected.p[2].X , triProjected.p[2].Y ),
			Vector2.new(triProjected.p[3].X , triProjected.p[3].Y ),
			Color3.fromRGB(255,255,255), false
		)
	end
end)

Beautiful video of a squashed cube:

2 Likes

Yeah the issue isn’t related to CanvasDraw itself. But judging by your code and the video, it seems mostly fine, all though I think there might be something wrong with your projection or matrix translations, because it only squashes when it rotates a certain amount.

Check your translations and maybe double check with the video (I had to rewatch the tutorial a good 4 times before I made my 3D rasteriser).

If you have any more questions, please do ask. I am more than happy to help!

1 Like

I think I found the issue. One of the triangles (the first bottom triangle) actually has a Y position of 1 and that’s obviously wrong. I must have copied the values over incorrectly. :sweat_smile: Ok so after fixing it the cube has lost it’s height. :confused: This is so annoying to deal with. :laughing:

1 Like

What’s the different between this and GreedyCanvas / GradientCanvas?

if you read the post, you will see that it can do much more than those modules. This module has way more drawing functions and features for creating games or programs. Some examples:

  • DrawImage()
  • DrawTriangle()
  • DrawLine()
  • DrawRectangle()
  • DrawCircle()
  • GetPixel()
  • ClearPixels()
  • FloodFill()
  • GetPixelFromImage()
  • GetImageDataFromSaveObject()
  • And more!

Now don’t get me wrong, i’m not trying to diss Boatbomber’s module. His stuff is amazing. It actually inspired me to make this module!

Think of this module as an extension for GradientCanvas in a way too (since it uses an upgraded version of it internally)

1 Like

nah bro its a nightmare, uploading a 90x50 1 minute gif wass hectic, I had arround 3000 images and i had to bump it down to 300. btw can you show me the script that you use to run that gif? Also did not solve the problem really…

3000? Frames jees! I can see your really determined to make actual long videos within CanvasDraw. You know what, ill see what I can do for optimisations on the plugin and hopefully you’ll be able to import images faster

I got great news! After literally just rewriting the entire script I finally fixed the issue! The issue was very simple. In Lua when you set 2 variables to the same table then they both own the same memory address of that table. So my 2 variables were interfering with each other and ruined everything. In C++ I guess that isn’t the case. :neutral_face: I already knew of this problem but since my VectorModule also consisted out of tables and weren’t normal Vector3s, the same memory address got referenced by 2 different variables.

1 Like

This works better than actual things built for this stuff like love2d, I get like 20 fps running this engine on there, but with this plugin I get a good 60 fps


10/10

edit:


5 Likes

Wow. Very glad to hear that are enjoying the module. Also love what you are making. Are you making a type of BSP renderer?

2 Likes

Yep! ive been messing around with it most of today, and i plan on adding textures and whatnot

3 Likes

Can I get banned from Roblox for this? It’s seems really useful. But i don’t know what roblox think about that. People can use this system for bad purposes, roblox has ban system for this (bad purposes)?

Im pretty sure an engine within roblox itself doesn’t break TOS.

Also people can make bad stuff with the normal roblox engine anyways

Just as long as your aren’t breaking general roblox laws, you should be more than fine

4 Likes

You always make the coolest stuff Ethan. This is gonna be really cool to play with.

3 Likes

I absolutely love you for this. I’ve been checking out your other work too, it’s awesome. The best thing about this, is that it’s probably more optimized than i could ever make.

Time to make some falling sand and physics games!!! A lot is possible with this.

2 Likes

Update - v2.6.0

This update contains some more optimisations, some new drawing functions and an update to the CanvasDraw Image Importer plugin!

Large Optimisations for Image Functions

  • CanvasDraw has received a bunch of optimisations to help CanvasDraw draw and sample images pixel data much quicker than before. The following image-based functions that have received major improvements are:

    • GetPixelFromImage()
    • GetPixelFromImageXY()
    • DrawImage()
    • DrawImageXY()
    • GetImageDataFromSaveObject()
  • The overall canvas rendering has also been further optimised a bit too

New DrawText Functions

  • There are two new functions for drawing simple text onto the canvas

    • DrawText()
    • DrawTextXY()
  • These functions allow you to set simple pixel text to canvas which can come in real handy for layering when drawing and rendering your game.
    DrawText also has some pretty useful features too, which are:

    • Text scaling - A multiplier for the size of the text. By default, text’s characters have a resolution of 3x5. So if you have a larger canvas, the text will be smaller. So you may want to double or triple the size of your text with this parameter in some cases.

    • Text wrapping - A feature that can be enabled or disabled in the parameters which determines if text should wrap, or cut off it it will end up being too long for the canvas.

    • Multiple lines - You can declare multiple new lines for a single text within the Text parameter by typing \n in your text string to place new lines.

  • Things to note:

    • There are no lower case letters. This limitation is due to the small resolutions of the text characters.
    • Most uncommon text characters and symbols are not featured in CanvasDraw text. Again, this is mostly due to the small resolution limit of text.
      All though all symbols and characters on a common computer keyboard should be usable in CanvasDraw text.

DrawText code sample:

CanvasDraw.CreateCanvas(CanvasFrame, Vector2.new(100, 100))

CanvasDraw.DrawText(
	"first line \n second line!",  -- The text to display (the '\n' means new line)
	Vector2.new(5, 5),  -- Text position
	Color3.new(0, 0, 0),  -- Text colour
	2, -- Text scale
    true, -- Text wrap in canvas if too long
    2 -- Text spacing in pixels
)

New Image Importer Plugin Features

  • The CanvasDraw Image Importer plugin has received a few simple but very useful updates.
  • These new features contains the following:
    • An Estimated Time Left text label that will accurately determine how long it will take to mass import all of your images.
    • A Total Size text label that shows current total file size of your SaveObjects being created.