HSV to RGB solution

General discussion about LÖVE, Lua, game development, puns, and unicorns.
User avatar
darkfrei
Party member
Posts: 1197
Joined: Sat Feb 08, 2020 11:09 pm

Re: HSV to RGB solution

Post by darkfrei »

zorg wrote: Sat Mar 05, 2022 10:07 am I think it broke because it wasn't parenthesized correctly; this might be better:

Code: Select all

r, g, b = vert==1 and r,g,b or (vert==2 and b,r,g or g,b,r)
For my opinion it can be bracketed as

Code: Select all

r, g, b = (vert==1) and r,g,b or (vert==2) and b,r,g or g,b,r
but brackets are not necessary.
:awesome: in Lua we Löve
:awesome: Platformer Guide
:awesome: freebies
User avatar
ReFreezed
Party member
Posts: 612
Joined: Sun Oct 25, 2015 11:32 pm
Location: Sweden
Contact:

Re: HSV to RGB solution

Post by ReFreezed »

darkfrei wrote: Sat Mar 05, 2022 10:19 am
zorg wrote: Sat Mar 05, 2022 10:07 am I think it broke because it wasn't parenthesized correctly; this might be better:

Code: Select all

r, g, b = vert==1 and r,g,b or (vert==2 and b,r,g or g,b,r)
For my opinion it can be bracketed as

Code: Select all

r, g, b = (vert==1) and r,g,b or (vert==2) and b,r,g or g,b,r
but brackets are not necessary.
People, this is not how Lua works. zorg's code even produces a parsing error.

Code: Select all

-- This:
r, g, b = vert==1 and r,g,b or vert==2 and b,r,g or g,b,r
-- Means this:
r, g, b = (vert==1 and r), (g), (b or (vert==2 and b)), (r), (g or g), (b), (r)
-- i.e.
-- r will always result in r or false.
-- g will always result in g.
-- b will always result in b.
-- The first (r) and forward are extra values that don't get assigned to any variable.

-- The logic you seek is something like this:
r, g, b = unpack(vert==1 and {r,g,b} or vert==2 and {b,r,g} or {g,b,r})
-- (This has terrible performance, of course.)
Tools: Hot Particles, LuaPreprocess, InputField, (more) Games: Momento Temporis
"If each mistake being made is a new one, then progress is being made."
User avatar
darkfrei
Party member
Posts: 1197
Joined: Sat Feb 08, 2020 11:09 pm

Re: HSV to RGB solution

Post by darkfrei »

@ReFreezed thanks, I've forgot that we have and return multiple arguments.

Zigzag with clamp on 0 and 1:
https://www.desmos.com/calculator/4vxbsuctu8

2022-03-05T17_46_28-Calculator.png
2022-03-05T17_46_28-Calculator.png (203.41 KiB) Viewed 4445 times


:awesome:
So the function:

Code: Select all

-- HSV to RGB
min = math.min
max = math.max
abs = math.abs

local function HSV2RGB (h, s, v)
	local k1 = v*(1-s)
	local k2 = v - k1
	local r = min (max (3*abs (((h	    )/180)%2-1)-1, 0), 1)
	local g = min (max (3*abs (((h	-120)/180)%2-1)-1, 0), 1)
	local b = min (max (3*abs (((h	+120)/180)%2-1)-1, 0), 1)
	return k1 + k2 * r, k1 + k2 * g, k1 + k2 * b
end
Image
lineal
lineal
2022-03-05T20_53_53-Untitled.png (11.26 KiB) Viewed 4429 times

Code: Select all

return (k1+k2*r)^0.5, (k1+k2*g)^0.5, (k1+k2*b)^0.5
square root
square root
2022-03-05T20_53_44-Untitled.png (11.24 KiB) Viewed 4429 times
Last edited by darkfrei on Sat Mar 05, 2022 9:22 pm, edited 10 times in total.
:awesome: in Lua we Löve
:awesome: Platformer Guide
:awesome: freebies
User avatar
pgimeno
Party member
Posts: 3656
Joined: Sun Oct 18, 2015 2:58 pm

Re: HSV to RGB solution

Post by pgimeno »

SliiX wrote: Sat Mar 05, 2022 12:06 am That select function is an interesting take, but, it broke on the second vertex, and on the third it didn't return one of the color values.
It works for me, I can't reproduce the results you're reporting.

