Colours to hex/ints to save bytes when posting to DataStore

I’m currently working on a game that saves a significant amount of data to the DataStore. Any attempt at reducing the amount of data you transfer back and forth to the DataStore will be beneficial to your users, and thus, you - mobile users will be especially grateful.

Let’s take a look at the differences in the byte sizes of the JSON encoded strings you might save:

Output:

 Memory sizes of colour JSON:
  	JSON [1] with '{"col":"FF960A"}' cost: 16 bytes
  	JSON [2] with '{"col":16750090}' cost: 16 bytes
  	JSON [3] with '{"col":250150010}' cost: 17 bytes
  	JSON [4] with '{"col":"250,150,10"}' cost: 20 bytes
  	JSON [5] with '{"col":[255,150,10]}' cost: 20 bytes
  	JSON [6] with '{"col":[1,0.588235318660736083984375,0.0392156876623630523681640625]}' cost: 69 bytes
  
Hex [FF960A] can be coverted back to Col -> 255, 150, 10
Int [16750090] can be coverted back to Col -> 255, 150, 10

  Int->Hex: FF960A	|	Hex->Int 16750090

Code:

-- set up our env
local http   = game:GetService('HttpService')
local memory = require(script.mem)
local rgbhex = require(script.rgbhexint)
for i, v in next, rgbhex do
	getfenv()[i] = v
end

-- collect our strings to compare
local colour  = Color3.fromRGB(255, 150, 10)
local strings = {
	http:JSONEncode {col = {colour.r, colour.g, colour.b}}; -- What if we save it as an array without keys?
	http:JSONEncode {col = rgbFromCol(colour)};							-- What if we save it as an array from 0 - 255?
	http:JSONEncode {col = '250,150,10'};										-- What if we save it as a string with a delim?
	http:JSONEncode {col = 250150010};											-- We know each rgb will be 0 - 255, so what if we save it as a 9 digit number?
	http:JSONEncode {col = rgb2int(colour)};								-- As an int?
	http:JSONEncode {col = rgb2hex(colour)};								-- As a hex code?
}

-- sort in order of bytesize & print info
table.sort(strings, function (a, b)
	return memory:getSize(a) < memory:getSize(b)
end)

print 'Memory sizes of colour JSON:'
table.foreach(strings, function (i, json)
	print(
		('\tJSON [%d] with \'%s\' cost: %s bytes'):format(i, json, memory:getSize(json))
	)
end)

-- demonstrate hex conversion
local hex = rgb2hex(colour)
print(
	('\rHex [%s] can be coverted back to Col ->'):format(hex),
	table.concat(rgbFromCol(hex2rgb(hex)), ', ')
)

-- demonstrate int conversion
local int = rgb2int(colour)
print(
	('Int [%s] can be coverted back to Col ->'):format(int),
	table.concat(rgbFromCol(int2rgb(int)), ', ')
)

-- demonstrate hex-int conversions
print(
	('Int->Hex: %s\t|\tHex->Int %s'):format(int2hex(int), hex2int(int2hex(int)))
)

As you can see, saving the colour as an int/hex has saved us 53 bytes! This might seem inconsequential but if you’re saving masses of data (e.g. games like Bloxburg) you’re going to be reducing the size of your JSON packets significantly.

Here’s a placefile with the scripts:colourdata.rbxl (20.1 KB)

and here’s the relevant code for conversions:

local module = { }

-- helper
function module.rgbFromCol(rgb)
	if typeof(rgb) ~= 'Color3' then
		return warn 'Color3 object not passed'
	end
	if rgb.r > 1 then
		return {rgb.r, rgb.g, rgb.b}
	else
		return {math.floor(rgb.r*255), math.floor(rgb.g*255), math.floor(rgb.b*255)}
	end
end

-- hex
function module.rgb2hex(rgb)
	rgb = typeof(rgb) == 'table' and rgb or (typeof(rgb) == 'Color3' and module.rgbFromCol(rgb) or nil)
	if rgb then
		local hex = ''
		for _, v in next, rgb do
			local h = ''
			while (v > 0) do
				local i = math.fmod(v, 16) + 1
				v = math.floor(v / 16)
				h = ('0123456789ABCDEF'):sub(i, i) .. h
			end
			h = #h == 0 and '00' or (#h == 1 and '0' .. h or h)
			hex = hex .. h
		end
		return hex
	end
end

function module.hex2rgb(hex)
    return Color3.fromRGB(tonumber('0x' .. hex:sub(1, 2)), tonumber('0x' .. hex:sub(3, 4)), tonumber('0x' .. hex:sub(5, 6)))
end

function module.hex2int(hex)
	return tonumber(hex, 16)
end

-- int
function module.rgb2int(rgb)
	rgb = typeof(rgb) == 'table' and rgb or (typeof(rgb) == 'Color3' and module.rgbFromCol(rgb) or nil)
	return rgb[1]*256^2 + rgb[2]*256 + rgb[3]
end

function module.int2rgb(int)
	return Color3.fromRGB(
		math.floor(int/256^2) % 256,
		math.floor(int/256) % 256,
		math.floor(int) % 256
	)
end

function module.int2hex(int)
	local hex = ('%x'):format(int * 256):upper()
	return hex:sub(1, #hex - 2)
end


return module
16 Likes