Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .stylua.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ line_endings = "Unix"
indent_type = "Spaces"
indent_width = 2
quote_style = "AutoPreferDouble"
call_parentheses = "None"
call_parentheses = "Always"
syntax = "LuaJIT"
247 changes: 100 additions & 147 deletions lua/volt/color.lua
Original file line number Diff line number Diff line change
Expand Up @@ -23,17 +23,18 @@
-- All credits to https://github.com/LeonHeidelbach for making this!
-- 90% of functions are written by him

---@class volt.Color
local M = {}

-- Convert a hex color value to RGB
-- @param hex: The hex color value
-- @return r: Red (0-255)
-- @return g: Green (0-255)
-- @return b: Blue (0-255)
M.hex2rgb = function(hex)
local hash = string.sub(hex, 1, 1) == "#"
if string.len(hex) ~= (7 - (hash and 0 or 1)) then
return nil
--- @param hex string: The hex color value
--- @return integer|nil|? r: Red (0-255)
--- @return integer|nil|? g: Green (0-255)
--- @return integer|nil|? b: Blue (0-255)
function M.hex2rgb(hex)
local hash = hex:sub(1, 1) == "#"
if hex:len() ~= (7 - (hash and 0 or 1)) then
return
end

local r = tonumber(hex:sub(2 - (hash and 0 or 1), 3 - (hash and 0 or 1)), 16)
Expand All @@ -43,27 +44,31 @@ M.hex2rgb = function(hex)
end

-- Convert a hex color value to RGB ratio
-- @param hex: The hex color value
-- @return r: Red (0-100)
-- @return g: Green (0-100)
-- @return b: Blue (0-100)
M.hex2rgb_ratio = function(hex)
--- @param hex string: The hex color value
--- @return integer r: Red (0-100)
--- @return integer g: Green (0-100)
--- @return integer b: Blue (0-100)
function M.hex2rgb_ratio(hex)
local r, g, b = M.hex2rgb(hex)
return math.floor(r / 255 * 100), math.floor(g / 255 * 100), math.floor(b / 255 * 100)
end

-- Convert an RGB color value to hex
-- @param r: Red (0-255)
-- @param g: Green (0-255)
-- @param b: Blue (0-255)
-- @return The hexadecimal string representation of the color
M.rgb2hex = function(r, g, b)
return string.format("#%02x%02x%02x", math.floor(r), math.floor(g), math.floor(b))
--- @param r number: Red (0-255)
--- @param g number: Green (0-255)
--- @param b number: Blue (0-255)
--- @return string hex: The hexadecimal string representation of the color
function M.rgb2hex(r, g, b)
return ("#%02x%02x%02x"):format(math.floor(r), math.floor(g), math.floor(b))
end

-- Helper function to convert a HSL color value to RGB
-- Not to be used directly, use M.hsl2rgb instead
M.hsl2rgb_helper = function(p, q, a)
--- @param p number
--- @param q number
--- @param a number
--- @return number
function M.hsl2rgb_helper(p, q, a)
if a < 0 then
a = a + 6
end
Expand All @@ -72,172 +77,139 @@ M.hsl2rgb_helper = function(p, q, a)
end
if a < 1 then
return (q - p) * a + p
elseif a < 3 then
end
if a < 3 then
return q
elseif a < 4 then
end
if a < 4 then
return (q - p) * (4 - a) + p
else
return p
end
return p
end

-- Convert a HSL color value to RGB
-- @param h: Hue (0-360)
-- @param s: Saturation (0-1)
-- @param l: Lightness (0-1)
-- @return r: Red (0-255)
-- @return g: Green (0-255)
-- @return b: Blue (0-255)
M.hsl2rgb = function(h, s, l)
--- @param h number: Hue (0-360)
--- @param s number: Saturation (0-1)
--- @param l number: Lightness (0-1)
--- @return integer r: Red (0-255)
--- @return integer g: Green (0-255)
--- @return integer b: Blue (0-255)
function M.hsl2rgb(h, s, l)
local t1, t2, r, g, b

h = h / 60
if l <= 0.5 then
t2 = l * (s + 1)
else
t2 = l + s - (l * s)
end

h = h / 60
t1 = l * 2 - t2
r = M.hsl2rgb_helper(t1, t2, h + 2) * 255
g = M.hsl2rgb_helper(t1, t2, h) * 255
b = M.hsl2rgb_helper(t1, t2, h - 2) * 255

