sorcery  color.lua at [93f944b581]

File lib/color.lua artifact e07c3f3979 part of check-in 93f944b581


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.5
				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.min(math.max(hsl.luminosity * fac, 0), 1)
				-- Turn back into RGB color
				local t = from_hsl(hsl, self.alpha)
				-- 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
				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