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

My code is heavily based off of this video https://www.youtube.com/watch?v=Qz0KTGYJtUk
(Parts are converted into meshes)

I’m using a i7-8850H (up to 4.3ghz, 6 cores 12 threads)
It runs on several threads at once using actors, and I plan to figure out a way to autodetect the optimal number of threads to use depending on the users hardware
image
Code uses strict mode and the native code gen beta
image
All Vector3s have been replaced with 3 number variables, as its MUCH faster when using native code gen at the moment

The resolution was 620x414, and the final render was supposed to have 480 samples per pixel (1 sample per pixel for each frame, 480 frames), but that screenshot was with only 78 samples, and estimated 9 minutes left (though I was running a cpu consuming task in the background so likely inaccurate)

Here’s me rendering a scene similar to yours, at 340x200 (1 sample * 60 frames * 8 threads = 480 samples per pixel)
Not sure why you’re getting less noise with less samples

6 Likes

will this ever support like decals like editable image does?
image

1 Like

yeah definitely something i can do. I will consider this for the next version!

2 Likes

I just made an update to the internal FastCanvas module that manages the pixel placement and EditableImage work.

Applying this update to CanvasDraw might take a bit to do, so in the mean time, you can use this module to work with both Frames, and Decals, Textures, MeshParts, etc.

The API only has barebones SetColor3, SetRGB, etc.


I am hoping to release this change to CanvasDraw sometime within a month

2 Likes

CanvasDraw support for Decals, Textures, MeshParts and SurfaceAppearence is going really well!

8 Likes

Hello.
How would you resize the canvas in code?

Canvas:Destroy()
Canvas = CanvasDraw.new(Frame, NewSize)

Something like this should work

1 Like

Ooo…


I settled to this workaround. using this very convenient function CreateBlankImageData I copy the colors and alphas to the given pixels

local RectOffsetX, RectOffsetY, RectSizeX, RectSizeY = quad[1], quad[2], quad[3], quad[4]
local SplicedImageData = CanvasDraw.CreateBlankImageData(RectSizeX, RectSizeY)

for y = 1, RectSizeY do
	for x = 1, RectSizeX do
		SplicedImageData:SetPixel(x, y, drawable.image:GetPixelXY(RectOffsetX + x, RectOffsetY + y))
	end
end
1 Like

Thats similar to how i probably would’ve coded it! I do plan on adding proper spritesheet/rect offset stuff soon!

Cool to see a work around!

1 Like

Actually, completely scratch what I just sent. It is a poorly optimized method.

I have devised a better strategy, doesn’t need you to clone anything:

function Canvas:DrawDistortedImageRect(PointA: Vector2, PointB: Vector2, PointC: Vector2, PointD: Vector2, RectOffset, RectSize, ImageData: {}, Brightness: number?)
	Canvas:DrawDistortedImageRectXY(
		PointA.X, PointA.Y, PointB.X, PointB.Y, PointC.X, PointC.Y, PointD.X, PointD.Y,
		RectOffset, RectSize,
		ImageData, Brightness
	)
end

function Canvas:DrawDistortedImageRectXY(X1, Y1, X2, Y2, X3, Y3, X4, Y4, RectOffset, RectSize, ImageData: {}, Brightness: number?)
	local U1, V1 = RectOffset.X / ImageData.Width, RectOffset.Y / ImageData.Height
	local U2, V2 = (RectOffset.X + RectSize.X) / ImageData.Width, RectOffset.Y / ImageData.Height
	local U3, V3 = (RectOffset.X + RectSize.X) / ImageData.Width, (RectOffset.Y + RectSize.Y) / ImageData.Height
	local U4, V4 = RectOffset.X / ImageData.Width, (RectOffset.Y + RectSize.Y) / ImageData.Height

	Canvas:DrawTexturedTriangleXY(
		X1, Y1, X2, Y2, X3, Y3,
		U1, V1, U2, V2, U3, V3,
		ImageData, Brightness
	)

	Canvas:DrawTexturedTriangleXY(
		X1, Y1, X4, Y4, X3, Y3,
		U1, V1, U4, V4, U3, V3,
		ImageData, Brightness
	)
end

