From 343f320e2f0e9b6c760e3b91478b3f4d24587e1d Mon Sep 17 00:00:00 2001 From: David Briscoe Date: Sun, 27 Mar 2022 00:30:30 -0700 Subject: [PATCH] [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. --- modules/color.lua | 76 ++++++++++++++++++++++++++++----------------- spec/color_spec.lua | 61 ++++++++++++++++++++++++++++-------- 2 files changed, 96 insertions(+), 41 deletions(-) diff --git a/modules/color.lua b/modules/color.lua index e26e361..04ec3d1 100644 --- a/modules/color.lua +++ b/modules/color.lua @@ -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:
--- 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 diff --git a/spec/color_spec.lua b/spec/color_spec.lua index 5f7aa16..5309ced 100644 --- a/spec/color_spec.lua +++ b/spec/color_spec.lua @@ -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)