i figured i could make a procedural fire system by manually manipulating the pixels so i need the background to be invisible
As of right now, you can simulate transparency using alpha blending.
Here are some (admittedly rather crude) examples of what that might look like:
local function drawAlphaBlendedPixel(canvas: any, position: Vector2, sourceColor: Color3, sourceAlpha: number)
sourceAlpha = math.clamp(sourceAlpha, 0, 1)
--Get the color of the pixel at our destination
local destColor = canvas:GetPixel(position)
--Destination alpha is inverse of source alpha
local destAlpha = 1 - math.clamp(sourceAlpha, 0, 1)
--Perform weighted averages of each color value
local blendR = (sourceColor.R * sourceAlpha) + (destColor.R * destAlpha)
local blendG = (sourceColor.G * sourceAlpha) + (destColor.G * destAlpha)
local blendB = (sourceColor.B * sourceAlpha) + (destColor.B * destAlpha)
--Draw pixel using averaged color values
canvas:SetPixel(position.X, position.Y, Color3.new(blendR, blendG, blendB))
end
local function getAlphaBlendedColorAtPosition(canvas: any, position: Vector2, sourceColor: Color3, sourceAlpha: number)
sourceAlpha = math.clamp(sourceAlpha, 0, 1)
--Get the color of the pixel at our destination
local destColor = canvas:GetPixel(position)
--Destination alpha is inverse of source alpha
local destAlpha = 1 - math.clamp(sourceAlpha, 0, 1)
--Perform weighted averages of each color value
local blendR = (sourceColor.R * sourceAlpha) + (destColor.R * destAlpha)
local blendG = (sourceColor.G * sourceAlpha) + (destColor.G * destAlpha)
local blendB = (sourceColor.B * sourceAlpha) + (destColor.B * destAlpha)
--Get new color
return Color3.new(blendR, blendG, blendB)
end
I might write a more detailed tutorial on this and other types of color blending later.
but how would this makes the pixel transparent? i am not very smart and i just see some color manipulation. i mean like in a 3d environment where i wanna make a billboard fire
Unfortunately, it doesnât. It just creates the appearance of transparency.
The only other option I can think of for your situation is either manipulating the Transparency property of individual gradients, or something involving raycasting/tracing.
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.
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
}
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()
andCanvasDraw.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()
andGetPixelFromImageXY()
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)
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)