From f3e16bc4854268cecc19a38671c6f68071b7955b Mon Sep 17 00:00:00 2001 From: Jean-Philippe Andre Date: Thu, 4 Jun 2015 19:42:38 +0900 Subject: [PATCH] Evas filters: Implement Lua classes for colors & buffer Reuse previous code for buffer. Keeps API stability. The new class "color" is here for a more convenient color representation. This way, colors can be represented in more natural ways like: {r,g,b[,a]}, 0xaarrggbb, "red", "#rrggbb" Class color is implemented in pure Lua, and adds a .lua file to Evas' share folder. --- src/Makefile_Evas.am | 8 + src/lib/evas/file/evas_module.c | 10 + src/lib/evas/filters/evas_filter.c | 1 + src/lib/evas/filters/evas_filter_parser.c | 583 +++++++++++---------- src/lib/evas/filters/evas_filter_private.h | 2 + src/lib/evas/filters/lua/color.lua | 302 +++++++++++ src/lib/evas/include/evas_private.h | 1 + 7 files changed, 639 insertions(+), 268 deletions(-) create mode 100644 src/lib/evas/filters/lua/color.lua diff --git a/src/Makefile_Evas.am b/src/Makefile_Evas.am index 941e1d21c0..ef59a2f185 100644 --- a/src/Makefile_Evas.am +++ b/src/Makefile_Evas.am @@ -2204,3 +2204,11 @@ installed_evasluadir = $(datadir)/elua/modules/evas nodist_installed_evaslua_DATA = $(generated_evas_lua_all) endif + +# Evas filters Lua stuff +evas_filters_lua = \ +lib/evas/filters/lua/color.lua \ +$(NULL) + +installed_evasfiltersdir = $(datadir)/evas/filters/lua +dist_installed_evasfilters_DATA = $(evas_filters_lua) diff --git a/src/lib/evas/file/evas_module.c b/src/lib/evas/file/evas_module.c index ead26123b1..bafc6e7261 100644 --- a/src/lib/evas/file/evas_module.c +++ b/src/lib/evas/file/evas_module.c @@ -680,6 +680,16 @@ _evas_module_libdir_get(void) return eina_prefix_lib_get(pfx); } +const char * +_evas_module_datadir_get(void) +{ + if (!pfx) pfx = eina_prefix_new + (NULL, _evas_module_libdir_get, "EVAS", "evas", "checkme", + PACKAGE_BIN_DIR, PACKAGE_LIB_DIR, PACKAGE_DATA_DIR, PACKAGE_DATA_DIR); + if (!pfx) return NULL; + return eina_prefix_data_get(pfx); +} + EAPI const char * evas_cserve_path_get(void) { diff --git a/src/lib/evas/filters/evas_filter.c b/src/lib/evas/filters/evas_filter.c index 1e369f027c..d420929b63 100644 --- a/src/lib/evas/filters/evas_filter.c +++ b/src/lib/evas/filters/evas_filter.c @@ -2002,6 +2002,7 @@ void evas_filter_shutdown() { if ((--init_cnt) > 0) return; + evas_filter_parser_shutdown(); eina_log_domain_unregister(_evas_filter_log_dom); _evas_filter_log_dom = 0; } diff --git a/src/lib/evas/filters/evas_filter_parser.c b/src/lib/evas/filters/evas_filter_parser.c index c5f409d79b..8992dcb519 100644 --- a/src/lib/evas/filters/evas_filter_parser.c +++ b/src/lib/evas/filters/evas_filter_parser.c @@ -215,35 +215,6 @@ @since 1.9 */ -// Map of the most common HTML color names -static struct -{ - const char *name; - DATA32 value; -} color_map[] = -{ - { "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 } -}; - static struct { const char *name; @@ -265,9 +236,15 @@ static struct { "stretch_xy", EVAS_FILTER_FILL_MODE_STRETCH_XY } }; -static const char *_lua_buffer_meta = "Filter.buffer"; +static const char *_lua_buffer_meta = "buffer"; +static const char *_lua_color_meta = "color"; +#define _lua_methods_table "__methods" +#define _lua_register_func "__register" +#define _lua_errfunc_name "__backtrace" static Evas_Filter_Fill_Mode _fill_mode_get(Evas_Filter_Instruction *instr); +static Eina_Bool _lua_instruction_run(lua_State *L, Evas_Filter_Instruction *instr); +static int _lua_backtrace(lua_State *L); typedef enum { @@ -618,46 +595,6 @@ _bool_parse(const char *str, Eina_Bool *b) #define PARSE_CHECK(a) do { if (!(a)) { ERR("Parsing failed because '%s' is false at %s:%d", #a, __FUNCTION__, __LINE__); PARSE_ABORT(); goto end; } } while (0) -static Eina_Bool -_color_parse(const char *word, DATA32 *color) -{ - DATA32 value; - Eina_Bool success = EINA_FALSE; - - PARSE_CHECK(word && *word); - - errno = 0; - if (*word == '#') - { - unsigned char a, r, g, b; - int slen = strlen(word); - PARSE_CHECK(evas_common_format_color_parse(word, slen, &r, &g, &b, &a)); - value = ARGB_JOIN(a, r, g, b); - } - else - { - unsigned int k; - for (k = 0; k < (sizeof(color_map) / sizeof(color_map[0])); k++) - { - if (!strcasecmp(word, color_map[k].name)) - { - if (color) *color = color_map[k].value; - return EINA_TRUE; - } - } - PARSE_CHECK(!"color name not found"); - } - - if ((value & 0xFF000000) == 0 && (value != 0)) - value |= 0xFF000000; - - if (color) *color = value; - success = EINA_TRUE; - -end: - return success; -} - /* Buffers */ static Buffer * _buffer_get(Evas_Filter_Program *pgm, const char *name) @@ -679,16 +616,19 @@ _lua_buffer_push(lua_State *L, Buffer *buf) { Buffer **ptr; - lua_getglobal(L, buf->name); - ptr = lua_newuserdata(L, sizeof(Buffer **)); + lua_getglobal(L, buf->name);//+1 + ptr = lua_newuserdata(L, sizeof(Buffer **));//+1 *ptr = buf; - luaL_getmetatable(L, _lua_buffer_meta); - lua_setmetatable(L, -2); - lua_setglobal(L, buf->name); + luaL_getmetatable(L, _lua_buffer_meta);//+1 + lua_setmetatable(L, -2);//-1 + lua_setglobal(L, buf->name);//-1 + lua_pop(L, 1); return EINA_TRUE; } +// Begin of Lua metamethods and stuff + static int _lua_buffer_tostring(lua_State *L) { @@ -717,12 +657,12 @@ _lua_buffer_index(lua_State *L) key = lua_tostring(L, 2); if (!key) return 0; - if (!strcmp(key, "width")) + if (!strcmp(key, "w") || !strcmp(key, "width")) { lua_pushinteger(L, buf->w); return 1; } - else if (!strcmp(key, "height")) + else if (!strcmp(key, "h") || !strcmp(key, "height")) { lua_pushinteger(L, buf->h); return 1; @@ -754,69 +694,30 @@ _lua_buffer_index(lua_State *L) return 1; } else + return luaL_error(L, "Unknown index '%s' for a buffer", key); + + return 0; +} + +// remove metatable from first argument if this is a __call metafunction +static inline int +_lua_implicit_metatable_drop(lua_State *L, const char *name) +{ + int ret = 0; + if (lua_istable(L, 1) && lua_getmetatable(L, 1)) { - DBG("Unknown index '%s' for a buffer", key); - return 0; + luaL_getmetatable(L, name); + if (lua_equal(L, -1, -2)) + { + lua_remove(L, 1); + ret = 1; + } + lua_pop(L, 2); } + return ret; } -static int -_lua_buffer_width(lua_State *L) -{ - Buffer *buf, **pbuf; - pbuf = lua_touserdata(L, 1); - buf = pbuf ? *pbuf : NULL; - if (!buf) return 0; - lua_pushnumber(L, buf->w); - return 1; -} - -static int -_lua_buffer_height(lua_State *L) -{ - Buffer *buf, **pbuf; - pbuf = lua_touserdata(L, 1); - buf = pbuf ? *pbuf : NULL; - if (!buf) return 0; - lua_pushnumber(L, buf->h); - return 1; -} - -static int -_lua_buffer_type(lua_State *L) -{ - Buffer *buf, **pbuf; - pbuf = lua_touserdata(L, 1); - buf = pbuf ? *pbuf : NULL; - if (!buf) return 0; - lua_pushstring(L, buf->alpha ? "alpha" : "rgba"); - return 1; -} - -static int -_lua_buffer_name(lua_State *L) -{ - Buffer *buf, **pbuf; - pbuf = lua_touserdata(L, 1); - buf = pbuf ? *pbuf : NULL; - if (!buf) return 0; - lua_pushstring(L, buf->name); - return 1; -} - -static int -_lua_buffer_source(lua_State *L) -{ - Buffer *buf, **pbuf; - pbuf = lua_touserdata(L, 1); - buf = pbuf ? *pbuf : NULL; - if (!buf) return 0; - if (!buf->proxy) - lua_pushnil(L); - else - lua_pushstring(L, buf->proxy); - return 1; -} +// End of all lua metamethods and stuff static Buffer * _buffer_add(Evas_Filter_Program *pgm, const char *name, Eina_Bool alpha, @@ -868,6 +769,19 @@ _buffer_del(Buffer *buf) free(buf); } +static const int this_is_not_a_cat = 42; + +static Evas_Filter_Program * +_lua_program_get(lua_State *L) +{ + Evas_Filter_Program *pgm; + lua_pushlightuserdata(L, (void *) &this_is_not_a_cat); + lua_gettable(L, LUA_REGISTRYINDEX); + pgm = lua_touserdata(L, -1); + lua_pop(L, 1); + return pgm; +} + /* Instruction definitions */ /** @@ -935,20 +849,33 @@ _buffer_instruction_parse_run(lua_State *L, return ok; } -static Eina_Bool -_buffer_instruction_prepare(Evas_Filter_Program *pgm EINA_UNUSED, - Evas_Filter_Instruction *instr) +static int +_lua_buffer_new(lua_State *L) { - EINA_SAFETY_ON_NULL_RETURN_VAL(instr, EINA_FALSE); - EINA_SAFETY_ON_NULL_RETURN_VAL(instr->name, EINA_FALSE); - EINA_SAFETY_ON_FALSE_RETURN_VAL(!strcmp(instr->name, "buffer"), EINA_FALSE); + // Reuse old "buffer" instruction code + Evas_Filter_Program *pgm = _lua_program_get(L); + Evas_Filter_Instruction *instr; + instr = _instruction_new(_lua_buffer_meta); instr->type = EVAS_FILTER_MODE_BUFFER; instr->parse_run = _buffer_instruction_parse_run; _instruction_param_seq_add(instr, "type", VT_STRING, "rgba"); _instruction_param_seq_add(instr, "src", VT_BUFFER, NULL); - return EINA_TRUE; + // drop "buffer" metatable + _lua_implicit_metatable_drop(L, _lua_buffer_meta); + + if (!_lua_instruction_run(L, instr)) + { + _instruction_del(instr); + return luaL_error(L, "buffer instanciation failed"); + } + else + { + pgm->instructions = eina_inlist_append(pgm->instructions, EINA_INLIST_GET(instr)); + } + + return instr->return_count; } static int @@ -1329,9 +1256,10 @@ _lua_curve_points_func(lua_State *L, int i, Evas_Filter_Program *pgm EINA_UNUSED case LUA_TFUNCTION: for (k = 0; k < 256; k++) { + lua_getglobal(L, _lua_errfunc_name); lua_pushvalue(L, i); lua_pushinteger(L, k); - if (!lua_pcall(L, 1, 1, 0)) + if (!lua_pcall(L, 1, 1, -3)) { if (!lua_isnumber(L, -1)) { @@ -1932,17 +1860,21 @@ evas_filter_program_del(Evas_Filter_Program *pgm) free(pgm); } -static const int this_is_not_a_cat = 42; - -static Evas_Filter_Program * -_lua_program_get(lua_State *L) +// [-1, +1, e] -- converts the top of the stack to a valid 'color' object +static Eina_Bool +_lua_convert_color(lua_State *L) { - Evas_Filter_Program *pgm; - lua_pushlightuserdata(L, (void *) &this_is_not_a_cat); - lua_gettable(L, LUA_REGISTRYINDEX); - pgm = lua_touserdata(L, -1); - lua_pop(L, 1); - return pgm; + int top = lua_gettop(L); + lua_getglobal(L, _lua_errfunc_name); //+1 + lua_getglobal(L, _lua_color_meta); //+1 (mt) + lua_getfield(L, -1, "__call"); //+1 (func) + lua_pushvalue(L, -2); //+1 (mt) + lua_pushvalue(L, top); //+1 (argument) + if (lua_pcall(L, 2, 1, top + 1) != 0) + return EINA_FALSE; + lua_insert(L, top); + lua_settop(L, top); + return EINA_TRUE; } static Eina_Bool @@ -1954,10 +1886,12 @@ _lua_parameter_parse(Evas_Filter_Program *pgm, lua_State *L, if (param->set) { ERR("Parameter %s has already been set", param->name); - luaL_error(L, "Parameter %s has already been set", param->name); - return 0; + return luaL_error(L, "Parameter %s has already been set", param->name); } + if (i < 0) + i = lua_gettop(L) + i + 1; + switch (param->type) { case VT_BOOL: @@ -1992,35 +1926,45 @@ _lua_parameter_parse(Evas_Filter_Program *pgm, lua_State *L, param->value.s = strdup(lua_tostring(L, i)); break; case VT_COLOR: - if ((lua_isnumber(L, i)) || (lua_type(L, i) == LUA_TSTRING)) - { - int A, R, G, B; - DATA32 color; - - if (lua_isnumber(L, i)) - color = (DATA32) lua_tonumber(L, i); - else - { - if (!_color_parse(lua_tostring(L, i), &color)) - goto fail; - } - - A = A_VAL(&color); - R = R_VAL(&color); - G = G_VAL(&color); - B = B_VAL(&color); - if (!A && (R || G || B)) A = 0xFF; - if ((A < R) || (A < G) || (A < B)) - { - ERR("Argument '%s' of function '%s' is not a valid premultiplied RGBA value!", - param->name, instr->name); - evas_color_argb_premul(A, &R, &G, &B); - //goto fail; - } - param->value.c = ARGB_JOIN(A, R, G, B); - } - else - goto fail; + { + // Auto convert values to a color() if they aren't one already + int cid = 0, pop = 0, A, R, G, B; + if (lua_istable(L, i)) + { + luaL_getmetatable(L, _lua_color_meta); + lua_getmetatable(L, i); + if (!lua_isnil(L, -1) && lua_equal(L, -2, -1)) + { + // this is a color already + cid = i; + } + lua_pop(L, 2); + } + if (!cid) + { + lua_pushvalue(L, i); //+1 (arg) + if (!_lua_convert_color(L)) //-1/+1 + { + ERR("Failed to convert color: %s", lua_tostring(L, -1)); + goto fail; + } + cid = lua_gettop(L); + pop = 1; + } + if (!lua_istable(L, cid)) + goto fail; + lua_getfield(L, cid, "a"); + A = lua_tointeger(L, -1); + lua_getfield(L, cid, "r"); + R = lua_tointeger(L, -1); + lua_getfield(L, cid, "g"); + G = lua_tointeger(L, -1); + lua_getfield(L, cid, "b"); + B = lua_tointeger(L, -1); + lua_pop(L, pop + 4); + evas_color_argb_premul(A, &R, &G, &B); + param->value.c = ARGB_JOIN(A, R, G, B); + } break; case VT_BUFFER: { @@ -2051,13 +1995,15 @@ _lua_parameter_parse(Evas_Filter_Program *pgm, lua_State *L, goto fail; } + if (i != lua_gettop(L)) + ERR("something is wrong"); + param->set = EINA_TRUE; return EINA_TRUE; fail: ERR("Invalid value for parameter %s", param->name); - luaL_error(L, "Invalid value for parameter %s", param->name); - return EINA_FALSE; + return luaL_error(L, "Invalid value for parameter %s", param->name); } static Instruction_Param * @@ -2089,7 +2035,7 @@ _lua_instruction_run(lua_State *L, Evas_Filter_Instruction *instr) if (eina_inlist_count(instr->params) < argc) { ERR("Too many arguments passed to the instruction %s", instr->name); - goto fail; + return EINA_FALSE; } if (lua_istable(L, 1)) @@ -2097,7 +2043,7 @@ _lua_instruction_run(lua_State *L, Evas_Filter_Instruction *instr) if (argc > 1) { ERR("Too many arguments passed to the instruction %s (in table mode)", instr->name); - goto fail; + return EINA_FALSE; } lua_pushnil(L); @@ -2110,7 +2056,7 @@ _lua_instruction_run(lua_State *L, Evas_Filter_Instruction *instr) if (!param) { ERR("Parameter %s does not exist", name); - goto fail; + return EINA_FALSE; } } else if (lua_isnumber(L, -2)) @@ -2120,24 +2066,24 @@ _lua_instruction_run(lua_State *L, Evas_Filter_Instruction *instr) if (!param) { ERR("Too many parameters for the function %s", instr->name); - goto fail; + return EINA_FALSE; } if (!param->allow_seq) { ERR("The parameter %s must be referred to by name in function %s", param->name, instr->name); - goto fail; + return EINA_FALSE; } } else { ERR("Invalid type for the parameter key in function %s", instr->name); - goto fail; + return EINA_FALSE; } if (!_lua_parameter_parse(pgm, L, instr, param, -1)) - goto fail; + return EINA_FALSE; lua_pop(L, 1); } } @@ -2147,7 +2093,7 @@ _lua_instruction_run(lua_State *L, Evas_Filter_Instruction *instr) { if ((++i) > argc) break; if (!_lua_parameter_parse(pgm, L, instr, param, i)) - goto fail; + return EINA_FALSE; } } @@ -2161,11 +2107,6 @@ _lua_instruction_run(lua_State *L, Evas_Filter_Instruction *instr) } return EINA_TRUE; - -fail: - ERR("Invalid parameters for instruction %s", instr->name); - luaL_error(L, "Invalid parameters for instruction %s", instr->name); - return EINA_FALSE; } static int @@ -2174,18 +2115,14 @@ _lua_generic_function(lua_State *L, const char *name, { Evas_Filter_Program *pgm = _lua_program_get(L); Evas_Filter_Instruction *instr; - Eina_Bool ok; instr = _instruction_new(name); prepare(pgm, instr); - ok = _lua_instruction_run(L, instr); - if (!ok) + if (!_lua_instruction_run(L, instr)) { - ERR("Instruction parsing failed"); _instruction_del(instr); - lua_error(L); - return 0; + return luaL_error(L, "Instruction parsing failed"); } else { @@ -2212,33 +2149,14 @@ _lua_print(lua_State *L) for (i = 1; i <= nargs; i++) { - if (lua_isstring(L, i)) - eina_strbuf_append(s, lua_tostring(L, i)); - else if (lua_isnumber(L, i)) - { - double d = lua_tonumber(L, i); - - if (fabs(d - floor(d)) < 0.000001) - eina_strbuf_append_printf(s, "%d", (int) d); - else - eina_strbuf_append_printf(s, "%f", d); - } - else if (luaL_checkudata(L, i, _lua_buffer_meta)) - { - Buffer *buf, **pbuf; - pbuf = lua_touserdata(L, i); - buf = pbuf ? *pbuf : NULL; - if (!buf) - eina_strbuf_append(s, "Buffer[null]"); - else - eina_strbuf_append_printf(s, "Buffer[#%d %dx%d %s%s%s]", - buf->cid, buf->w, buf->h, - buf->alpha ? "alpha" : "rgba", - buf->proxy ? " src: " : "", - buf->proxy ? buf->proxy : ""); - } - else - eina_strbuf_append(s, "<>"); + const char *str; + lua_getglobal(L, _lua_errfunc_name); + lua_getglobal(L, "tostring"); //+1 + lua_pushvalue(L, i); //+1 + lua_pcall(L, 1, 1, -3); //-2/+1 + str = lua_tostring(L, -1); + eina_strbuf_append(s, str ? str : "(nil)"); + lua_pop(L, 2); eina_strbuf_append_char(s, ' '); } @@ -2264,7 +2182,6 @@ _lua_##name(lua_State *L) \ lua_pushcfunction(L, _lua_##name); \ lua_setglobal(L, #name); -LUA_GENERIC_FUNCTION(buffer) LUA_GENERIC_FUNCTION(blend) LUA_GENERIC_FUNCTION(blur) LUA_GENERIC_FUNCTION(bump) @@ -2276,21 +2193,98 @@ LUA_GENERIC_FUNCTION(mask) LUA_GENERIC_FUNCTION(padding_set) LUA_GENERIC_FUNCTION(transform) -static const luaL_Reg buffer_methods[] = { - { "width", _lua_buffer_width }, - { "height", _lua_buffer_height }, - { "type", _lua_buffer_type }, - { "name", _lua_buffer_name }, - { "source", _lua_buffer_source }, - { NULL, NULL } -}; - -static const luaL_Reg buffer_meta[] = { +static const luaL_Reg _lua_buffer_metamethods[] = { + { "__call", _lua_buffer_new }, { "__tostring", _lua_buffer_tostring }, { "__index", _lua_buffer_index }, { NULL, NULL } }; +static char *_lua_color_code = NULL; + +static inline void +_lua_import_path_get(char *path, size_t len, const char *name) +{ + const char *pfx = _evas_module_datadir_get(); + struct stat st; + size_t r; + +//#ifdef FILTERS_DEBUG + // This is a hack to fetch the most recent file from source + if (stat(path, &st) == -1) + { + char *sep = evas_file_path_join("", ""); + char *src = strdup(__FILE__); + char *slash = strrchr(src, *sep); + if (slash) + { + *slash = '\0'; + if (*src == '/') + r = snprintf(path, len - 1, "%s/lua/%s.lua", src, name); + else // abs_srcdir is unknown here + r = snprintf(path, len - 1, "%s/src/%s/lua/%s.lua", PACKAGE_BUILD_DIR, src, name); + if (r >= len) path[len - 1] = '\0'; + } + free(sep); + free(src); + if (!stat(path, &st)) return; + } +//#endif + + r = snprintf(path, len - 1, "%s/filters/lua/%s.lua", pfx ? pfx : ".", name); + if (r >= len) path[len - 1] = '\0'; +} + +static Eina_Bool +_lua_import_class(lua_State *L, const char *name, char **code) +{ + // Load code from file + if (!*code) + { + char path[PATH_MAX]; + Eina_File *f; + void *map; + size_t sz; + + _lua_import_path_get(path, PATH_MAX, name); + f = eina_file_open(path, EINA_FALSE); + if (!f) return EINA_FALSE; + sz = eina_file_size_get(f); + *code = malloc(sz); + if (!*code) return EINA_FALSE; + map = eina_file_map_all(f, EINA_FILE_SEQUENTIAL); + if (!map) return EINA_FALSE; + memcpy(*code, map, sz); + eina_file_map_free(f, map); + eina_file_close(f); + } + + if (!luaL_dostring(L, *code)) //+1 + { + lua_getglobal(L, _lua_errfunc_name); //+1 + lua_pushliteral(L, _lua_register_func); //+1 + lua_rawget(L, -3); //-1/+1 + if (lua_isfunction(L, -1)) + { + lua_getglobal(L, "_G"); //+1 + if (lua_pcall(L, 1, 0, -3) != 0) //-2 + { + ERR("Failed to register globals for '%s': %s", name, lua_tostring(L, -1)); + lua_pop(L, 1); + } + } + else lua_pop(L, 1); + lua_pop(L, 1); // -1 (errfunc) + lua_setglobal(L, name); //-1 + } + else + { + ERR("Lua class '%s' could not be loaded: %s", name, lua_tostring(L, -1)); + return EINA_FALSE; + } + return EINA_TRUE; +} + static void _filter_program_buffers_set(Evas_Filter_Program *pgm) { @@ -2311,6 +2305,27 @@ _filter_program_buffers_set(Evas_Filter_Program *pgm) } } +static inline void +_lua_class_create(lua_State *L, const char *name, + const luaL_Reg *meta, const luaL_Reg *methods) +{ + luaL_newmetatable(L, name); + luaL_register(L, NULL, meta); + lua_pushliteral(L, "__metatable"); + lua_pushvalue(L, -2); + lua_rawset(L, -3); + if (methods) + { + lua_pushliteral(L, _lua_methods_table); + lua_newtable(L); + luaL_register(L, NULL, methods); + lua_rawset(L, -3); + } + lua_pushvalue(L, -1); + lua_setmetatable(L, -2); + lua_setglobal(L, name); +} + static lua_State * _lua_state_create(Evas_Filter_Program *pgm) { @@ -2327,19 +2342,24 @@ _lua_state_create(Evas_Filter_Program *pgm) luaopen_table(L); luaopen_string(L); luaopen_math(L); + luaopen_debug(L); + lua_settop(L, 0); // Implement print lua_getglobal(L, "_G"); luaL_register(L, NULL, printlib); lua_pop(L, 1); + // Add backtrace error function + lua_pushcfunction(L, _lua_backtrace); + lua_setglobal(L, _lua_errfunc_name); + // Store program lua_pushlightuserdata(L, (void *) &this_is_not_a_cat); lua_pushlightuserdata(L, pgm); lua_settable(L, LUA_REGISTRYINDEX); // Register functions - PUSH_LUA_FUNCTION(buffer) PUSH_LUA_FUNCTION(blend) PUSH_LUA_FUNCTION(blur) PUSH_LUA_FUNCTION(bump) @@ -2351,13 +2371,6 @@ _lua_state_create(Evas_Filter_Program *pgm) PUSH_LUA_FUNCTION(padding_set) PUSH_LUA_FUNCTION(transform) - // Register special variables - for (unsigned k = 0; k < (sizeof(color_map) / sizeof(color_map[0])); k++) - { - lua_pushnumber(L, color_map[k].value); - lua_setglobal(L, color_map[k].name); - } - for (unsigned k = 0; k < (sizeof(fill_modes) / sizeof(fill_modes[0])); k++) { if (strcmp("repeat", fill_modes[k].name)) @@ -2391,18 +2404,40 @@ _lua_state_create(Evas_Filter_Program *pgm) lua_setglobal(L, booleans[k].name); } - // Register buffer meta stuff - luaL_openlib(L, _lua_buffer_meta, buffer_methods, 0); - luaL_newmetatable(L, _lua_buffer_meta); - luaL_openlib(L, NULL, buffer_meta, 0); - lua_pushliteral(L, "__metatable"); - lua_pushvalue(L, -3); - lua_rawset(L, -3); - lua_pop(L, 1); + // Create buffer class based on userdata + _lua_class_create(L, _lua_buffer_meta, _lua_buffer_metamethods, NULL); + + // Load color class + if (!_lua_import_class(L, _lua_color_meta, &_lua_color_code)) + ERR("Could not load color class!"); return L; } +static int +_lua_backtrace(lua_State *L) +{ + if (!lua_isstring(L, 1)) /* 'message' not a string? */ + return 1; /* keep it intact */ + ERR("Lua error: %s", lua_tolstring(L, 1, NULL)); + lua_getfield(L, LUA_GLOBALSINDEX, "debug"); + if (!lua_istable(L, -1)) + { + lua_pop(L, 1); + return 1; + } + lua_getfield(L, -1, "traceback"); + if (!lua_isfunction(L, -1)) + { + lua_pop(L, 2); + return 1; + } + lua_pushvalue(L, 1); /* pass error message */ + lua_pushinteger(L, 2); /* skip this function and traceback */ + lua_call(L, 2, 1); /* call debug.traceback */ + return 1; +} + #ifdef FILTERS_LEGACY_COMPAT // This function is here to avoid breaking the ABI too much. // It should not stay here long, only until all client apps have changed the filters' code to Lua. @@ -2545,8 +2580,10 @@ _filter_program_state_set(Evas_Filter_Program *pgm) * } */ -#define JOINC(k) ARGB_JOIN(pgm->state.k.a, pgm->state.k.r, pgm->state.k.g, pgm->state.k.b) +#define JOINC(k) (double) ({ DATA32 d; int A = pgm->state.k.a, R = pgm->state.k.r, G = pgm->state.k.g, B = pgm->state.k.b; \ + evas_color_argb_unpremul(A, &R, &G, &B); d = ARGB_JOIN(A, R, G, B); d; }) #define SETFIELD(name, val) do { lua_pushnumber(L, val); lua_setfield(L, -2, name); } while(0) +#define SETCOLOR(name, val) do { lua_pushnumber(L, val); _lua_convert_color(L); lua_setfield(L, -2, name); } while(0) // TODO: Mark program as dependent on some values so we can improve // the changed flag (ie. re-run the filter only when required) @@ -2555,7 +2592,7 @@ _filter_program_state_set(Evas_Filter_Program *pgm) lua_newtable(L); // "state" { - SETFIELD("color", JOINC(color)); + SETCOLOR("color", JOINC(color)); SETFIELD("scale", pgm->state.scale); SETFIELD("pos", pgm->state.pos); lua_newtable(L); // "cur" @@ -2577,10 +2614,10 @@ _filter_program_state_set(Evas_Filter_Program *pgm) } lua_newtable(L); // "text" { - SETFIELD("outline", JOINC(text.outline)); - SETFIELD("shadow", JOINC(text.shadow)); - SETFIELD("glow", JOINC(text.glow)); - SETFIELD("glow2", JOINC(text.glow2)); + SETCOLOR("outline", JOINC(text.outline)); + SETCOLOR("shadow", JOINC(text.shadow)); + SETCOLOR("glow", JOINC(text.glow)); + SETCOLOR("glow2", JOINC(text.glow2)); lua_setfield(L, -2, "text"); } } @@ -2588,6 +2625,7 @@ _filter_program_state_set(Evas_Filter_Program *pgm) #undef JOINC #undef SETFIELD +#undef SETCOLOR } static void @@ -2656,8 +2694,9 @@ evas_filter_program_parse(Evas_Filter_Program *pgm, const char *str) { pgm->lua_func = luaL_ref(L, LUA_REGISTRYINDEX); _filter_program_reset(pgm); + lua_getglobal(L, _lua_errfunc_name); lua_rawgeti(L, LUA_REGISTRYINDEX, pgm->lua_func); - ok = !lua_pcall(L, 0, LUA_MULTRET, 0); + ok = !lua_pcall(L, 0, LUA_MULTRET, -2); } if (!ok) @@ -3382,8 +3421,9 @@ evas_filter_context_program_use(Evas_Filter_Context *ctx, { pgm->changed = EINA_FALSE; _filter_program_reset(pgm); + lua_getglobal(pgm->L, _lua_errfunc_name); lua_rawgeti(pgm->L, LUA_REGISTRYINDEX, pgm->lua_func); - success = !lua_pcall(pgm->L, 0, LUA_MULTRET, 0); + success = !lua_pcall(pgm->L, 0, LUA_MULTRET, -2); if (!success) { const char *msg = lua_tostring(pgm->L, -1); @@ -3416,3 +3456,10 @@ end: if (dc) ENFN->context_free(ENDT, dc); return success; } + +void +evas_filter_parser_shutdown(void) +{ + free(_lua_color_code); + _lua_color_code = NULL; +} diff --git a/src/lib/evas/filters/evas_filter_private.h b/src/lib/evas/filters/evas_filter_private.h index 002b2daa5f..7a4ba85ed1 100644 --- a/src/lib/evas/filters/evas_filter_private.h +++ b/src/lib/evas/filters/evas_filter_private.h @@ -268,4 +268,6 @@ Eina_Bool evas_filter_interpolate(DATA8* output /* 256 values */, int Evas_Filter_Command *_evas_filter_command_get(Evas_Filter_Context *ctx, int cmdid); int evas_filter_smallest_pow2_larger_than(int val); +void evas_filter_parser_shutdown(void); + #endif // EVAS_FILTER_PRIVATE_H diff --git a/src/lib/evas/filters/lua/color.lua b/src/lib/evas/filters/lua/color.lua new file mode 100644 index 0000000000..3fb1e9b0c2 --- /dev/null +++ b/src/lib/evas/filters/lua/color.lua @@ -0,0 +1,302 @@ +--[[ +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 diff --git a/src/lib/evas/include/evas_private.h b/src/lib/evas/include/evas_private.h index 163ed0e677..215e996581 100644 --- a/src/lib/evas/include/evas_private.h +++ b/src/lib/evas/include/evas_private.h @@ -1840,6 +1840,7 @@ void _evas_unwalk(Evas_Public_Data *e_pd); // expose for use in engines EAPI int _evas_module_engine_inherit(Evas_Func *funcs, char *name); EAPI const char *_evas_module_libdir_get(void); +const char *_evas_module_datadir_get(void); Eina_Bool evas_render_mapped(Evas_Public_Data *e, Evas_Object *obj, Evas_Object_Protected_Data *source_pd,