Give images strokes, with Outline!

Outline

Let's you add an outline to your images

Get it from the library



There is not a lot of pros using this module, it's more like a concept.

Please note that the image can’t be transparent, since currently how the module works is that it clones the image and offsets it 8 times.

Updates:

  • Added outline:rebake()

API:

  • outline.new()
  • outline:destroy()


outline.new(config)

outline.new(config) is used to create an outline around an image.

The config table can have these properties:

Property name Must-have Default value Description
Object yes none This is what the module will use to make the outline of, preferably the image that you want to have an outline
Size no 3 How thick the outline will be (recommended size between 1-25)
Parent no object Where the module will parent the outlines
Data no default data How the module will construct the outlines
Sides no 8 How many positional data is in data and the duration of the loop
Rotation no 0 How much the module should rotate the outlines
Customize no color: black, gradient: true Table, 2 properties: Color: Color3 and Gradient: boolean. Color stands for the outline’s color, gradient stands for to use gradient to paint the outline, or set ImageColor3

outline:rebake()

'Rebakes' the outlines, to match the updated image

outline:destroy()

Destroys the outlines, and cleans up the class to be later garbage collected.

How a custom data table should look like:

local myCustomData = {
   [1] = {"%s", "%s"}
}

[1] - the index of the positional data
{"%s", “%s”} - %s later on will be replaced by the Size property, currently these are the only supported data values:

  • %s
  • -%s
  • 0

Currently the module does not support math operations.
Source code
--[[

	Outline
	
	Not the best solution to give an image an outline, but at least it somewhat works.

	Config:

		Name:					Must-have:	Default value:	Description:

		Object: GuiBase2D		yes			none			Module will use as the clone template
		Size: number			no			3				How thicc the outline will be
		Parent: Instance		no			Object			Where to parent it
		Data: table				no			DEFAULT_DATA	Positional data
		Sides: number			no			8				For the loop and for the positional data
		Rotation: number		no			0				How much the module should rotate the outlines
		Customize: table
			Color: Color3		no			black			What color
			Gradient: boolean	no			true			Use gradient or use ImageColor3


    API:

    outline.new()
	outline:rebake
    outline:destroy()

]]

local SIDES = 8
local ALLOWED_PROPERTY_TO_UPDATE = {
	"Image",
	"ImageRectOffset",
	"ImageRectSize",
	"ImageTransparency",
}
local NOT_ALLOWED_INSTANCES = {
	"Script",
	"ModuleScript",
	"LocalScript",
}
local DEFAULT_DATA = {
	[1] = { "-%s", "0" },
	[2] = { "-%s", "-%s" },
	[3] = { "0", "-%s" },
	[4] = { "%s", "-%s" },
	[5] = { "0", "%s" },
	[6] = { "%s", "%s" },
	[7] = { "-%s", "%s" },
	[8] = { "%s", "0" },
}

local function createNewFrame(self)
	local clone = self.template:Clone()
	clone.Size = UDim2.fromScale(1, 1)
	clone.Position = UDim2.fromScale(0.5, 0.5)
	clone.ZIndex = 0
	clone.AnchorPoint = Vector2.new(0.5, 0.5)
	clone.BorderSizePixel = 0
	clone.Name = "Outline"
	clone.Rotation = self.rotation

	if self.useGradient then
		local gradient = Instance.new("UIGradient")
		if typeof(self.color) == "ColorSequence" then
			gradient.Color = self.color
		else
			gradient.Color = ColorSequence.new(self.color, self.color)
		end
		gradient.Parent = clone
	else
		clone.ImageColor3 = self.color
	end

	clone.Parent = self.outlineParent
	return clone
end

local function checkIfNotZero(dirString)
	if dirString == "0" then
		return 0
	else
		return tonumber(dirString)
	end
end

local function convertString(dataString, size)
	local formatted = string.format(dataString, tostring(size))
	local converted = checkIfNotZero(formatted)
	return converted
end

