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]; alpha = t[4];
} 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)
-- Based on the algorithm in Computer Graphics: Principles and Practice, by
-- James D. Foley et. al., 2nd ed., p. 596
-- Degree version, though radian is more natural, I don't want to translate it yet
local h = hsl.hue
local s = hsl.saturation
local l = hsl.luminosity
local value = function(n1, n2, hue)
if hue > 360 then
hue = hue - 360
elseif hue < 0 then
hue = hue + 360
end
if hue < 60 then
return n1 + (n2 - n1) * hue/60
elseif hue < 180 then
return n2
elseif hue < 240 then
return n1 + (n2 - n1) * (240 - hue)/60
else
return n1
end
end
local m2
if l < 0.5 then
m2 = l * (1 + s)
else
m2 = l + s - l * s
end
local m1 = 2 * l - m2
if s == 0 then
-- Achromatic, there is no hue
-- In book this errors if hue is not undefined, but we set hue to 0 in this case, not nil or something, so
return color(l, l, l, alpha)
else
-- Chromatic case, so there is a hue
return color(
clip(value(m1, m2, h + 120)*255),
clip(value(m1, m2, h)*255),
clip(value(m1, m2, h - 120)*255),
alpha
)
end
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)
-- Based on the algorithm in Computer Graphics: Principles and Practice, by
-- James D. Foley et. al., 2nd ed., p. 595
-- We need the rgb between 0 and 1
local r = self.red/255
local g = self.green/255
local b = self.blue/255
local max = math.max(r, g, b)
local min = math.min(r, g, b)
local luminosity = (max + min)/2
local hue = 0
local saturation = 0
if max == min then
-- Achromatic case, because r=g=b
saturation = 0
hue = 0 -- Undefined, so just replace w/ 0 for usability
else
-- Chromatic case
if luminosity <= 0.5 then
saturation = (max - min)/(max + min)
else
saturation = (max - min)/(2 - max - min)
end
-- Next calculate the hue
local delta = max - min
if r == max then
hue = (g - b)/delta
elseif g == max then
hue = 2 + (b - r)/delta
else -- blue must be max, so no point in checking
hue = 4 + (r - g)/delta
end
hue = hue * 60 -- degrees
--hue = hue * (math.pi / 3) -- for hue in radians instead of degrees
if hue < 0 then
hue = hue + 2 * math.pi
end
end
-- print("r"..self.red.."g"..self.green.."b"..self.blue.." is h"..hue.."s"..saturation.."l"..luminosity)
local temp = from_hsl({hue=hue,saturation=saturation,luminosity=luminosity},self.alpha)
-- print("back is r"..temp.red.."g"..temp.green.."b"..temp.blue)
return { hue = hue, saturation = saturation, luminosity = luminosity }
end;
readable = function(self, target)
target = target or 0.6
local hsl = self:to_hsl()
hsl.luminosity = target
local worstHue = 230
local nearness = math.abs(worstHue - hsl.hue)
if nearness <= 70 then
local boost = 1.0 - (nearness / 70)
hsl.luminosity = math.min(1, hsl.luminosity * (1 + (boost*0.4)))
end
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.min(math.max(hsl.luminosity * fac, 0), 1)
-- Turn back into RGB color
-- local t = from_hsl(hsl, self.alpha)
-- print("darker is r"..hsl.red.."g"..hsl.green.."b"..hsl.blue)
-- print("brighten is r"..t.red.."g"..t.green.."b"..t.blue)
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
if r.hue then
return from_hsl(r, r.alpha or g)
elseif r.r and r.g and r.b then
new.red = r.r
new.green = r.g
new.blue = r.b
new.alpha = r.a
else
new.red = r[1]
new.green = r[2]
new.blue = r[3]
new.alpha = r[4]
end
else assert(false) end
else
new.red = r
new.green = g
new.blue = b
new.alpha = a
end
return new
end
}
return color