mirror of
https://github.com/SashLilac/cambridge.git
synced 2024-11-22 20:29:02 -06:00
401 lines
9.1 KiB
Lua
401 lines
9.1 KiB
Lua
|
--- Color utilities
|
||
|
-- @module color
|
||
|
|
||
|
local modules = (...):gsub('%.[^%.]+$', '') .. "."
|
||
|
local constants = require(modules .. "constants")
|
||
|
local utils = require(modules .. "utils")
|
||
|
local precond = require(modules .. "_private_precond")
|
||
|
local color = {}
|
||
|
local color_mt = {}
|
||
|
|
||
|
local function new(r, g, b, a)
|
||
|
local c = { r, g, b, a }
|
||
|
c._c = c
|
||
|
return setmetatable(c, color_mt)
|
||
|
end
|
||
|
|
||
|
-- HSV utilities (adapted from http://www.cs.rit.edu/~ncs/color/t_convert.html)
|
||
|
-- hsv_to_color(hsv)
|
||
|
-- Converts a set of HSV values to a color. hsv is a table.
|
||
|
-- See also: hsv(h, s, v)
|
||
|
local function hsv_to_color(hsv)
|
||
|
local i
|
||
|
local f, q, p, t
|
||
|
local h, s, v
|
||
|
local a = hsv[4] or 1
|
||
|
s = hsv[2]
|
||
|
v = hsv[3]
|
||
|
|
||
|
if s == 0 then
|
||
|
return new(v, v, v, a)
|
||
|
end
|
||
|
|
||
|
h = hsv[1] * 6 -- sector 0 to 5
|
||
|
|
||
|
i = math.floor(h)
|
||
|
f = h - i -- factorial part of h
|
||
|
p = v * (1-s)
|
||
|
q = v * (1-s*f)
|
||
|
t = v * (1-s*(1-f))
|
||
|
|
||
|
if i == 0 then return new(v, t, p, a)
|
||
|
elseif i == 1 then return new(q, v, p, a)
|
||
|
elseif i == 2 then return new(p, v, t, a)
|
||
|
elseif i == 3 then return new(p, q, v, a)
|
||
|
elseif i == 4 then return new(t, p, v, a)
|
||
|
else return new(v, p, q, a)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- color_to_hsv(c)
|
||
|
-- Takes in a normal color and returns a table with the HSV values.
|
||
|
local function color_to_hsv(c)
|
||
|
local r = c[1]
|
||
|
local g = c[2]
|
||
|
local b = c[3]
|
||
|
local a = c[4] or 1
|
||
|
local h, s, v
|
||
|
|
||
|
local min = math.min(r, g, b)
|
||
|
local max = math.max(r, g, b)
|
||
|
v = max
|
||
|
|
||
|
local delta = max - min
|
||
|
|
||
|
-- black, nothing else is really possible here.
|
||
|
if min == 0 and max == 0 then
|
||
|
return { 0, 0, 0, a }
|
||
|
end
|
||
|
|
||
|
if max ~= 0 then
|
||
|
s = delta / max
|
||
|
else
|
||
|
-- r = g = b = 0 s = 0, v is undefined
|
||
|
s = 0
|
||
|
h = -1
|
||
|
return { h, s, v, 1 }
|
||
|
end
|
||
|
|
||
|
-- Prevent division by zero.
|
||
|
if delta == 0 then
|
||
|
delta = constants.DBL_EPSILON
|
||
|
end
|
||
|
|
||
|
if r == max then
|
||
|
h = ( g - b ) / delta -- yellow/magenta
|
||
|
elseif g == max then
|
||
|
h = 2 + ( b - r ) / delta -- cyan/yellow
|
||
|
else
|
||
|
h = 4 + ( r - g ) / delta -- magenta/cyan
|
||
|
end
|
||
|
|
||
|
h = h / 6 -- normalize from segment 0..5
|
||
|
|
||
|
if h < 0 then
|
||
|
h = h + 1
|
||
|
end
|
||
|
|
||
|
return { h, s, v, a }
|
||
|
end
|
||
|
|
||
|
--- The public constructor.
|
||
|
-- @param x Can be of three types: </br>
|
||
|
-- number red component 0-1
|
||
|
-- table {r, g, b, a}
|
||
|
-- nil for {0,0,0,0}
|
||
|
-- @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
|
||
|
if r and g and b and a then
|
||
|
precond.typeof(r, "number", "new: Wrong argument type for r")
|
||
|
precond.typeof(g, "number", "new: Wrong argument type for g")
|
||
|
precond.typeof(b, "number", "new: Wrong argument type for b")
|
||
|
precond.typeof(a, "number", "new: Wrong argument type for a")
|
||
|
|
||
|
return new(r, g, b, a)
|
||
|
|
||
|
-- {r, g, b, a}
|
||
|
elseif type(r) == "table" then
|
||
|
local rr, gg, bb, aa = r[1], r[2], r[3], r[4]
|
||
|
precond.typeof(rr, "number", "new: Wrong argument type for r")
|
||
|
precond.typeof(gg, "number", "new: Wrong argument type for g")
|
||
|
precond.typeof(bb, "number", "new: Wrong argument type for b")
|
||
|
precond.typeof(aa, "number", "new: Wrong argument type for a")
|
||
|
|
||
|
return new(rr, gg, bb, aa)
|
||
|
end
|
||
|
|
||
|
return new(0, 0, 0, 0)
|
||
|
end
|
||
|
|
||
|
--- Convert hue,saturation,value table to color object.
|
||
|
-- @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-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-1
|
||
|
-- @tparam number s saturation 0-1
|
||
|
-- @tparam number v value 0-1
|
||
|
-- @treturn color out
|
||
|
function color.from_hsv(h, s, v)
|
||
|
return hsv_to_color { h, s, v }
|
||
|
end
|
||
|
|
||
|
--- Convert hue,saturation,value to color object.
|
||
|
-- @tparam number h hue 0-1
|
||
|
-- @tparam number s saturation 0-1
|
||
|
-- @tparam number v value 0-1
|
||
|
-- @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 }
|
||
|
end
|
||
|
|
||
|
--- Invert a color.
|
||
|
-- @tparam color to invert
|
||
|
-- @treturn color out
|
||
|
function color.invert(c)
|
||
|
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-1 scale
|
||
|
-- @treturn color out
|
||
|
function color.lighten(c, v)
|
||
|
return new(
|
||
|
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-1 scale
|
||
|
-- @treturn color out
|
||
|
function color.darken(c, v)
|
||
|
return new(
|
||
|
utils.clamp(c[1] - v, 0, 1),
|
||
|
utils.clamp(c[2] - v, 0, 1),
|
||
|
utils.clamp(c[3] - v, 0, 1),
|
||
|
c[4]
|
||
|
)
|
||
|
end
|
||
|
|
||
|
--- Multiply a color's components by a value (alpha unchanged)
|
||
|
-- @tparam color to multiply
|
||
|
-- @tparam number to multiply each component by
|
||
|
-- @treturn color out
|
||
|
function color.multiply(c, v)
|
||
|
local t = color.new()
|
||
|
for i = 1, 3 do
|
||
|
t[i] = c[i] * v
|
||
|
end
|
||
|
|
||
|
t[4] = c[4]
|
||
|
return t
|
||
|
end
|
||
|
|
||
|
-- directly set alpha channel
|
||
|
-- @tparam color to alter
|
||
|
-- @tparam number new alpha 0-1
|
||
|
-- @treturn color out
|
||
|
function color.alpha(c, v)
|
||
|
local t = color.new()
|
||
|
for i = 1, 3 do
|
||
|
t[i] = c[i]
|
||
|
end
|
||
|
|
||
|
t[4] = v
|
||
|
return t
|
||
|
end
|
||
|
|
||
|
--- Multiply a color's alpha by a value
|
||
|
-- @tparam color to multiply
|
||
|
-- @tparam number to multiply alpha by
|
||
|
-- @treturn color out
|
||
|
function color.opacity(c, v)
|
||
|
local t = color.new()
|
||
|
for i = 1, 3 do
|
||
|
t[i] = c[i]
|
||
|
end
|
||
|
|
||
|
t[4] = c[4] * v
|
||
|
return t
|
||
|
end
|
||
|
|
||
|
--- Set a color's hue (saturation, value, alpha unchanged)
|
||
|
-- @tparam color to alter
|
||
|
-- @tparam hue to set 0-1
|
||
|
-- @treturn color out
|
||
|
function color.hue(col, hue)
|
||
|
local c = color_to_hsv(col)
|
||
|
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 saturation to set 0-1
|
||
|
-- @treturn color out
|
||
|
function color.saturation(col, percent)
|
||
|
local c = color_to_hsv(col)
|
||
|
c[2] = utils.clamp(percent, 0, 1)
|
||
|
return hsv_to_color(c)
|
||
|
end
|
||
|
|
||
|
--- Set a color's value (saturation, hue, alpha unchanged)
|
||
|
-- @tparam color to alter
|
||
|
-- @tparam value to set 0-1
|
||
|
-- @treturn color out
|
||
|
function color.value(col, percent)
|
||
|
local c = color_to_hsv(col)
|
||
|
c[3] = utils.clamp(percent, 0, 1)
|
||
|
return hsv_to_color(c)
|
||
|
end
|
||
|
|
||
|
-- 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
|
||
|
return 1.0
|
||
|
elseif c < 0.0 then
|
||
|
return 0.0
|
||
|
elseif c <= 0.04045 then
|
||
|
return c / 12.92
|
||
|
else
|
||
|
return math.pow((c + 0.055) / 1.055, 2.4)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
if type(r) == "table" then
|
||
|
local c = {}
|
||
|
for i = 1, 3 do
|
||
|
c[i] = convert(r[i])
|
||
|
end
|
||
|
|
||
|
c[4] = r[4]
|
||
|
return c
|
||
|
else
|
||
|
return convert(r), convert(g), convert(b), a or 1
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- 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
|
||
|
return 1.0
|
||
|
elseif c < 0.0 then
|
||
|
return 0.0
|
||
|
elseif c < 0.0031308 then
|
||
|
return c * 12.92
|
||
|
else
|
||
|
return 1.055 * math.pow(c, 0.41666) - 0.055
|
||
|
end
|
||
|
end
|
||
|
|
||
|
if type(r) == "table" then
|
||
|
local c = {}
|
||
|
for i = 1, 3 do
|
||
|
c[i] = convert(r[i])
|
||
|
end
|
||
|
|
||
|
c[4] = r[4]
|
||
|
return c
|
||
|
else
|
||
|
return convert(r), convert(g), convert(b), a or 1
|
||
|
end
|
||
|
end
|
||
|
|
||
|
--- Check if color is valid
|
||
|
-- @tparam color to test
|
||
|
-- @treturn boolean is color
|
||
|
function color.is_color(a)
|
||
|
if type(a) ~= "table" then
|
||
|
return false
|
||
|
end
|
||
|
|
||
|
for i = 1, 4 do
|
||
|
if type(a[i]) ~= "number" then
|
||
|
return false
|
||
|
end
|
||
|
end
|
||
|
|
||
|
return true
|
||
|
end
|
||
|
|
||
|
--- Return a formatted string.
|
||
|
-- @tparam color a color to be turned into a string
|
||
|
-- @treturn string formatted
|
||
|
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
|
||
|
|
||
|
color_mt.__index = color
|
||
|
color_mt.__tostring = color.to_string
|
||
|
|
||
|
function color_mt.__call(_, r, g, b, a)
|
||
|
return color.new(r, g, b, a)
|
||
|
end
|
||
|
|
||
|
function color_mt.__add(a, b)
|
||
|
return new(a[1] + b[1], a[2] + b[2], a[3] + b[3], a[4] + b[4])
|
||
|
end
|
||
|
|
||
|
function color_mt.__sub(a, b)
|
||
|
return new(a[1] - b[1], a[2] - b[2], a[3] - b[3], a[4] - b[4])
|
||
|
end
|
||
|
|
||
|
function color_mt.__mul(a, b)
|
||
|
if type(a) == "number" then
|
||
|
return new(a * b[1], a * b[2], a * b[3], a * b[4])
|
||
|
elseif type(b) == "number" then
|
||
|
return new(b * a[1], b * a[2], b * a[3], b * a[4])
|
||
|
else
|
||
|
return new(a[1] * b[1], a[2] * b[2], a[3] * b[3], a[4] * b[4])
|
||
|
end
|
||
|
end
|
||
|
|
||
|
return setmetatable({}, color_mt)
|