efl/src/lib/evas/filters/lua/color.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