--!native --!optimize 2 -- Compiled with roblox-ts v3.0.0 local TS = _G[script] local MathTools = TS.import(script, script.Parent, "math_tools").MathTools local ColorTools = {} do local _container = ColorTools --*packs color in binary form 32bit local function PackColor(r, g, b, a) return bit32.bor(bit32.bor(bit32.bor((r * 255), (bit32.lshift((g * 255), 8))), (bit32.lshift((b * 255), 16))), (bit32.lshift((a * 255), 24))) end _container.PackColor = PackColor --*packs color in binary form 32bit local function PackColor3(color) return PackColor(color.R, color.G, color.B, 1) end _container.PackColor3 = PackColor3 --*unpacks color from binary local UnpackColor local function UnpackColor3(packed_color) local r, g, b, a = UnpackColor(packed_color) return Color3.new(r, g, b) end _container.UnpackColor3 = UnpackColor3 --*unpacks color from binary function UnpackColor(packed_color) local r = (bit32.band(packed_color, 0xff)) / 255 local g = (bit32.rshift((bit32.band(packed_color, 0xff00)), 8)) / 255 local b = (bit32.rshift((bit32.band(packed_color, 0xff0000)), 16)) / 255 local a = (bit32.rshift((bit32.band(packed_color, 0xff000000)), 24)) / 255 return r, g, b, a end _container.UnpackColor = UnpackColor local min = math.min local max = math.max local pow = math.pow local abs = math.abs local sqrt = math.sqrt local atan2 = math.atan2 local pi = math.pi local angle_60 = pi / 3 local angle_120 = angle_60 * 2 local angle_240 = angle_120 * 2 local angle_300 = angle_60 * 5 local tau = pi * 2 --*@link https://medium.muz.li/the-science-of-color-contrast-an-expert-designers-guide-33e84c41d156 local function ConvertValueToLinearColorSpace(value) if value <= 0.03928 then return value / 12.92 end return pow((value + 0.055) / 1.055, 2.4) end local function ConvertToLinearColorSpace(color) local r = ConvertValueToLinearColorSpace(color.R) local g = ConvertValueToLinearColorSpace(color.G) local b = ConvertValueToLinearColorSpace(color.B) return r, g, b end local function GetRelativeLuminance(color) local r, g, b = ConvertToLinearColorSpace(color) return 0.2126 * r + 0.7152 * g + 0.0722 * b end _container.GetRelativeLuminance = GetRelativeLuminance local function CalculateContrastRatio(color_1, color_2) local luminance_1 = GetRelativeLuminance(color_1) local luminance_2 = GetRelativeLuminance(color_2) local max_luminance = max(luminance_1, luminance_2) local min_luminance = min(luminance_1, luminance_2) return (max_luminance + 0.05) / (min_luminance + 0.05) end _container.CalculateContrastRatio = CalculateContrastRatio local black = Color3.new(0, 0, 0) local white = Color3.new(1, 1, 1) --[[ * * @returns if the colors luminance if greater than .6 ]] local function IsBright(color) return GetRelativeLuminance(color) >= 0.6 end _container.IsBright = IsBright --[[ * * @returns black or white ]] local function GetSimpleContrastColor(color) return if IsBright(color) then black else white end _container.GetSimpleContrastColor = GetSimpleContrastColor --[[ * * @returns contrast rgb color ]] local function GetContrastColor(color) local r, g, b = ConvertToLinearColorSpace(color) local luminance = GetRelativeLuminance(color) local saturation = (max(r, g, b) - min(r, g, b)) / max(r, g, b) local alpha = 0.5 * (2 * r - g - b) local beta = sqrt(3) * 0.5 * (g - b) local hue = atan2(beta, alpha) return Color3.fromHSV(hue, saturation, luminance) end _container.GetContrastColor = GetContrastColor --[[ * * @link https://www.rapidtables.com/convert/color/rgb-to-hsv.html * * ]] local function RGBToHSV(r, g, b) local max_value = max(r, g, b) local min_value = min(r, g, b) local hue = -1 local saturation = -1 local difference = max_value - min_value --can be bigger than tau, so clamp if difference == 0 then hue = 0 elseif max_value == r then hue = (angle_60 * (((g - b) / difference) % 6)) % tau elseif max_value == g then hue = (angle_60 * ((b - r) / difference + 2)) % tau elseif max_value == b then hue = (angle_60 * ((r - g) / difference + 4)) % tau end if max_value == 0 then saturation = 0 else saturation = difference / max_value end local value = max_value --maps the hue from angle to value [0 - 1]; hue = MathTools.Map(hue, 0, tau, 0, 1, false) return hue, saturation, value end _container.RGBToHSV = RGBToHSV --[[ * * @link https://www.rapidtables.com/convert/color/hsv-to-rgb.html * * ]] local function HSVToRGB(h, s, v) --maps hue from [0 - 1] to angle h = MathTools.Map(h, 0, 1, 0, tau) local c = v * s local x = c * (1 - (abs(h / angle_60) % 2) - 1) local m = v - c local r = 0 local g = 0 local b = 0 if h < angle_60 then r = c g = x elseif h < angle_120 then r = x g = c elseif h < pi then g = c b = x elseif h < angle_240 then g = x b = c elseif h < angle_300 then r = c b = x else r = c b = x end r += m g += m b += m return r, g, b end _container.HSVToRGB = HSVToRGB --[[ * * @see https://github.com/littensy/rbxts-react-example/blob/main/src/client/utils/color-utils.ts * @param color The color to brighten or darken * @param brightness The amount to brighten or darken the color * @param vibrancy How much saturation changes with brightness ]] local function Brighten(color, brightness, vibrancy) if vibrancy == nil then vibrancy = 0.5 end local h, s, v = color:ToHSV() return Color3.fromHSV(h, math.clamp(s - brightness * vibrancy, 0, 1), math.clamp(v + brightness, 0, 1)) end _container.Brighten = Brighten --[[ * * @see https://github.com/littensy/rbxts-react-example/blob/main/src/client/utils/color-utils.ts * @param color The color to saturate or desaturate * @param saturation How much to add or remove from the color's saturation ]] local function Saturate(color, saturation) local h, s, v = color:ToHSV() return Color3.fromHSV(h, math.clamp(s + saturation, 0, 1), v) end _container.Saturate = Saturate end return { ColorTools = ColorTools, }