Page 3 of 3

Re: HSV to RGB solution

Posted: Wed Mar 09, 2022 8:22 pm
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

Re: HSV to RGB solution

Posted: Thu Mar 10, 2022 2:31 am
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.

Re: HSV to RGB solution

Posted: Thu Mar 10, 2022 2:48 am
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.

Re: HSV to RGB solution

Posted: Fri Mar 11, 2022 6:49 pm
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

Re: HSV to RGB solution

Posted: Sat Mar 12, 2022 1:51 am
by darkfrei
The k1 and k2 can be precalculated too :)

Re: HSV to RGB solution

Posted: Fri Sep 02, 2022 5:59 am
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.