HSV to RGB solution

General discussion about LÖVE, Lua, game development, puns, and unicorns.
SliiX
Prole
Posts: 12
Joined: Mon Apr 13, 2020 9:04 am

Re: HSV to RGB solution

Post by SliiX »

pgimeno wrote: Wed Mar 09, 2022 12:14 pm 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.
yeah, I have no idea what could be the cause of that. But, I hacked together this little vision representation of what I'm trying to accomplish here.

The dot following the circle represents H and the dot following the triangle represents the 'ratio' between whichever to colors it falls between, or how far along each line segment it lies on. Because I'm basically trying to use that ratio to convert to RGB.

Code: Select all

love.graphics.setDefaultFilter("nearest", "nearest")

local rad = 100
local angle = 0

local ceil = math.ceil
local abs = math.abs

-- hsv alg
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 = clamp(ceil(h / 120), 1, 3)
    local rat = abs((h / 60) - 2 * vert)

    love.graphics.print(vert)

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

    -- 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

-- visualization
local c = {
    rad, 0,
    rad * math.cos(2 * math.pi / 3), rad * math.sin(2 * math.pi / 3),
    rad * math.cos(4 * math.pi / 3), rad * math.sin(4 * math.pi / 3)
}

local tsize = 24
local a, b = c[3] - c[5], c[4] - c[6]
local l = math.sqrt((a * a) + (b * b)) / 2
local function getCoords(h)
    local index = clamp(ceil(h / 120), 1, 3)
    local rat = abs((h / 60) - 2 * index)

    local dx, dy, x, y
    if index == 1 then
        dx, dy = c[1] - c[3], c[2] - c[4]
        local a = math.atan(dy / dx)

        x, y = rat * l * math.cos(a) + c[3], rat * l * math.sin(a) + c[4]
    elseif index == 2 then
        dx, dy = c[3] - c[5], c[4] - c[6]
        local a = math.atan(dy / dx)

        x, y = rat * l * math.cos(a) + c[3], rat * l * math.sin(a) - c[4]
    else
        dx, dy = c[5] - c[1], c[6] - c[2]
        local a = math.atan(dy / dx)
    
        x, y = (2 - rat) * l * math.cos(a) + c[5], (2 - rat) * l * math.sin(a) + c[6]
    end

    return x, y
end


function love.update(dt)
    angle = angle + 0.6

    if angle >= 359 then
        angle = 0
    end
end

function love.draw()
    love.graphics.setBackgroundColor(0.2, 0.2, 0.2)


    love.graphics.translate(love.graphics.getWidth() / 2, love.graphics.getHeight() / 2)
    love.graphics.setLineWidth(3)

    local h, s, v = HSV(angle, 1, 1)
    local x, y = getCoords(angle)

    love.graphics.setColor(h, s, v)

    love.graphics.polygon("fill", c)

    love.graphics.setColor(1, 1, 1)

    love.graphics.circle("fill", math.cos(angle * math.pi / 180) * rad, math.sin(angle * math.pi / 180) * rad, 5)
    love.graphics.circle("line", 0, 0, rad)
    love.graphics.circle("fill", x, y, 5)

    love.graphics.polygon("line", c)

    love.graphics.setColor(1, 0, 0, 1)
    love.graphics.print("R", c[1], c[2] - tsize / 2, 0, 2, 2)
    love.graphics.setColor(0, 1, 0, 1)
    love.graphics.print("G", c[3] - tsize / 2, c[4], 0, 2, 2)
    love.graphics.setColor(0, 0, 1, 1)
    love.graphics.print("B", c[5] - tsize / 2, c[6] - tsize, 0, 2, 2)
end
User avatar
pgimeno
Party member
Posts: 3656
Joined: Sun Oct 18, 2015 2:58 pm

Re: HSV to RGB solution

Post by pgimeno »

darkfrei wrote: Wed Mar 09, 2022 1:15 pm 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
Yeah, your formulation gives identical results to the one I used as reference.
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: Wed Mar 09, 2022 8:22 pm yeah, I have no idea what could be the cause of that. But, I hacked together this little vision representation of what I'm trying to accomplish here.

The dot following the circle represents H and the dot following the triangle represents the 'ratio' between whichever to colors it falls between, or how far along each line segment it lies on. Because I'm basically trying to use that ratio to convert to RGB.
And that's working, when s=1 and v=1. If you look at the top row of pixels (corresponding to s=1) in my test program when v=1 (mouse all the way to the right), you can see that it matches the reference formulation (and darkfrei's). The problem is in how you deal with s and v. You get right s=1, v=1; you get right s=0 for any v (grey tones); you get right v=0 for any s (all black); but these are the only cases that work properly, the rest don't. For example, v should do nothing but darken, yet it is influencing the hues at every saturation level other than zero.
SliiX
Prole
Posts: 12
Joined: Mon Apr 13, 2020 9:04 am

Re: HSV to RGB solution

Post by SliiX »

pgimeno wrote: Thu Mar 10, 2022 2:48 am
SliiX wrote: Wed Mar 09, 2022 8:22 pm yeah, I have no idea what could be the cause of that. But, I hacked together this little vision representation of what I'm trying to accomplish here.

The dot following the circle represents H and the dot following the triangle represents the 'ratio' between whichever to colors it falls between, or how far along each line segment it lies on. Because I'm basically trying to use that ratio to convert to RGB.
And that's working, when s=1 and v=1. If you look at the top row of pixels (corresponding to s=1) in my test program when v=1 (mouse all the way to the right), you can see that it matches the reference formulation (and darkfrei's). The problem is in how you deal with s and v. You get right s=1, v=1; you get right s=0 for any v (grey tones); you get right v=0 for any s (all black); but these are the only cases that work properly, the rest don't. For example, v should do nothing but darken, yet it is influencing the hues at every saturation level other than zero.
I've decided to go with Darkfrel's method, it seems the cleanest, I just did something like this:

Code: Select all

local abs = math.abs
local min, max = math.min, math.max

local function channel(h, s, v, o)
    local k1 = v*(1-s); local k2 = v - k1
    return k1 + k2 * min(max(3 * abs(((h - o)/180) % 2 - 1) - 1, 0), 1) 
end

function HSV(h, s, v)
    return channel(h, s, v, 0), channel(h, s, v, 120), channel(h, s, v, 240)
end
User avatar
darkfrei
Party member
Posts: 1197
Joined: Sat Feb 08, 2020 11:09 pm

Re: HSV to RGB solution

Post by darkfrei »

The k1 and k2 can be precalculated too :)
:awesome: in Lua we Löve
:awesome: Platformer Guide
:awesome: freebies
tarksur
Prole
Posts: 1
Joined: Fri Sep 02, 2022 5:29 am
Contact:

Re: HSV to RGB solution

Post by tarksur »

if H = 135, then (H/60)mod_2 = (2.25)mod_2 = 0.25. In modulo 2 division, the output is the remainder of the quantity when you divide it by 2. B = m. B = m.
Post Reply

Who is online

Users browsing this forum: No registered users and 2 guests