SliiX wrote: Sat Mar 05, 2022 12:06 am Also I already tried that clamp function, and the min/max method appears to be slightly slower when I timed it.
Did you measure using LuaJIT? Can you show your benchmark?

I've found two major problems with your formulation. First, when h = 0 it returns no results. This is bad; h = 0 should be a valid input. Second, v is not working as it should: you always get one component equal to 1, so you can never produce black with that function.
User avatar
zorg
Party member
Posts: 3465
Joined: Thu Dec 13, 2012 2:55 pm
Location: Absurdistan, Hungary
Contact:

Re: HSV to RGB solution

Post by zorg »

ReFreezed wrote: Sat Mar 05, 2022 12:16 pm People, this is not how Lua works. zorg's code even produces a parsing error.
Right, i was busy fixing the and/or parenthetization so i completely missed the fact that there were multiple values in them, which indeed procduces an error.
Me and my stuff :3True Neutral Aspirant. Why, yes, i do indeed enjoy sarcastically correcting others when they make the most blatant of spelling mistakes. No bullying or trolling the innocent tho.
SliiX
Prole
Posts: 12
Joined: Mon Apr 13, 2020 9:04 am

Re: HSV to RGB solution

Post by SliiX »

pgimeno wrote: Sat Mar 05, 2022 5:20 pm
SliiX wrote: Sat Mar 05, 2022 12:06 am That select function is an interesting take, but, it broke on the second vertex, and on the third it didn't return one of the color values.
It works for me, I can't reproduce the results you're reporting.

SliiX wrote: Sat Mar 05, 2022 12:06 am Also I already tried that clamp function, and the min/max method appears to be slightly slower when I timed it.
Did you measure using LuaJIT? Can you show your benchmark?

I've found two major problems with your formulation. First, when h = 0 it returns no results. This is bad; h = 0 should be a valid input. Second, v is not working as it should: you always get one component equal to 1, so you can never produce black with that function.
for the bench mark I just wrote a quick function recording the time before and after executing it "n" amount of times. As for the issue with value, if you look later on, I posted an updated version fixing that, for I had forgotten to implement it. and h = 0 should work?
User avatar
pgimeno
Party member
Posts: 3656
Joined: Sun Oct 18, 2015 2:58 pm

Re: HSV to RGB solution

Post by pgimeno »

SliiX wrote: Mon Mar 07, 2022 10:40 pm
pgimeno wrote: Sat Mar 05, 2022 5:20 pm Did you measure using LuaJIT? Can you show your benchmark?

I've found two major problems with your formulation. First, when h = 0 it returns no results. This is bad; h = 0 should be a valid input. Second, v is not working as it should: you always get one component equal to 1, so you can never produce black with that function.
for the bench mark I just wrote a quick function recording the time before and after executing it "n" amount of times.
Did you test using Lua or LuaJIT/Löve? Lua does not compile anything to native code. On SSE2, min and max compile to single machine code instructions without the branch misprediction problems of conditional comparisons. Maybe you didn't test on a machine with SSE2?

SliiX wrote: Mon Mar 07, 2022 10:40 pm As for the issue with value, if you look later on, I posted an updated version fixing that, for I had forgotten to implement it. and h = 0 should work?
Oh right, I missed that post. Yes, with the final IF removed, it works when h = 0. For that variation, the select line would look like:

Code: Select all

  r, g, s = select(3 - vert % 3, s, r, g, s, r)
  return r, g, s
but I still recommend against it when using LuaJIT/Löve.

I've made a more thorough test of your revised function, and it's weird. For example, with h=0, s=0, v=0.5 I would expect to get 50% grey (due to the saturation being at 0), but I get bright red (#FF8080). To get 50% grey you have to use a hue of 60, 180 or 300, which isn't what I'd expect.

Here's a test program comparing your code to a known good implementation taken from https://github.com/EmmanuelOga/columns/ ... r.lua#L113 and slightly modified to return values in the 0..1 range:

Code: Select all

local HSV
------------------------- begin literal snippet -----------------------
local ceil = math.ceil
local abs = math.abs

local function clamp(v, min, max)
    if v < min then return min end
    if v > max then return max end

    return v
end

function HSV(h, s, v)
    v = v or 1; s = (1 - s) * v or 0

    local vert = ceil(h / 120)
    local rat = abs((h / 60) - 2 * vert)

    -- arc to vertex ratios along with extra channel
    local r, g = clamp(rat * v, s, 1), clamp((2 - rat) * v, s, 1)

    -- vertex shift
    if vert == 1 then return r, g, s end
    if vert == 2 then return s, r, g end
    return g, s, r
end
------------------------- end literal snippet -------------------------


local formula = 1
local imgd = love.image.newImageData(360, 360)
local img = love.graphics.newImage(imgd)
local
--[[
 * Converts an HSV color value to RGB. Conversion formula
 * adapted from http://en.wikipedia.org/wiki/HSV_color_space.
 * Assumes h, s, and v are contained in the set [0, 1] and
 * returns r, g, and b in the set [0, 1].
 *
 * @param   Number  h       The hue
 * @param   Number  s       The saturation
 * @param   Number  v       The value
 * @return  Array           The RGB representation
]]
function hsvToRgb(h, s, v)
  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);

  i = i % 6

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


