jpeg pushed a commit to branch master. http://git.enlightenment.org/core/efl.git/commit/?id=01a4ecd92c41e9159aafa59754a7be842f5ca0fb
commit 01a4ecd92c41e9159aafa59754a7be842f5ca0fb Author: Jean-Philippe Andre <[email protected]> Date: Wed Apr 5 15:32:07 2017 +0900 evas filters: Adjust downscale coordinates to avoid artifacts This avoids sampling artifacts when moving or resizing a snapshot object over a region with sharp content (eg. text). --- src/lib/evas/canvas/evas_filter_mixin.c | 4 +- src/lib/evas/canvas/evas_object_textblock.c | 2 +- src/lib/evas/filters/evas_filter.c | 64 ++++++++++++++-------- src/lib/evas/filters/evas_filter_parser.c | 4 +- src/lib/evas/filters/evas_filter_private.h | 6 ++ src/lib/evas/include/evas_filter.h | 4 +- .../engines/gl_generic/filters/gl_filter_blend.c | 56 +++++++++++-------- 7 files changed, 90 insertions(+), 50 deletions(-) diff --git a/src/lib/evas/canvas/evas_filter_mixin.c b/src/lib/evas/canvas/evas_filter_mixin.c index d9ada4f..31a7628 100644 --- a/src/lib/evas/canvas/evas_filter_mixin.c +++ b/src/lib/evas/canvas/evas_filter_mixin.c @@ -401,7 +401,7 @@ evas_filter_object_render(Eo *eo_obj, Evas_Object_Protected_Data *obj, if (filter) { - ok = evas_filter_context_program_reuse(filter, pd->data->chain); + ok = evas_filter_context_program_reuse(filter, pd->data->chain, X, Y); if (!ok) { fcow = FCOW_BEGIN(pd); @@ -417,7 +417,7 @@ evas_filter_object_render(Eo *eo_obj, Evas_Object_Protected_Data *obj, filter = evas_filter_context_new(obj->layer->evas, do_async, 0); // Run script - ok = evas_filter_context_program_use(filter, pd->data->chain, EINA_FALSE); + ok = evas_filter_context_program_use(filter, pd->data->chain, EINA_FALSE, X, Y); if (!filter || !ok) { ERR("Parsing failed?"); diff --git a/src/lib/evas/canvas/evas_object_textblock.c b/src/lib/evas/canvas/evas_object_textblock.c index f65624f..abf076c 100644 --- a/src/lib/evas/canvas/evas_object_textblock.c +++ b/src/lib/evas/canvas/evas_object_textblock.c @@ -13476,7 +13476,7 @@ evas_object_textblock_render(Evas_Object *eo_obj EINA_UNUSED, ctx = evas_filter_context_new(obj->layer->evas, do_async, ti->gfx_filter); evas_filter_state_prepare(eo_obj, &state, ti); evas_filter_program_state_set(pgm, &state); - ok = evas_filter_context_program_use(ctx, pgm, EINA_FALSE); + ok = evas_filter_context_program_use(ctx, pgm, EINA_FALSE, 0, 0); if (!ok) { evas_filter_context_destroy(ctx); diff --git a/src/lib/evas/filters/evas_filter.c b/src/lib/evas/filters/evas_filter.c index 2500590..419c662 100644 --- a/src/lib/evas/filters/evas_filter.c +++ b/src/lib/evas/filters/evas_filter.c @@ -165,7 +165,7 @@ evas_filter_context_proxy_render_all(Evas_Filter_Context *ctx, Eo *eo_obj, } Eina_Bool -evas_filter_context_program_reuse(Evas_Filter_Context *ctx, Evas_Filter_Program *pgm) +evas_filter_context_program_reuse(Evas_Filter_Context *ctx, Evas_Filter_Program *pgm, int x, int y) { Evas_Filter_Buffer *fb; Eina_List *li; @@ -196,7 +196,7 @@ evas_filter_context_program_reuse(Evas_Filter_Context *ctx, Evas_Filter_Program fb->dirty = EINA_FALSE; } - return evas_filter_context_program_use(ctx, pgm, EINA_TRUE); + return evas_filter_context_program_use(ctx, pgm, EINA_TRUE, x, y); } static void @@ -691,19 +691,24 @@ evas_filter_command_blur_add_gl(Evas_Filter_Context *ctx, int rx, int ry, int ox, int oy, int count, int R, int G, int B, int A) { - Evas_Filter_Command *cmd; + Evas_Filter_Command *cmd = NULL; Evas_Filter_Buffer *dx_in, *dx_out, *dy_in, *dy_out, *tmp = NULL; + int down_x = 1, down_y = 1; + int pad_x = 0, pad_y = 0; double dx, dy; /* GL blur implementation: - * - Create intermediate buffer T (variable size) - * - Downscale to buffer T - * - Apply X blur kernel - * - Apply Y blur kernel while scaling up * - * - TODO: Fix distortion X vs. Y - * - TODO: Calculate best scaline and blur radius - * - TODO: Add post-processing? (2D single-pass) + * - Create intermediate buffer T1, T2 + * - Downscale input to buffer T1 + * - Apply X blur kernel from T1 to T2 + * - Apply Y blur kernel from T2 back to output + * + * In order to avoid sampling artifacts when moving or resizing a filtered + * snapshot, we make sure that we always sample and scale based on the same + * original pixels positions: + * - Input pixels must be aligned to down_x,down_y boundaries + * - T1/T2 buffer size is up to 1px larger than [input / scale_x,y] */ dx = rx; @@ -714,34 +719,43 @@ evas_filter_command_blur_add_gl(Evas_Filter_Context *ctx, #if 1 if (type == EVAS_FILTER_BLUR_DEFAULT) { - int down_x = 1, down_y = 1; - - /* For now, disable scaling - testing perfect gaussian blur until it's - * ready: */ + // Apply downscaling for large enough radii only. down_x = 1 << evas_filter_smallest_pow2_larger_than(dx / 2) / 2; down_y = 1 << evas_filter_smallest_pow2_larger_than(dy / 2) / 2; + + // Downscaling to max 4 times for perfect picture quality (with + // the standard scaling fragment shader and SHD_SAM22). if (down_x > 4) down_x = 4; if (down_y > 4) down_y = 4; if (down_x > 1 && down_y > 1) { - tmp = evas_filter_temporary_buffer_get(ctx, - ceil(ctx->w / down_x), - ceil(ctx->h / down_y), - in->alpha_only, EINA_TRUE); + int ww, hh; + + pad_x = ctx->x % down_x; + pad_y = ctx->y % down_y; + + ww = ceil((double) ctx->w / down_x) + 1; + hh = ceil((double) ctx->h / down_y) + 1; + + tmp = evas_filter_temporary_buffer_get(ctx, ww, hh, in->alpha_only, EINA_TRUE); if (!tmp) goto fail; - dx = rx / down_x; - dy = ry / down_y; + dx /= (double) down_x; + dy /= (double) down_y; XDBG("Add GL downscale %d (%dx%d) -> %d (%dx%d)", in->id, in->w, in->h, tmp->id, tmp->w, tmp->h); cmd = _command_new(ctx, EVAS_FILTER_MODE_BLEND, in, NULL, tmp); if (!cmd) goto fail; cmd->draw.fillmode = EVAS_FILTER_FILL_MODE_STRETCH_XY; + cmd->draw.scale.down = EINA_TRUE; + cmd->draw.scale.pad_x = pad_x; + cmd->draw.scale.pad_y = pad_y; + cmd->draw.scale.factor_x = down_x; + cmd->draw.scale.factor_y = down_y; dx_in = tmp; - tmp = evas_filter_temporary_buffer_get(ctx, ctx->w / down_x, ctx->h / down_y, - in->alpha_only, EINA_TRUE); + tmp = evas_filter_temporary_buffer_get(ctx, ww, hh, in->alpha_only, EINA_TRUE); if (!tmp) goto fail; dy_out = tmp; } @@ -780,6 +794,7 @@ evas_filter_command_blur_add_gl(Evas_Filter_Context *ctx, cmd->blur.count = count; } + EINA_SAFETY_ON_NULL_RETURN_VAL(cmd, NULL); if (cmd->output != out) { XDBG("Add GL upscale %d (%dx%d) -> %d (%dx%d)", @@ -787,6 +802,11 @@ evas_filter_command_blur_add_gl(Evas_Filter_Context *ctx, cmd = _command_new(ctx, EVAS_FILTER_MODE_BLEND, cmd->output, NULL, out); if (!cmd) goto fail; cmd->draw.fillmode = EVAS_FILTER_FILL_MODE_STRETCH_XY; + cmd->draw.scale.down = EINA_FALSE; + cmd->draw.scale.pad_x = pad_x; + cmd->draw.scale.pad_y = pad_y; + cmd->draw.scale.factor_x = down_x; + cmd->draw.scale.factor_y = down_y; } cmd->draw.ox = ox; diff --git a/src/lib/evas/filters/evas_filter_parser.c b/src/lib/evas/filters/evas_filter_parser.c index 60a7ecb..ccf2806 100644 --- a/src/lib/evas/filters/evas_filter_parser.c +++ b/src/lib/evas/filters/evas_filter_parser.c @@ -3474,7 +3474,7 @@ _instruction_dump(Evas_Filter_Instruction *instr) Eina_Bool evas_filter_context_program_use(Evas_Filter_Context *ctx, Evas_Filter_Program *pgm, - Eina_Bool reuse) + Eina_Bool reuse, int object_x, int object_y) { Evas_Filter_Instruction *instr; Eina_Bool success = EINA_FALSE; @@ -3489,6 +3489,8 @@ evas_filter_context_program_use(Evas_Filter_Context *ctx, // Copy current state (size, edje state val, color class, etc...) ctx->w = pgm->state.w; ctx->h = pgm->state.h; + ctx->x = object_x; + ctx->y = object_y; // Create empty context with all required buffers evas_filter_context_clear(ctx, reuse); diff --git a/src/lib/evas/filters/evas_filter_private.h b/src/lib/evas/filters/evas_filter_private.h index 01cda48..576cef3 100644 --- a/src/lib/evas/filters/evas_filter_private.h +++ b/src/lib/evas/filters/evas_filter_private.h @@ -125,6 +125,7 @@ struct _Evas_Filter_Context evas_filter_buffer_scaled_get_func buffer_scaled_get; // Variables changing at each run + int x, y; // Position of the object (for GL downscaling of snapshots) int w, h; // Dimensions of the input/output buffers struct { @@ -233,6 +234,11 @@ struct _Evas_Filter_Command int l, r, t, b; }; } clip; + struct { + int factor_x, factor_y; + int pad_x, pad_y; + Eina_Bool down; + } scale; Evas_Filter_Fill_Mode fillmode; Eina_Bool clip_use : 1; Eina_Bool clip_mode_lrtb : 1; diff --git a/src/lib/evas/include/evas_filter.h b/src/lib/evas/include/evas_filter.h index 6972159..ca79b36 100644 --- a/src/lib/evas/include/evas_filter.h +++ b/src/lib/evas/include/evas_filter.h @@ -147,8 +147,8 @@ void *evas_filter_context_data_get(Evas_Filter_Context *ctx); Eina_Bool evas_filter_context_async_get(Evas_Filter_Context *ctx); void evas_filter_context_size_get(Evas_Filter_Context *ctx, int *w, int *H); void evas_filter_context_destroy(Evas_Filter_Context *ctx); -Eina_Bool evas_filter_context_program_use(Evas_Filter_Context *ctx, Evas_Filter_Program *pgm, Eina_Bool reuse); -Eina_Bool evas_filter_context_program_reuse(Evas_Filter_Context *ctx, Evas_Filter_Program *pgm); +Eina_Bool evas_filter_context_program_use(Evas_Filter_Context *ctx, Evas_Filter_Program *pgm, Eina_Bool reuse, int object_x, int object_y); +Eina_Bool evas_filter_context_program_reuse(Evas_Filter_Context *ctx, Evas_Filter_Program *pgm, int x, int y); void evas_filter_context_proxy_render_all(Evas_Filter_Context *ctx, Eo *eo_obj, Eina_Bool do_async); void evas_filter_context_post_run_callback_set(Evas_Filter_Context *ctx, Evas_Filter_Cb cb, void *data); #define evas_filter_context_autodestroy(ctx) evas_filter_context_post_run_callback_set(ctx, ((Evas_Filter_Cb) evas_filter_context_destroy), ctx) diff --git a/src/modules/evas/engines/gl_generic/filters/gl_filter_blend.c b/src/modules/evas/engines/gl_generic/filters/gl_filter_blend.c index 5f6c84e..3cc1b0f 100644 --- a/src/modules/evas/engines/gl_generic/filters/gl_filter_blend.c +++ b/src/modules/evas/engines/gl_generic/filters/gl_filter_blend.c @@ -12,8 +12,6 @@ _mapped_blend(Evas_Engine_GL_Context *gc, int row, col, rows, cols; Eina_Bool ret = EINA_TRUE; - EINA_SAFETY_ON_FALSE_RETURN_VAL((sx == 0) && (sy == 0), EINA_FALSE); - if (fillmode == EVAS_FILTER_FILL_MODE_NONE) { DBG("blend: %d,%d,%d,%d --> %d,%d,%d,%d", sx, sy, sw, sh, dx, dy, sw, sh); @@ -35,7 +33,6 @@ _mapped_blend(Evas_Engine_GL_Context *gc, else if (fillmode & EVAS_FILTER_FILL_MODE_STRETCH_X) { cols = 0; - dx = 0; } else { @@ -58,7 +55,6 @@ _mapped_blend(Evas_Engine_GL_Context *gc, else if (fillmode & EVAS_FILTER_FILL_MODE_STRETCH_Y) { rows = 0; - dy = 0; } else { @@ -95,9 +91,10 @@ _mapped_blend(Evas_Engine_GL_Context *gc, src_y = 0; if (fillmode & EVAS_FILTER_FILL_MODE_STRETCH_Y) { + src_y = sy; src_h = sh; + dst_y = dy; dst_h = dh; - dst_y = 0; } else { @@ -133,9 +130,10 @@ _mapped_blend(Evas_Engine_GL_Context *gc, src_x = 0; if (fillmode & EVAS_FILTER_FILL_MODE_STRETCH_X) { + src_x = sx; src_w = sw; + dst_x = dx; dst_w = dw; - dst_x = 0; } else { @@ -163,7 +161,7 @@ _gl_filter_blend(Render_Engine_GL_Generic *re, Evas_Filter_Command *cmd) Evas_Engine_GL_Context *gc; Evas_GL_Image *image, *surface; RGBA_Draw_Context *dc_save; - int src_w, src_h, dst_w, dst_h; + int src_w, src_h, dst_w, dst_h, src_x, src_y, dst_x, dst_y; DEBUG_TIME_BEGIN(); @@ -188,40 +186,54 @@ _gl_filter_blend(Render_Engine_GL_Generic *re, Evas_Filter_Command *cmd) if ((cmd->draw.fillmode == EVAS_FILTER_FILL_MODE_STRETCH_XY) && ((image->w != surface->w) || (image->h != surface->h))) { - int scale_w, scale_h; + double scale_w, scale_h; + int pad_x, pad_y; - if ((image->w >= surface->w) || - (image->h >= surface->h)) + pad_x = cmd->draw.scale.pad_x; + pad_y = cmd->draw.scale.pad_y; + scale_w = MAX(cmd->draw.scale.factor_x, 1); + scale_h = MAX(cmd->draw.scale.factor_y, 1); + + if (cmd->draw.scale.down) { - scale_w = image->w / surface->w; - scale_h = image->h / surface->h; - src_w = ((image->w + (scale_w - 1)) / scale_w) * scale_w; - src_h = ((image->h + (scale_h - 1)) / scale_h) * scale_h; - dst_w = surface->w; - dst_h = surface->h; + src_x = -pad_x; + src_y = -pad_y; + src_w = (ceil(image->w / scale_w) + 1) * scale_w; + src_h = (ceil(image->h / scale_h) + 1) * scale_h; + dst_x = 0; + dst_y = 0; + dst_w = src_w / scale_w; + dst_h = src_h / scale_h; } else { - scale_w = surface->w / image->w; - scale_h = surface->h / image->h; + src_x = 0; + src_y = 0; src_w = image->w; src_h = image->h; - dst_w = ((surface->w + (scale_w - 1)) / scale_w) * scale_w; - dst_h = ((surface->h + (scale_h - 1)) / scale_h) * scale_h; + dst_x = cmd->draw.ox - pad_x; + dst_y = cmd->draw.oy - pad_y; + dst_w = (ceil(surface->w / scale_w) + 1) * scale_w; + dst_h = (ceil(surface->h / scale_h) + 1) * scale_h; } } else { + src_x = 0; + src_y = 0; src_w = image->w; src_h = image->h; + dst_x = cmd->draw.ox; + dst_y = cmd->draw.oy; dst_w = surface->w; dst_h = surface->h; } DBG("blend %d @%p -> %d @%p", cmd->input->id, cmd->input->buffer, cmd->output->id, cmd->output->buffer); - _mapped_blend(gc, image, cmd->draw.fillmode, 0, 0, src_w, src_h, - cmd->draw.ox, cmd->draw.oy, dst_w, dst_h); + _mapped_blend(gc, image, cmd->draw.fillmode, + src_x, src_y, src_w, src_h, + dst_x, dst_y, dst_w, dst_h); evas_common_draw_context_free(gc->dc); gc->dc = dc_save; --