local function renderize(self)
	-- "bake" the lines into the image
	for side = 1, self.sides do
		if self.data[side] then
			local clone = createNewFrame(self)
			local pos = clone.Position
			local dirData = self.data[side]

			local x = convertString(dirData[1], self.outlineSize)
			local y = convertString(dirData[2], self.outlineSize)

			pos += UDim2.fromOffset(x, y)
			clone.Position = pos

			self.lines[#self.lines + 1] = clone
		else
			warn("No dir data found!")
		end
	end
	self.template:Destroy()
end

local function rerenderize(self)
	for _, obj in ipairs(self.lines) do
		for _, allowedProperty in ipairs(ALLOWED_PROPERTY_TO_UPDATE) do
			obj[allowedProperty] = self.obj[allowedProperty]
		end
	end
end

local function checkIfExists(data, value)
	if data then
		return data[value]
	else
		return nil
	end
end

local outline = {}
outline.__index = outline

function outline.new(config)
	local self = {}
	---
	self.obj = config.Object or error("Must-have property: Object is missing!")
	self.outlineSize = config.Size or 3
	self.outlineParent = config.Parent or self.obj
	self.data = config.Data or DEFAULT_DATA
	self.sides = config.Sides or SIDES
	self.rotation = config.Rotation or 0

	self.color = checkIfExists(config.Customize, "Color") or Color3.fromRGB(0, 0, 0)
	self.useGradient = checkIfExists(config.Customize, "Gradient") or true

	self.template = self.obj:Clone()

	-- makes sure that no scripts get cloned, or else it will create a recursion
	for _, instances in ipairs(self.template:GetDescendants()) do
		for _, notAllowedClass in ipairs(NOT_ALLOWED_INSTANCES) do
			if instances:IsA(notAllowedClass) then
				instances:Destroy()
			end
		end
	end

	self.lines = {}

	-- makes sure that the ZIndex behavior is global
	local screenGui = self.obj:FindFirstAncestorOfClass("ScreenGui")
	if screenGui.ZIndexBehavior ~= Enum.ZIndexBehavior.Global then
		screenGui.ZIndexBehavior = Enum.ZIndexBehavior.Global
	end

	-- render the outlines
	renderize(self)
	---
	return setmetatable(self, outline)
end

function outline:rebake()
	rerenderize(self)
end

function outline:destroy()
	self.obj = nil
	self.outlineParent = nil
	self.data = nil

	for _, v in ipairs(self.lines) do
		v:Destroy()
	end
	for _, v in ipairs(self.changedEvents) do
		v:Disconnect()
	end
end

return outline

Example
local outline = require(game.ReplicatedStorage.Outline) -- replace to the path of the module

local players = game:GetService("Players")
local player = players.LocalPlayer
local playerGui = player:WaitForChild("PlayerGui")

local myScreenGui = Instance.new("ScreenGui")
myScreenGui.Name = "Outline showcase"
myScreenGui.Parent = playerGui

local original = Instance.new("ImageLabel")
original.Name = "Original"
original.AnchorPoint = Vector2.new(0.5, 0.5)
original.BackgroundTransparency = 1
original.Position = UDim2.fromScale(0.454, 0.5)
original.Size = UDim2.fromScale(0.091, 0.195)
original.Image = "rbxassetid://7893601012"
original.ScaleType = Enum.ScaleType.Fit
original.Parent = myScreenGui

local withOutline = Instance.new("ImageLabel")
withOutline.Name = "With outline"
withOutline.AnchorPoint = Vector2.new(0.5, 0.5)
withOutline.BackgroundTransparency = 1
withOutline.Position = UDim2.new(0.546, 0, 0.5, 0)
withOutline.Size = UDim2.fromScale(0.091, 0.195)
withOutline.Image = "rbxassetid://7893601012"
withOutline.ScaleType = Enum.ScaleType.Fit
withOutline.Parent = myScreenGui


local myOutlineClass = outline.new({
	Object = withOutline,
	Size = 3,
})

Result:

showcase


Have fun using the module!
13 Likes

I could just go into a paint software and create a good outline for an imagine like what you are using for the result.

2 Likes

I was really hoping that roblox would add that! But at the same time I could do what @Modxno said. But this is sick! Thx for making this!!

Edit: nvm, all this does is add a duplicate image of the previous image. It also isn’t that good. If you have an image that isn’t transparent it’ll break and cover the whole image. You’d be better off going into a paint software and doing it there.

You’re not wrong. Your way of making it is much more simpler and cleaner. But I thought that this module can come in handy if you don’t want to go make the image have an outline outside robox.

Mostly this could come in handy if you’re making a 2D game and you don’t want to make an outline version of every single image.

1 Like

Sadly there isn’t much more of a work around doing it. It’s the best what you can do to add outlines to images without external paint software

I do think this is a really cool feature roblox should implement into the studio.

1 Like

Is this in real - time?, because it could be used to add outlines in moving sprites.

Well, it can work in real time, since how currently the module works is that it copies the image and then offsets it, to mimic an outline, then parents it to the image. (I’m aware that it’s not the best solution, and I’m trying to figure out a different way of doing it)

However in the screengui you should change the ZIndexBehaviour to global.

1 Like