local function updateImage(v)
  if formula == 1 then
    imgd:mapPixel(function(x, y)
      return HSV(x, 1 - y/359, v)
    end)
  elseif formula == 2 then
    imgd:mapPixel(function(x, y)
      return hsvToRgb(x/360, 1 - y/359, v)
    end)
  else
    error("Unknown formula")
  end
  img:replacePixels(imgd)
end

love.window.setMode(360, 360)
updateImage(1)

function love.draw()
  love.graphics.draw(img)
end

function love.mousemoved(mx, my)
  updateImage(mx/359)
end

function love.mousepressed(mx, my, mb)
  if mb == 1 then
    formula = formula + 1
    if formula > 2 then formula = 1 end
    updateImage(mx/359)
  end
end

function love.keypressed(k) return k == "escape" and love.event.quit() end
Move cursor left and right to adjust V; click to switch between implementations
SliiX
Prole
Posts: 12
Joined: Mon Apr 13, 2020 9:04 am

Re: HSV to RGB solution

Post by SliiX »

pgimeno wrote: Tue Mar 08, 2022 4:15 pm
SliiX wrote: Mon Mar 07, 2022 10:40 pm
pgimeno wrote: Sat Mar 05, 2022 5:20 pm Did you measure using LuaJIT? Can you show your benchmark?

I've found two major problems with your formulation. First, when h = 0 it returns no results. This is bad; h = 0 should be a valid input. Second, v is not working as it should: you always get one component equal to 1, so you can never produce black with that function.
for the bench mark I just wrote a quick function recording the time before and after executing it "n" amount of times.
Did you test using Lua or LuaJIT/Löve? Lua does not compile anything to native code. On SSE2, min and max compile to single machine code instructions without the branch misprediction problems of conditional comparisons. Maybe you didn't test on a machine with SSE2?

SliiX wrote: Mon Mar 07, 2022 10:40 pm As for the issue with value, if you look later on, I posted an updated version fixing that, for I had forgotten to implement it. and h = 0 should work?
Oh right, I missed that post. Yes, with the final IF removed, it works when h = 0. For that variation, the select line would look like:

Code: Select all

  r, g, s = select(3 - vert % 3, s, r, g, s, r)
  return r, g, s
but I still recommend against it when using LuaJIT/Löve.

I've made a more thorough test of your revised function, and it's weird. For example, with h=0, s=0, v=0.5 I would expect to get 50% grey (due to the saturation being at 0), but I get bright red (#FF8080). To get 50% grey you have to use a hue of 60, 180 or 300, which isn't what I'd expect.

Here's a test program comparing your code to a known good implementation taken from https://github.com/EmmanuelOga/columns/ ... r.lua#L113 and slightly modified to return values in the 0..1 range:

Code: Select all

local HSV
------------------------- begin literal snippet -----------------------
local ceil = math.ceil
local abs = math.abs

local function clamp(v, min, max)
    if v < min then return min end
    if v > max then return max end

    return v
end

function HSV(h, s, v)
    v = v or 1; s = (1 - s) * v or 0

    local vert = ceil(h / 120)
    local rat = abs((h / 60) - 2 * vert)

    -- arc to vertex ratios along with extra channel
    local r, g = clamp(rat * v, s, 1), clamp((2 - rat) * v, s, 1)

    -- vertex shift
    if vert == 1 then return r, g, s end
    if vert == 2 then return s, r, g end
    return g, s, r
end
------------------------- end literal snippet -------------------------


local formula = 1
local imgd = love.image.newImageData(360, 360)
local img = love.graphics.newImage(imgd)
local
--[[
 * Converts an HSV color value to RGB. Conversion formula
 * adapted from http://en.wikipedia.org/wiki/HSV_color_space.
 * Assumes h, s, and v are contained in the set [0, 1] and
 * returns r, g, and b in the set [0, 1].
 *
 * @param   Number  h       The hue
 * @param   Number  s       The saturation
 * @param   Number  v       The value
 * @return  Array           The RGB representation
]]
function hsvToRgb(h, s, v)
  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);

  i = i % 6

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


