sorcery  Check-in [794d5b023a]

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: 794d5b023ab5b43a9c04b00165c61e8e425a3ea3ca925915146f0a8bc05dacc5
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))