Differences From
Artifact [16b55419da]:
- 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