Created
January 16, 2025 21:26
-
-
Save EgoMoose/d82214f96f974eee7314d99ab7e4a4fd to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
--!strict | |
local AssetService = game:GetService("AssetService") | |
local THICKNESS = 5 | |
local RGBA = { 255, 255, 255, 255 } | |
local NEIGHBOR_OFFSETS: { { number } } = { | |
{ -1, -1 }, | |
{ 0, -1 }, | |
{ 1, -1 }, | |
{ -1, 0 }, | |
{ 1, 0 }, | |
{ -1, 1 }, | |
{ 0, 1 }, | |
{ 1, 1 }, | |
} | |
local function outlineBuffer(imgBuffer: buffer, width: number, height: number, thickness: number, rgba: {number}) | |
local result = buffer.create(width * height * 4) | |
buffer.copy(result, 0, imgBuffer) | |
local function readRGBA(index: number) | |
local r = buffer.readu8(result, index + 0) | |
local g = buffer.readu8(result, index + 1) | |
local b = buffer.readu8(result, index + 2) | |
local a = buffer.readu8(result, index + 3) | |
return r, g, b, a | |
end | |
local function writeRGBA(index: number, r: number, g: number, b: number, a: number) | |
buffer.writeu8(result, index + 0, r) | |
buffer.writeu8(result, index + 1, g) | |
buffer.writeu8(result, index + 2, b) | |
buffer.writeu8(result, index + 3, a) | |
end | |
local queue = {} | |
local queueFront = 1 | |
local queueLength = 0 | |
local function pushBack(value: number) | |
queue[queueFront + queueLength] = value | |
queueLength = queueLength + 1 | |
end | |
local function popFront() | |
local value = queue[queueFront] | |
queue[queueFront] = nil | |
queueFront = queueFront + 1 | |
queueLength = queueLength - 1 | |
return value | |
end | |
local function getNeighbors(index: number) | |
local j = index / 4 | |
local y = math.floor(j / width) | |
local x = j - y * width | |
local neighbors = {} | |
for _, offset in NEIGHBOR_OFFSETS do | |
local nx = x + offset[1] + 1 | |
local ny = y + offset[2] + 1 | |
if nx > 0 and nx <= width and ny > 0 and ny <= height then | |
local k = ((nx - 1) + (ny - 1) * width) * 4 | |
table.insert(neighbors, k) | |
end | |
end | |
return neighbors | |
end | |
local visited = {} | |
for x = 1, width do | |
for y = 1, height do | |
local index = ((x - 1) + (y - 1) * width) * 4 | |
local _r, _g, _b, a = readRGBA(index) | |
if a == 255 then | |
visited[index] = true | |
continue | |
end | |
local hasFullyOpaqueNeighbor = false | |
for _, neighborIndex in getNeighbors(index) do | |
local _nr, _ng, _nb, na = readRGBA(neighborIndex) | |
if na == 255 then | |
hasFullyOpaqueNeighbor = true | |
break | |
end | |
end | |
if hasFullyOpaqueNeighbor then | |
visited[index] = true | |
pushBack(index) | |
end | |
end | |
end | |
for _ = 1, thickness do | |
for _ = 1, queueLength do | |
local poppedIndex = popFront() | |
writeRGBA(poppedIndex, rgba[1], rgba[2], rgba[3], rgba[4]) | |
for _, neighborIndex in getNeighbors(poppedIndex) do | |
if not visited[neighborIndex] then | |
pushBack(neighborIndex) | |
visited[neighborIndex] = true | |
end | |
end | |
end | |
end | |
return result | |
end | |
local function outlineEditImage(editImage: EditableImage) | |
local imgSize = editImage.Size | |
local imgBuffer = editImage:ReadPixelsBuffer(Vector2.zero, imgSize) | |
local outlinedBuffer = outlineBuffer(imgBuffer, imgSize.X, imgSize.Y, THICKNESS, RGBA) | |
local outlinedEditImage = AssetService:CreateEditableImage({ Size = imgSize }) | |
outlinedEditImage:WritePixelsBuffer(Vector2.zero, imgSize, outlinedBuffer) | |
return outlinedEditImage | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
A few examples:


