Index: lib/color.lua ================================================================== --- lib/color.lua +++ lib/color.lua @@ -53,10 +53,54 @@ 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 @@ -72,25 +116,60 @@ 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 200 - local lum = self:luminosity() - if lum < target then - local f = 1.0 + (target - lum) / 255 - local nc = self:brighten(f * 1.5) - -- i don't know why the *1.5 is necessary. it's - -- an ugly hack to work around broken math, - -- because i'm too much of a mathtard to actually - -- find what's wrong - return nc - else - return self - end + 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; @@ -97,20 +176,24 @@ fade = warp(function(new, fac) new.alpha = math.min(255, (new.alpha or 255) * fac) end); - brighten = warp(function(new, fac) - local lum = new:luminosity() - local newlum = lum * fac - local delta = (newlum - lum) - new.red = clip(new.red + delta) - new.blue = clip(new.blue + delta) - new.green = clip(new.green + delta) - 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); }