local function updateImage(v)
  if formula == 1 then
    imgd:mapPixel(function(x, y)
      return HSV(x, 1 - y/359, v)
    end)
  elseif formula == 2 then
    imgd:mapPixel(function(x, y)
      return hsvToRgb(x/360, 1 - y/359, v)
    end)
  else
    error("Unknown formula")
  end
  img:replacePixels(imgd)
end

love.window.setMode(360, 360)
updateImage(1)

function love.draw()
  love.graphics.draw(img)
end

function love.mousemoved(mx, my)
  updateImage(mx/359)
end

function love.mousepressed(mx, my, mb)
  if mb == 1 then
    formula = formula + 1
    if formula > 2 then formula = 1 end
    updateImage(mx/359)
  end
end

function love.keypressed(k) return k == "escape" and love.event.quit() end
Move cursor left and right to adjust V; click to switch between implementations
yes, I fixed the middle gray issue just now, it was because I was setting the max to 1 rather than value.

Code: Select all


-- old
local r, g = clamp(rat * v, s, 1), clamp((2 - rat) * v, s, 1)

-- new
local r, g = clamp(rat, s, v), clamp((2 - rat), s, v)
end
User avatar
pgimeno
Party member
Posts: 3656
Joined: Sun Oct 18, 2015 2:58 pm

Re: HSV to RGB solution

Post by pgimeno »

Good, but there are still problems. A blue hue is supposed to be at 240°; however, if you look at the image obtained with the main.lua program above, you'll see that primary colours invade the neighbouring secondary colour's hue, forming triangles, instead of being uniformly changing vertical bars. As a result, at s=0.5 and v=1, any hue between 210 and 270 yields the same bright blue colour:

Code: Select all

print(HSV(210, 1, 1)) -- prints 0, 0.5, 1 (ok)
print(HSV(240, 1, 1)) -- prints 0, 0, 1 (ok)
print(HSV(270, 1, 1)) -- prints 0.5, 0, 1 (ok)

print(HSV(210, 0.5, 1)) -- prints 0.5, 0.5, 1 (?????)
print(HSV(240, 0.5, 1)) -- prints 0.5, 0.5, 1 (ok)
print(HSV(270, 0.5, 1)) -- prints 0.5, 0.5, 1 (?????)
And the shorter the saturation, the more each primary colour invades the secondary colour's region (at v=1).

As for the clamp() function, I've made a benchmark and in my computer, with 100,000,000 iterations and random inputs, the `max/min` version takes 3.1 seconds while the `if` version takes 3.9 seconds. That's in the ballpark of what I expected.
User avatar
darkfrei
Party member
Posts: 1197
Joined: Sat Feb 08, 2020 11:09 pm

Re: HSV to RGB solution

Post by darkfrei »

My function returns:

Code: Select all

local function HSV2RGB (h, s, v)
	local k1 = v*(1-s)
	local k2 = v - k1
	local r = min (max (3*abs (((h	    )/180)%2-1)-1, 0), 1)
	local g = min (max (3*abs (((h	-120)/180)%2-1)-1, 0), 1)
	local b = min (max (3*abs (((h	+120)/180)%2-1)-1, 0), 1)
	return k1 + k2 * r, k1 + k2 * g, k1 + k2 * b
end

Code: Select all

print(HSV2RGB(210, 1, 1))	-- 0	0.5	1
print(HSV2RGB(240, 1, 1))	-- 0	0.0	1
print(HSV2RGB(270, 1, 1))	-- 0.5	0	1

print(HSV2RGB(210, 0.5, 1))	-- 0.5	0.75	1.0
print(HSV2RGB(240, 0.5, 1))	-- 0.5	0.5	1.0
print(HSV2RGB(270, 0.5, 1))	-- 0.75	0.5	1.0
:awesome: in Lua we Löve
:awesome: Platformer Guide
:awesome: freebies
Post Reply

Who is online

Users browsing this forum: Ahrefs [Bot], Google [Bot] and 2 guests