# Color3.fromHSV and Color3.toHSV do not map to each other

When using both `Color3.fromHSV` and `Color3.toHSV` I noticed that the domain and the codomain do not map to each other when they should since these functions are inverses of each other.

For example:

``````local color = Color3.fromHex("cc7245")
local sameColor = Color3.fromHSV(color:ToHSV())

print(color:ToHex()) -- cc7245
print(sameColor:ToHex()) -- cc7244 ????
``````

In fact, if you iterate over the colors on the RGB color block you’ll find that the two functions properly map to each other only ~42% of the time:

``````local colorCount = 0
local matchedHSVCount = 0

for r = 0, 255 do
for g = 0, 255 do
for b = 0, 255 do
local color = Color3.fromRGB(r, g, b)

local h, s, v = color:ToHSV()
local hsvColor = Color3.fromHSV(h, s, v)

if color:ToHex() == hsvColor:ToHex() then
matchedHSVCount = matchedHSVCount + 1
end

colorCount = colorCount + 1

if colorCount % 1000000 == 0 then
end
end
end
end

print("Total Colors:", colorCount) -- 16777216
print("Matched Count:", matchedHSVCount) -- 7118997
print("Unmatched Count:", colorCount - matchedHSVCount) -- 9658219
print("Percentage matched: %" .. (matchedHSVCount / colorCount) * 100) -- %42.43252873420715
``````

These functions can map to each other correctly, but for some reason the built in functions do not. I ran the above code with my own replacement functions for `Color3.fromHSV` and `Color3.toHSV` and I was able to get a 100% match rate.

``````local function toHSV(color: Color3)
-- https://en.wikipedia.org/wiki/HSL_and_HSV#From_RGB
local r, g, b = color.R, color.G, color.B
local max, min = math.max(r, g, b), math.min(r, g, b)

local d = max - min
local s = max == 0 and 0 or d / max

local h = 0
if max ~= min then
if max == r then
h = (g - b) / d + (g < b and 6 or 0)
elseif max == g then
h = (b - r) / d + 2
elseif max == b then
h = (r - g) / d + 4
end
h = h / 6
end

return h, s, max
end

local function fromHSV(h: number, s: number, v: number): Color3
-- https://en.wikipedia.org/wiki/HSL_and_HSV#HSV_to_RGB
local r, g, b

local i = math.floor(h * 6)
local f = h * 6 - i
local p = v * (1 - s)
local q = v * (1 - f * s)
local t = v * (1 - (1 - f) * s)

local j = i % 6
if j == 0 then
r, g, b = v, t, p
elseif j == 1 then
r, g, b = q, v, p
elseif j == 2 then
r, g, b = p, v, t
elseif j == 3 then
r, g, b = p, q, v
elseif j == 4 then
r, g, b = t, p, v
elseif j == 5 then
r, g, b = v, p, q
end

return Color3.new(r, g, b)
end
``````

Expected behavior

I would expect the two functions to map to each other. If I get the HSV values from `Color3.toHSV` and then plug them back into `Color3.fromHSV` I expect to get the exact same color.

14 Likes

Thanks for the report! We’ll follow up when we have an update for you.

5 Likes