evas: Make save() work on snapshots

This make save() work on snapshot objects, provided the call
is done from inside render_post.

Also, this saves the filtered output of an image, rather than
its source pixels. Any call to save() on a filtered image must
be done from post-render as well.

Fixes T2102

@feature
This commit is contained in:
Jean-Philippe Andre 2017-03-28 16:09:34 +09:00
parent e7eb97f3b0
commit c53f1526f1
9 changed files with 216 additions and 144 deletions

View File

@ -53,6 +53,35 @@ _anim_toggle(void *data, const Efl_Event *ev EINA_UNUSED)
eina_iterator_free(it);
}
static void
_render_post(void *data, const Efl_Event *ev)
{
Eo *snap = data;
efl_event_callback_del(ev->object, EFL_CANVAS_EVENT_RENDER_POST, _render_post, data);
efl_file_save(snap, eina_slstr_printf("%s/snap-efl.png", eina_environment_tmp_get()), NULL, NULL);
}
static void
_save_image(void *data, const Efl_Event *ev EINA_UNUSED)
{
Eo *win = data;
Eo *snap;
int w, h;
// Save is available only during render_post
snap = efl_key_wref_get(win, "snap");
efl_event_callback_add(win, EFL_CANVAS_EVENT_RENDER_POST, _render_post, snap);
// Force a render in order to ensure post_render is called. EO API provides
// no way to do manual render, so we add a damage to the snapshot object.
// This is a special case handled by snapshot for the purpose of taking
// screenshots like this. This is useful only if the button click has no
// animation on screen and there is no spinning wheel either.
efl_gfx_size_get(snap, &w, &h);
efl_gfx_buffer_update_add(snap, 0, 0, w, h);
}
static void
_radius_set(void *data, const Efl_Event *ev)
{
@ -76,7 +105,7 @@ _close(void *data, const Efl_Event *ev EINA_UNUSED)
void
test_evas_snapshot(void *data EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *event_info EINA_UNUSED)
{
Eo *win, *grid, *o, *snap;
Eo *win, *grid, *o, *snap, *box;
const char *path;
win = efl_add(EFL_UI_WIN_STANDARD_CLASS, NULL,
@ -141,19 +170,32 @@ test_evas_snapshot(void *data EINA_UNUSED, Evas_Object *obj EINA_UNUSED, void *e
efl_event_callback_add(efl_added, ELM_SLIDER_EVENT_CHANGED, _radius_set, win),
efl_gfx_visible_set(efl_added, 1));
box = efl_add(EFL_UI_BOX_CLASS, win,
efl_gfx_size_hint_align_set(efl_added, -1.0, -1.0),
efl_pack_grid(grid, efl_added, 0, GRID_SIZE + 1, GRID_SIZE, 1),
efl_gfx_visible_set(efl_added, 1));
efl_add(ELM_BUTTON_CLASS, win,
efl_text_set(efl_added, "Toggle animation"),
efl_gfx_size_hint_align_set(efl_added, -1.0, -1.0),
efl_gfx_size_hint_weight_set(efl_added, 1.0, 0.0),
efl_pack_grid(grid, efl_added, 0, GRID_SIZE + 1, (GRID_SIZE + 1) / 2, 1),
efl_pack(box, efl_added),
efl_event_callback_add(efl_added, EFL_UI_EVENT_CLICKED, _anim_toggle, win),
efl_gfx_visible_set(efl_added, 1));
efl_add(ELM_BUTTON_CLASS, win,
efl_text_set(efl_added, "Save to file"),
efl_gfx_size_hint_align_set(efl_added, -1.0, -1.0),
efl_gfx_size_hint_weight_set(efl_added, 1.0, 0.0),
efl_pack(box, efl_added),
efl_event_callback_add(efl_added, EFL_UI_EVENT_CLICKED, _save_image, win),
efl_gfx_visible_set(efl_added, 1));
efl_add(ELM_BUTTON_CLASS, win,
efl_text_set(efl_added, "Close"),
efl_gfx_size_hint_align_set(efl_added, -1.0, -1.0),
efl_gfx_size_hint_weight_set(efl_added, 1.0, 0.0),
efl_pack_grid(grid, efl_added, (GRID_SIZE + 1) / 2, GRID_SIZE + 1, (GRID_SIZE + 1) / 2, 1),
efl_pack(box, efl_added),
efl_event_callback_add(efl_added, EFL_UI_EVENT_CLICKED, _close, win),
efl_gfx_visible_set(efl_added, 1));

View File

@ -21,4 +21,16 @@ _efl_canvas_snapshot_efl_object_constructor(Eo *eo_obj, void *pd EINA_UNUSED)
return eo_obj;
}
EOLIAN static void
_efl_canvas_snapshot_efl_gfx_buffer_buffer_update_add(Eo *eo_obj, void *_pd EINA_UNUSED,
int x EINA_UNUSED, int y EINA_UNUSED,
int w EINA_UNUSED, int h EINA_UNUSED)
{
Evas_Object_Protected_Data *obj;
// FIXME: Prevent obscures in the region x,y,w,h
obj = efl_data_scope_get(eo_obj, EFL_CANVAS_OBJECT_CLASS);
evas_object_change(eo_obj, obj);
}
#include "efl_canvas_snapshot.eo.c"

View File

@ -9,5 +9,6 @@ class Efl.Canvas.Snapshot (Efl.Canvas.Image.Internal)
data: null;
implements {
Efl.Object.constructor;
Efl.Gfx.Buffer.buffer_update_add;
}
}

View File

@ -148,6 +148,7 @@ struct _Evas_Image_Data
void _evas_image_init_set(const Eina_File *f, const char *file, const char *key, Eo *eo_obj, Evas_Object_Protected_Data *obj, Evas_Image_Data *o, Evas_Image_Load_Opts *lo);
void _evas_image_done_set(Eo *eo_obj, Evas_Object_Protected_Data *obj, Evas_Image_Data *o);
void _evas_image_cleanup(Evas_Object *eo_obj, Evas_Object_Protected_Data *obj, Evas_Image_Data *o);
void *_evas_image_pixels_get(Eo *eo_obj, Evas_Object_Protected_Data *obj, void *output, void *context, void *surface, int x, int y, int *imagew, int *imageh, int *uvw, int *uvh, Eina_Bool filtered, Eina_Bool needs_post_render);
/* Efl.Gfx.Fill */
void _evas_image_fill_set(Eo *eo_obj, Evas_Image_Data *o, int x, int y, int w, int h);

View File

@ -753,6 +753,8 @@ _efl_canvas_image_internal_efl_gfx_buffer_buffer_update_add(Eo *eo_obj, Evas_Ima
RECTS_CLIP_TO_RECT(x, y, w, h, 0, 0, o->cur->image.w, o->cur->image.h);
if ((w <= 0) || (h <= 0)) return;
if (obj->cur->snapshot)
evas_object_change(eo_obj, obj);
if (!o->written) return;
evas_object_async_block(obj);
cnt = eina_list_count(o->pixels->pixel_updates);
@ -875,69 +877,33 @@ _efl_canvas_image_internal_efl_image_ratio_get(Eo *eo_obj EINA_UNUSED, Evas_Imag
EOLIAN static Eina_Bool
_efl_canvas_image_internal_efl_file_save(const Eo *eo_obj, Evas_Image_Data *o, const char *file, const char *key, const char *flags)
{
DATA32 *data = NULL;
int quality = 80, compress = 9, ok = 0;
char *encoding = NULL;
Image_Entry *ie;
Eina_Bool putback = EINA_FALSE, tofree = EINA_FALSE, tgv = EINA_FALSE;
Evas_Colorspace cspace = EVAS_COLORSPACE_ARGB8888;
Evas_Colorspace want_cspace = EVAS_COLORSPACE_ARGB8888;
int imagew, imageh;
void *pixels;
Evas_Object_Protected_Data *obj;
Eina_Bool unmap_it = EINA_FALSE;
int imagew, imageh, uvw, uvh;
Eina_Rw_Slice slice = {};
DATA32 *data = NULL;
void *pixels = NULL;
Evas_Object_Protected_Data *obj = efl_data_scope_get(eo_obj, EFL_CANVAS_OBJECT_CLASS);
Evas_Object_Protected_Data *source = (o->cur->source ? efl_data_scope_get(o->cur->source, EFL_CANVAS_OBJECT_CLASS) : NULL);
// FIXME: Use _evas_image_pixels_get()
obj = efl_data_scope_get(eo_obj, EFL_CANVAS_OBJECT_CLASS);
EINA_SAFETY_ON_NULL_RETURN_VAL(file, EINA_FALSE);
evas_object_async_block(obj);
if (o->cur->scene)
{
_evas_image_3d_render(obj->layer->evas->evas, (Eo *) eo_obj, obj, o, o->cur->scene);
pixels = obj->data_3d->surface;
imagew = obj->data_3d->w;
imageh = obj->data_3d->h;
}
else if (!o->cur->source)
{
// pixels = evas_process_dirty_pixels(eo_obj, obj, o, output, surface, o->engine_data);
pixels = o->engine_data;
imagew = o->cur->image.w;
imageh = o->cur->image.h;
putback = EINA_TRUE;
}
else if (source->proxy->surface && !source->proxy->redraw)
{
pixels = source->proxy->surface;
imagew = source->proxy->w;
imageh = source->proxy->h;
}
else if (source->type == o_type &&
((Evas_Image_Data *)efl_data_scope_get(o->cur->source, MY_CLASS))->engine_data)
{
Evas_Image_Data *oi;
oi = efl_data_scope_get(o->cur->source, MY_CLASS);
pixels = oi->engine_data;
imagew = oi->cur->image.w;
imageh = oi->cur->image.h;
}
else
{
o->proxyrendering = EINA_TRUE;
evas_render_proxy_subrender(obj->layer->evas->evas, o->cur->source,
(Eo *) eo_obj, obj, o->proxy_src_clip, EINA_FALSE);
pixels = source->proxy->surface;
imagew = source->proxy->w;
imageh = source->proxy->h;
o->proxyrendering = EINA_FALSE;
}
pixels = _evas_image_pixels_get((Eo *) eo_obj, obj, ENDT, NULL, NULL, 0, 0,
&imagew, &imageh, &uvw, &uvh, EINA_TRUE, EINA_TRUE);
if (!pixels) goto no_pixels;
cspace = ENFN->image_file_colorspace_get(ENDT, pixels);
want_cspace = cspace;
if (flags)
{
const char *ext;
char *p, *pp;
char *tflags;
@ -955,69 +921,61 @@ _efl_canvas_image_internal_efl_file_save(const Eo *eo_obj, Evas_Image_Data *o, c
else break;
}
if (file)
if (encoding)
{
const char *ext = strrchr(file, '.');
ext = strrchr(file, '.');
if (ext && !strcasecmp(ext, ".tgv"))
tgv = EINA_TRUE;
}
if (encoding && tgv)
{
if (!strcmp(encoding, "auto"))
want_cspace = cspace;
else if (!strcmp(encoding, "etc1"))
want_cspace = EVAS_COLORSPACE_ETC1;
else if (!strcmp(encoding, "etc2"))
{
if (!ENFN->image_alpha_get(ENDT, pixels))
want_cspace = EVAS_COLORSPACE_RGB8_ETC2;
else
want_cspace = EVAS_COLORSPACE_RGBA8_ETC2_EAC;
if (!strcmp(encoding, "auto"))
want_cspace = cspace;
else if (!strcmp(encoding, "etc1"))
want_cspace = EVAS_COLORSPACE_ETC1;
else if (!strcmp(encoding, "etc2"))
{
if (!ENFN->image_alpha_get(ENDT, pixels))
want_cspace = EVAS_COLORSPACE_RGB8_ETC2;
else
want_cspace = EVAS_COLORSPACE_RGBA8_ETC2_EAC;
}
else if (!strcmp(encoding, "etc1+alpha"))
want_cspace = EVAS_COLORSPACE_ETC1_ALPHA;
}
else if (!strcmp(encoding, "etc1+alpha"))
want_cspace = EVAS_COLORSPACE_ETC1_ALPHA;
}
else
{
free(encoding);
encoding = NULL;
}
}
if (ENFN->image_data_direct_get && (o->cur->orient == EVAS_IMAGE_ORIENT_NONE))
{
Evas_Colorspace cs;
Eina_Slice slice;
Eina_Slice sl;
ENFN->image_colorspace_set(ENDT, pixels, want_cspace);
ok = ENFN->image_data_direct_get(ENDT, pixels, 0, &slice, &cs, EINA_TRUE);
if (ok && (want_cspace == cs))
{
data = (DATA32 *) slice.mem;
putback = EINA_FALSE;
}
else ENFN->image_colorspace_set(ENDT, pixels, cspace);
ok = ENFN->image_data_direct_get(ENDT, pixels, 0, &sl, &cs, EINA_TRUE);
if (ok && (cs == want_cspace))
data = (DATA32 *) sl.mem;
}
if ((o->cur->orient == EVAS_IMAGE_ORIENT_90) ||
(o->cur->orient == EVAS_IMAGE_ORIENT_270) ||
(o->cur->orient == EVAS_IMAGE_FLIP_TRANSPOSE) ||
(o->cur->orient == EVAS_IMAGE_FLIP_TRANSVERSE))
{
int tmp = imagew;
imagew = imageh;
imageh = tmp;
}
if (!data)
{
int stride;
cspace = EVAS_COLORSPACE_ARGB8888;
ENFN->image_colorspace_set(ENDT, pixels, cspace);
pixels = ENFN->image_data_get(ENDT, pixels, 0, &data, &o->load_error, &tofree);
}
ok = ENFN->image_data_map(ENDT, &pixels, &slice, &stride, 0, 0, imagew, imageh,
cspace, EFL_GFX_BUFFER_ACCESS_MODE_READ, 0);
if (!ok || !slice.mem) goto no_pixels;
unmap_it = EINA_TRUE;
data = slice.mem;
if (EINA_UNLIKELY(cspace != o->cur->cspace))
{
EINA_COW_IMAGE_STATE_WRITE_BEGIN(o, state_write)
state_write->cspace = cspace;
EINA_COW_IMAGE_STATE_WRITE_END(o, state_write)
}
if (!pixels || !data)
{
WRN("Could not get image pixels.");
return EINA_FALSE;
if (stride != (imagew * 4))
WRN("Invalid stride: saved image may look wrong!");
}
ie = evas_cache_image_data(evas_common_image_cache_get(),
@ -1031,13 +989,16 @@ _efl_canvas_image_internal_efl_file_save(const Eo *eo_obj, Evas_Image_Data *o, c
}
else ok = EINA_FALSE;
if (tofree)
ENFN->image_free(ENDT, pixels);
else if (putback)
o->engine_data = ENFN->image_data_put(ENDT, pixels, data);
if (unmap_it)
ENFN->image_data_unmap(ENDT, pixels, &slice);
free(encoding);
if (!ok) ERR("Image save failed.");
return ok;
no_pixels:
ERR("Could not get image pixels for saving.");
return EINA_FALSE;
}
EOLIAN static Efl_Gfx_Colorspace
@ -1890,20 +1851,32 @@ evas_object_image_render(Evas_Object *eo_obj, Evas_Object_Protected_Data *obj, v
void *
_evas_image_pixels_get(Eo *eo_obj, Evas_Object_Protected_Data *obj,
void *output, void *context, void *surface, int x, int y,
int *imagew, int *imageh, int *uvw, int *uvh)
int *imagew, int *imageh, int *uvw, int *uvh,
Eina_Bool filtered, Eina_Bool needs_post_render)
{
Evas_Image_Data *o = obj->private_data, *oi = NULL;
Evas_Object_Protected_Data *source = NULL;
void *pixels;
void *pixels = NULL;
if (o->cur->source)
EVAS_OBJECT_DATA_ALIVE_CHECK(obj, NULL);
if (filtered && o->has_filter)
pixels = evas_filter_output_buffer_get(eo_obj);
if (!pixels && o->cur->source)
{
source = efl_data_scope_get(o->cur->source, EFL_CANVAS_OBJECT_CLASS);
if (source && (source->type == o_type))
oi = efl_data_scope_get(o->cur->source, MY_CLASS);
}
if (o->cur->scene)
if (pixels)
{
ENFN->image_size_get(ENDT, pixels, imagew, imageh);
*uvw = *imagew;
*uvh = *imageh;
}
else if (o->cur->scene)
{
_evas_image_3d_render(obj->layer->evas->evas, eo_obj, obj, o, o->cur->scene);
pixels = obj->data_3d->surface;
@ -1922,6 +1895,8 @@ _evas_image_pixels_get(Eo *eo_obj, Evas_Object_Protected_Data *obj,
}
else if (!o->cur->source || !source)
{
// normal image (from file or user pixel set)
needs_post_render = EINA_FALSE;
if (output && surface)
pixels = evas_process_dirty_pixels(eo_obj, obj, o, output, surface, o->engine_data);
else
@ -1941,14 +1916,10 @@ _evas_image_pixels_get(Eo *eo_obj, Evas_Object_Protected_Data *obj,
}
else if (oi && oi->engine_data)
{
pixels = oi->engine_data;
if (oi->has_filter)
{
void *output_buffer = NULL;
output_buffer = evas_filter_output_buffer_get(source->object);
if (output_buffer)
pixels = output_buffer;
}
pixels = evas_filter_output_buffer_get(source->object);
if (!pixels)
pixels = oi->engine_data;
*imagew = oi->cur->image.w;
*imageh = oi->cur->image.h;
*uvw = source->cur->geometry.w;
@ -1976,6 +1947,14 @@ _evas_image_pixels_get(Eo *eo_obj, Evas_Object_Protected_Data *obj,
o->proxyrendering = EINA_FALSE;
}
if (needs_post_render && !obj->layer->evas->inside_post_render)
{
ERR("Can not save or map this image now! Proxies, snapshots and "
"filtered images support those operations only from inside a "
"post-render event.");
return NULL;
}
return pixels;
}
@ -1991,7 +1970,7 @@ _evas_image_render(Eo *eo_obj, Evas_Object_Protected_Data *obj,
void *pixels;
pixels = _evas_image_pixels_get(eo_obj, obj, output, context, surface, x, y,
&imagew, &imageh, &uvw, &uvh);
&imagew, &imageh, &uvw, &uvh, EINA_FALSE, EINA_FALSE);
if (!pixels) return;
if (ENFN->context_clip_get(ENDT, context, NULL, NULL, &cw, &ch) && (!cw || !ch))
@ -2965,8 +2944,7 @@ evas_object_image_is_inside(Evas_Object *eo_obj,
* draw, just get the pixels so we can check the transparency.
*/
pixels = _evas_image_pixels_get(eo_obj, obj, ENDT, NULL, NULL, 0, 0,
&imagew, &imageh, &uvw, &uvh);
&imagew, &imageh, &uvw, &uvh, EINA_TRUE, EINA_FALSE);
if (!pixels) return is_inside;
/* TODO: not handling o->dirty_pixels && o->pixels->func.get_pixels,

View File

@ -3513,7 +3513,9 @@ evas_render_updates_internal(Evas *eo_e,
ru->surface = NULL;
}
eina_spinlock_take(&(e->render.lock));
e->inside_post_render = EINA_TRUE;
_cb_always_call(eo_e, EVAS_CALLBACK_RENDER_POST, &post);
e->inside_post_render = EINA_FALSE;
eina_spinlock_release(&(e->render.lock));
if (post.updated_area) eina_list_free(post.updated_area);
}
@ -3600,6 +3602,8 @@ evas_render_wakeup(Evas *eo_e)
jobs_il = EINA_INLIST_GET(evas->post_render.jobs);
evas->post_render.jobs = NULL;
SLKU(evas->post_render.lock);
evas->inside_post_render = EINA_TRUE;
EINA_INLIST_FREE(jobs_il, job)
{
jobs_il = eina_inlist_remove(jobs_il, EINA_INLIST_GET(job));
@ -3614,6 +3618,7 @@ evas_render_wakeup(Evas *eo_e)
post.updated_area = ret_updates;
_cb_always_call(eo_e, EVAS_CALLBACK_RENDER_POST, &post);
evas->inside_post_render = EINA_FALSE;
evas_render_updates_free(ret_updates);

View File

@ -956,6 +956,7 @@ struct _Evas_Public_Data
Eina_Bool rendering : 1;
Eina_Bool render2 : 1;
Eina_Bool common_init : 1;
Eina_Bool inside_post_render : 1;
};
struct _Evas_Layer

View File

@ -527,6 +527,7 @@ struct _Evas_GL_Image_Data_Map
EINA_INLIST;
Evas_GL_Texture *tex; // one or the other
RGBA_Image *im; // one or the other
Evas_GL_Image *glim;
Eina_Rw_Slice slice;
int stride; // in bytes
int rx, ry, rw, rh; // actual map region

View File

@ -2688,46 +2688,73 @@ eng_ector_end(void *data, void *context EINA_UNUSED, Ector_Surface *ector,
}
static Eina_Bool
eng_image_data_map(void *engdata EINA_UNUSED, void **image, Eina_Rw_Slice *slice,
eng_image_data_map(void *engdata, void **image, Eina_Rw_Slice *slice,
int *stride, int x, int y, int w, int h,
Evas_Colorspace cspace, Efl_Gfx_Buffer_Access_Mode mode,
int plane)
{
Render_Engine_GL_Generic *re = engdata;
Evas_GL_Image_Data_Map *map = NULL;
Evas_GL_Image *im;
Eina_Bool ok;
Evas_GL_Image *glim, *glim2 = NULL;
Eina_Bool ok = EINA_FALSE;
RGBA_Image *im = NULL;
int strid;
EINA_SAFETY_ON_FALSE_RETURN_VAL(image && *image && slice, EINA_FALSE);
im = *image;
if (im->im)
glim = *image;
slice->mem = NULL;
slice->len = 0;
if (glim->im && (glim->orient == EVAS_IMAGE_ORIENT_NONE))
{
int strid = 0;
// Call sw generic implementation. Should work for simple cases.
ok = pfunc.image_data_map(NULL, (void **) &im->im, slice, &strid,
x, y, w, h, cspace, mode, plane);
if (ok)
{
map = calloc(1, sizeof(*map));
map->cspace = cspace;
map->rx = x;
map->ry = y;
map->rw = w;
map->rh = h;
map->mode = mode;
map->slice = *slice;
map->stride = strid;
map->im = im->im; // ref?
im->maps = eina_inlist_prepend(im->maps, EINA_INLIST_GET(map));
}
if (stride) *stride = strid;
return ok;
evas_gl_common_image_ref(glim);
glim2 = glim;
}
else
{
glim2 = _rotate_image_data(re, glim);
}
// TODO: glReadPixels from FBO if possible
if (!glim2) return EINA_FALSE;
im = glim2->im;
if (im)
{
// Call sw generic implementation.
ok = pfunc.image_data_map(NULL, (void **) &im, slice, &strid,
x, y, w, h, cspace, mode, plane);
}
return EINA_FALSE;
if (!ok)
{
eng_image_free(re, glim2);
return EINA_FALSE;
}
evas_cache_image_ref(&im->cache_entry);
map = calloc(1, sizeof(*map));
map->cspace = cspace;
map->rx = x;
map->ry = y;
map->rw = w;
map->rh = h;
map->mode = mode;
map->slice = *slice;
map->stride = strid;
map->im = im;
map->glim = glim2;
glim->maps = eina_inlist_prepend(glim->maps, EINA_INLIST_GET(map));
if (stride) *stride = strid;
if (mode & EFL_GFX_BUFFER_ACCESS_MODE_WRITE)
{
evas_gl_common_image_ref(glim2);
evas_gl_common_image_free(glim);
*image = glim2;
}
return EINA_TRUE;
}
static Eina_Bool
@ -2746,13 +2773,17 @@ eng_image_data_unmap(void *engdata EINA_UNUSED, void *image, const Eina_Rw_Slice
{
found = EINA_TRUE;
if (map->im)
found = pfunc.image_data_unmap(NULL, map->im, slice);
{
found = pfunc.image_data_unmap(NULL, map->im, slice);
evas_cache_image_drop(&map->im->cache_entry);
}
if (found)
{
if (im->im && im->tex &&
(map->mode & EFL_GFX_BUFFER_ACCESS_MODE_WRITE))
evas_gl_common_texture_update(im->tex, im->im);
im->maps = eina_inlist_remove(im->maps, EINA_INLIST_GET(map));
if (map->glim) evas_gl_common_image_free(map->glim);
free(map);
}
return found;