return r, g, b
return math.floor(r), math.floor(g), math.floor(b)
end

-- Convert an RGB color value to HSL
-- @param r Red (0-255)
-- @param g Green (0-255)
-- @param b Blue (0-255)
-- @return h Hue (0-360)
-- @return s Saturation (0-1)
-- @return l Lightness (0-1)
M.rgb2hsl = function(r, g, b)
local min, max, l, s, maxcolor, h
--- @param r number: Red (0-255)
--- @param g number: Green (0-255)
--- @param b number: Blue (0-255)
--- @return number h: Hue (0-360)
--- @return number s: Saturation (0-1)
--- @return number l: Lightness (0-1)
function M.rgb2hsl(r, g, b)
r, g, b = r / 255, g / 255, b / 255

min = math.min(r, g, b)
max = math.max(r, g, b)
maxcolor = 1 + (max == b and 2 or (max == g and 1 or 0))
local min = math.min(r, g, b)
local max = math.max(r, g, b)
local maxcolor = 1 + (max == b and 2 or (max == g and 1 or 0))

if maxcolor == 1 then
h = (g - b) / (max - min)
elseif maxcolor == 2 then
h = 2 + (b - r) / (max - min)
elseif maxcolor == 3 then
h = 4 + (r - g) / (max - min)
end
local h = maxcolor == 1 and ((g - b) / (max - min))
or (maxcolor == 2 and (2 + (b - r) / (max - min)) or (maxcolor == 3 and (4 + (r - g) / (max - min)) or nil))

if not rawequal(type(h), "number") then
h = 0
end

h = h * 60

if h < 0 then
h = h + 360
end

l = (min + max) / 2

if min == max then
s = 0
else
if l < 0.5 then
s = (max - min) / (max + min)
else
s = (max - min) / (2 - max - min)
end
end

local l = (min + max) / 2
local s = min == max and 0 or (l < 0.5 and ((max - min) / (max + min)) or ((max - min) / (2 - max - min)))
return h, s, l
end

-- Convert a hex color value to HSL
-- @param hex: The hex color value
-- @param h: Hue (0-360)
-- @param s: Saturation (0-1)
-- @param l: Lightness (0-1)
M.hex2hsl = function(hex)
--- @param hex string: The hex color value
--- @return number h: Hue (0-360)
--- @return number s: Saturation (0-1)
--- @return number l: Lightness (0-1)
function M.hex2hsl(hex)
local r, g, b = M.hex2rgb(hex)
return M.rgb2hsl(r, g, b)
end

-- Convert a HSL color value to hex
-- @param h: Hue (0-360)
-- @param s: Saturation (0-1)
-- @param l: Lightness (0-1)
-- @returns hex color value
M.hsl2hex = function(h, s, l)
--- @param h number: Hue (0-360)
--- @param s number: Saturation (0-1)
--- @param l number: Lightness (0-1)
--- @return string hex: The hex color value
function M.hsl2hex(h, s, l)
local r, g, b = M.hsl2rgb(h, s, l)
return M.rgb2hex(r, g, b)
end

-- Change the hue of a color by a given amount
-- @param hex The hex color value
-- @param amount The amount to change the hue.
-- Negative values decrease the hue, positive values increase it.
-- @return The hex color value
M.change_hex_hue = function(hex, percent)
--- @param hex string: The hex color value
--- @param percent integer: The amount to change the hue
--- @return string hex: The hex color value
function M.change_hex_hue(hex, percent)
local h, s, l = M.hex2hsl(hex)
-- Convert percentage to a degree shift
local shift = (percent / 100) * 360
h = (h + shift) % 360
h = (h + (percent / 100) * 360) % 360
if h < 0 then
h = h + 360
end
return M.hsl2hex(h, s, l)
end

-- Desaturate or saturate a color by a given percentage
-- @param hex The hex color value
-- @param percent The percentage to desaturate or saturate the color.
-- Negative values desaturate the color, positive values saturate it
-- @return The hex color value
M.change_hex_saturation = function(hex, percent)
--- @param hex string: The hex color value
--- @param percent number: The percentage to desaturate or saturate the color.
--- @return string hex: The hex color value
function M.change_hex_saturation(hex, percent)
local h, s, l = M.hex2hsl(hex)
s = s + (percent / 100)
if s > 1 then
s = 1
end
if s < 0 then
s = 0
end
s = s > 1 and 1 or (s < 0 and 0 or s)
return M.hsl2hex(h, s, l)
end

