mirror of
https://github.com/SpaceVim/SpaceVim.git
synced 2025-02-03 00:20:05 +08:00
521 lines
13 KiB
Lua
521 lines
13 KiB
Lua
--=============================================================================
|
||
-- color.lua ---
|
||
-- Copyright (c) 2019-2024 Wang Shidong & Contributors
|
||
-- Author: Wang Shidong < wsdjeg@outlook.com >
|
||
-- URL: https://spacevim.org
|
||
-- License: GPLv3
|
||
--=============================================================================
|
||
|
||
local color = {}
|
||
|
||
-- rgb <-> hsl - hwb
|
||
-- rgb <-> cmyk
|
||
-- rgb <-> hsv
|
||
-- rgb <-> hex
|
||
-- rgb <-> linear <-> xyz <-> lab <-> lch
|
||
|
||
-- 参考: https://blog.csdn.net/Sunshine_in_Moon/article/details/45131285
|
||
|
||
color.rgb2hsl = function(r, g, b)
|
||
local max = math.max(r, g, b)
|
||
local min = math.min(r, g, b)
|
||
local h, s, l
|
||
if max == min then
|
||
h = 0
|
||
elseif max == r and g >= b then
|
||
h = 60 * (g - b) / (max - min) + 0
|
||
elseif max == r and g < b then
|
||
h = 60 * (g - b) / (max - min) + 360
|
||
elseif max == g then
|
||
h = 60 * (b - r) / (max - min) + 120
|
||
elseif max == b then
|
||
h = 60 * (r - g) / (max - min) + 240
|
||
end
|
||
|
||
l = 1 / 2 * (max + min)
|
||
|
||
if l == 0 or max == min then
|
||
s = 0
|
||
elseif l > 0 and l <= 0.5 then
|
||
s = (max - min) / (2 * l)
|
||
elseif l > 0.5 then
|
||
s = (max - min) / (2 - 2 * l)
|
||
end
|
||
|
||
return h, s, l
|
||
end
|
||
|
||
-- https://stackoverflow.com/questions/68317097/how-to-properly-convert-hsl-colors-to-rgb-colors-in-lua
|
||
|
||
color.hsl2rgb = function(h, s, l)
|
||
h = h / 360
|
||
|
||
local r, g, b
|
||
|
||
if s == 0 then
|
||
r, g, b = l, l, l
|
||
else
|
||
local function hue2rgb(p, q, t)
|
||
if t < 0 then
|
||
t = t + 1
|
||
end
|
||
if t > 1 then
|
||
t = t - 1
|
||
end
|
||
if t < 1 / 6 then
|
||
return p + (q - p) * 6 * t
|
||
end
|
||
if t < 1 / 2 then
|
||
return q
|
||
end
|
||
if t < 2 / 3 then
|
||
return p + (q - p) * (2 / 3 - t) * 6
|
||
end
|
||
return p
|
||
end
|
||
|
||
local q = l < 0.5 and l * (1 + s) or l + s - l * s
|
||
local p = 2 * l - q
|
||
r = hue2rgb(p, q, h + 1 / 3)
|
||
g = hue2rgb(p, q, h)
|
||
b = hue2rgb(p, q, h - 1 / 3)
|
||
end
|
||
|
||
return r, g, b
|
||
end
|
||
|
||
-- https://www.rapidtables.com/convert/color/rgb-to-cmyk.html
|
||
|
||
color.rgb2cmyk = function(r, g, b)
|
||
local c, m, y, k
|
||
k = 1 - math.max(r, g, b)
|
||
if k ~= 1 then
|
||
c = (1 - r - k) / (1 - k)
|
||
m = (1 - g - k) / (1 - k)
|
||
y = (1 - b - k) / (1 - k)
|
||
else
|
||
c, m, y = 0, 0, 0
|
||
end
|
||
return c, m, y, k
|
||
end
|
||
|
||
color.cmyk2rgb = function(c, m, y, k)
|
||
local r, g, b
|
||
r = (1 - c) * (1 - k)
|
||
g = (1 - m) * (1 - k)
|
||
b = (1 - y) * (1 - k)
|
||
return r, g, b
|
||
end
|
||
|
||
-- https://www.rapidtables.com/convert/color/rgb-to-hsv.html
|
||
|
||
color.rgb2hsv = function(r, g, b)
|
||
local cmax = math.max(r, g, b)
|
||
local cmin = math.min(r, g, b)
|
||
local d = cmax - cmin
|
||
|
||
local h, s, v
|
||
|
||
if d == 0 then
|
||
h = 0
|
||
elseif cmax == r then
|
||
h = 60 * (((g - b) / d) % 6)
|
||
elseif cmax == g then
|
||
h = 60 * (((b - r) / d) + 2)
|
||
elseif cmax == b then
|
||
h = 60 * (((r - g) / d) + 4)
|
||
end
|
||
|
||
if cmax == 0 then
|
||
s = 0
|
||
else
|
||
s = d / cmax
|
||
end
|
||
|
||
v = cmax
|
||
|
||
return h, s, v
|
||
end
|
||
|
||
-- https://www.rapidtables.com/convert/color/hsv-to-rgb.html
|
||
color.hsv2rgb = function(h, s, v)
|
||
local c = v * s
|
||
local x = c * (1 - math.abs((h / 60) % 2 - 1))
|
||
local m = v - c
|
||
local r, g, b
|
||
|
||
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
|
||
r, g, b = (r + m), (g + m), (b + m)
|
||
return r, g, b
|
||
end
|
||
color.hsv2hsl = function(h, s, v)
|
||
return color.rgb2hsl(color.hsv2rgb(h, s, v))
|
||
end
|
||
color.hsl2hsv = function(h, s, l)
|
||
return color.rgb2hsv(color.hsl2rgb(h, s, l))
|
||
end
|
||
color.hsl2cmyk = function(h, s, l)
|
||
return color.rgb2cmyk(color.hsl2rgb(h, s, l))
|
||
end
|
||
color.hsv2cmyk = function(h, s, v)
|
||
return color.rgb2cmyk(color.hsv2rgb(h, s, v))
|
||
end
|
||
color.cmyk2hsv = function(c, m, y, k)
|
||
return color.rgb2hsv(color.cmyk2rgb(c, m, y, k))
|
||
end
|
||
|
||
color.cmyk2hsl = function(c, m, y, k)
|
||
return color.rgb2hsl(color.cmyk2rgb(c, m, y, k))
|
||
end
|
||
|
||
color.hwb2rgb = function(h, w, b)
|
||
if w + b >= 1 then
|
||
local grey = w / (w + b)
|
||
return grey, grey, grey
|
||
end
|
||
local R, G, B = color.hsl2rgb(h, 1, 0.5)
|
||
local function f(c)
|
||
c = c * (1 - w - b) + w
|
||
if c > 1 then
|
||
return 1
|
||
elseif c <= 0 then
|
||
return 0
|
||
else
|
||
return c
|
||
end
|
||
end
|
||
return f(R), f(G), f(B)
|
||
end
|
||
|
||
color.rgb2hwb = function(r, g, b)
|
||
local h = color.rgb2hsl(r, g, b)
|
||
local w = math.min(r, g, b)
|
||
b = 1 - math.max(r, g, b)
|
||
return h, w, b
|
||
end
|
||
|
||
color.hsv2hwb = function(h, s, v)
|
||
return color.rgb2hwb(color.hsv2rgb(h, s, v))
|
||
end
|
||
|
||
color.hsl2hwb = function(h, s, l)
|
||
return color.rgb2hwb(color.hsl2rgb(h, s, l))
|
||
end
|
||
|
||
color.cmyk2hwb = function(c, m, y, k)
|
||
return color.rgb2hwb(color.cmyk2rgb(c, m, y, k))
|
||
end
|
||
|
||
color.hwb2hsl = function(h, w, b)
|
||
return color.rgb2hsl(color.hwb2rgb(h, w, b))
|
||
end
|
||
|
||
color.hwb2hsv = function(h, w, b)
|
||
return color.rgb2hsv(color.hwb2rgb(h, w, b))
|
||
end
|
||
|
||
color.hwb2cmyk = function(h, w, b)
|
||
return color.rgb2cmyk(color.hwb2rgb(h, w, b))
|
||
end
|
||
|
||
local function decimalToHex(decimal)
|
||
local hex = ''
|
||
local hexChars = '0123456789ABCDEF'
|
||
while decimal > 0 do
|
||
local mod = decimal % 16
|
||
hex = string.sub(hexChars, mod + 1, mod + 1) .. hex
|
||
decimal = math.floor(decimal / 16)
|
||
end
|
||
if hex == '' then
|
||
return '00'
|
||
elseif #hex == 1 then
|
||
return '0' .. hex
|
||
else
|
||
return hex
|
||
end
|
||
end
|
||
color.rgb2hex = function(r, g, b)
|
||
r = decimalToHex(math.floor(r * 255 + 0.5))
|
||
g = decimalToHex(math.floor(g * 255 + 0.5))
|
||
b = decimalToHex(math.floor(b * 255 + 0.5))
|
||
return '#' .. r .. g .. b
|
||
end
|
||
color.hex2rgb = function(hex)
|
||
-- make sure hex is '#[0123456789ABCDEF]\+'
|
||
local r, g, b
|
||
r = tonumber(string.sub(hex, 2, 3), 16)
|
||
g = tonumber(string.sub(hex, 4, 5), 16)
|
||
b = tonumber(string.sub(hex, 6, 7), 16)
|
||
return r / 255, g / 255, b / 255
|
||
end
|
||
color.hsv2hex = function(h, s, v)
|
||
return color.rgb2hex(color.hsv2rgb(h, s, v))
|
||
end
|
||
color.hsl2hex = function(h, s, l)
|
||
return color.rgb2hex(color.hsl2rgb(h, s, l))
|
||
end
|
||
color.cmyk2hex = function(c, y, m, k)
|
||
return color.rgb2hex(color.cmyk2rgb(c, y, m, k))
|
||
end
|
||
|
||
color.hwb2hex = function(h, w, b)
|
||
return color.rgb2hex(color.hwb2rgb(h, w, b))
|
||
end
|
||
|
||
color.rgb2linear = function(r, g, b)
|
||
return unpack(vim.tbl_map(function(x)
|
||
if x <= 0.04045 then
|
||
return x / 12.92
|
||
end
|
||
return ((x + 0.055) / 1.055) ^ 2.4
|
||
end, { r, g, b }))
|
||
end
|
||
|
||
color.linear2rgb = function(r, g, b)
|
||
return unpack(vim.tbl_map(function(x)
|
||
if x <= 0.0031308 then
|
||
local a = 12.92 * x
|
||
if a > 1 then
|
||
return 1
|
||
elseif a < 0 then
|
||
return 0
|
||
else
|
||
return a
|
||
end
|
||
else
|
||
local a = 1.055 * x ^ (1 / 2.4) - 0.055
|
||
if a > 1 then
|
||
return 1
|
||
elseif a < 0 then
|
||
return 0
|
||
else
|
||
return a
|
||
end
|
||
end
|
||
end, { r, g, b }))
|
||
end
|
||
|
||
color.linear2hsl = function(r, g, b)
|
||
return color.rgb2hsl(color.linear2rgb(r, g, b))
|
||
end
|
||
|
||
color.linear2hwb = function(r, g, b)
|
||
return color.rgb2hwb(color.linear2rgb(r, g, b))
|
||
end
|
||
color.linear2cmyk = function(r, g, b)
|
||
return color.rgb2cmyk(color.linear2rgb(r, g, b))
|
||
end
|
||
color.linear2hsv = function(r, g, b)
|
||
return color.rgb2hsv(color.linear2rgb(r, g, b))
|
||
end
|
||
color.linear2hex = function(r, g, b)
|
||
return color.rgb2hex(color.linear2rgb(r, g, b))
|
||
end
|
||
color.linear2lab = function(r, g, b)
|
||
return color.xyz2lab(color.linear2xyz(r, g, b))
|
||
end
|
||
|
||
color.hsl2linear = function(h, s, l)
|
||
return color.rgb2linear(color.hsl2rgb(h, s, l))
|
||
end
|
||
|
||
color.hwb2linear = function(h, w, b)
|
||
return color.rgb2linear(color.hwb2rgb(h, w, b))
|
||
end
|
||
|
||
color.cmyk2linear = function(c, m, y, k)
|
||
return color.rgb2linear(color.cmyk2rgb(c, m, y, k))
|
||
end
|
||
|
||
color.hsv2linear = function(h, s, v)
|
||
return color.rgb2linear(color.hsv2rgb(h, s, v))
|
||
end
|
||
|
||
--------------------------------------------------------------------------------
|
||
-- XYZ color space
|
||
--------------------------------------------------------------------------------
|
||
|
||
local linear2xyz = {
|
||
{ 0.41239079926595, 0.35758433938387, 0.18048078840183 },
|
||
{ 0.21263900587151, 0.71516867876775, 0.072192315360733 },
|
||
{ 0.019330818715591, 0.11919477979462, 0.95053215224966 },
|
||
}
|
||
local xyz2linear = {
|
||
{ 3.240969941904521, -1.537383177570093, -0.498610760293 },
|
||
{ -0.96924363628087, 1.87596750150772, 0.041555057407175 },
|
||
{ 0.055630079696993, -0.20397695888897, 1.056971514242878 },
|
||
}
|
||
|
||
local function dot(a, b)
|
||
assert(#a == #b)
|
||
local result = 0
|
||
for i = 1, #a do
|
||
result = result + a[i] * b[i]
|
||
end
|
||
return result
|
||
end
|
||
|
||
local function product(m, v)
|
||
local row = #m
|
||
local result = {}
|
||
for i = 1, row do
|
||
result[i] = dot(m[i], v)
|
||
end
|
||
return unpack(result)
|
||
end
|
||
|
||
function color.linear2xyz(r, g, b)
|
||
return product(linear2xyz, { r, g, b })
|
||
end
|
||
|
||
function color.xyz2linear(x, y, z)
|
||
return product(xyz2linear, { x, y, z })
|
||
end
|
||
|
||
function color.rgb2xyz(r, g, b)
|
||
return color.linear2xyz(color.rgb2linear(r, g, b))
|
||
end
|
||
function color.xyz2rgb(x, y, z)
|
||
return color.linear2rgb(color.xyz2linear(x, y, z))
|
||
end
|
||
function color.xyz2hex(x, y, z)
|
||
return color.rgb2hex(color.xyz2rgb(x, y, z))
|
||
end
|
||
function color.xyz2hsv(x, y, z)
|
||
return color.rgb2hsv(color.xyz2rgb(x, y, z))
|
||
end
|
||
function color.hsv2xyz(h, s, v)
|
||
return color.rgb2xyz(color.hsv2rgb(h, s, v))
|
||
end
|
||
function color.xyz2hsl(x, y, z)
|
||
return color.rgb2hsl(color.xyz2rgb(x, y, z))
|
||
end
|
||
function color.hsl2xyz(h, s, l)
|
||
return color.rgb2xyz(color.hsl2rgb(h, s, l))
|
||
end
|
||
function color.xyz2cmyk(x, y, z)
|
||
return color.rgb2cmyk(color.xyz2rgb(x, y, z))
|
||
end
|
||
function color.cmyk2xyz(c, m, y, k)
|
||
return color.rgb2xyz(color.cmyk2rgb(c, m, y, k))
|
||
end
|
||
function color.xyz2hwb(x, y, z)
|
||
return color.rgb2hwb(color.xyz2rgb(x, y, z))
|
||
end
|
||
function color.hwb2xyz(h, w, b)
|
||
return color.rgb2xyz(color.hwb2rgb(h, w, b))
|
||
end
|
||
|
||
--------------------------------------------------------------------------------
|
||
-- Lab color space
|
||
--------------------------------------------------------------------------------
|
||
-- What Does CIE L*a*b* Stand for?
|
||
--
|
||
-- The CIE in CIELAB is the abbreviation for the International Commission on
|
||
-- Illumination’s French name, Commission Internationale de l´Eclairage.
|
||
-- The letters L*, a* and b* represent each of the three values the CIELAB color
|
||
-- space uses to measure objective color and calculate color differences.
|
||
-- L* represents lightness from black to white on a scale of zero to 100,
|
||
-- while a* and b* represent chromaticity with no specific numeric limits.
|
||
-- Negative a* corresponds with green, positive a* corresponds with red,
|
||
-- negative b* corresponds with blue and positive b* corresponds with yellow.
|
||
--
|
||
-- https://www.hunterlab.com/blog/what-is-cielab-color-space/
|
||
-- https://zh.wikipedia.org/wiki/CIELAB%E8%89%B2%E5%BD%A9%E7%A9%BA%E9%97%B4
|
||
-- https://en.wikipedia.org/wiki/CIELAB_color_space
|
||
-- https://developer.mozilla.org/zh-CN/docs/Web/CSS/color_value/lab
|
||
|
||
function color.xyz2lab(x, y, z)
|
||
local Xn, Yn, Zn = 0.9505, 1, 1.089
|
||
local function f(t)
|
||
if t > (6 / 29) ^ 3 then
|
||
return 116 * t ^ (1 / 3) - 16
|
||
end
|
||
return (29 / 3) ^ 3 * t
|
||
end
|
||
return f(y / Yn), (500 / 116) * (f(x / Xn) - f(y / Yn)), (200 / 116) * (f(y / Yn) - f(z / Zn))
|
||
end
|
||
|
||
function color.lab2xyz(l, a, b)
|
||
local Xn, Yn, Zn = 0.9505, 1, 1.089
|
||
local fy = (l + 16) / 116
|
||
local fx = fy + (a / 500)
|
||
local fz = fy - (b / 200)
|
||
local function t(f)
|
||
if f > 6 / 29 then
|
||
return f ^ 3
|
||
end
|
||
return (116 * f - 16) * (3 / 29) ^ 3
|
||
end
|
||
return t(fx) * Xn, t(fy) * Yn, t(fz) * Zn
|
||
end
|
||
|
||
function color.rgb2lab(r, g, b)
|
||
return color.xyz2lab(color.linear2xyz(color.rgb2linear(r, g, b)))
|
||
end
|
||
|
||
function color.hsl2lab(h, s, l)
|
||
return color.rgb2lab(color.hsl2rgb(h, s, l))
|
||
end
|
||
function color.cmyk2lab(c, m, y, k)
|
||
return color.rgb2lab(color.cmyk2rgb(c, m, y, k))
|
||
end
|
||
function color.hsv2lab(h, s, v)
|
||
return color.rgb2lab(color.hsv2rgb(h, s, v))
|
||
end
|
||
function color.hwb2lab(h, w, b)
|
||
return color.rgb2lab(color.hwb2rgb(h, w, b))
|
||
end
|
||
|
||
function color.lab2rgb(l, a, b)
|
||
return color.linear2rgb(color.xyz2linear(color.lab2xyz(l, a, b)))
|
||
end
|
||
|
||
function color.lab2hex(l, a, b)
|
||
return color.rgb2hex(color.lab2rgb(l, a, b))
|
||
end
|
||
|
||
function color.lab2hsl(l, a, b)
|
||
return color.rgb2hsl(color.lab2rgb(l, a, b))
|
||
end
|
||
function color.lab2hsv(l, a, b)
|
||
return color.rgb2hsv(color.lab2rgb(l, a, b))
|
||
end
|
||
function color.lab2hwb(l, a, b)
|
||
return color.rgb2hwb(color.lab2rgb(l, a, b))
|
||
end
|
||
function color.lab2cmyk(l, a, b)
|
||
return color.rgb2cmyk(color.lab2rgb(l, a, b))
|
||
end
|
||
function color.lab2linear(l, a, b)
|
||
return color.xyz2linear(color.lab2xyz(l, a, b))
|
||
end
|
||
|
||
function color.lab2lch(L, a, b)
|
||
local H = math.atan2(b, a)
|
||
local C = math.sqrt(a ^ 2 + b ^ 2)
|
||
H = H / (2 * math.pi) * 360 -- [rad] -> [deg]
|
||
H = H % 360
|
||
return L, C, H
|
||
end
|
||
|
||
function color.lch2lab(L, C, H)
|
||
H = H / 360 * (2 * math.pi) -- [deg] -> [rad]
|
||
local a = C * math.cos(H)
|
||
local b = C * math.sin(H)
|
||
return L, a, b
|
||
end
|
||
return color
|