forked from enlightenment/efl
303 lines
8.9 KiB
Lua
303 lines
8.9 KiB
Lua
--[[
|
|
A simple 'color' class for Evas filters.
|
|
|
|
The default alpha value will be 255 unless specified,
|
|
which means the default color is opaque black.
|
|
|
|
r,g,b,a values range from 0 to 255 and are straight
|
|
(ie. NOT pre-multiplied).
|
|
--]]
|
|
|
|
local __color, __inrange, __color_parse
|
|
|
|
--[[Checks that a number is valid and in the range 0-255]]
|
|
__inrange = function(a)
|
|
if ((not tonumber(a)) or (tonumber(a) < 0) or (tonumber(a) > 255)) then
|
|
return false
|
|
else
|
|
return true
|
|
end
|
|
end
|
|
|
|
--[[
|
|
Parses a string of one of the formats:
|
|
1. "#RRGGBB"
|
|
2. "#RRGGBBAA"
|
|
3. "#RGB"
|
|
4. "#RGBA"
|
|
To the rgba values.
|
|
Same as evas_common_format_color_parse, except we don't premultiply here.
|
|
--]]
|
|
__color_parse = function(str)
|
|
local r,g,b,a
|
|
if not str then return 0,0,0,0 end
|
|
if not string.match(str, "^#[%x]+$") then return 0,0,0,0 end
|
|
len = string.len(str)
|
|
if len == 7 then -- #rrggbb
|
|
r = tonumber(string.sub(str, 2, 3), 16)
|
|
g = tonumber(string.sub(str, 4, 5), 16)
|
|
b = tonumber(string.sub(str, 6, 7), 16)
|
|
a = 0xff
|
|
return r,g,b,a
|
|
end
|
|
if len == 9 then -- #rrggbbaa
|
|
r = tonumber(string.sub(str, 2, 3), 16)
|
|
g = tonumber(string.sub(str, 4, 5), 16)
|
|
b = tonumber(string.sub(str, 6, 7), 16)
|
|
a = tonumber(string.sub(str, 8, 9), 16)
|
|
return r,g,b,a
|
|
end
|
|
if len == 4 then -- #rgb
|
|
r = tonumber(string.sub(str, 2, 2), 16)
|
|
g = tonumber(string.sub(str, 3, 3), 16)
|
|
b = tonumber(string.sub(str, 4, 4), 16)
|
|
r = (r * 0x10) + r
|
|
g = (g * 0x10) + g
|
|
b = (b * 0x10) + b
|
|
a = 0xff
|
|
return r,g,b,a
|
|
end
|
|
if len == 5 then -- #rgba
|
|
r = tonumber(string.sub(str, 2, 2), 16)
|
|
g = tonumber(string.sub(str, 3, 3), 16)
|
|
b = tonumber(string.sub(str, 4, 4), 16)
|
|
a = tonumber(string.sub(str, 5, 5), 16)
|
|
r = (r * 0x10) + r
|
|
g = (g * 0x10) + g
|
|
b = (b * 0x10) + b
|
|
a = (a * 0x10) + a
|
|
return r,g,b,a
|
|
end
|
|
return 0,0,0,255
|
|
end
|
|
|
|
__color = {
|
|
__names = {
|
|
white = 0xFFFFFFFF,
|
|
black = 0xFF000000,
|
|
red = 0xFFFF0000,
|
|
green = 0xFF008000,
|
|
blue = 0xFF0000FF,
|
|
darkblue = 0xFF0000A0,
|
|
yellow = 0xFFFFFF00,
|
|
magenta = 0xFFFF00FF,
|
|
cyan = 0xFF00FFFF,
|
|
orange = 0xFFFFA500,
|
|
purple = 0xFF800080,
|
|
brown = 0xFFA52A2A,
|
|
maroon = 0xFF800000,
|
|
lime = 0xFF00FF00,
|
|
gray = 0xFF808080,
|
|
grey = 0xFF808080,
|
|
silver = 0xFFC0C0C0,
|
|
olive = 0xFF808000,
|
|
invisible = 0x00000000,
|
|
transparent = 0x00000000
|
|
},
|
|
|
|
__methods = {
|
|
--[[
|
|
Assign a value to a color object.
|
|
|
|
Accepted formats include:
|
|
- 'colorname' (eg. 'red')
|
|
- another color object
|
|
- {r,g,b} or {r,g,b,a}
|
|
- a single integer value from 0x00000000 to 0xFFFFFFFF (0xAARRGGBB)
|
|
- 3 or 4 arguments (c:set(r,g,b) or c:set(r,g,b,a))
|
|
- a string like "#aarrggbb"
|
|
--]]
|
|
set = function (self, A, B, C, D)
|
|
-- nil
|
|
if not A then
|
|
return self:set(0xFF000000)
|
|
end
|
|
|
|
-- name or #value or 0xvalue
|
|
if (type(A) == 'string') then
|
|
if string.sub(A, 1, 1) == "#" then
|
|
return self:set(__color_parse(A))
|
|
end
|
|
if string.sub(A, 1, 2) == "0x" then
|
|
return self:set(tonumber(A))
|
|
end
|
|
return self:set(__color.__names[A])
|
|
end
|
|
|
|
-- another color
|
|
if (getmetatable(A) == getmetatable(self)) then
|
|
self.r = math.floor(A.r)
|
|
self.g = math.floor(A.g)
|
|
self.b = math.floor(A.b)
|
|
self.a = math.floor(A.a)
|
|
return self
|
|
end
|
|
|
|
-- input {r,g,b} or {r,g,b,a}
|
|
if (type(A) == 'table') then
|
|
if ((not __inrange(A[1])) or (not __inrange(A[2])) or (not __inrange(A[3]))) then
|
|
error('Invalid color value: ' .. tostring(A[1]) .. " , " .. tostring(A[2]) .. " , " .. tostring(A[3]))
|
|
end
|
|
self.r = math.floor(A[1])
|
|
self.g = math.floor(A[2])
|
|
self.b = math.floor(A[3])
|
|
if (__inrange(A[4])) then self.a = math.floor(A[4]) else self.a = 255 end
|
|
return self
|
|
end
|
|
|
|
-- input single value 0xAARRGGBB
|
|
if ((B == nil) and (type(A) == 'number')) then
|
|
A = math.floor(A) -- % 0x100000000
|
|
if ((A < 0) or (A > 0xFFFFFFFF)) then
|
|
error('Invalid color value: ' .. string.format("0x%x", A))
|
|
end
|
|
self.a = math.floor(A / 0x1000000)
|
|
self.r = math.floor((A / 0x10000) % 0x100)
|
|
self.g = math.floor((A / 0x100) % 0x100)
|
|
self.b = math.floor(A % 0x100)
|
|
return self
|
|
end
|
|
|
|
-- simplest method (r,g,b[,a])
|
|
if ((not __inrange(A)) or (not __inrange(B)) or (not __inrange(C))) then
|
|
error('Invalid color value: ' .. tostring(A) .. " , " .. tostring(B) .. " , " .. tostring(C))
|
|
end
|
|
if (__inrange(D)) then self.a = math.floor(D) else self.a = 255 end
|
|
self.r = math.floor(A)
|
|
self.g = math.floor(B)
|
|
self.b = math.floor(C)
|
|
return self
|
|
end,
|
|
|
|
--[[
|
|
Multiply a color by a value (another color or an alpha value).
|
|
Returns a new value.
|
|
--]]
|
|
mul = function (self, A)
|
|
local C = __color(self)
|
|
if tonumber(A) ~= nil then
|
|
C.a = C.a * tonumber(A) / 255
|
|
else
|
|
A = __color(A)
|
|
C.r = C.r * A.r / 255
|
|
C.g = C.g * A.g / 255
|
|
C.b = C.b * A.b / 255
|
|
C.a = C.a * A.a / 255
|
|
end
|
|
return C
|
|
end,
|
|
|
|
--[[
|
|
Add a color to another.
|
|
Returns a new value.
|
|
--]]
|
|
add = function (self, A)
|
|
local C = __color(self)
|
|
A = __color(A)
|
|
C.a = math.min(C.a + A.a, 255)
|
|
C.r = math.min(C.r + A.r, 255)
|
|
C.g = math.min(C.g + A.g, 255)
|
|
C.b = math.min(C.b + A.b, 255)
|
|
return C
|
|
end,
|
|
|
|
--[[
|
|
Alpha blending function: A:blend(B) returns A.a*A.rgb + B.a*(255-A.a)*B.rgb
|
|
This blends A on top of B.
|
|
Returns a new value.
|
|
--]]
|
|
blend = function (self, A)
|
|
local C = __color(self)
|
|
A = __color(A)
|
|
C.r = ((C.a * C.r) / 255) + ((255 - C.a) * A.a) * A.r / (255 * 255);
|
|
C.g = ((C.a * C.g) / 255) + ((255 - C.a) * A.a) * A.g / (255 * 255);
|
|
C.b = ((C.a * C.b) / 255) + ((255 - C.a) * A.a) * A.b / (255 * 255);
|
|
C.a = C.a + ((255 - C.a) * A.a) / 255;
|
|
return C
|
|
end
|
|
},
|
|
|
|
__index = function (self, key)
|
|
methods = getmetatable(self).__methods
|
|
if (rawget(methods, key)) then return rawget(methods, key) end
|
|
error('Invalid index \'' .. tostring(key) .. '\' for a color')
|
|
end,
|
|
|
|
__tostring = function (self)
|
|
return string.format('#%02x%02x%02x%02x', self.r, self.g, self.b, self.a)
|
|
end,
|
|
|
|
__call = function (mt, ...)
|
|
local C = {}
|
|
setmetatable(C, mt)
|
|
return C:set(...)
|
|
end,
|
|
|
|
__mul = function (self, ...)
|
|
return __color(self):mul(...)
|
|
end,
|
|
|
|
__add = function (self, ...)
|
|
return __color(self):add(...)
|
|
end,
|
|
|
|
-- Register all global values into global env (_G)
|
|
__register = function (tbl)
|
|
for k, v in pairs(__color.__names) do
|
|
rawset(tbl, k, __color(v))
|
|
end
|
|
end,
|
|
|
|
-- Test case
|
|
__test = function ()
|
|
local A, B, C
|
|
|
|
C = __color()
|
|
assert(tostring(C) == '#000000ff')
|
|
C:set({0xFE, 0xAB, 0x12})
|
|
assert(tostring(C) == '#feab12ff')
|
|
C:set(0xFFFEAB99)
|
|
assert(tostring(C) == '#feab99ff')
|
|
C:set()
|
|
assert(tostring(C) == '#000000ff')
|
|
C:set(0xfe, 0xab, 0x12, 0xff)
|
|
assert(tostring(C) == '#feab12ff')
|
|
C = __color{0xfe, 0xab, 0x12}
|
|
assert(tostring(C) == '#feab12ff')
|
|
B = __color(C)
|
|
assert(tostring(B) == '#feab12ff')
|
|
B = B * 128
|
|
assert(tostring(B) == '#feab1280')
|
|
A = B * C
|
|
assert(tostring(A) == '#fd720180')
|
|
A = B + C
|
|
assert(tostring(A) == '#ffff24ff')
|
|
A = __color(0xFF012345):blend(0xFFFFFFFF)
|
|
assert(tostring(A) == '#012345ff')
|
|
A = __color(0x00012345):blend(0xFFFFFFFF)
|
|
assert(tostring(A) == '#ffffffff')
|
|
A = __color(0x80102030):blend(0xFFFFFFFF)
|
|
assert(tostring(A) == '#878f97ff') -- check this
|
|
A = __color(0x80102030):blend("transparent")
|
|
assert(tostring(A) == '#08101880')
|
|
A = __color("#ff0000ff") * 255
|
|
assert(tostring(A) == '#ff0000ff')
|
|
A = A * 0x80
|
|
assert(tostring(A) == '#ff000080')
|
|
assert(tostring(__color('#123')) == '#112233ff')
|
|
assert(tostring(__color('#1234')) == '#11223344')
|
|
assert(tostring(__color('#123456')) == '#123456ff')
|
|
assert(tostring(__color('#12345678')) == '#12345678')
|
|
|
|
__color.__register(_G)
|
|
assert(tostring(white) == '#ffffffff')
|
|
assert(tostring(red) == '#ff0000ff')
|
|
|
|
print('All color tests passed')
|
|
return true
|
|
end
|
|
}
|
|
setmetatable(__color, __color)
|
|
if arg and arg[1] == "-t" then __color.__test() end
|
|
return __color
|