2021-11-05 04:38:54 +01:00
|
|
|
local tonumber = tonumber
|
|
|
|
local string = string
|
|
|
|
local math = math
|
|
|
|
local floor = math.floor
|
|
|
|
local max = math.max
|
|
|
|
local min = math.min
|
|
|
|
local abs = math.abs
|
|
|
|
local format = string.format
|
|
|
|
|
2021-01-23 22:31:04 +01:00
|
|
|
local _color = {}
|
|
|
|
|
|
|
|
--- Try to guess if a color is dark or light.
|
|
|
|
--
|
|
|
|
-- @string color The color with hexadecimal HTML format `"#RRGGBB"`.
|
|
|
|
-- @treturn bool `true` if the color is dark, `false` if it is light.
|
|
|
|
function _color.is_dark(color)
|
|
|
|
-- Try to determine if the color is dark or light
|
2021-08-27 20:01:22 +02:00
|
|
|
local numeric_value = 0
|
2021-01-23 22:31:04 +01:00
|
|
|
for s in color:gmatch("[a-fA-F0-9][a-fA-F0-9]") do
|
2021-08-27 20:01:22 +02:00
|
|
|
numeric_value = numeric_value + tonumber("0x" .. s)
|
2021-01-23 22:31:04 +01:00
|
|
|
end
|
|
|
|
return (numeric_value < 383)
|
|
|
|
end
|
|
|
|
|
2021-11-05 04:38:54 +01:00
|
|
|
function _color.is_opaque(color)
|
|
|
|
if type(color) == "string" then
|
|
|
|
color = _color.hex_to_rgba(color)
|
|
|
|
end
|
|
|
|
|
|
|
|
return color.a < 0.01
|
|
|
|
end
|
|
|
|
|
2021-01-23 22:31:04 +01:00
|
|
|
--- Lighten a color.
|
|
|
|
--
|
|
|
|
-- @string color The color to lighten with hexadecimal HTML format `"#RRGGBB"`.
|
|
|
|
-- @int[opt=26] amount How much light from 0 to 255. Default is around 10%.
|
|
|
|
-- @treturn string The lighter color
|
|
|
|
function _color.lighten(color, amount)
|
|
|
|
amount = amount or 26
|
|
|
|
local c = {
|
2021-08-27 20:01:22 +02:00
|
|
|
r = tonumber("0x" .. color:sub(2, 3)),
|
|
|
|
g = tonumber("0x" .. color:sub(4, 5)),
|
|
|
|
b = tonumber("0x" .. color:sub(6, 7)),
|
2021-01-23 22:31:04 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
c.r = c.r + amount
|
|
|
|
c.r = c.r < 0 and 0 or c.r
|
|
|
|
c.r = c.r > 255 and 255 or c.r
|
|
|
|
c.g = c.g + amount
|
|
|
|
c.g = c.g < 0 and 0 or c.g
|
|
|
|
c.g = c.g > 255 and 255 or c.g
|
|
|
|
c.b = c.b + amount
|
|
|
|
c.b = c.b < 0 and 0 or c.b
|
|
|
|
c.b = c.b > 255 and 255 or c.b
|
|
|
|
|
2021-08-27 20:01:22 +02:00
|
|
|
return string.format("#%02x%02x%02x", c.r, c.g, c.b)
|
2021-01-23 22:31:04 +01:00
|
|
|
end
|
|
|
|
|
|
|
|
--- Darken a color.
|
|
|
|
--
|
|
|
|
-- @string color The color to darken with hexadecimal HTML format `"#RRGGBB"`.
|
|
|
|
-- @int[opt=26] amount How much dark from 0 to 255. Default is around 10%.
|
|
|
|
-- @treturn string The darker color
|
|
|
|
function _color.darken(color, amount)
|
|
|
|
amount = amount or 26
|
|
|
|
return _color.lighten(color, -amount)
|
|
|
|
end
|
|
|
|
|
2021-11-05 04:38:54 +01:00
|
|
|
-- Returns a value that is clipped to interval edges if it falls outside the interval
|
|
|
|
function _color.clip(num, min_num, max_num)
|
|
|
|
return max(min(num, max_num), min_num)
|
|
|
|
end
|
|
|
|
|
|
|
|
-- Converts the given hex color to rgba
|
|
|
|
function _color.hex_to_rgba(color)
|
|
|
|
color = color:gsub("#", "")
|
|
|
|
return { r = tonumber("0x" .. color:sub(1, 2)),
|
|
|
|
g = tonumber("0x" .. color:sub(3, 4)),
|
|
|
|
b = tonumber("0x" .. color:sub(5, 6)),
|
|
|
|
a = #color == 8 and tonumber("0x" .. color:sub(7, 8)) or 255 }
|
|
|
|
end
|
|
|
|
|
|
|
|
-- Converts the given rgba color to hex
|
|
|
|
function _color.rgba_to_hex(color)
|
|
|
|
local r = _color.clip(color.r or color[1], 0, 255)
|
|
|
|
local g = _color.clip(color.g or color[2], 0, 255)
|
|
|
|
local b = _color.clip(color.b or color[3], 0, 255)
|
|
|
|
local a = _color.clip(color.a or color[4] or 255, 0, 255)
|
|
|
|
return "#" .. format("%02x%02x%02x%02x",
|
|
|
|
floor(r),
|
|
|
|
floor(g),
|
|
|
|
floor(b),
|
|
|
|
floor(a))
|
|
|
|
end
|
|
|
|
|
|
|
|
-- Converts the given hex color to hsv
|
|
|
|
function _color.hex_to_hsv(color)
|
|
|
|
local color = _color.hex2rgb(color)
|
|
|
|
local C_max = max(color.r, color.g, color.b)
|
|
|
|
local C_min = min(color.r, color.g, color.b)
|
|
|
|
local delta = C_max - C_min
|
|
|
|
local H, S, V
|
|
|
|
if delta == 0 then
|
|
|
|
H = 0
|
|
|
|
elseif C_max == color.r then
|
|
|
|
H = 60 * (((color.g - color.b) / delta) % 6)
|
|
|
|
elseif C_max == color.g then
|
|
|
|
H = 60 * (((color.b - color.r) / delta) + 2)
|
|
|
|
elseif C_max == color.b then
|
|
|
|
H = 60 * (((color.r - color.g) / delta) + 4)
|
|
|
|
end
|
|
|
|
if C_max == 0 then
|
|
|
|
S = 0
|
|
|
|
else
|
|
|
|
S = delta / C_max
|
|
|
|
end
|
|
|
|
V = C_max
|
|
|
|
|
|
|
|
return { h = H,
|
|
|
|
s = S * 100,
|
|
|
|
v = V * 100 }
|
|
|
|
end
|
|
|
|
|
|
|
|
-- Converts the given hsv color to hex
|
|
|
|
function _color.hsv_to_hex(H, S, V)
|
|
|
|
S = S / 100
|
|
|
|
V = V / 100
|
|
|
|
if H > 360 then H = 360 end
|
|
|
|
if H < 0 then H = 0 end
|
|
|
|
local C = V * S
|
|
|
|
local X = C * (1 - abs(((H / 60) % 2) - 1))
|
|
|
|
local m = V - C
|
|
|
|
local r_, g_, b_ = 0, 0, 0
|
|
|
|
if H >= 0 and H < 60 then
|
|
|
|
r_, g_, b_ = C, X, 0
|
|
|
|
elseif H >= 60 and H < 120 then
|
|
|
|
r_, g_, b_ = X, C, 0
|
|
|
|
elseif H >= 120 and H < 180 then
|
|
|
|
r_, g_, b_ = 0, C, X
|
|
|
|
elseif H >= 180 and H < 240 then
|
|
|
|
r_, g_, b_ = 0, X, C
|
|
|
|
elseif H >= 240 and H < 300 then
|
|
|
|
r_, g_, b_ = X, 0, C
|
|
|
|
elseif H >= 300 and H < 360 then
|
|
|
|
r_, g_, b_ = C, 0, X
|
|
|
|
end
|
|
|
|
local r, g, b = (r_ + m) * 255, (g_ + m) * 255, (b_ + m) * 255
|
|
|
|
return ("#%02x%02x%02x"):format(floor(r), floor(g), floor(b))
|
|
|
|
end
|
|
|
|
|
|
|
|
function _color.multiply(color, amount)
|
|
|
|
return { _color.clip(color.r * amount, 0, 255),
|
|
|
|
_color.clip(color.g * amount, 0, 255),
|
|
|
|
_color.clip(color.b * amount, 0, 255),
|
|
|
|
255 }
|
|
|
|
end
|
|
|
|
|
2021-01-23 22:31:04 +01:00
|
|
|
return _color
|