160 lines
4.7 KiB
Lua
160 lines
4.7 KiB
Lua
-- => Colors
|
|
-- Provides utility functions for handling colors
|
|
-- ============================================================
|
|
local math = math
|
|
local floor = math.floor
|
|
local max = math.max
|
|
local min = math.min
|
|
local pow = math.pow
|
|
local random = math.random
|
|
local debug = require("helpers").debug
|
|
|
|
-- Returns a value that is clipped to interval edges if it falls outside the interval
|
|
local function clip(num, min_num, max_num) return
|
|
max(min(num, max_num), min_num) end
|
|
|
|
-- Converts the given hex color to normalized rgba
|
|
local function hex2rgb(color)
|
|
color = color:gsub("#", "")
|
|
local strlen = color:len()
|
|
if strlen == 6 then
|
|
return tonumber("0x" .. color:sub(1, 2)) / 255,
|
|
tonumber("0x" .. color:sub(3, 4)) / 255,
|
|
tonumber("0x" .. color:sub(5, 6)) / 255, 1
|
|
end
|
|
if strlen == 8 then
|
|
return tonumber("0x" .. color:sub(1, 2)) / 255,
|
|
tonumber("0x" .. color:sub(3, 4)) / 255,
|
|
tonumber("0x" .. color:sub(5, 6)) / 255,
|
|
tonumber("0x" .. color:sub(7, 8)) / 255
|
|
end
|
|
end
|
|
|
|
-- Converts the given hex color to hsv
|
|
local function hex2hsv(color)
|
|
local r, g, b = hex2rgb(color)
|
|
local C_max = max(r, g, b)
|
|
local C_min = min(r, g, b)
|
|
local delta = C_max - C_min
|
|
local H, S, V
|
|
if delta == 0 then
|
|
H = 0
|
|
elseif C_max == r then
|
|
H = 60 * (((g - b) / delta) % 6)
|
|
elseif C_max == g then
|
|
H = 60 * (((b - r) / delta) + 2)
|
|
elseif C_max == b then
|
|
H = 60 * (((r - g) / delta) + 4)
|
|
end
|
|
if C_max == 0 then
|
|
S = 0
|
|
else
|
|
S = delta / C_max
|
|
end
|
|
V = C_max
|
|
return H, S * 100, V * 100
|
|
end
|
|
|
|
-- Converts the given hsv color to hex
|
|
local function hsv2hex(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 - math.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
|
|
|
|
-- Calculates the relative luminance of the given color
|
|
local function relative_luminance(color)
|
|
local r, g, b = hex2rgb(color)
|
|
local function from_sRGB(u)
|
|
return u <= 0.0031308 and 25 * u / 323 or
|
|
pow(((200 * u + 11) / 211), 12 / 5)
|
|
end
|
|
return 0.2126 * from_sRGB(r) + 0.7152 * from_sRGB(g) + 0.0722 * from_sRGB(b)
|
|
end
|
|
|
|
-- Calculates the contrast ratio between the two given colors
|
|
local function contrast_ratio(fg, bg)
|
|
return (relative_luminance(fg) + 0.05) / (relative_luminance(bg) + 0.05)
|
|
end
|
|
|
|
-- Returns true if the contrast between the two given colors is suitable
|
|
local function is_contrast_acceptable(fg, bg)
|
|
return contrast_ratio(fg, bg) >= 7 and true
|
|
end
|
|
|
|
-- Returns a bright-ish, saturated-ish, color of random hue
|
|
local function rand_hex(lb_angle, ub_angle)
|
|
return hsv2hex(random(lb_angle or 0, ub_angle or 360), 70, 90)
|
|
end
|
|
|
|
-- Rotates the hue of the given hex color by the specified angle (in degrees)
|
|
local function rotate_hue(color, angle)
|
|
local H, S, V = hex2hsv(color)
|
|
angle = clip(angle or 0, 0, 360)
|
|
H = (H + angle) % 360
|
|
return hsv2hex(H, S, V)
|
|
end
|
|
|
|
-- Lightens a given hex color by the specified amount
|
|
local function lighten(color, amount)
|
|
local r, g, b
|
|
r, g, b = hex2rgb(color)
|
|
r = 255 * r
|
|
g = 255 * g
|
|
b = 255 * b
|
|
r = r + floor(2.55 * amount)
|
|
g = g + floor(2.55 * amount)
|
|
b = b + floor(2.55 * amount)
|
|
r = r > 255 and 255 or r
|
|
g = g > 255 and 255 or g
|
|
b = b > 255 and 255 or b
|
|
return ("#%02x%02x%02x"):format(r, g, b)
|
|
end
|
|
|
|
-- Darkens a given hex color by the specified amount
|
|
local function darken(color, amount)
|
|
local r, g, b
|
|
r, g, b = hex2rgb(color)
|
|
r = 255 * r
|
|
g = 255 * g
|
|
b = 255 * b
|
|
r = max(0, r - floor(r * (amount / 100)))
|
|
g = max(0, g - floor(g * (amount / 100)))
|
|
b = max(0, b - floor(b * (amount / 100)))
|
|
return ("#%02x%02x%02x"):format(r, g, b)
|
|
end
|
|
|
|
return {
|
|
clip = clip,
|
|
hex2rgb = hex2rgb,
|
|
hex2hsv = hex2hsv,
|
|
hsv2hex = hsv2hex,
|
|
relative_luminance = relative_luminance,
|
|
contrast_ratio = contrast_ratio,
|
|
is_contrast_acceptable = is_contrast_acceptable,
|
|
rand_hex = rand_hex,
|
|
rotate_hue = rotate_hue,
|
|
lighten = lighten,
|
|
darken = darken,
|
|
}
|