[Breaking] color: Convert 0-255 -> 0-1 range

Our hsv logic assumes 0-1 (cs.rit.edu says "r,g,b values are from 0 to
1") and it makes more sense to provide __mul for multiplying in 0-1
since the result will stay in that range. Additionally, love switched to
0-1 color since 11.0.

Included an as_255() function to unpack the color in 0-255 for some
amount of backwards compatibility. Doesn't make sense to provide any
kind of color object for it since most of our functions won't work
correctly.

Add tests for hue(), saturation(), value() that fail (even with /255
removed) for the old code, but pass on the new 0-1 range because the hsv
logic outputs colors in 0-1. Test comparisons use reduced precision
because my input data has limited precision.
This commit is contained in:
David Briscoe 2022-03-27 00:30:30 -07:00
parent df92f0c5cd
commit 343f320e2f
2 changed files with 96 additions and 41 deletions

View File

@ -21,7 +21,7 @@ local function hsv_to_color(hsv)
local i
local f, q, p, t
local h, s, v
local a = hsv[4] or 255
local a = hsv[4] or 1
s = hsv[2]
v = hsv[3]
@ -29,10 +29,10 @@ local function hsv_to_color(hsv)
return new(v, v, v, a)
end
h = hsv[1] / 60
h = hsv[1] / 60 -- sector 0 to 5
i = math.floor(h)
f = h - i
f = h - i -- factorial part of h
p = v * (1-s)
q = v * (1-s*f)
t = v * (1-s*(1-f))
@ -52,7 +52,7 @@ local function color_to_hsv(c)
local r = c[1]
local g = c[2]
local b = c[3]
local a = c[4] or 255
local a = c[4] or 1
local h, s, v
local min = math.min(r, g, b)
@ -72,7 +72,7 @@ local function color_to_hsv(c)
-- r = g = b = 0 s = 0, v is undefined
s = 0
h = -1
return { h, s, v, 255 }
return { h, s, v, 1 }
end
if r == max then
@ -94,12 +94,12 @@ end
--- The public constructor.
-- @param x Can be of three types: </br>
-- number red component 0-255
-- number red component 0-1
-- table {r, g, b, a}
-- nil for {0,0,0,0}
-- @tparam number g Green component 0-255
-- @tparam number b Blue component 0-255
-- @tparam number a Alpha component 0-255
-- @tparam number g Green component 0-1
-- @tparam number b Blue component 0-1
-- @tparam number a Alpha component 0-1
-- @treturn color out
function color.new(r, g, b, a)
-- number, number, number, number
@ -126,13 +126,13 @@ function color.new(r, g, b, a)
end
--- Convert hue,saturation,value table to color object.
-- @tparam table hsva {hue 0-359, saturation 0-1, value 0-1, alpha 0-255}
-- @tparam table hsva {hue 0-359, saturation 0-1, value 0-1, alpha 0-1}
-- @treturn color out
color.hsv_to_color_table = hsv_to_color
--- Convert color to hue,saturation,value table
-- @tparam color in
-- @treturn table hsva {hue 0-359, saturation 0-1, value 0-1, alpha 0-255}
-- @treturn table hsva {hue 0-359, saturation 0-1, value 0-1, alpha 0-1}
color.color_to_hsv_table = color_to_hsv
--- Convert hue,saturation,value to color object.
@ -148,7 +148,7 @@ end
-- @tparam number h hue 0-359
-- @tparam number s saturation 0-1
-- @tparam number v value 0-1
-- @tparam number a alpha 0-255
-- @tparam number a alpha 0-1
-- @treturn color out
function color.from_hsva(h, s, v, a)
return hsv_to_color { h, s, v, a }
@ -158,18 +158,18 @@ end
-- @tparam color to invert
-- @treturn color out
function color.invert(c)
return new(255 - c[1], 255 - c[2], 255 - c[3], c[4])
return new(1 - c[1], 1 - c[2], 1 - c[3], c[4])
end
--- Lighten a color by a component-wise fixed amount (alpha unchanged)
-- @tparam color to lighten
-- @tparam number amount to increase each component by, 0-255 scale
-- @tparam number amount to increase each component by, 0-1 scale
-- @treturn color out
function color.lighten(c, v)
return new(
utils.clamp(c[1] + v, 0, 255),
utils.clamp(c[2] + v, 0, 255),
utils.clamp(c[3] + v, 0, 255),
utils.clamp(c[1] + v, 0, 1),
utils.clamp(c[2] + v, 0, 1),
utils.clamp(c[3] + v, 0, 1),
c[4]
)
end
@ -178,15 +178,35 @@ function color.lerp(a, b, s)
return a + s * (b - a)
end
--- Unpack a color into individual components in 0-1.
-- @tparam color to unpack
-- @treturn number r in 0-1
-- @treturn number g in 0-1
-- @treturn number b in 0-1
-- @treturn number a in 0-1
function color.unpack(c)
return c[1], c[2], c[3], c[4]
end
--- Unpack a color into individual components in 0-255.
-- @tparam color to unpack
-- @treturn number r in 0-255
-- @treturn number g in 0-255
-- @treturn number b in 0-255
-- @treturn number a in 0-255
function color.as_255(c)
return c[1] * 255, c[2] * 255, c[3] * 255, c[4] * 255
end
--- Darken a color by a component-wise fixed amount (alpha unchanged)
-- @tparam color to darken
-- @tparam number amount to decrease each component by, 0-255 scale
-- @tparam number amount to decrease each component by, 0-1 scale
-- @treturn color out
function color.darken(c, v)
return new(
utils.clamp(c[1] - v, 0, 255),
utils.clamp(c[2] - v, 0, 255),
utils.clamp(c[3] - v, 0, 255),
utils.clamp(c[1] - v, 0, 1),
utils.clamp(c[2] - v, 0, 1),
utils.clamp(c[3] - v, 0, 1),
c[4]
)
end
@ -207,7 +227,7 @@ end
-- directly set alpha channel
-- @tparam color to alter
-- @tparam number new alpha 0-255
-- @tparam number new alpha 0-1
-- @treturn color out
function color.alpha(c, v)
local t = color.new()
@ -280,13 +300,13 @@ function color.gamma_to_linear(r, g, b, a)
if type(r) == "table" then
local c = {}
for i = 1, 3 do
c[i] = convert(r[i] / 255) * 255
c[i] = convert(r[i])
end
c[4] = convert(r[4] / 255) * 255
c[4] = convert(r[4])
return c
else
return convert(r / 255) * 255, convert(g / 255) * 255, convert(b / 255) * 255, a or 255
return convert(r), convert(g), convert(b), a or 1
end
end
@ -307,13 +327,13 @@ function color.linear_to_gamma(r, g, b, a)
if type(r) == "table" then
local c = {}
for i = 1, 3 do
c[i] = convert(r[i] / 255) * 255
c[i] = convert(r[i])
end
c[4] = convert(r[4] / 255) * 255
c[4] = convert(r[4])
return c
else
return convert(r / 255) * 255, convert(g / 255) * 255, convert(b / 255) * 255, a or 255
return convert(r), convert(g), convert(b), a or 1
end
end

View File

@ -65,28 +65,66 @@ describe("color:", function()
end
end)
it("unpack", function()
local c = color(122/255, 20/255, 122/255, 255/255)
local r, g, b, a = c:unpack()
assert_is_float_equal(c[1], r)
assert_is_float_equal(c[2], g)
assert_is_float_equal(c[3], b)
assert_is_float_equal(c[4], a)
r, g, b, a = c:as_255()
assert_is_float_equal(122, r)
assert_is_float_equal(20, g)
assert_is_float_equal(122, b)
assert_is_float_equal(255, a)
end)
it("set hsv", function()
-- hsv value conversion values from http://colorizer.org/
local c = color(122/255, 20/255, 122/255, 1)
local hsv = c:color_to_hsv_table()
assert_is_approx_equal(hsv[1], 300)
assert_is_approx_equal(hsv[2], 0.8361)
assert_is_approx_equal(hsv[3], 0.4784)
local r = c:hue(200)
assert_is_approx_equal(r[1], 20/255)
assert_is_approx_equal(r[2], 88/255)
assert_is_approx_equal(r[3], 122/255)
r = c:saturation(0.2)
assert_is_approx_equal(r[1], 122/255)
assert_is_approx_equal(r[2], 97.6/255)
assert_is_approx_equal(r[3], 122/255)
r = c:value(0.2)
assert_is_approx_equal(r[1], 51/255)
assert_is_approx_equal(r[2], 8.36/255)
assert_is_approx_equal(r[3], 51/255)
end)
it("lighten a color", function()
local c = color(0, 0, 0, 0)
local r = c:lighten(10)
assert.is.equal(r[1], 10)
local r = c:lighten(0.1)
assert.is.equal(r[1], 0.1)
r = c:lighten(1000)
assert.is.equal(r[1], 255)
assert.is.equal(r[1], 1)
end)
it("darken a color", function()
local c = color(255, 255, 255, 255)
local r = c:darken(10)
assert.is.equal(r[1], 245)
local c = color(1, 1, 1, 1)
local r = c:darken(0.04)
assert.is.equal(r[1], 0.96)
r = c:darken(1000)
assert.is.equal(r[1], 0)
end)
it("modify alpha", function()
local c = color(255, 255, 255, 255)
local r = c:alpha(10)
assert.is.equal(r[4], 10)
local c = color(1, 1, 1, 1)
local r = c:alpha(0.1)
assert.is.equal(r[4], 0.1)
r = c:opacity(0.5)
assert.is.equal(r[4], 255/2)
assert.is.equal(r[4], 0.5)
r = c:opacity(0.5)
:opacity(0.5)
assert.is.equal(r[4], 0.25)
end)
end)
@ -95,9 +133,6 @@ end)
invert(c)
lerp(a, b, s)
multiply(c, v)
hue(color, hue)
saturation(color, percent)
value(color, percent)
gamma_to_linear(r, g, b, a)
linear_to_gamma(r, g, b, a)
to_string(a)