-- Lighten or darken a color by a given percentage
-- @param hex The hex color value
-- @param percent The percentage to lighten or darken the color.
-- Negative values darken the color, positive values lighten it
-- @return The hex color value
M.change_hex_lightness = function(hex, percent)
--- @param hex string: The hex color value
--- @param percent number: The percentage to lighten or darken the color.
--- @return string hex: The hex color value
function M.change_hex_lightness(hex, percent)
local h, s, l = M.hex2hsl(hex)
l = l + (percent / 100)
if l > 1 then
l = 1
end
if l < 0 then
l = 0
end
l = l > 1 and 1 or (l < 0 and 0 or l)
return M.hsl2hex(h, s, l)
end

-- Compute a gradient between two colors
-- @param hex1 The first hex color value
-- @param hex2 The second hex color value
-- @param steps The number of steps to compute
-- @return A table of hex color values
M.compute_gradient = function(hex1, hex2, steps)
--- @param hex1 string: The first hex color value
--- @param hex2 string: The second hex color value
--- @param steps integer: The number of steps to compute
--- @return string[] gradient: A table of hex color values
function M.compute_gradient(hex1, hex2, steps)
local h1, s1, l1 = M.hex2hsl(hex1)
local h2, s2, l2 = M.hex2hsl(hex2)
local h, s, l
local h, s, l ---@type number, number, number
local h_step = (h2 - h1) / (steps - 1)
local s_step = (s2 - s1) / (steps - 1)
local l_step = (l2 - l1) / (steps - 1)
local gradient = {}

local gradient = {} ---@type string[]
for i = 0, steps - 1 do
h = h1 + (h_step * i)
s = s1 + (s_step * i)
Expand All @@ -249,57 +221,38 @@ M.compute_gradient = function(hex1, hex2, steps)
end

-- Generate complementary colors
-- @param hex The hex color value (string)
-- @param count The number of complementary colors to generate
-- @return A table containing the complementary colors in hex format
M.hex2complementary = function(hex, count)
--- @param hex string: The hex color value (string)
--- @param count integer: The number of complementary colors to generate
--- @return string[] complementary_colors: A table containing the complementary colors in hex format
function M.hex2complementary(hex, count)
local h, s, l = M.hex2hsl(hex)
local complementary_colors = {}

-- Calculate the hue for the complementary color (180 degrees shift)
local complementary_colors = {} ---@type string[]
local complementary_hue = (h + 180) % 360

-- Create a gradient of colors by slightly varying the complementary hue
local hue_step = 360 / count
for i = 0, count - 1 do
local new_hue = (complementary_hue + (hue_step * i)) % 360
local complementary_hex = M.hsl2hex(new_hue, s, l)
table.insert(complementary_colors, complementary_hex)
table.insert(complementary_colors, M.hsl2hex((complementary_hue + (hue_step * i)) % 360, s, l))
end

return complementary_colors
end

-- Mix two colors with a given percentage.
-- @param first The primary hex color.
-- @param second The hex color you want to mix into the first color.
-- @param strength The percentage of second color in the output.
-- This needs to be a number between 0 - 100.
-- @return The mixed color as a hex value
M.mix = function(first, second, strength)
if strength == nil then
strength = 0.5
end

local s = strength / 100
--- @param first string: The primary hex color.
--- @param second string: The hex color you want to mix into the first color.
--- @param strength? number: The percentage of second color in the output (0-100).
--- @return string mixed: The mixed color as a hex value
function M.mix(first, second, strength)
local s = (strength or 0.5) / 100
local r1, g1, b1 = M.hex2rgb(first)
local r2, g2, b2 = M.hex2rgb(second)

if r1 == nil or r2 == nil then
if not (r1 and r2) or s == 0 then
return first
end

if s == 0 then
return first
elseif s == 1 then
if s == 1 then
return second
end

local r3 = r1 * (1 - s) + r2 * s
local g3 = g1 * (1 - s) + g2 * s
local b3 = b1 * (1 - s) + b2 * s

return M.rgb2hex(r3, g3, b3)
return M.rgb2hex(r1 * (1 - s) + r2 * s, g1 * (1 - s) + g2 * s, b1 * (1 - s) + b2 * s)
end

return M
Loading