local x, y, width, height = 0,0, 320, 320
local currentsize = Vector2.new(.1, .1)
while true do
	local dt = RunService.Heartbeat:Wait()
	local PointA = Vector2.new(x, y)  -- Top-left
	local PointB = Vector2.new(x + width, y)  -- Top-right
	local PointC = Vector2.new(x + width, y + height)  -- Bottom-right
	local PointD = Vector2.new(x, y + height)  -- Bottom-left
	Canvas:DrawDistortedImageRect(PointA, PointB, PointC, PointD, Vector2.new(), currentsize, ImageData)
	currentsize = currentsize:Lerp(Vector2.new(ImageData.Width, ImageData.Height), .1)
end

1 Like

Oh very cool!

I haven’t considered that, and also if you’re doing real-time stuff like this interpolation loop, yeah, you’re gonna wanna something fast. It could be much faster if you didnt use the DrawDistortedImage() method as that has a lot of math and pixel translations involved.

In most cases, you’re never going to rotate or distort the image, so you can just use a simple for loop in the X and Y directions and just draw the pixels within the rect size and position.

I will make an official draw image rect method soon!


EDIT:

You gave me an idea. I’m going to be adding a DrawRotatedImage method, as well!

Also I plan to make the API look something like this:

Canvas:DrawRotatedImageXY(
    ImageData, 
    math.rad(45), -- Angle 
    64, 64, -- Placement position
    0.5, 0.5, -- Position/Rotation anchor point
    0.5, 0.5 -- Image scale
)

Canvas:DrawImageRectXY(
    ImageData, 
    1, 1, -- Rect offset
    16, 16, -- Rect size
    0.5, 0.5 -- Image scale
)

-- DrawImageRect() will use Vector2 values

2 Likes

Looks awesome!
Would it be possible to add the rotation property onto DrawImageRectXY too? It would better suit my usecase of importing maps from Tiled to CanvasDraw

1 Like

Yeah sure! Can definitely do that since I’m using DrawTexturedTriangle internally

2 Likes

Unrelated, I’ve been working on a framework that is a simple fork of Love2D (dubbed Love2C, abbreviated Love2Canvas) and I was wondering how would someone go from making a roblox module to a github repo? Is there any known steps in doing this or is it something I’d have to make manually?

1 Like

When are you dropping this? Excited to play around with it :fire:

I plan to drop the update tonight or tomorrow!

CanvasDraw Update - v4.2.0b

  • Added canvas support for Decal, Texture, MeshPart and SurfaceAppereance

  • Added Canvas:DrawRotatedImage(), which lets you draw a rotated image with a pivot point

  • Added Canvas:DrawImageRect(), which lets you draw specific parts of an image.
    Contributed by @DukeAunarky

  • Added two new ImageData methods: ImageData:GetRGBA(), ImageData:SetRGBA()

  • Heavily optimised the Canvas:Clear() method. This method now runs much faster.

  • Added ImageData:Freeze() method. This method will convert the ImageData to a read-only object.
    This will make fetching pixels faster and more efficient, but will remove the ability to modify the image

  • Fixed Canvas:DrawDistortedImage() having the wrong rotation.

4 Likes

Can you make it open source?, or how to do that graphics?

1 Like

Howdy. I’ve been using the new rect implementation on a few Tiled test maps and I have been having some trouble getting the function to work properly after a couple of hours.

Some funnies that has happened:

RobloxStudioBeta_wfWtYsyP5H
RobloxStudioBeta_ZhLJHMNPx2

The thing is, it would actually give me the right RectOffset and RectSize variables. For example the sky would correctly return RectOffset 204, 102 with a RectSize of 16, 16. But actually making the final call to draw it had me stuck for a while, and hardcoding the decimal variables for these 16x16 tiles looks ugly as heck, could you help me out with what is the appropriate setup to use for this?

This is the call I used, Note that I actually modified the last arguments of the function in CanvasDraw to set ImageSizeX and ImageSizeY directly because the original implementation would have me doing RectSizeX/ImageData.Width and I thought it didnt look pretty and would’ve been better to set the required ImageSize directly.

Description:
x: x Position
y: y Position
RectOffsetX: Offset X, in Pixels
RectOffsetY: Offset Y, In Pixels
RectSizeX: Rect Size, In Pixels
RectSizeY: Rect Size, In Pixels
sx, sy: Image Scale, In decimal range of (0, 1)

Canvas:DrawImageRectXY(ImageData, x, y, (RectOffsetX/RectSizeX)*1.25, (RectOffsetY/RectSizeY)*1.25, sx+.2, sy+.2, RectSizeX, RectSizeY)

This is the tilesheet used for testing
SMB-Tiles

1 Like