Merge pull request #72 from idbrii/color
Overhaul color: fix bugs, change to 0-1
This commit is contained in:
commit
4350c25d0c
@ -2,6 +2,7 @@
|
||||
-- @module color
|
||||
|
||||
local modules = (...):gsub('%.[^%.]+$', '') .. "."
|
||||
local constants = require(modules .. "constants")
|
||||
local utils = require(modules .. "utils")
|
||||
local precond = require(modules .. "_private_precond")
|
||||
local color = {}
|
||||
@ -10,7 +11,7 @@ local color_mt = {}
|
||||
local function new(r, g, b, a)
|
||||
local c = { r, g, b, a }
|
||||
c._c = c
|
||||
return setmetatable(c, color)
|
||||
return setmetatable(c, color_mt)
|
||||
end
|
||||
|
||||
-- HSV utilities (adapted from http://www.cs.rit.edu/~ncs/color/t_convert.html)
|
||||
@ -21,7 +22,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 +30,10 @@ local function hsv_to_color(hsv)
|
||||
return new(v, v, v, a)
|
||||
end
|
||||
|
||||
h = hsv[1] / 60
|
||||
h = hsv[1] * 6 -- 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 +53,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 +73,12 @@ 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
|
||||
|
||||
-- Prevent division by zero.
|
||||
if delta == 0 then
|
||||
delta = constants.DBL_EPSILON
|
||||
end
|
||||
|
||||
if r == max then
|
||||
@ -83,10 +89,10 @@ local function color_to_hsv(c)
|
||||
h = 4 + ( r - g ) / delta -- magenta/cyan
|
||||
end
|
||||
|
||||
h = h * 60 -- degrees
|
||||
h = h / 6 -- normalize from segment 0..5
|
||||
|
||||
if h < 0 then
|
||||
h = h + 360
|
||||
h = h + 1
|
||||
end
|
||||
|
||||
return { h, s, v, a }
|
||||
@ -94,12 +100,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,17 +132,17 @@ 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-1, 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-1, saturation 0-1, value 0-1, alpha 0-1}
|
||||
color.color_to_hsv_table = color_to_hsv
|
||||
|
||||
--- Convert hue,saturation,value to color object.
|
||||
-- @tparam number h hue 0-359
|
||||
-- @tparam number h hue 0-1
|
||||
-- @tparam number s saturation 0-1
|
||||
-- @tparam number v value 0-1
|
||||
-- @treturn color out
|
||||
@ -145,10 +151,10 @@ function color.from_hsv(h, s, v)
|
||||
end
|
||||
|
||||
--- Convert hue,saturation,value to color object.
|
||||
-- @tparam number h hue 0-359
|
||||
-- @tparam number h hue 0-1
|
||||
-- @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,35 +164,60 @@ 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 * 255, 0, 255),
|
||||
utils.clamp(c[2] + v * 255, 0, 255),
|
||||
utils.clamp(c[3] + v * 255, 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
|
||||
|
||||
--- Interpolate between two colors.
|
||||
-- @tparam color at start
|
||||
-- @tparam color at end
|
||||
-- @tparam number s in 0-1 progress between the two colors
|
||||
-- @treturn color out
|
||||
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 * 255, 0, 255),
|
||||
utils.clamp(c[2] - v * 255, 0, 255),
|
||||
utils.clamp(c[3] - v * 255, 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 +238,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()
|
||||
@ -215,7 +246,7 @@ function color.alpha(c, v)
|
||||
t[i] = c[i]
|
||||
end
|
||||
|
||||
t[4] = v * 255
|
||||
t[4] = v
|
||||
return t
|
||||
end
|
||||
|
||||
@ -235,17 +266,17 @@ end
|
||||
|
||||
--- Set a color's hue (saturation, value, alpha unchanged)
|
||||
-- @tparam color to alter
|
||||
-- @tparam hue to set 0-359
|
||||
-- @tparam hue to set 0-1
|
||||
-- @treturn color out
|
||||
function color.hue(col, hue)
|
||||
local c = color_to_hsv(col)
|
||||
c[1] = (hue + 360) % 360
|
||||
c[1] = (hue + 1) % 1
|
||||
return hsv_to_color(c)
|
||||
end
|
||||
|
||||
--- Set a color's saturation (hue, value, alpha unchanged)
|
||||
-- @tparam color to alter
|
||||
-- @tparam hue to set 0-359
|
||||
-- @tparam saturation to set 0-1
|
||||
-- @treturn color out
|
||||
function color.saturation(col, percent)
|
||||
local c = color_to_hsv(col)
|
||||
@ -255,7 +286,7 @@ end
|
||||
|
||||
--- Set a color's value (saturation, hue, alpha unchanged)
|
||||
-- @tparam color to alter
|
||||
-- @tparam hue to set 0-359
|
||||
-- @tparam value to set 0-1
|
||||
-- @treturn color out
|
||||
function color.value(col, percent)
|
||||
local c = color_to_hsv(col)
|
||||
@ -263,7 +294,7 @@ function color.value(col, percent)
|
||||
return hsv_to_color(c)
|
||||
end
|
||||
|
||||
-- http://en.wikipedia.org/wiki/SRGB#The_reverse_transformation
|
||||
-- https://en.wikipedia.org/wiki/SRGB#From_sRGB_to_CIE_XYZ
|
||||
function color.gamma_to_linear(r, g, b, a)
|
||||
local function convert(c)
|
||||
if c > 1.0 then
|
||||
@ -280,17 +311,17 @@ 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] = 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
|
||||
|
||||
-- http://en.wikipedia.org/wiki/SRGB#The_forward_transformation_.28CIE_xyY_or_CIE_XYZ_to_sRGB.29
|
||||
-- https://en.wikipedia.org/wiki/SRGB#From_CIE_XYZ_to_sRGB
|
||||
function color.linear_to_gamma(r, g, b, a)
|
||||
local function convert(c)
|
||||
if c > 1.0 then
|
||||
@ -307,13 +338,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] = 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
|
||||
|
||||
@ -341,24 +372,7 @@ function color.to_string(a)
|
||||
return string.format("[ %3.0f, %3.0f, %3.0f, %3.0f ]", a[1], a[2], a[3], a[4])
|
||||
end
|
||||
|
||||
function color_mt.__index(t, k)
|
||||
if type(t) == "cdata" then
|
||||
if type(k) == "number" then
|
||||
return t._c[k-1]
|
||||
end
|
||||
end
|
||||
|
||||
return rawget(color, k)
|
||||
end
|
||||
|
||||
function color_mt.__newindex(t, k, v)
|
||||
if type(t) == "cdata" then
|
||||
if type(k) == "number" then
|
||||
t._c[k-1] = v
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
color_mt.__index = color
|
||||
color_mt.__tostring = color.to_string
|
||||
|
||||
function color_mt.__call(_, r, g, b, a)
|
||||
|
@ -1,24 +1,184 @@
|
||||
local color = require "modules.color"
|
||||
local DBL_EPSILON = require("modules.constants").DBL_EPSILON
|
||||
|
||||
local function assert_is_float_equal(a, b)
|
||||
if math.abs(a - b) > DBL_EPSILON then
|
||||
assert.is.equal(a, b)
|
||||
end
|
||||
end
|
||||
|
||||
local function assert_is_approx_equal(a, b)
|
||||
if math.abs(a - b) > 0.001 then
|
||||
assert.is.equal(a, b)
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
describe("color:", function()
|
||||
it("operators: add, subract, multiply", function()
|
||||
local c = color(1, 1, 1, 1)
|
||||
assert.is_true(c:is_color())
|
||||
local r = c + c
|
||||
assert.is_true(r:is_color())
|
||||
assert_is_float_equal(r[1], 2)
|
||||
assert_is_float_equal(r[2], 2)
|
||||
assert_is_float_equal(r[3], 2)
|
||||
r = c - c
|
||||
assert.is_true(r:is_color())
|
||||
assert_is_float_equal(r[1], 0)
|
||||
assert_is_float_equal(r[2], 0)
|
||||
assert_is_float_equal(r[3], 0)
|
||||
r = c * 5
|
||||
assert.is_true(r:is_color())
|
||||
assert_is_float_equal(r[1], 5)
|
||||
assert_is_float_equal(r[2], 5)
|
||||
assert_is_float_equal(r[3], 5)
|
||||
end)
|
||||
|
||||
it("rgb -> hsv -> rgb", function()
|
||||
local c = color(1,1,1,1)
|
||||
local hsv = c:color_to_hsv_table()
|
||||
local c1 = color.hsv_to_color_table(hsv)
|
||||
local c2 = color.from_hsva(hsv[1], hsv[2], hsv[3], hsv[4])
|
||||
local c3 = color.from_hsv(hsv[1], hsv[2], hsv[3])
|
||||
c3[4] = c[4]
|
||||
for i=1,4 do
|
||||
assert_is_float_equal(c[i], c1[i])
|
||||
assert_is_float_equal(c[i], c2[i])
|
||||
assert_is_float_equal(c[i], c3[i])
|
||||
end
|
||||
assert.is_true(c:is_color())
|
||||
assert.is_true(c1:is_color())
|
||||
assert.is_true(c2:is_color())
|
||||
assert.is_true(c3:is_color())
|
||||
end)
|
||||
|
||||
it("hsv -> rgb -> hsv", function()
|
||||
local hsv1 = { 0, 0.3, 0.8, 0.9 }
|
||||
for h=0,1, 0.1 do
|
||||
hsv1[1] = h
|
||||
local cc = color.hsv_to_color_table(hsv1)
|
||||
local hsv2 = cc:color_to_hsv_table()
|
||||
for i=1,4 do
|
||||
assert_is_approx_equal(hsv1[i], hsv2[i])
|
||||
end
|
||||
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/360)
|
||||
assert_is_approx_equal(hsv[2], 0.8361)
|
||||
assert_is_approx_equal(hsv[3], 0.4784)
|
||||
local r = c:hue(200/360)
|
||||
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(0.1)
|
||||
assert.is.equal(r[1], 0.1)
|
||||
r = c:lighten(1000)
|
||||
assert.is.equal(r[1], 1)
|
||||
end)
|
||||
|
||||
it("darken a color", function()
|
||||
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("multiply a color by a scalar", function()
|
||||
local c = color(1, 1, 1, 1)
|
||||
local r = c:multiply(0.04)
|
||||
assert.is.equal(r[1], 0.04)
|
||||
|
||||
r = c:multiply(0)
|
||||
for i=1,3 do
|
||||
assert.is.equal(0, r[i])
|
||||
end
|
||||
assert.is.equal(1, r[4])
|
||||
end)
|
||||
|
||||
it("modify alpha", function()
|
||||
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], 0.5)
|
||||
r = c:opacity(0.5)
|
||||
:opacity(0.5)
|
||||
assert.is.equal(r[4], 0.25)
|
||||
end)
|
||||
|
||||
it("invert", function()
|
||||
local c = color(1, 0.6, 0.25, 1)
|
||||
local r = c:invert()
|
||||
assert_is_float_equal(r[1], 0)
|
||||
assert_is_float_equal(r[2], 0.4)
|
||||
assert_is_float_equal(r[3], 0.75)
|
||||
assert_is_float_equal(r[4], 1)
|
||||
r = c:invert()
|
||||
:invert()
|
||||
for i=1,4 do
|
||||
assert.is.equal(c[i], r[i])
|
||||
end
|
||||
end)
|
||||
|
||||
it("lerp", function()
|
||||
local a = color(1, 0.6, 0.25, 1)
|
||||
local b = color(1, 0.8, 0.75, 0.5)
|
||||
local r = a:lerp(b, 0.5)
|
||||
assert_is_float_equal(r[1], 1)
|
||||
assert_is_float_equal(r[2], 0.7)
|
||||
assert_is_float_equal(r[3], 0.5)
|
||||
assert_is_float_equal(r[4], 0.75)
|
||||
local r_a = a:lerp(b, 0)
|
||||
local r_b = a:lerp(b, 1)
|
||||
for i=1,4 do
|
||||
assert.is.equal(a[i], r_a[i])
|
||||
assert.is.equal(b[i], r_b[i])
|
||||
end
|
||||
end)
|
||||
|
||||
it("linear_to_gamma -> gamma_to_linear round trip", function()
|
||||
local c = color(0.25, 0.25, 0.25, 1)
|
||||
local r = color.gamma_to_linear(c:linear_to_gamma())
|
||||
for i=1,4 do
|
||||
assert_is_approx_equal(c[i], r[i])
|
||||
end
|
||||
end)
|
||||
|
||||
end)
|
||||
|
||||
--[[
|
||||
new(r, g, b, a)
|
||||
from_hsv(h, s, v)
|
||||
from_hsva(h, s, v, a)
|
||||
invert(c)
|
||||
lighten(c, v)
|
||||
lerp(a, b, s)
|
||||
darken(c, v)
|
||||
multiply(c, v)
|
||||
alpha(c, v)
|
||||
opacity(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)
|
||||
is_color(a)
|
||||
to_string(a)
|
||||
--]]
|
||||
--]]
|
||||
|
Loading…
x
Reference in New Issue
Block a user