You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

314 lines
6.6 KiB
Lua

--[=[
@c Color
@t ui
@mt mem
@p value number
@d Wrapper for 24-bit colors packed as a decimal value. See the static constructors for more information.
]=]
local class = require('class')
local format = string.format
local min, max, abs, floor = math.min, math.max, math.abs, math.floor
local lshift, rshift = bit.lshift, bit.rshift
local band, bor = bit.band, bit.bor
local bnot = bit.bnot
local isInstance = class.isInstance
local Color, get = class('Color')
local function check(self, other)
if not isInstance(self, Color) or not isInstance(other, Color) then
return error('Cannot perform operation with non-Color object', 2)
end
end
local function clamp(n, mn, mx)
return min(max(n, mn), mx)
end
function Color:__init(value)
value = tonumber(value)
self._value = value and band(value, 0xFFFFFF) or 0
end
function Color:__tostring()
return format('Color: %s (%i, %i, %i)', self:toHex(), self:toRGB())
end
function Color:__eq(other) check(self, other)
return self._value == other._value
end
function Color:__add(other) check(self, other)
local r = clamp(self.r + other.r, 0, 0xFF)
local g = clamp(self.g + other.g, 0, 0xFF)
local b = clamp(self.b + other.b, 0, 0xFF)
return Color.fromRGB(r, g, b)
end
function Color:__sub(other) check(self, other)
local r = clamp(self.r - other.r, 0, 0xFF)
local g = clamp(self.g - other.g, 0, 0xFF)
local b = clamp(self.b - other.b, 0, 0xFF)
return Color.fromRGB(r, g, b)
end
function Color:__mul(other)
if not isInstance(self, Color) then
self, other = other, self
end
other = tonumber(other)
if other then
local r = clamp(self.r * other, 0, 0xFF)
local g = clamp(self.g * other, 0, 0xFF)
local b = clamp(self.b * other, 0, 0xFF)
return Color.fromRGB(r, g, b)
else
return error('Cannot perform operation with non-numeric object')
end
end
function Color:__div(other)
if not isInstance(self, Color) then
return error('Division with Color is not commutative')
end
other = tonumber(other)
if other then
local r = clamp(self.r / other, 0, 0xFF)
local g = clamp(self.g / other, 0, 0xFF)
local b = clamp(self.b / other, 0, 0xFF)
return Color.fromRGB(r, g, b)
else
return error('Cannot perform operation with non-numeric object')
end
end
--[=[
@m fromHex
@t static
@p hex string
@r Color
@d Constructs a new Color object from a hexadecimal string. The string may or may
not be prefixed by `#`; all other characters are interpreted as a hex string.
]=]
function Color.fromHex(hex)
return Color(tonumber(hex:match('#?(.*)'), 16))
end
--[=[
@m fromRGB
@t static
@p r number
@p g number
@p b number
@r Color
@d Constructs a new Color object from RGB values. Values are allowed to overflow
though one component will not overflow to the next component.
]=]
function Color.fromRGB(r, g, b)
r = band(lshift(r, 16), 0xFF0000)
g = band(lshift(g, 8), 0x00FF00)
b = band(b, 0x0000FF)
return Color(bor(bor(r, g), b))
end
local function fromHue(h, c, m)
local x = c * (1 - abs(h / 60 % 2 - 1))
local r, g, b
if 0 <= h and h < 60 then
r, g, b = c, x, 0
elseif 60 <= h and h < 120 then
r, g, b = x, c, 0
elseif 120 <= h and h < 180 then
r, g, b = 0, c, x
elseif 180 <= h and h < 240 then
r, g, b = 0, x, c
elseif 240 <= h and h < 300 then
r, g, b = x, 0, c
elseif 300 <= h and h < 360 then
r, g, b = c, 0, x
end
r = (r + m) * 0xFF
g = (g + m) * 0xFF
b = (b + m) * 0xFF
return r, g, b
end
local function toHue(r, g, b)
r = r / 0xFF
g = g / 0xFF
b = b / 0xFF
local mn = min(r, g, b)
local mx = max(r, g, b)
local d = mx - mn
local h
if d == 0 then
h = 0
elseif mx == r then
h = (g - b) / d % 6
elseif mx == g then
h = (b - r) / d + 2
elseif mx == b then
h = (r - g) / d + 4
end
h = floor(h * 60 + 0.5)
return h, d, mx, mn
end
--[=[
@m fromHSV
@t static
@p h number
@p s number
@p v number
@r Color
@d Constructs a new Color object from HSV values. Hue is allowed to overflow
while saturation and value are clamped to [0, 1].
]=]
function Color.fromHSV(h, s, v)
h = h % 360
s = clamp(s, 0, 1)
v = clamp(v, 0, 1)
local c = v * s
local m = v - c
local r, g, b = fromHue(h, c, m)
return Color.fromRGB(r, g, b)
end
--[=[
@m fromHSL
@t static
@p h number
@p s number
@p l number
@r Color
@d Constructs a new Color object from HSL values. Hue is allowed to overflow
while saturation and lightness are clamped to [0, 1].
]=]
function Color.fromHSL(h, s, l)
h = h % 360
s = clamp(s, 0, 1)
l = clamp(l, 0, 1)
local c = (1 - abs(2 * l - 1)) * s
local m = l - c * 0.5
local r, g, b = fromHue(h, c, m)
return Color.fromRGB(r, g, b)
end
--[=[
@m toHex
@r string
@d Returns a 6-digit hexadecimal string that represents the color value.
]=]
function Color:toHex()
return format('#%06X', self._value)
end
--[=[
@m toRGB
@r number
@r number
@r number
@d Returns the red, green, and blue values that are packed into the color value.
]=]
function Color:toRGB()
return self.r, self.g, self.b
end
--[=[
@m toHSV
@r number
@r number
@r number
@d Returns the hue, saturation, and value that represents the color value.
]=]
function Color:toHSV()
local h, d, mx = toHue(self.r, self.g, self.b)
local v = mx
local s = mx == 0 and 0 or d / mx
return h, s, v
end
--[=[
@m toHSL
@r number
@r number
@r number
@d Returns the hue, saturation, and lightness that represents the color value.
]=]
function Color:toHSL()
local h, d, mx, mn = toHue(self.r, self.g, self.b)
local l = (mx + mn) * 0.5
local s = d == 0 and 0 or d / (1 - abs(2 * l - 1))
return h, s, l
end
--[=[@p value number The raw decimal value that represents the color value.]=]
function get.value(self)
return self._value
end
local function getByte(value, offset)
return band(rshift(value, offset), 0xFF)
end
--[=[@p r number The value that represents the color's red-level.]=]
function get.r(self)
return getByte(self._value, 16)
end
--[=[@p g number The value that represents the color's green-level.]=]
function get.g(self)
return getByte(self._value, 8)
end
--[=[@p b number The value that represents the color's blue-level.]=]
function get.b(self)
return getByte(self._value, 0)
end
local function setByte(value, offset, new)
local byte = lshift(0xFF, offset)
value = band(value, bnot(byte))
return bor(value, band(lshift(new, offset), byte))
end
--[=[
@m setRed
@r nil
@d Sets the color's red-level.
]=]
function Color:setRed(r)
self._value = setByte(self._value, 16, r)
end
--[=[
@m setGreen
@r nil
@d Sets the color's green-level.
]=]
function Color:setGreen(g)
self._value = setByte(self._value, 8, g)
end
--[=[
@m setBlue
@r nil
@d Sets the color's blue-level.
]=]
function Color:setBlue(b)
self._value = setByte(self._value, 0, b)
end
--[=[
@m copy
@r Color
@d Returns a new copy of the original color object.
]=]
function Color:copy()
return Color(self._value)
end
return Color