Overview
Comment: | fix(color): Actually get hsl brightening working Reimplemented based on the algorithms in Computer Graphics: Principles and Practice. Only lightens based on luminosity right now, which makes beautifully saturated, but not pastel colors, so some tweaking might be recommended. |
---|---|
Downloads: | Tarball | ZIP archive | SQL archive |
Timelines: | family | ancestors | descendants | both | glowpelt/hsl |
Files: | files | file ages | folders |
SHA3-256: |
794d5b023ab5b43a9c04b00165c61e8e |
User & Date: | glowpelt on 2020-08-14 06:17:34 |
Other Links: | branch diff | manifest | tags |
Context
2020-08-16
| ||
02:05 | changes, merges, additions galore check-in: 82178e0a16 user: lexi tags: trunk | |
2020-08-14
| ||
06:17 | fix(color): Actually get hsl brightening working Reimplemented based on the algorithms in Computer Graphics: Principles and Practice. Only lightens based on luminosity right now, which makes beautifully saturated, but not pastel colors, so some tweaking might be recommended. Leaf check-in: 794d5b023a user: glowpelt tags: glowpelt/hsl | |
2020-08-13
| ||
05:11 | 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? check-in: 0a49ac4849 user: glowpelt tags: glowpelt/hsl | |
Changes
Modified lib/color.lua from [1e12e4a44b] to [ef48edf309].
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 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)) 59 + -- Based on the algorithm in Computer Graphics: Principles and Practice, by 60 + -- James D. Foley et. al., 2nd ed., p. 596 61 + -- Degree version, though radian is more natural, I don't want to translate it yet 62 + local h = hsl.hue 63 + local s = hsl.saturation 64 + local l = hsl.luminosity 65 + local value = function(n1, n2, hue) 66 + if hue > 360 then 67 + hue = hue - 360 68 + elseif hue < 0 then 69 + hue = hue + 360 70 + end 71 + if hue < 60 then 72 + return n1 + (n2 - n1) * hue/60 73 + elseif hue < 180 then 74 + return n2 75 + elseif hue < 240 then 76 + return n1 + (n2 - n1) * (240 - hue)/60 77 + else 78 + return n1 79 + end 80 + end 81 + local m2 82 + if l < 0.5 then 83 + m2 = l * (1 + s) 84 + else 85 + m2 = l + s - l * s 65 86 end 66 - return color(clip(weird(0)*255), clip(weird(8)*255), clip(weird(4)*255), alpha) 87 + local m1 = 2 * l - m2 88 + if s == 0 then 89 + -- Achromatic, there is no hue 90 + -- In book this errors if hue is not undefined, but we set hue to 0 in this case, not nil or something, so 91 + return color(l, l, l, alpha) 92 + else 93 + -- Chromatic case, so there is a hue 94 + return color( 95 + clip(value(m1, m2, h + 120)*255), 96 + clip(value(m1, m2, h)*255), 97 + clip(value(m1, m2, h - 120)*255), 98 + alpha 99 + ) 100 + end 67 101 end; 68 102 local warp = function(f) 69 103 return function(self, ...) 70 104 local n = color(self) 71 105 f(n, ...) 72 106 return n 73 107 end; ................................................................................ 82 116 end; 83 117 84 118 luminosity = function(self) return 85 119 (self.red + self.green + self.blue) / 3 86 120 end; 87 121 88 122 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 123 + -- Based on the algorithm in Computer Graphics: Principles and Practice, by 124 + -- James D. Foley et. al., 2nd ed., p. 595 93 125 -- 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 126 + local r = self.red/255 127 + local g = self.green/255 128 + local b = self.blue/255 129 + local max = math.max(r, g, b) 130 + local min = math.min(r, g, b) 131 + local luminosity = (max + min)/2 132 + local hue = 0 105 133 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)) 134 + if max == min then 135 + -- Achromatic case, because r=g=b 136 + saturation = 0 137 + hue = 0 -- Undefined, so just replace w/ 0 for usability 138 + else 139 + -- Chromatic case 140 + if luminosity <= 0.5 then 141 + saturation = (max - min)/(max + min) 142 + else 143 + saturation = (max - min)/(2 - max - min) 144 + end 145 + -- Next calculate the hue 146 + local delta = max - min 147 + if r == max then 148 + hue = (g - b)/delta 149 + elseif g == max then 150 + hue = 2 + (b - r)/delta 151 + else -- blue must be max, so no point in checking 152 + hue = 4 + (r - g)/delta 153 + end 154 + hue = hue * 60 -- degrees 155 + --hue = hue * (math.pi / 3) -- for hue in radians instead of degrees 156 + if hue < 0 then 157 + hue = hue + 2 * math.pi 158 + end 111 159 end 160 + print("r"..self.red.."g"..self.green.."b"..self.blue.." is h"..hue.."s"..saturation.."l"..luminosity) 161 + local temp = from_hsl({hue=hue,saturation=saturation,luminosity=luminosity},self.alpha) 162 + print("back is r"..temp.red.."g"..temp.green.."b"..temp.blue) 112 163 return { hue = hue, saturation = saturation, luminosity = luminosity } 113 164 end; 114 165 115 166 readable = function(self, target) 116 - target = target or 200 167 + target = target or 0.5 117 168 local hsl = self:to_hsl() 118 169 hsl.luminosity = target 119 170 return from_hsl(hsl, self.alpha) 120 171 end; 121 172 122 173 bg = function(self, text) return 123 174 text .. minetest.get_background_escape_sequence(self:hex()) ................................................................................ 128 179 end); 129 180 130 181 brighten = function(self, fac) 131 182 -- Use HSL to brighten 132 183 -- To HSL 133 184 local hsl = self:to_hsl() 134 185 -- Do the calculation, clamp to 0-1 instead of the clamp fn 135 - hsl.luminosity = math.max(math.min(hsl.luminosity * fac, 0), 1) 186 + hsl.luminosity = math.min(math.max(hsl.luminosity * fac, 0), 1) 136 187 -- Turn back into RGB color 188 + local t = from_hsl(hsl, self.alpha) 189 + print("brighten is r"..t.red.."g"..t.green.."b"..t.blue) 137 190 return from_hsl(hsl, self.alpha) 138 191 end; 139 192 140 193 darken = warp(function(new, fac) 141 194 -- TODO: is there any point to this being different than brighten? Probably especially not now. 142 195 new.red = clip(new.red - (new.red * fac)) 143 196 new.blue = clip(new.blue - (new.blue * fac))