local color
color = sorcery.lib.class {
__tostring = function(self)
-- return minetest.rgba(
-- self.red,
-- self.green,
-- self.blue,
-- self.alpha
-- )
local hex = function(val)
return string.format('%02X',math.max(0,math.min(255,math.floor(val))))
end
local str = '#' ..
hex(self.red) ..
hex(self.green) ..
hex(self.blue)
if self.alpha then str = str .. hex(self.alpha) end
return str
end;
__add = function(self, other)
local sfac = (self.alpha or 255) / 255
local ofac = (other.alpha or 255) / 255
if self.alpha == other.alpha then
sfac = 1 ofac = 1
end
local sr, sg, sb = other.red * ofac, other.blue * ofac, other.green * ofac
local nr, ng, nb = self.red * sfac, self.blue * sfac, self.green * sfac
local saturate = function(a,b)
return math.max(0, math.min(255, a+b))
end
local alpha = nil
if self.alpha and other.alpha then
alpha = saturate(self.alpha or 255, other.alpha or 255)
end
return color(
saturate(sr, nr),
saturate(sg, ng),
saturate(sb, nb),
alpha
)
end;
cast = {
number = function(n) return {
red = n; green = n; blue = n;
} end;
table = function(t) return {
red = t[1]; green = t[2]; blue = t[3];
} end;
};
construct = function(r,g,b,a)
local clip = function(v)
return math.max(0,math.min(255,v))
end;
local from_hsl = function(hsl, alpha)
-- convert from a hsl table and alpha value to a color
local weird = function(n)
-- Yeah... this is a really weird function, only named f
local k = math.fmod(n + hsl.hue/(math.pi/6),12)
local a = hsl.saturation * math.min(hsl.luminosity, 1 - hsl.luminosity)
return hsl.luminosity * math.max(-1, math.min(k-3,9-k,1))
end
return color(clip(weird(0)*255), clip(weird(8)*255), clip(weird(4)*255), alpha)
end;
local warp = function(f)
return function(self, ...)
local n = color(self)
f(n, ...)
return n
end;
end;
local new = {
hex = function(self) return
getmetatable(self).__tostring(self)
end;
fmt = function(self, text) return
minetest.colorize(self:hex(), text)
end;
luminosity = function(self) return
(self.red + self.green + self.blue) / 3
end;
to_hsl = function(self)
-- https://en.wikipedia.org/wiki/HSL_and_HSV
-- www.niwa.nu/2013/05/math-behind-colorspace-conversions-rgb-hsl/
-- https://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.211.6425
-- https://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.413.9004
-- We need the rgb between 0 and 1
local rgb = { r = self.red/255, g = self.green/255, b = self.blue/255 }
-- First, the hue.
-- This version of the calculation can be up to 1.12deg off at the right few colors
-- but is overall very close, easier to implement, and runs much faster
-- TODO: consider memoizing something to do with this all?
local alpha = 0.5 * (2*rgb.r - rgb.g - rgb.b)
local beta = (math.sqrt(3)/2)*(rgb.g - rgb.b)
local hue = math.atan2(beta, alpha)
-- Next the luminosity/lightness. This one's easy enough
local luminosity = 0.5*(math.max(rgb.r, rgb.g, rgb.b) + math.min(rgb.r, rgb.g, rgb.b))
-- Finally, saturation
local saturation = 0
-- If luminosity isn't essentially 1 or 0
if math.abs(luminosity - 1) < 1e-18 or math.abs(luminosity) < 1e-18 then
-- need the chroma
local chroma = math.max(rgb.r,rgb.g,rgb.b) - math.min(rgb.r,rgb.g,rgb.b)
saturation = chroma / (1 - math.abs(2*luminosity - 1))
end
return { hue = hue, saturation = saturation, luminosity = luminosity }
end;
readable = function(self, target)
target = target or 200
local hsl = self:to_hsl()
hsl.luminosity = target
return from_hsl(hsl, self.alpha)
end;
bg = function(self, text) return
text .. minetest.get_background_escape_sequence(self:hex())
end;
fade = warp(function(new, fac)
new.alpha = math.min(255, (new.alpha or 255) * fac)
end);
brighten = function(self, fac)
-- Use HSL to brighten
-- To HSL
local hsl = self:to_hsl()
-- Do the calculation, clamp to 0-1 instead of the clamp fn
hsl.luminosity = math.max(math.min(hsl.luminosity * fac, 0), 1)
-- Turn back into RGB color
return from_hsl(hsl, self.alpha)
end;
darken = warp(function(new, fac)
-- TODO: is there any point to this being different than brighten? Probably especially not now.
new.red = clip(new.red - (new.red * fac))
new.blue = clip(new.blue - (new.blue * fac))
new.green = clip(new.green - (new.green * fac))
end);
}
if g == nil then
if type(r) == 'string' then
assert(false) -- TODO parse color string
elseif type(r) == 'table' then
new.red = r[1]
new.green = r[2]
new.blue = r[3]
new.alpha = r[4]
else assert(false) end
else
new.red = r
new.green = g
new.blue = b
new.alpha = a
end
return new
end
}
return color