Evas filters: Implement table & function support for curve

Now the points can be specified by passing a table or a proper Lua
function. The previous functionality (parsing a string) is still
valid.
This commit is contained in:
Jean-Philippe Andre 2015-05-28 14:37:10 +09:00
parent 1b2819f2cb
commit 70efb699a5
3 changed files with 211 additions and 65 deletions

View File

@ -276,7 +276,8 @@ typedef enum
VT_REAL,
VT_STRING,
VT_COLOR,
VT_BUFFER
VT_BUFFER,
VT_SPECIAL
} Value_Type;
typedef struct _Buffer
@ -293,7 +294,8 @@ typedef struct _Buffer
Eina_Bool manual : 1; // created by "buffer" instruction
} Buffer;
typedef struct _Instruction_Param
typedef struct _Instruction_Param Instruction_Param;
struct _Instruction_Param
{
EINA_INLIST;
Eina_Stringshare *name;
@ -305,11 +307,15 @@ typedef struct _Instruction_Param
char *s;
unsigned int c;
Buffer *buf;
struct {
void *data;
Eina_Bool (*func)(lua_State *L, int i, Evas_Filter_Program *, Evas_Filter_Instruction *, Instruction_Param *);
} special;
} value;
Eina_Bool set : 1;
Eina_Bool allow_seq : 1;
Eina_Bool allow_any_string : 1;
} Instruction_Param;
};
struct _Evas_Filter_Instruction
{
@ -402,6 +408,10 @@ _instruction_param_addv(Evas_Filter_Instruction *instr, const char *name,
case VT_COLOR:
param->value.c = va_arg(args, DATA32);
break;
case VT_SPECIAL:
param->value.special.func = va_arg(args, typeof(param->value.special.func));
param->value.special.data = va_arg(args, void *);
break;
case VT_NONE:
default:
return EINA_FALSE;
@ -426,8 +436,8 @@ _instruction_param_adda(Evas_Filter_Instruction *instr, const char *name,
return ok;
}
#define _instruction_param_seq_add(a,b,c,d) _instruction_param_adda((a),(b),(c),1,(d))
#define _instruction_param_name_add(a,b,c,d) _instruction_param_adda((a),(b),(c),0,(d))
#define _instruction_param_seq_add(a,b,c,...) _instruction_param_adda((a),(b),(c),1,__VA_ARGS__)
#define _instruction_param_name_add(a,b,c,...) _instruction_param_adda((a),(b),(c),0,__VA_ARGS__)
static void
_instruction_del(Evas_Filter_Instruction *instr)
@ -439,6 +449,8 @@ _instruction_del(Evas_Filter_Instruction *instr)
{
if (param->type == VT_STRING)
free(param->value.s);
else if (param->type == VT_SPECIAL)
free(param->value.special.data);
eina_stringshare_del(param->name);
instr->params = eina_inlist_remove(instr->params, EINA_INLIST_GET(param));
free(param);
@ -510,6 +522,23 @@ _instruction_param_getc(Evas_Filter_Instruction *instr, const char *name,
return 0;
}
static void *
_instruction_param_getspecial(Evas_Filter_Instruction *instr, const char *name,
Eina_Bool *isset)
{
Instruction_Param *param;
EINA_INLIST_FOREACH(instr->params, param)
if (!strcasecmp(name, param->name))
{
if (isset) *isset = param->set;
return param->value.special.data;
}
if (isset) *isset = EINA_FALSE;
return 0;
}
static const char *
_instruction_param_gets(Evas_Filter_Instruction *instr, const char *name,
Eina_Bool *isset)
@ -1200,6 +1229,138 @@ _bump_instruction_prepare(Evas_Filter_Program *pgm, Evas_Filter_Instruction *ins
return EINA_TRUE;
}
static Eina_Bool
_lua_curve_points_func(lua_State *L, int i, Evas_Filter_Program *pgm EINA_UNUSED,
Evas_Filter_Instruction *instr, Instruction_Param *param)
{
int values[256];
char *token, *copy = NULL;
const char *points_str;
lua_Number n;
int k;
switch (lua_type(L, i))
{
case LUA_TTABLE:
// FIXME: indices start from 0 here. Lua prefers starting with 1.
for (k = 0; k < 256; k++)
{
lua_rawgeti(L, i, k);
if (lua_isnil(L, -1))
{
lua_pop(L, 1);
values[k] = -1;
}
else if (lua_isnumber(L, -1))
{
n = lua_tonumber(L, -1);
if ((n < -1) || (n > 255))
{
WRN("Value out of range in argument '%s' of function '%s' (got %d, expected 0-255)",
param->name, instr->name, (int) n);
if (n < -1) n = 0;
if (n > 255) n = 255;
}
lua_pop(L, 1);
values[k] = (int) n;
}
else
{
lua_pop(L, 1);
ERR("Invalid value type '%s' (expected number) in table for argument '%s' of function '%s'",
lua_typename(L, -1), param->name, instr->name);
return EINA_FALSE;
}
}
break;
case LUA_TSTRING:
for (k = 0; k < 256; k++)
values[k] = -1;
points_str = lua_tostring(L, i);
copy = strdup(points_str);
if (!copy) return EINA_FALSE;
token = strtok(copy, "-");
if (!token)
{
ERR("Invalid string format for argument '%s' of function '%s'",
param->name, instr->name);
free(copy);
return EINA_FALSE;
}
while (token)
{
int x, y, r, minx = 0;
r = sscanf(token, "%i:%i", &x, &y);
if ((r != 2) || (x < minx) || (x >= 256))
{
ERR("Invalid string format for argument '%s' of function '%s'",
param->name, instr->name);
free(copy);
return EINA_FALSE;
}
minx = x + 1;
if ((y < -1) || (y > 255))
{
WRN("Value out of range in argument '%s' of function '%s' (got %d, expected 0-255)",
param->name, instr->name, y);
if (y < -1) y = 0;
if (y > 255) y = 255;
}
values[x] = y;
token = strtok(NULL, "-");
}
free(copy);
break;
case LUA_TFUNCTION:
for (k = 0; k < 256; k++)
{
lua_pushvalue(L, i);
lua_pushinteger(L, k);
if (!lua_pcall(L, 1, 1, 0))
{
if (!lua_isnumber(L, -1))
{
ERR("Function returned an invalid type '%s' (expected number) "
"in argument '%s' of function '%s'",
lua_typename(L, -1), param->name, instr->name);
return EINA_FALSE;
}
n = lua_tonumber(L, -1);
if ((n < -1) || (n > 255))
{
WRN("Value out of range in argument '%s' of function '%s' (got %d, expected 0-255)",
param->name, instr->name, (int) n);
if (n < -1) n = 0;
if (n > 255) n = 255;
}
lua_pop(L, 1);
values[k] = (int) n;
}
else
{
ERR("Failed to call function for argument '%s' of function '%s': %s",
param->name, instr->name, lua_tostring(L, -1));
return EINA_FALSE;
}
}
break;
default:
ERR("Invalid type '%s' for argument '%s' of function '%s'",
lua_typename(L, i), param->name, instr->name);
return EINA_FALSE;
}
free(param->value.special.data);
param->value.special.data = malloc(sizeof(values));
if (!param->value.special.data) return EINA_FALSE;
memcpy(param->value.special.data, values, sizeof(values));
return EINA_TRUE;
}
/**
@page evasfiltersref
@ -1258,7 +1419,7 @@ _curve_instruction_prepare(Evas_Filter_Program *pgm, Evas_Filter_Instruction *in
// TODO: Allow passing an array of 256 values as points.
// It could be easily computed from another function in the script.
_instruction_param_seq_add(instr, "points", VT_STRING, NULL);
_instruction_param_seq_add(instr, "points", VT_SPECIAL, _lua_curve_points_func, NULL);
param = EINA_INLIST_CONTAINER_GET(eina_inlist_last(instr->params), Instruction_Param);
param->allow_any_string = EINA_TRUE;
@ -1865,6 +2026,11 @@ _lua_parameter_parse(Evas_Filter_Program *pgm, lua_State *L,
}
break;
}
case VT_SPECIAL:
if (!param->value.special.func ||
!param->value.special.func(L, i, pgm, instr, param))
goto fail;
break;
case VT_NONE:
default:
// This should not happen
@ -2369,6 +2535,11 @@ _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 SETFIELD(name, val) do { lua_pushinteger(L, val); 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)
// eg. edje state_val changed but it is not used by the filter --> no redraw
// --> this needs a metatable with __index
lua_newtable(L); // "state"
{
SETFIELD("color", JOINC(color));
@ -2434,6 +2605,10 @@ evas_filter_program_parse(Evas_Filter_Program *pgm, const char *str)
if (!L) return EINA_FALSE;
ok = !luaL_loadstring(L, str);
if (!ok)
{
ERR("Failed to load Lua program: %s", lua_tostring(L, -1));
}
#ifdef FILTERS_LEGACY_COMPAT
if (!ok)
@ -2954,18 +3129,18 @@ _instr2cmd_curve(Evas_Filter_Context *ctx,
{
Evas_Filter_Interpolation_Mode mode = EVAS_FILTER_INTERPOLATION_MODE_LINEAR;
Evas_Filter_Channel channel = EVAS_FILTER_CHANNEL_RGB;
const char *points_str, *interpolation, *channel_name;
DATA8 values[256] = {0}, points[512];
int cmdid, point_count = 0;
char *token, *copy = NULL;
const char *interpolation, *channel_name;
Buffer *src, *dst;
Eina_Bool parse_ok = EINA_FALSE;
DATA8 values[256];
int *points;
int cmdid;
src = _instruction_param_getbuf(instr, "src", NULL);
dst = _instruction_param_getbuf(instr, "dst", NULL);
points_str = _instruction_param_gets(instr, "points", NULL);
points = _instruction_param_getspecial(instr, "points", NULL);
interpolation = _instruction_param_gets(instr, "interpolation", NULL);
channel_name = _instruction_param_gets(instr, "channel", NULL);
INSTR_PARAM_CHECK(points);
INSTR_PARAM_CHECK(src);
INSTR_PARAM_CHECK(dst);
@ -2989,28 +3164,7 @@ _instr2cmd_curve(Evas_Filter_Context *ctx,
if (interpolation && !strcasecmp(interpolation, "none"))
mode = EVAS_FILTER_INTERPOLATION_MODE_NONE;
if (!points_str) goto interpolated;
copy = strdup(points_str);
token = strtok(copy, "-");
if (!token) goto interpolated;
while (token)
{
int x, y, r, maxx = 0;
r = sscanf(token, "%u:%u", &x, &y);
if (r != 2) goto interpolated;
if (x < maxx || x >= 256) goto interpolated;
points[point_count * 2 + 0] = x;
points[point_count * 2 + 1] = y;
point_count++;
token = strtok(NULL, "-");
}
parse_ok = evas_filter_interpolate(values, points, point_count, mode);
interpolated:
free(copy);
if (!parse_ok)
if (!evas_filter_interpolate(values, points, mode))
{
int x;
ERR("Failed to parse the interpolation chain");

View File

@ -239,7 +239,7 @@ Evas_Filter_Buffer *_filter_buffer_data_new(Evas_Filter_Context *ctx, void *data
#define evas_filter_buffer_alloc_new(ctx, w, h, a) _filter_buffer_data_new(ctx, NULL, w, h, a)
Evas_Filter_Buffer *evas_filter_temporary_buffer_get(Evas_Filter_Context *ctx, int w, int h, Eina_Bool alpha_only);
Evas_Filter_Buffer *evas_filter_buffer_scaled_get(Evas_Filter_Context *ctx, Evas_Filter_Buffer *src, unsigned w, unsigned h);
Eina_Bool evas_filter_interpolate(DATA8* output /* 256 values */, DATA8* points /* pairs x + y */, int point_count, Evas_Filter_Interpolation_Mode mode);
Eina_Bool evas_filter_interpolate(DATA8* output /* 256 values */, int *points /* 256 values */, Evas_Filter_Interpolation_Mode mode);
Evas_Filter_Command *_evas_filter_command_get(Evas_Filter_Context *ctx, int cmdid);
int evas_filter_smallest_pow2_larger_than(int val);

View File

@ -84,60 +84,52 @@ evas_filter_buffer_scaled_get(Evas_Filter_Context *ctx,
}
static Eina_Bool
_interpolate_none(DATA8 *output, DATA8 *points, int point_count)
_interpolate_none(DATA8 *output, int *points)
{
int j, k, val, x1, x2;
for (j = 0; j < point_count; j++)
DATA8 val = 0;
int j;
for (j = 0; j < 256; j++)
{
x1 = points[j * 2];
val = points[j * 2 + 1];
if (j < (point_count - 1))
x2 = points[(j + 1) * 2];
if (points[j] == -1)
output[j] = val;
else
x2 = 256;
if (x2 < x1) return EINA_FALSE;
for (k = x1; k < x2; k++)
output[k] = val;
val = output[j] = (DATA8) points[j];
}
return EINA_TRUE;
}
static Eina_Bool
_interpolate_linear(DATA8 *output, DATA8 *points, int point_count)
_interpolate_linear(DATA8 *output, int *points)
{
int j, k, val1, val2, x1, x2;
for (j = 0; j < point_count; j++)
DATA8 val = 0;
int j, k, last_idx = 0;
for (j = 0; j < 256; j++)
{
x1 = points[j * 2];
val1 = points[j * 2 + 1];
if (j < (point_count - 1))
if (points[j] != -1)
{
x2 = points[(j + 1) * 2];
val2 = points[(j + 1) * 2 + 1];
output[j] = (DATA8) points[j];
for (k = last_idx + 1; k < j; k++)
output[k] = (DATA8) (points[j] + ((k - last_idx) * (points[j] - points[last_idx]) / (j - last_idx)));
last_idx = j;
}
else
{
x2 = 256;
val2 = val1;
}
if (x2 < x1) return EINA_FALSE;
for (k = x1; k < x2; k++)
output[k] = val1 + ((val2 - val1) * (k - x1)) / (x2 - x1);
}
val = (DATA8) points[last_idx];
for (j = last_idx + 1; j < 256; j++)
output[j] = val;
return EINA_TRUE;
}
Eina_Bool
evas_filter_interpolate(DATA8 *output, DATA8 *points, int point_count,
evas_filter_interpolate(DATA8 *output, int *points,
Evas_Filter_Interpolation_Mode mode)
{
switch (mode)
{
case EVAS_FILTER_INTERPOLATION_MODE_NONE:
return _interpolate_none(output, points, point_count);
return _interpolate_none(output, points);
case EVAS_FILTER_INTERPOLATION_MODE_LINEAR:
default:
return _interpolate_linear(output, points, point_count);
return _interpolate_linear(output, points);
}
}