--[[ * Converts an HSL color value to RGB. Conversion formula * adapted from http://en.wikipedia.org/wiki/HSL_color_space. * Assumes h, s, and l are contained in the set [0, 1] and * returns r, g, and b in the set [0, 1]. ]] local function hslToRgb(h, s, l) local r, g, b if s == 0 then r, g, b = l, l, l -- achromatic else function hue2rgb(p, q, t) if t < 0 then t = t + 1 end if t > 1 then t = t - 1 end if t < 1/6 then return p + (q - p) * 6 * t end if t < 1/2 then return q end if t < 2/3 then return p + (q - p) * (2/3 - t) * 6 end return p end local q if l < 0.5 then q = l * (1 + s) else q = l + s - l * s end local p = 2 * l - q r = hue2rgb(p, q, h + 1/3) g = hue2rgb(p, q, h) b = hue2rgb(p, q, h - 1/3) end return r, g, b end --[[ * Converts an HSL color value to RGB. Conversion formula * adapted from http://en.wikipedia.org/wiki/HSL_color_space. * Assumes h, s, and l are contained in the set [0, 1] and * returns r, g, and b in the set [0, 1]. ]] local function rgbToHsl(r, g, b) local max, min = math.max(r, g, b), math.min(r, g, b) local h, s, l l = (max + min) / 2 if max == 0 then s = 0 else s = (max - min) / max end if max == min then h, s = 0, 0 -- achromatic else local d = max - min local s if l > 0.5 then s = d / (2 - max - min) else s = d / (max + min) end if max == r then h = (g - b) / d if g < b then h = h + 6 end elseif max == g then h = (b - r) / d + 2 elseif max == b then h = (r - g) / d + 4 end h = h / 6 end return h, s, l end --[[ * Converts an RGB color value to HSV. Conversion formula * adapted from http://en.wikipedia.org/wiki/HSV_color_space. * Assumes r, g, and b are contained in the set [0, 1] and * returns h, s, and v in the set [0, 1]. ]] local function rgbToHsv(r, g, b) local max, min = math.max(r, g, b), math.min(r, g, b) local h, s, v v = max local d = max - min if max == 0 then s = 0 else s = d / max end if max == min then h = 0 -- achromatic else if max == r then h = (g - b) / d if g < b then h = h + 6 end elseif max == g then h = (b - r) / d + 2 elseif max == b then h = (r - g) / d + 4 end h = h / 6 end return h, s, v end --[[ * Converts an HSV color value to RGB. Conversion formula * adapted from http://en.wikipedia.org/wiki/HSV_color_space. * Assumes h, s, and v are contained in the set [0, 1] and * returns r, g, and b in the set [0, 1]. ]] local function hsvToRgb(h, s, v) local r, g, b local i = math.floor(h * 6); local f = h * 6 - i; local p = v * (1 - s); local q = v * (1 - f * s); local t = v * (1 - (1 - f) * s); i = i % 6 if i == 0 then r, g, b = v, t, p elseif i == 1 then r, g, b = q, v, p elseif i == 2 then r, g, b = p, v, t elseif i == 3 then r, g, b = p, q, v elseif i == 4 then r, g, b = t, p, v elseif i == 5 then r, g, b = v, p, q end return r, g, b end ---@class color ---@field r number @red, 0.0 - 1.0 ---@field g number @green, 0.0 - 1.0 ---@field b number @blue, 0.0 - 1.0 ---@field a number @alpha, 0.0 - 1.0 ---@operator add(color): color ---@operator add(number): color ---@operator sub(color): color ---@operator sub(number): color ---@operator mul(color): color ---@operator mul(number): color ---@operator div(color): color ---@operator div(number): color local col = {} --- for use in actor:diffuse(col:unpack()) ---@return number, number, number, number function col:unpack() return self.r, self.g, self.b, self.a end -- conversions ---@return number, number, number function col:rgb() return self.r, self.g, self.b end ---@return number, number, number function col:hsl() return rgbToHsl(self.r, self.g, self.b) end ---@return number, number, number function col:hsv() return rgbToHsv(self.r, self.g, self.b) end ---@return string function col:hex() return string.format('%02x%02x%02x', math.floor(self.r * 255), math.floor(self.g * 255), math.floor(self.b * 255)) end -- setters ---@return color function col:hue(h) local _, s, v = self:hsv() return hsv(h % 1, s, v, self.a) end ---@return color function col:huesmooth(h) local _, s, v = self:hsv() return shsv(h % 1, s, v, self.a) end ---@return color function col:alpha(a) return rgb(self.r, self.g, self.b, a) end --- multiplies current alpha by provided value ---@return color function col:malpha(a) return rgb(self.r, self.g, self.b, self.a * a) end -- effects ---@return color function col:invert() return rgb(1 - self.r, 1 - self.g, 1 - self.b, self.a) end ---@return color function col:grayscale() return rgb(self.r * 0.299 + self.g * 0.587 + self.b * 0.114, self.a) end ---@return color function col:hueshift(a) local h, s, v = self:hsv() return hsv((h + a) % 1, s, v, self.a) end local colmeta = {} function colmeta:__index(i) if i == 1 then return self.r end if i == 2 then return self.g end if i == 3 then return self.b end if i == 4 then return self.a end return col[i] end local function typ(a) return (type(a) == 'table' and a.r and a.g and a.b and a.a) and 'color' or type(a) end local function genericop(a, b, f, name) local typea = typ(a) local typeb = typ(b) if typea == 'number' then return rgb(f(b.r, a), f(b.g, a), f(b.b, a), b.a) elseif typeb == 'number' then return rgb(f(a.r, b), f(a.g, b), f(a.b, b), a.a) elseif typea == 'color' and typeb == 'color' then return rgb(f(a.r, b.r), f(a.g, b.g), f(a.b, b.b), f(a.a, b.a)) end error('cant apply ' .. name .. ' to ' .. typea .. ' and ' .. typeb, 3) end function colmeta.__add(a, b) return genericop(a, b, function(a, b) return a + b end, 'add') end function colmeta.__sub(a, b) return genericop(a, b, function(a, b) return a - b end, 'sub') end function colmeta.__mul(a, b) return genericop(a, b, function(a, b) return a * b end, 'mul') end function colmeta.__div(a, b) return genericop(a, b, function(a, b) return a / b end, 'div') end function colmeta.__eq(a, b) return (typ(a) == 'color' and typ(b) == 'color') and (a.r == b.r and a.g == b.g and a.b == b.b and a.a == b.a) end function colmeta:__tostring() return '#' .. self:hex() end colmeta.__name = 'color' -- constructors ---@return color function rgb(r, g, b, a) a = a or 1 return setmetatable({r = r, g = g, b = b, a = a or 1}, colmeta) end ---@return color function hsl(h, s, l, a) a = a or 1 local r, g, b = hslToRgb(h % 1, s, l) return setmetatable({r = r, g = g, b = b, a = a or 1}, colmeta) end ---@return color function hsv(h, s, v, a) a = a or 1 local r, g, b = hsvToRgb(h % 1, s, v) return setmetatable({r = r, g = g, b = b, a = a or 1}, colmeta) end --- smoother hsv. not correct but looks nicer ---@return color function shsv(h, s, v, a) h = h % 1 return hsv(h * h * (3 - 2 * h), s, v, a) end ---@param hex string ---@return color function hex(hex) hex = string.gsub(hex, '#', '') if string.len(hex) == 3 then return rgb((tonumber('0x' .. string.sub(hex, 1, 1)) * 17) / 255, (tonumber('0x' .. string.sub(hex, 2, 2)) * 17) / 255, (tonumber('0x' .. string.sub(hex, 3, 3)) * 17) / 255) else return rgb(tonumber('0x' .. string.sub(hex, 1, 2)) / 255, tonumber('0x' .. string.sub(hex, 3, 4)) / 255, tonumber('0x' .. string.sub(hex, 5, 6)) / 255) end end