From 59cf013db9c8da3d30d11c2249f1b9dc4711f7bb Mon Sep 17 00:00:00 2001 From: Jean-Philippe Andre Date: Mon, 17 Mar 2014 16:10:03 +0900 Subject: [PATCH] Evas filters: Add support for Evas_Object_Image This adds filter support to Image objects as well. The exact same filters can run on Text and on Images (provided some colorspace limitations are respected). This basically adds: - Support for RGBA input buffer - Eo entry points for Image filter support - Implement basic filter support in Evas_Image --- src/lib/evas/canvas/evas_image.eo | 40 +++ src/lib/evas/canvas/evas_object_image.c | 293 +++++++++++++++++++++- src/lib/evas/canvas/evas_object_text.c | 4 +- src/lib/evas/filters/evas_filter.c | 58 +++++ src/lib/evas/filters/evas_filter_parser.c | 4 +- src/lib/evas/include/evas_filter.h | 3 +- src/tests/evas/evas_test_filters.c | 2 +- 7 files changed, 394 insertions(+), 10 deletions(-) diff --git a/src/lib/evas/canvas/evas_image.eo b/src/lib/evas/canvas/evas_image.eo index 2bd4c535d5..988fbba5e7 100644 --- a/src/lib/evas/canvas/evas_image.eo +++ b/src/lib/evas/canvas/evas_image.eo @@ -1023,6 +1023,46 @@ class Evas_Image (Evas_Object) Evas_3D_Scene *scene; /*@ 3D scene on an image object. */ } } + filter_program { + set { + /*@ Set an Evas filter program on this Text Object. + + If the program fails to compile (syntax error, invalid + buffer name, etc...), the standard text effects will be + applied instead (SHADOW, etc...). switch back to the + standard text effects. + + @since 1.9 + @note EXPERIMENTAL FEATURE. This is an unstable API, + please use only for testing purposes. + @see @ref evasfiltersref "Evas filters reference" + */ + } + values { + const char* program; /*@ The program code, as defined + by the @ref evasfiltersref "Evas filters script language". + Pass NULL to remove the former program and switch back + to the standard text effect + + @since 1.9 + @note EXPERIMENTAL FEATURE. This is an unstable API, + please use only for testing purposes. + @see @ref evasfiltersref "Evas filters reference" + */ + } + } + filter_source { + set { + /*@ Bind an object to use as a mask or texture with Evas Filters. + + This will create automatically a new RGBA buffer containing + the source object's pixels (as it is rendered). */ + } + values { + const char* name; /*@ Object name as used in the program code */ + Eo* eobj; /*@ Eo object to use through proxy rendering */ + } + } } methods { preload_begin { diff --git a/src/lib/evas/canvas/evas_object_image.c b/src/lib/evas/canvas/evas_object_image.c index 5c1e83a0ff..a3931d2398 100644 --- a/src/lib/evas/canvas/evas_object_image.c +++ b/src/lib/evas/canvas/evas_object_image.c @@ -18,6 +18,7 @@ #include "../common/evas_convert_color.h" #include "../common/evas_convert_colorspace.h" #include "../common/evas_convert_yuv.h" +#include "evas_filter.h" #include @@ -91,6 +92,16 @@ struct _Evas_Object_Image_State Evas_Colorspace cspace; + struct { + Eina_Stringshare *code; + Evas_Filter_Program *chain; + Eina_Hash *sources; // Evas_Filter_Proxy_Binding + int sources_count; + void *output; + Eina_Bool changed : 1; + Eina_Bool invalid : 1; // Code parse failed + } filter; + Eina_Bool smooth_scale : 1; Eina_Bool has_alpha :1; Eina_Bool opaque_valid : 1; @@ -233,6 +244,11 @@ static const Evas_Object_Image_State default_state = { 0, //frame EVAS_TEXTURE_REPEAT, EVAS_COLORSPACE_ARGB8888, + + // filter + { NULL, NULL, NULL, 0, NULL, EINA_FALSE, EINA_FALSE }, + + // flags EINA_TRUE, EINA_FALSE, EINA_FALSE, EINA_FALSE, EINA_FALSE }; @@ -3103,6 +3119,14 @@ evas_object_image_render(Evas_Object *eo_obj, Evas_Object_Protected_Data *obj, v } else { + Evas_Filter_Context *filter = NULL; + void * const surface_save = surface; + void * const context_save = context; + const Eina_Bool do_async_save = do_async; + int l = 0, r = 0, t = 0, b = 0; + Eina_Bool clear = EINA_FALSE; + int W, H, offx, offy; + obj->layer->evas->engine.func->image_scale_hint_set(output, pixels, o->scale_hint); @@ -3118,6 +3142,69 @@ evas_object_image_render(Evas_Object *eo_obj, Evas_Object_Protected_Data *obj, v if (idh < 1) idh = 1; if (idx > 0) idx -= idw; if (idy > 0) idy -= idh; + + offx = obj->cur->geometry.x + x; + offy = obj->cur->geometry.y + y; + + /* Evas Filters on Image Object. Yet another experimental piece of code. + * Replace current context, offset and surface by filter-made targets. + */ + if (!o->cur->filter.invalid && o->cur->filter.code) + { + Evas_Filter_Program *pgm = o->cur->filter.chain;; + Eina_Bool ok; + + evas_filter_program_padding_get(pgm, &l, &r, &t, &b); + W = obj->cur->geometry.w + l + r; + H = obj->cur->geometry.h + t + b; + + if (!pgm) + { + pgm = evas_filter_program_new("Image", EINA_FALSE); + evas_filter_program_source_set_all(pgm, o->cur->filter.sources); + ok = evas_filter_program_parse(pgm, o->cur->filter.code); + if (!ok) goto state_write; + } + + filter = evas_filter_context_new(obj->layer->evas, do_async); + ok = evas_filter_context_program_use(filter, pgm); + if (!ok) goto state_write; + + evas_filter_context_proxy_render_all(filter, eo_obj, EINA_FALSE); + ok = evas_filter_context_buffers_allocate_all(filter, W, H); + if (!ok) goto state_write; + + if (obj->layer->evas->engine.func->gl_surface_read_pixels) + { + evas_filter_context_proxy_render_all(filter, eo_obj, EINA_FALSE); + surface = obj->layer->evas->engine.func->image_map_surface_new(output, W, H, EINA_TRUE); + } + +state_write: + EINA_COW_IMAGE_STATE_WRITE_BEGIN(o, state_write) + { + if (ok) + { + offx = l; + offy = t; + context = obj->layer->evas->engine.func->context_new(output); + clear = EINA_TRUE; + do_async = EINA_FALSE; + state_write->filter.chain = pgm; + } + else + { + ERR("Failed to apply this filter"); + evas_filter_context_destroy(filter); + evas_filter_program_del(pgm); + state_write->filter.invalid = EINA_TRUE; + state_write->filter.chain = NULL; + filter = NULL; + } + } + EINA_COW_IMAGE_STATE_WRITE_END(o, state_write) + } + while ((int)idx < obj->cur->geometry.w) { Evas_Coord ydy; @@ -3133,6 +3220,16 @@ evas_object_image_render(Evas_Object *eo_obj, Evas_Object_Protected_Data *obj, v } else iw = ((int)(idx + idw)) - ix; + + if (clear) + { + obj->layer->evas->engine.func->context_color_set(output, context, 0, 0, 0, 0); + obj->layer->evas->engine.func->context_render_op_set(output, context, EVAS_RENDER_COPY); + obj->layer->evas->engine.func->rectangle_draw(output, context, surface, 0, 0, W, H, EINA_FALSE); + obj->layer->evas->engine.func->context_color_set(output, context, 255, 255, 255, 255); + obj->layer->evas->engine.func->context_render_op_set(output, context, EVAS_RENDER_BLEND); + } + while ((int)idy < obj->cur->geometry.h) { int dobreak_h = 0; @@ -3156,8 +3253,8 @@ evas_object_image_render(Evas_Object *eo_obj, Evas_Object_Protected_Data *obj, v (obj, output, context, surface, pixels, 0, 0, imagew, imageh, - obj->cur->geometry.x + ix + x, - obj->cur->geometry.y + iy + y, + offx + ix, + offy + iy, iw, ih, o->cur->smooth_scale, do_async); @@ -3168,8 +3265,8 @@ evas_object_image_render(Evas_Object *eo_obj, Evas_Object_Protected_Data *obj, v int bl, br, bt, bb, bsl, bsr, bst, bsb; int imw, imh, ox, oy; - ox = obj->cur->geometry.x + ix + x; - oy = obj->cur->geometry.y + iy + y; + ox = offx + ix; + oy = offy + iy; imw = imagew; imh = imageh; bl = o->cur->border.l; @@ -3327,6 +3424,24 @@ evas_object_image_render(Evas_Object *eo_obj, Evas_Object_Protected_Data *obj, v idy = ydy; if (dobreak_w) break; } + + /* Evas filters wrap-up */ + if (filter) + { + //void *outbuf = evas_filter_buffer_backing_steal(filter, EVAS_FILTER_BUFFER_OUTPUT_ID); + //EINA_COW_IMAGE_STATE_WRITE_BEGIN(o, state_write) + //state_write->filter.output = outbuf; + //EINA_COW_IMAGE_STATE_WRITE_END(o, state_write) + + evas_filter_image_draw(filter, context, EVAS_FILTER_BUFFER_INPUT_ID, surface, do_async_save); + obj->layer->evas->engine.func->context_free(output, context); + + evas_filter_target_set(filter, context_save, surface_save, obj->cur->geometry.x + x, obj->cur->geometry.y + y); + evas_filter_context_autodestroy(filter); + evas_filter_run(filter); + + obj->layer->evas->engine.func->image_map_surface_free(output, surface); + } } } } @@ -4477,6 +4592,176 @@ _evas_object_image_video_overlay_do(Evas_Object *eo_obj) o->delayed.video_hide = EINA_FALSE; } +/* Evas Filters functions. Similar to Evas_Text. */ + +EOLIAN static void +_evas_image_filter_program_set(Eo *eo_obj, Evas_Image_Data *o, const char *arg) +{ + Evas_Object_Protected_Data *obj; + Evas_Filter_Program *pgm = NULL; + + if (!o) return; + if (o->cur->filter.code == arg) return; + if (o->cur->filter.code && arg && !strcmp(arg, o->cur->filter.code)) return; + + // Parse filter program + evas_filter_program_del(o->cur->filter.chain); + if (arg) + { + pgm = evas_filter_program_new("Evas_Text: Filter Program", EINA_FALSE); + evas_filter_program_source_set_all(pgm, o->cur->filter.sources); + if (!evas_filter_program_parse(pgm, arg)) + { + ERR("Parsing failed!"); + evas_filter_program_del(pgm); + pgm = NULL; + } + } + + EINA_COW_IMAGE_STATE_WRITE_BEGIN(o, state_write) + { + state_write->filter.chain = pgm; + state_write->filter.changed = EINA_TRUE; + state_write->filter.invalid = (pgm == NULL); + eina_stringshare_replace(&state_write->filter.code, arg); + } + EINA_COW_IMAGE_STATE_WRITE_END(o, state_write); + + // Update object + obj = eo_data_scope_get(eo_obj, EVAS_OBJ_CLASS); + o->changed = EINA_TRUE; + o->written = EINA_FALSE; + evas_object_change(eo_obj, obj); + evas_object_clip_dirty(eo_obj, obj); + evas_object_coords_recalc(eo_obj, obj); + evas_object_inform_call_resize(eo_obj); +} + +static void +_filter_source_hash_free_cb(void *data) +{ + Evas_Filter_Proxy_Binding *pb = data; + Evas_Object_Protected_Data *proxy, *source; + Evas_Image_Data *o; + + proxy = eo_data_scope_get(pb->eo_proxy, EVAS_OBJ_CLASS); + source = eo_data_scope_get(pb->eo_source, EVAS_OBJ_CLASS); + + if (source) + { + EINA_COW_WRITE_BEGIN(evas_object_proxy_cow, source->proxy, + Evas_Object_Proxy_Data, source_write) + source_write->proxies = eina_list_remove(source_write->proxies, pb->eo_proxy); + EINA_COW_WRITE_END(evas_object_proxy_cow, source->proxy, source_write) + } + + o = eo_data_scope_get(pb->eo_proxy, MY_CLASS); + + if (o && proxy) + { + EINA_COW_IMAGE_STATE_WRITE_BEGIN(o, state_write) + state_write->filter.sources_count--; + EINA_COW_IMAGE_STATE_WRITE_END(o, state_write) + + if (!o->cur->filter.sources_count) + { + EINA_COW_WRITE_BEGIN(evas_object_proxy_cow, proxy->proxy, + Evas_Object_Proxy_Data, proxy_write) + proxy_write->is_proxy = EINA_FALSE; + EINA_COW_WRITE_END(evas_object_proxy_cow, source->proxy, proxy_write) + } + } + + eina_stringshare_del(pb->name); + free(pb); +} + +EOLIAN static void +_evas_image_filter_source_set(Eo *eo_obj, Evas_Image_Data *o, const char *name, + Evas_Object *eo_source) +{ + + Evas_Object_Protected_Data *obj; + Evas_Filter_Program *pgm = o->cur->filter.chain; + Evas_Filter_Proxy_Binding *pb, *pb_old = NULL; + Evas_Object_Protected_Data *source = NULL; + Eina_Hash *sources = o->cur->filter.sources; + int sources_count = o->cur->filter.sources_count; + + obj = eo_data_scope_get(eo_obj, EVAS_OBJ_CLASS); + if (eo_source) source = eo_data_scope_get(eo_source, EVAS_OBJ_CLASS); + + if (!name) + { + if (!eo_source || !o->cur->filter.sources) return; + if (eina_hash_del_by_data(o->cur->filter.sources, eo_source)) + goto update; + return; + } + + if (!sources) + { + if (!source) return; + sources = eina_hash_string_small_new + (EINA_FREE_CB(_filter_source_hash_free_cb)); + } + else + { + pb_old = eina_hash_find(sources, name); + if (pb_old) + { + if (pb_old->eo_source == eo_source) goto update; + eina_hash_del(sources, name, pb_old); + } + } + + if (!source) + { + pb_old = eina_hash_find(sources, name); + if (!pb_old) return; + eina_hash_del_by_key(sources, name); + goto update; + } + + pb = calloc(1, sizeof(*pb)); + pb->eo_proxy = eo_obj; + pb->eo_source = eo_source; + pb->name = eina_stringshare_add(name); + + EINA_COW_WRITE_BEGIN(evas_object_proxy_cow, source->proxy, + Evas_Object_Proxy_Data, source_write) + if (!eina_list_data_find(source_write->proxies, eo_obj)) + source_write->proxies = eina_list_append(source_write->proxies, eo_obj); + EINA_COW_WRITE_END(evas_object_proxy_cow, source->proxy, source_write) + + EINA_COW_WRITE_BEGIN(evas_object_proxy_cow, obj->proxy, + Evas_Object_Proxy_Data, proxy_write) + proxy_write->is_proxy = EINA_TRUE; + EINA_COW_WRITE_END(evas_object_proxy_cow, obj->proxy, proxy_write) + + eina_hash_add(sources, pb->name, pb); + sources_count++; + + evas_filter_program_source_set_all(pgm, sources); + +update: + EINA_COW_IMAGE_STATE_WRITE_BEGIN(o, state_write) + { + state_write->filter.changed = EINA_TRUE; + state_write->filter.invalid = EINA_FALSE; + state_write->filter.sources = sources; + state_write->filter.sources_count = sources_count; + } + EINA_COW_IMAGE_STATE_WRITE_END(o, state_write); + + // Update object + o->changed = 1; + evas_object_change(eo_obj, obj); + evas_object_clip_dirty(eo_obj, obj); + evas_object_coords_recalc(eo_obj, obj); + evas_object_inform_call_resize(eo_obj); +} + /* vim:set ts=8 sw=3 sts=3 expandtab cino=>5n-2f0^-2{2(0W1st0 :*/ #include "canvas/evas_image.eo.c" diff --git a/src/lib/evas/canvas/evas_object_text.c b/src/lib/evas/canvas/evas_object_text.c index 2e77b1644a..3c5b7f04ab 100644 --- a/src/lib/evas/canvas/evas_object_text.c +++ b/src/lib/evas/canvas/evas_object_text.c @@ -1740,7 +1740,7 @@ evas_object_text_render(Evas_Object *eo_obj, if (!o->cur.filter.chain) { Evas_Filter_Program *pgm; - pgm = evas_filter_program_new("Evas_Text"); + pgm = evas_filter_program_new("Evas_Text", EINA_TRUE); evas_filter_program_source_set_all(pgm, o->cur.filter.sources); if (!evas_filter_program_parse(pgm, o->cur.filter.code)) { @@ -2315,7 +2315,7 @@ _evas_text_filter_program_set(Eo *eo_obj, Evas_Text_Data *o, const char *arg) evas_filter_program_del(o->cur.filter.chain); if (arg) { - pgm = evas_filter_program_new("Evas_Text: Filter Program"); + pgm = evas_filter_program_new("Evas_Text: Filter Program", EINA_TRUE); evas_filter_program_source_set_all(pgm, o->cur.filter.sources); if (!evas_filter_program_parse(pgm, arg)) { diff --git a/src/lib/evas/filters/evas_filter.c b/src/lib/evas/filters/evas_filter.c index e5536da704..5410569137 100644 --- a/src/lib/evas/filters/evas_filter.c +++ b/src/lib/evas/filters/evas_filter.c @@ -1753,6 +1753,64 @@ evas_filter_font_draw(Evas_Filter_Context *ctx, void *draw_context, int bufid, } +/* Image draw: glReadPixels or just use SW buffer */ +Eina_Bool +evas_filter_image_draw(Evas_Filter_Context *ctx, void *draw_context, int bufid, + void *image, Eina_Bool do_async) +{ + int w = 0, h = 0; + + ENFN->image_size_get(ENDT, image, &w, &h); + if (!w || !h) return EINA_FALSE; + + if (!ctx->gl_engine) + { + Eina_Bool async_unref; + int dw = 0, dh = 0; + + // Copy the image into our input buffer. We could optimize by reusing the buffer. + + void *surface = evas_filter_buffer_backing_get(ctx, bufid); + if (!surface) return EINA_FALSE; + + ENFN->image_size_get(ENDT, image, &dw, &dh); + if (!dw || !dh) return EINA_FALSE; + + if (w != dw || h != dh) + WRN("Target surface size differs from the image to draw"); + + async_unref = ENFN->image_draw(ENDT, draw_context, surface, image, + 0, 0, w, h, + 0, 0, dw, dh, + EINA_TRUE, do_async); + if (do_async && async_unref) + { +#ifdef EVAS_CSERVE2 + if (evas_cserve2_use_get()) + evas_cache2_image_ref((Image_Entry *)image); + else +#endif + evas_cache_image_ref((Image_Entry *)image); + + evas_unref_queue_image_put(ctx->evas, image); + } + } + else + { + Evas_Filter_Buffer *fb; + + fb = _filter_buffer_get(ctx, bufid); + _filter_buffer_backing_free(fb); + fb->glimage = image; + fb->allocated_gl = EINA_FALSE; + _filter_buffer_glimage_pixels_read(fb); + fb->glimage = NULL; + } + + return EINA_TRUE; +} + + /* Clip full input rect (0, 0, sw, sh) to target (dx, dy, dw, dh) * and get source's clipped sx, sy as well as destination x, y, cols and rows */ void diff --git a/src/lib/evas/filters/evas_filter_parser.c b/src/lib/evas/filters/evas_filter_parser.c index d0dd6de51f..4f2c0009f0 100644 --- a/src/lib/evas/filters/evas_filter_parser.c +++ b/src/lib/evas/filters/evas_filter_parser.c @@ -2094,14 +2094,14 @@ evas_filter_program_padding_get(Evas_Filter_Program *pgm, /** Create an empty filter program for style parsing */ EAPI Evas_Filter_Program * -evas_filter_program_new(const char *name) +evas_filter_program_new(const char *name, Eina_Bool input_alpha) { Evas_Filter_Program *pgm; pgm = calloc(1, sizeof(Evas_Filter_Program)); if (!pgm) return NULL; pgm->name = eina_stringshare_add(name); - _buffer_add(pgm, "input", EINA_TRUE, NULL); + _buffer_add(pgm, "input", input_alpha, NULL); _buffer_add(pgm, "output", EINA_FALSE, NULL); return pgm; diff --git a/src/lib/evas/include/evas_filter.h b/src/lib/evas/include/evas_filter.h index 09b7bd4aca..7bbecf3555 100644 --- a/src/lib/evas/include/evas_filter.h +++ b/src/lib/evas/include/evas_filter.h @@ -92,7 +92,7 @@ enum _Evas_Filter_Transform_Flags }; /* Parser stuff (high level API) */ -EAPI Evas_Filter_Program *evas_filter_program_new(const char *name); +EAPI Evas_Filter_Program *evas_filter_program_new(const char *name, Eina_Bool input_alpha); EAPI Eina_Bool evas_filter_program_parse(Evas_Filter_Program *pgm, const char *str); EAPI void evas_filter_program_del(Evas_Filter_Program *pgm); Eina_Bool evas_filter_context_program_use(Evas_Filter_Context *ctx, Evas_Filter_Program *pgm); @@ -116,6 +116,7 @@ Eina_Bool evas_filter_buffer_backing_release(Evas_Filter_Context Eina_Bool evas_filter_run(Evas_Filter_Context *ctx); Eina_Bool evas_filter_font_draw(Evas_Filter_Context *ctx, void *draw_context, int bufid, Evas_Font_Set *font, int x, int y, Evas_Text_Props *text_props, Eina_Bool do_async); +Eina_Bool evas_filter_image_draw(Evas_Filter_Context *ctx, void *draw_context, int bufid, void *image, Eina_Bool do_async); Eina_Bool evas_filter_target_set(Evas_Filter_Context *ctx, void *draw_context, void *surface, int x, int y); /** diff --git a/src/tests/evas/evas_test_filters.c b/src/tests/evas/evas_test_filters.c index 19d92716e8..98d1a9c744 100644 --- a/src/tests/evas/evas_test_filters.c +++ b/src/tests/evas/evas_test_filters.c @@ -71,7 +71,7 @@ START_TEST(evas_filter_parser) Evas_Filter_Program *pgm; #define CHECK_FILTER(_a, _v) do { \ - pgm = evas_filter_program_new("evas_suite"); \ + pgm = evas_filter_program_new("evas_suite", EINA_TRUE); \ fail_if(evas_filter_program_parse(pgm, _a) != _v); \ evas_filter_program_del(pgm); \ } while (0)