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
task.wait()
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.