Optimization: Color Compression Module

Hello, I have wrotten a module for compressing rgb colors into a string so I can save them into a datastore and save characters out of the 4,000,00 limit. When using this code and hoping to cycle through say a million characters though, it is quite laggy and I am hoping by posting it here that we can optimize it and make it more efficent.

Here is a quick overview of what my code does for its compression:
For the first character it is essentially 3 digit binary defining each r, g and b to start at 0 or 127, (0 being 0, 1 being 127)
The following 3 characters are utf8 characters 0 - 127 representing each values 0 - 127

Here is a quick overview of what my code does for its decompression:
It takes the string and transfers the first character into 3 Booleans for if I will add 127 to r, g, or b.
Afterwards it gets the utf8 character code and then based off the Boolean it will add 0 or 128.

local colorcompress = {}

function colorcompress:compress(rgbtable)
	local compressed = ""
	for i, color in pairs(rgbtable) do
		local r = math.floor(color.r * 255)
		local g = math.floor(color.g * 255)
		local b = math.floor(color.b * 255)

		local r_dark = r > 127
		local g_dark = g > 127
		local b_dark = b > 127

		local _3bitid = 0
		if r_dark then _3bitid = _3bitid + 1 r = r - 128 end
		if g_dark then _3bitid = _3bitid + 2 g = g - 128 end
		if b_dark then _3bitid = _3bitid + 4 b = b - 128 end
		
		compressed = compressed .. tostring(_3bitid) .. utf8.char(r, g, b)
	end
	return compressed
end

function colorcompress:decompress(compressedcolorstring)
	local rgbtable = {}

	for currentSet = 0, #compressedcolorstring / 4 - 1 do
		local start = currentSet * 4 + 1
		local stop = (currentSet + 1) * 4

		local set = string.sub(compressedcolorstring, start, stop)
		local _3bitid = tonumber(string.sub(set,1,1))

		local r_dark = (_3bitid % 4 % 2) / 1 >= 1
		local g_dark = (_3bitid % 4) / 2 >= 1
		local b_dark = (_3bitid) / 4 >= 1

		local r = string.sub(set,2,2)
		local g = string.sub(set,3,3)
		local b = string.sub(set,4,4)

		for i = 0, 127 do
			if utf8.char(i) == r then r = i end 
			if utf8.char(i) == g then g = i end
			if utf8.char(i) == b then b = i end
		end
		
		if r_dark then r = r + 128 end
		if g_dark then g = g + 128 end
		if b_dark then b = b + 128 end

		table.insert(rgbtable, Color3.fromRGB(r,g,b))
	end

	return rgbtable
end

return colorcompress

I would recommend to make the methods static, since it does not look like you will ever instantiate the ‘colorcompress’ table anyway.

function colorcompress.compress(rgbtable)