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

How did you manage it to run at 256x256? If I try anything more than 150x82 it runs at 4 FPS, and every GUI (Including the one from CanvasDraw) starts to flash, pretty seizure incurring.

Also is there any way to draw more efficiently?

Lets say I have this code:

function renderImage()
	local currentTime = (tick() - math.floor(tick())) * 255
	local color_y = currentTime

	if lastCurrentTime > currentTime then
		iteration += 1
	end

	if iteration % 2 == 0  then
		color_y = 255 - currentTime
	end
	
	lastCurrentTime = currentTime

	for x = 1, resolution.X do
		for y = 1, resolution.Y do
			local color_x = math.floor((256 / resolution.X) * x) - 1
			local color_z = math.floor((256 / resolution.Y) * y) - 1

			Canvas:SetPixel(x, y, Color3.fromRGB(color_x, color_y, color_z))
		end
	end
end

It barely runs at 30FPS (on 150x82 and calling renderImage 10 times a second.
Is there maybe a image object, so I can make a top variable, and only change the pixels that need changing (Since Im not changing them) and apply the image after I set some pixels, at the end of the function?

Also it seems that Update is slower than normal.

If I do it without Update (And let Canvas.AutoUpdate to true) it runs at 30FPS
If I set Canvas.AutoUpdate to false and call Canvas:Update() every time the functions runs it only manages 20FPS

If anyone wants to see what the function does

ignore the weird background sounds

Time to go through the painful time of having to learn this resource to build a video player, but it will be worth it, I will finally be able to play 144p videos of stupid memes.

This formula is possibly slow.

Might wanna optimize this too.

The first thing I immediately noticed is that your not taking one of the major limitations into account

You are using way too many colours in that demo.

The reason why this is an issue, is because CanvasDraw uses UIGradients to merge same coloured pixels, meaning way less calculations are done, and since you are rendering a lossless gradient, this will cause lag as we are doing waaaay more per-pixel calculations.

to solve this, just reduce the amount of colours allowed through each of the RBG values. The less colours, the more FPS you’ll get.

color_z = math.floor(color_z / compressionAmount) * compressionAmount

This will reduce the possible amount of colours from 16,000,000, to something like 1,000, depending on how much your compress it.

I also tested this with your code, and now I get almost 60 FPS in real time with the compressionAmount variable set to 5

Here’s your updated code:

function renderImage()
	local currentTime = (tick() - math.floor(tick())) * 255
	local color_y = currentTime

	if lastCurrentTime > currentTime then
		iteration += 1
	end

	if iteration % 2 == 0  then
		color_y = 255 - currentTime
	end
	
	color_y = math.floor(color_y / compressionAmount) * compressionAmount

	lastCurrentTime = currentTime

	for x = 1, resolution.X do
        -- I moved this so we aren't doing as much calculations
		local color_x = math.floor((256 / resolution.X) * x) - 1
		color_x = math.floor(color_x / compressionAmount) * compressionAmount
		
		for y = 1, resolution.Y do
			local color_z = math.floor((256 / resolution.Y) * y) - 1
			color_z = math.floor(color_z / compressionAmount) * compressionAmount

			Canvas:SetPixel(x, y, Color3.fromRGB(color_x, color_y, color_z))
		end
	end
end

-- This is how I tested the rendering, I used the updated event (which is just RunService.Heartbeat) 
Canvas.Updated:Connect(function()
	renderImage()
	Canvas:Update()
end)

That formula is just (x - floor(x)) * 255. A CPU does billions of calculations every frame, there is no reason it slows by a few thousand percent from 3 simple calculations every 1/10 seconds.

Outside of moving color_x up a row there is no possible optimisation. Also removing Canvas:SetPixels shows that the code only uses 0.1% of script usage, so its pretty light.

Now it looks horrible, at this point, I could just decrease the pixel ratio. Is there a reason you removed color blending a few updates before? It would help very much.

Ah, the BlurEnabled parameter. Well, main reason why I removed it is because almost no one uses it as it doesn’t look that good at all, and it only works in the one direction. It especially looks bad if you have textures in your project too as 10 pixels in a column, now can only hold 2 colours.

Removing this parameter also meant that i can slightly improve performance on the per-pixel calculations.

I completely forgot that Gradients are 1D and not 2D, my bad. Maybe using Parts instead of gradients works better?

UIGradients are by far the best way to have minimal instances being updated as you can store up to 10 pixels in a UIGradient frame, and with parts or frames, you can only store the one colour which heavily increases the amount of instances being updated

insane amount of effort when to this. honestly amazed.

1 Like

any updates on transparencies for each pixels?

When you create a image using CanvasDraw.GetImageDataFromSaveObject, the ImageData doesn’t come with the GetPixel function. That happens because in the line 1355 of Canvas Draw, when creating the image data, the module doesn’t put the GetPixel function in the table

-- Convert the SaveObject into image data
	local ImageData = {ImageColours = PixelColours, ImageAlphas = PixelAlphas, ImageResolution = SaveDataImageResolution}

I think it should be like:

-- Convert the SaveObject into image data
local ImageData = {
	ImageColours = PixelColours,
	ImageAlphas = PixelAlphas,
	ImageResolution = SaveDataImageResolution,
    GetPixel = CanvasDrawModule.GetPixelFromImage,
    GetPixelXY = CanvasDrawModule.GetPixelFromImageXY
}
1 Like

I released an update yesterday which fixes that issue. Check out the new module and api documentation as you can now call ImageData:GetPixel()

I forgot to actually announce the update before haha

Small ImageData Update - v3.2.0

Hey all. I have made some small needed changes to how we work and use ImageData with CanvasDraw.

For those who don’t know, ImageData is an object type containing cached pixel data from a PNG image file created from a SaveObject instance (Which can be imported by using this plugin)


Here’s a complete list of the changes:

  • ImageData is no longer treated as a table, it is now treated as an object (like the Canvas object) and can now hold methods.

  • ImageData now has two new usable methods to sample pixel data:

    • ImageData:GetPixel() and ImageData:GetPixelXY()

    • These methods replace the CanvasDraw.GetPixelFromImage() and CanvasDraw.GetPixelFromImageXY() functions as they cause some mild problems and are less practical as far as modern image manipulation standards goes. These functions have now been marked as deprecated.

  • The CanvasDraw functions GetPixelFromImage() and GetPixelFromImageXY() have now been marked as deprecated and is no longer listed in the API documentation.
    Please use the new ImageData methods instead for new work as these functions will no longer be maintained.

  • Renamed the function CanvasDraw.GetImageDataFromSaveObject() to CanvasDraw.GetImageData(). This was needed as the function name was far too long, and there currently isn’t any other ways to obtain ImageData.

  • Slight performance increase with pixel sampling (Applies to all pixel-based fetch methods)

1 Like

sooo, any update on transparency?

Transparency is probably not going to be added anytime soon unfortunately, as it will impact on performance too much.

Sorry if this might be a bother, but I’m still very confused on how to make a working videoplayer without causing a huge amount of lag everytime it loads an image, my code is currently a sample, however everytime I load the image, it causes alot of lag, I can’t imagine loading those images every 0.1 seconds. Is there anyway to make it so it doesn’t create a whole bunch of lag when loading in the pixels? I’m not really familiar with the module as I just started using it a couple days ago. (Also yes im aware this code will only load one image.)

heres the code:

local Gui = script.Parent
local Frame = Gui.Frame

local CanvasDraw = require(Gui.CanvasDraw)

local Canvas = CanvasDraw.new(Frame)

-- Load our image
local ImageData = CanvasDraw.GetImageDataFromSaveObject(script.Parent.ImageTest) -- Our Image

local Canvas = CanvasDraw.new(Frame, ImageData.ImageResolution)

-- Main

local CompressionAmount = 10 -- From 0 to 256 (The lower, more colours, the higher, less colours)

local NewColours = {}

for i, OldColour in pairs(ImageData.ImageColours) do -- Loop through the raw image Color3 values

	-- Compress the RGB channels
	local CompressedR = math.floor((OldColour.R * 255) / CompressionAmount) * CompressionAmount
	local CompressedG = math.floor((OldColour.G * 255) / CompressionAmount) * CompressionAmount
	local CompressedB = math.floor((OldColour.B * 255) / CompressionAmount) * CompressionAmount

	CompressedColour = Color3.fromRGB(CompressedR, CompressedG, CompressedB)
	NewColours[i] = CompressedColour
end

ImageData.ImageColours = NewColours

Canvas:DrawImageXY(ImageData, 1, 1)
1 Like

What you’re meant to be doing is slowly loading all the images once, and then store them in a table, and then play the video based on that large table.

Once you have a table full of your loaded imageData, loop through that table and call DrawImageXY() for each object in that table

2 Likes

I have tried using imageData and its very slow at loading + I am not getting stable FPS its very jumpy. Any recommendations?

I am using the code by PixelatedCherry

Im new to using CanvasDraw and want to get smooth video playing. Thanks!

Heres my current code:

local Gui = game.Players.LocalPlayer.PlayerGui:WaitForChild("ScreenGui", 3)
local Frame = Gui.Frame

local CanvasDraw = require(script:WaitForChild("CanvasDraw", 3))

-- Load our image

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

-- Main

local CompressionAmount = 10 -- From 0 to 256 (The lower, more colours, the higher, less colours)

local NewColours = {}

local Frames = {}

for i, v in pairs(game.Workspace.Files:GetChildren()) do
	local ImageData = CanvasDraw.GetImageData(v)

	for i, OldColour in pairs(ImageData.ImageColours) do -- Loop through the raw image Color3 values
	
		-- Compress the RGB channels
		local CompressedR = math.floor((OldColour.R * 255) / CompressionAmount) * CompressionAmount
		local CompressedG = math.floor((OldColour.G * 255) / CompressionAmount) * CompressionAmount
		local CompressedB = math.floor((OldColour.B * 255) / CompressionAmount) * CompressionAmount
	
		CompressedColour = Color3.fromRGB(CompressedR, CompressedG, CompressedB)
		NewColours[i] = CompressedColour
	end
	
	print("Loaded frame number: " .. i)
	
	ImageData.ImageColours = NewColours
	
	table.insert(Frames, ImageData)
	task.wait()
end

for i, iFrame in pairs(Frames) do
	Canvas:DrawImageXY(iFrame, 1, 1)
	task.wait()
end

print("done")

The reason for the jumpy framerate is due to different colour positions and quantities.

This is pretty bad, you are loading and rendering large 256x256 images every frame (or heartbeat idk).

I don’t reccomend trying to do real-time videos at 256x256, and especially trying to do it on every task.wait(). it is just way too computational for roblox on the CPU to run smoothly.

The highest resolution I ever went for a GIF player of mine was 256x150 at 30FPS, which i’d say is a nice hard limit for CanvasDraw videos.

Try rendering the video at 30 or 25 FPS, at a lower resolution. it should be a more consistent and put way less stress on roblox.


Another thing too, is that you can crash or slow down the roblox client if you load up way too many high-resolution ImageData frames, for example, if you were to do a 256x256 video with 1000 frames, you would end up storing around 65 million Color3 values in tables, which will take up A LOT of memory.

The best way around this is just to stick to a lower resolution and framerate.

Yeah that does make sense. The video was already at 20 FPS and 7s long as a test.

Did you do anything special to get 256x150 at 30 FPS or is just using roughly the same code here?

Also are there any good resources for getting the wait times for 25 - 30 FPS?