While messing around with EditableImages for the first time, I noticed the DrawLine function doesn’t have a thickness/radius parameter. Is there any great way to add thickness to a line, and if not, are they planning on adding the parameter?
The line is one pixel wide, so you can thicken it by drawing more parallel lines connecting two neighbouring pixels of p1 and p2.
I haven’t read about any plans for thickness param.
Would I do that by just increasing/decreasing the x and y by one?
Yes, by one, but depending on the angle either both or only x or y. For example, one pixel to the right or left (x-axis) on both points if the angle is 90 degrees.
At the moment I’m typing on mobile, so I can’t really experiment, but I’ve thickened the line like that before.
You can simply use :DrawRectangle()
DrawRectangle()
would be superb if only it could be rotated.
For that I wrote some code to draw it from parallel lines.
1. Using DrawLine()
Code
local function DrawThickLine(
p1: Vector2, p2: Vector2, color: Color3, transparency: number, thickness: number
): ()
local angle = math.atan2(p2.Y - p1.Y, p2.X - p1.X)
local side1 = math.ceil(thickness/2)
local side2 = math.floor(thickness/2)
for i=-side2, side1, 1 do
EditableImage:DrawLine(
p1 + Vector2.new(
math.round(i * math.cos(math.pi/2 + angle) - 0 * math.sin(math.pi/2 + angle)),
math.round(i * math.sin(math.pi/2 + angle) + 0 * math.cos(math.pi/2 + angle))
),
p2 + Vector2.new(
math.round(i * math.cos(math.pi/2 + angle) - 0 * math.sin(math.pi/2 + angle)),
math.round(i * math.sin(math.pi/2 + angle) + 0 * math.cos(math.pi/2 + angle))
),
color, transparency
)
end
end
Steps:
- Calculate the signed angle between the points.
- Place the lines on both sides of the base line (i = 0) as evenly as possible.
- Translate each new point around the original by the calculated angle.
→math.pi/2
rad respectively 90 degrees is added to the angle because the coordinate system is rotated and the origin is in the top left corner.
DrawLine()
performs anti-aliasing on each line. If you would for whatever reason like to avoid it, you should probably draw each pixel of the line yourself using WritePixels()
.
In action:
2. Using WritePixels() and Bresenham line drawing algorithm
→ (Optional and most likely not necessary.)
Bitmap images consist of square pixels. This algorithm aims for a precise approximation of pixels to draw to in order to render as straight line as possible between two pixels. Roblox engine probably uses it internally too.
I took the derivation from Wikipedia - Bresenham’s line algorithm to cover positive and negative slopes.
Code
local function PlotBresenhamLineLow(x1: number, y1: number, x2: number, y2: number): {Vector2}
local pixels = {}
local dx, dy = x2 - x1, y2 - y1
local yi = 1
local D: number, y: number
if dy < 0 then
yi = -1
dy = -dy
end
D = 2 * dy - dx
y = y1
for x=x1, x2, (if x1 < x2 then 1 else -1) do
table.insert(pixels, Vector2.new(x, y))
if D > 0 then
y += yi
D = D + 2 * (dy - dx)
else
D = D + 2 * dy
end
end
return pixels
end
local function PlotBresenhamLineHigh(x1: number, y1: number, x2: number, y2: number): {Vector2}
local pixels = {}
local dx, dy = x2 - x1, y2 - y1
local xi = 1
local D: number, x: number
if dx < 0 then
xi = -1
dx = -dx
end
D = 2 * dx - dy
x = x1
for y=y1, y2, (if y1 < y2 then 1 else -1) do
table.insert(pixels, Vector2.new(x, y))
if D > 0 then
x += xi
D = D + 2 * (dx - dy)
else
D = D + 2 * dx
end
end
return pixels
end
local function PlotBresenhamLine(p1: Vector2, p2: Vector2): {Vector2}
local x1, y1, x2, y2 = p1.X, p1.Y, p2.X, p2.Y
if math.abs(y2 - y1) < math.abs(x2 - x1) then
if x1 > x2 then
return PlotBresenhamLineLow(x2, y2, x1, y1)
else
return PlotBresenhamLineLow(x1, y1, x2, y2)
end
else
if y1 > y2 then
return PlotBresenhamLineHigh(x2, y2, x1, y1)
else
return PlotBresenhamLine(x1, y1, x2, y2)
end
end
end
local function DrawThickLine(
p1: Vector2, p2: Vector2, color: Color3, transparency: number, thickness: number
): ()
local angle = math.atan2(p2.Y - p1.Y, p2.X - p1.X)
local side1 = math.ceil(thickness/2)
local side2 = math.floor(thickness/2)
local formattedColor = {color.R, color.G, color.B, 1}
local pixelSize = Vector2.new(1,1)
for i=-side2, side1, 1 do
local pixels = PlotBresenhamLine(
p1 + Vector2.new(
math.round(i * math.cos(math.pi/2 + angle) - 0 * math.sin(math.pi/2 + angle)),
math.round(i * math.sin(math.pi/2 + angle) + 0 * math.cos(math.pi/2 + angle))
),
p2 + Vector2.new(
math.round(i * math.cos(math.pi/2 + angle) - 0 * math.sin(math.pi/2 + angle)),
math.round(i * math.sin(math.pi/2 + angle) + 0 * math.cos(math.pi/2 + angle))
)
)
for _,pixel in pixels do
EditableImage:WritePixels(pixel, pixelSize, formattedColor)
end
end
end
Using this option is a trade-off between avoiding anti-aliasing and performance. I’m sure the code can be optimised in the slowest link - the drawing - by attempting to form pixels into larger areas and reducing the number of calls. Nevertheless, that is way too complicated for a readable example.
In action:
Looking at the second image, I’m thinking that in case of opting for aliasing, another option is to only WritePixels()
to outermost pixels.
Edit @SkrubDaNub
When I give a post a like or reply in a topic, I personally prefer to keep track or be notified of new replies to see if anyone has given any additional info to what I said or liked. Since this thread is small with only the OP, Aqua, and me, and you liked the reply I’m replying to with some more info, I thought it fair to treat you as a minor participant and ping you. I hope you don’t mind. I suppose it wasn’t really necessary.
erm, hello. Why have you summoned me?