sorcery  Diff

Differences From Artifact [16b55419da]:

To Artifact [1e12e4a44b]:

  • File lib/color.lua — part of check-in [0a49ac4849] at 2020-08-13 05:11:22 on branch glowpelt/hsl — feat(color): Change color lightening to use HSL Change color lightening, including the readable utility, to use HSL. This is because the earlier implementation was broken and hacky, and using HSL is a way to implement this in a much more natural-feeling way (being closer to percieved lightness), especially for the current uses. Add a color:to_hsl() function to make this easier, as well as a from_hsl utility that is only in color, for now, but maybe should be exposed as an alternate constructor? (user: glowpelt, size: 6197) [annotate] [blame] [check-ins using]

    51     51   		} end;
    52     52   	};
    53     53   
    54     54   	construct = function(r,g,b,a)
    55     55   		local clip = function(v)
    56     56   			return math.max(0,math.min(255,v))
    57     57   		end;
           58  +                local from_hsl = function(hsl, alpha)
           59  +                        -- convert from a hsl table and alpha value to a color
           60  +                        local weird = function(n)
           61  +                                -- Yeah... this is a really weird function, only named f
           62  +                                local k = math.fmod(n + hsl.hue/(math.pi/6),12)
           63  +                                local a = hsl.saturation * math.min(hsl.luminosity, 1 - hsl.luminosity)
           64  +                                return hsl.luminosity * math.max(-1, math.min(k-3,9-k,1))
           65  +                        end
           66  +                        return color(clip(weird(0)*255), clip(weird(8)*255), clip(weird(4)*255), alpha)
           67  +                end;
    58     68   		local warp = function(f)
    59     69   			return function(self, ...)
    60     70   				local n = color(self)
    61     71   				f(n, ...)
    62     72   				return n
    63     73   			end;
    64     74   		end;
................................................................................
    70     80   			fmt = function(self, text) return
    71     81   				minetest.colorize(self:hex(), text)
    72     82   			end;
    73     83   
    74     84   			luminosity = function(self) return
    75     85   				(self.red + self.green + self.blue) / 3
    76     86   			end;
           87  +
           88  +                        to_hsl = function(self)
           89  +                                -- https://en.wikipedia.org/wiki/HSL_and_HSV
           90  +                                -- www.niwa.nu/2013/05/math-behind-colorspace-conversions-rgb-hsl/
           91  +                                -- https://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.211.6425
           92  +                                -- https://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.413.9004
           93  +                                -- We need the rgb between 0 and 1
           94  +                                local rgb = { r = self.red/255, g = self.green/255, b = self.blue/255 }
           95  +                                -- First, the hue.
           96  +                                -- This version of the calculation can be up to 1.12deg off at the right few colors
           97  +                                -- but is overall very close, easier to implement, and runs much faster
           98  +                                -- TODO: consider memoizing something to do with this all?
           99  +                                local alpha = 0.5 * (2*rgb.r - rgb.g - rgb.b)
          100  +                                local beta = (math.sqrt(3)/2)*(rgb.g - rgb.b)
          101  +                                local hue = math.atan2(beta, alpha)
          102  +                                -- Next the luminosity/lightness. This one's easy enough
          103  +                                local luminosity = 0.5*(math.max(rgb.r, rgb.g, rgb.b) + math.min(rgb.r, rgb.g, rgb.b))
          104  +                                -- Finally, saturation
          105  +                                local saturation = 0
          106  +                                -- If luminosity isn't essentially 1 or 0
          107  +                                if math.abs(luminosity - 1) < 1e-18 or math.abs(luminosity) < 1e-18 then
          108  +                                        -- need the chroma
          109  +                                        local chroma = math.max(rgb.r,rgb.g,rgb.b) - math.min(rgb.r,rgb.g,rgb.b)
          110  +                                        saturation = chroma / (1 - math.abs(2*luminosity - 1))
          111  +                                end
          112  +                                return { hue = hue, saturation = saturation, luminosity = luminosity }
          113  +                        end;
    77    114   
    78    115   			readable = function(self, target)
    79    116   				target = target or 200
    80         -				local lum = self:luminosity()
    81         -				if lum < target then
    82         -					local f = 1.0 + (target - lum) / 255
    83         -					local nc = self:brighten(f * 1.5)
    84         -					-- i don't know why the *1.5 is necessary. it's
    85         -					-- an ugly hack to work around broken math,
    86         -					-- because i'm too much of a mathtard to actually
    87         -					-- find what's wrong
    88         -					return nc
    89         -				else
    90         -					return self
    91         -				end
          117  +                                local hsl = self:to_hsl()
          118  +                                hsl.luminosity = target
          119  +                                return from_hsl(hsl, self.alpha)
    92    120   			end;
    93    121   
    94    122   			bg = function(self, text) return
    95    123   				text .. minetest.get_background_escape_sequence(self:hex())
    96    124   			end;
    97    125   
    98    126   			fade = warp(function(new, fac)
    99    127   				new.alpha = math.min(255, (new.alpha or 255) * fac)
   100    128   			end);
   101    129   
   102         -			brighten = warp(function(new, fac)
   103         -				local lum = new:luminosity()
   104         -				local newlum = lum * fac
   105         -				local delta = (newlum - lum)
   106         -				new.red   = clip(new.red   + delta)
   107         -				new.blue  = clip(new.blue  + delta)
   108         -				new.green = clip(new.green + delta)
   109         -			end);
          130  +			brighten = function(self, fac)
          131  +                                -- Use HSL to brighten
          132  +                                -- To HSL
          133  +                                local hsl = self:to_hsl()
          134  +                                -- Do the calculation, clamp to 0-1 instead of the clamp fn
          135  +                                hsl.luminosity = math.max(math.min(hsl.luminosity * fac, 0), 1)
          136  +                                -- Turn back into RGB color
          137  +                                return from_hsl(hsl, self.alpha)
          138  +			end;
   110    139   
   111    140   			darken = warp(function(new, fac)
          141  +                                -- TODO: is there any point to this being different than brighten? Probably especially not now.
   112    142   				new.red = clip(new.red - (new.red * fac))
   113    143   				new.blue = clip(new.blue - (new.blue * fac))
   114    144   				new.green = clip(new.green - (new.green * fac))
   115    145   			end);
   116    146   		}
   117    147   		if g == nil then
   118    148   			if type(r) == 'string' then