From 9fbc5dfc66af70f42c31e6c68a7a15b70f0fbbce Mon Sep 17 00:00:00 2001 From: Cedric BAIL Date: Wed, 8 May 2019 17:54:55 -0700 Subject: [PATCH] evas: add support for stretchable region. This is the first step into introducing support for Android 9 patch format (extension: .9.png). The principle is to expose a new property on image object that define a complete behavior incompatible with other border and fill logic. The reason is that 9 patch allow for any number of stretchable area inside an image, not just for each corner. The way to define this is by giving a pointer to an array of the proper type that define stretchable region relative to each other. The logic being slightly more complex than the border and fill logic, it is slightly slower. If you are just defining corner on your image for something like a button, you would still get better performance using border. I will try to make edje_cc detect those case and fallback to border when possible. Reviewed-by: Hermet Park Differential Revision: https://phab.enlightenment.org/D9096 --- src/lib/efl/interfaces/efl_gfx_image.eo | 31 ++ .../evas/canvas/efl_canvas_image_internal.eo | 1 + src/lib/evas/canvas/evas_image_private.h | 7 + src/lib/evas/canvas/evas_object_image.c | 412 +++++++++++++++++- 4 files changed, 450 insertions(+), 1 deletion(-) diff --git a/src/lib/efl/interfaces/efl_gfx_image.eo b/src/lib/efl/interfaces/efl_gfx_image.eo index e1853feb99..f5ef474471 100644 --- a/src/lib/efl/interfaces/efl_gfx_image.eo +++ b/src/lib/efl/interfaces/efl_gfx_image.eo @@ -40,6 +40,17 @@ enum @beta Efl.Gfx.Image_Scale_Type none [[Not scale the image]] } +struct Efl.Gfx.Image.Stretch_Region +{ + [[This struct holds the description of a stretchable region in one dimension (vertical or horizontal). + Used when scaling an image. + + $offset + $length should be smaller than image size in that dimension. + ]] + offset: uint; [[First pixel of the stretchable region, starting at 0.]] + length: uint; [[Length of the stretchable region in pixels.]] +} + interface @beta Efl.Gfx.Image { [[Common APIs for all 2D images that can be rendered on the canvas.]] @@ -154,6 +165,26 @@ interface @beta Efl.Gfx.Image fill: Efl.Gfx.Border_Fill_Mode; [[Fill mode of the center region.]] } } + @property stretch_region { + [[This property defines the stretchable pixels region of an image. + + When the regions are set by the user, the method will walk the iterators + once and then destroy them. When the regions are retrieved by the user, + it is his responsibility to destroy the iterators.. It will remember the + information for the lifetime of the object. It will ignore all value + of @.border, @.border_scale and @.border_center_fill . To reset the object + you can just pass $null to both horizontal and vertical at the same + time. + ]] + set { + return: Eina.Error; [[return an error code if the stretch_region provided are incorrect.]] + } + get {} + values { + horizontal: iterator; [[Representation of area that are stretchable in the image horizontal space.]] + vertical: iterator; [[Representation of area that are stretchable in the image vertical space.]] + } + } @property image_size { [[This represents the size of the original image in pixels. diff --git a/src/lib/evas/canvas/efl_canvas_image_internal.eo b/src/lib/evas/canvas/efl_canvas_image_internal.eo index ae210621c2..98cf4949d9 100644 --- a/src/lib/evas/canvas/efl_canvas_image_internal.eo +++ b/src/lib/evas/canvas/efl_canvas_image_internal.eo @@ -18,6 +18,7 @@ abstract @beta Efl.Canvas.Image_Internal extends Efl.Canvas.Object implements Ef Efl.Gfx.Image.border { get; set; } Efl.Gfx.Image.border_scale { get; set; } Efl.Gfx.Image.border_center_fill { get; set; } + Efl.Gfx.Image.stretch_region { get; set; } Efl.Gfx.Image.scale_hint { get; set; } Efl.Gfx.Image.content_hint { get; set; } Efl.Gfx.Image.image_size { get; } diff --git a/src/lib/evas/canvas/evas_image_private.h b/src/lib/evas/canvas/evas_image_private.h index 3fe76324fe..21b989aae0 100644 --- a/src/lib/evas/canvas/evas_image_private.h +++ b/src/lib/evas/canvas/evas_image_private.h @@ -75,6 +75,13 @@ struct _Evas_Object_Image_State short l, r, t, b; unsigned char fill; } border; + struct { + struct { + uint8_t *region; + uint32_t stretchable; + uint32_t total; + } horizontal, vertical; + } stretch; Evas_Object *source; Evas_Map *defmap; diff --git a/src/lib/evas/canvas/evas_object_image.c b/src/lib/evas/canvas/evas_object_image.c index 998eec0484..1961f59b97 100644 --- a/src/lib/evas/canvas/evas_object_image.c +++ b/src/lib/evas/canvas/evas_object_image.c @@ -92,6 +92,7 @@ static const Evas_Object_Image_State default_state = { { 0, 0, 0, 0 }, // fill { 0, 0, 0 }, // image { 1.0, 0, 0, 0, 0, 1 }, // border + { { NULL, 0, 0 }, { NULL, 0, 0 } }, NULL, NULL, NULL, //source, defmap, scene NULL, //f NULL, //key @@ -545,6 +546,262 @@ _toggle_fill_listener(Eo *eo_obj, Evas_Image_Data *o) NULL); } +static inline Eina_Bool +_efl_canvas_image_internal_stretch_region_push(uint8_t **stretch_region, + uint32_t *stretch_region_length, + const uint8_t region) +{ + uint8_t *tmp; + + tmp = realloc(*stretch_region, (*stretch_region_length + 1) * sizeof (uint8_t)); + if (!tmp) return EINA_FALSE; + *stretch_region = tmp; + (*stretch_region)[*stretch_region_length] = region; + (*stretch_region_length) += 1; + + return EINA_TRUE; +} + +static inline Eina_Error +_efl_canvas_image_internal_stretch_region_build(uint8_t **stretch_region, + uint32_t *stretch_region_length, + uint32_t value, uint8_t mask) +{ + while (value > 0x7F) + { + if (!_efl_canvas_image_internal_stretch_region_push(stretch_region, + stretch_region_length, + mask | 0x7F)) + { + free(*stretch_region); + return ENOMEM; + } + + value -= 0x7F; + } + + if (!value) return 0; + + if (!_efl_canvas_image_internal_stretch_region_push(stretch_region, + stretch_region_length, + mask | value)) + { + free(*stretch_region); + return ENOMEM; + } + + return 0; +} + +static inline uint8_t * +_efl_canvas_image_internal_stretch_region_iterate(Eina_Iterator *it) +{ + Efl_Gfx_Image_Stretch_Region sz; + uint8_t *stretch_region = NULL; + uint32_t stretch_region_length = 0; + + EINA_ITERATOR_FOREACH(it, sz) + { + if (_efl_canvas_image_internal_stretch_region_build(&stretch_region, + &stretch_region_length, + sz.offset, 0)) + return NULL; + + // The upper bit means stretchable region if set + if (_efl_canvas_image_internal_stretch_region_build(&stretch_region, + &stretch_region_length, + sz.length, 0x80)) + return NULL; + } + + if (!_efl_canvas_image_internal_stretch_region_push(&stretch_region, + &stretch_region_length, + 0)) + { + free(stretch_region); + return NULL; + } + + return stretch_region; +} + +static Eina_Error +_efl_canvas_image_internal_efl_gfx_image_stretch_region_set(Eo *eo_obj, Evas_Image_Data *pd, + Eina_Iterator *horizontal, + Eina_Iterator *vertical) +{ + uint8_t *fhsz = NULL, *fvsz = NULL, *walk; + uint32_t hstretch = 0, vstretch = 0; + uint32_t htotal = 0, vtotal = 0; + Eina_Error err = EINVAL; + Evas_Object_Protected_Data *obj = efl_data_scope_get(eo_obj, EFL_CANVAS_OBJECT_CLASS); + + // We do not duplicate the stretch region in memory, just move pointer. So when + // we do change it, we have to make sure nobody is accessing them anymore by + // blocking rendering. + evas_object_async_block(obj); + EINA_COW_IMAGE_STATE_WRITE_BEGIN(pd, state_write) + { + free(state_write->stretch.horizontal.region); + state_write->stretch.horizontal.region = NULL; + + free(state_write->stretch.vertical.region); + state_write->stretch.vertical.region = NULL; + } + EINA_COW_IMAGE_STATE_WRITE_END(pd, state_write); + + if (!horizontal && !vertical) return 0; + if (!horizontal || !vertical) goto on_error; + + err = ENOMEM; + + fhsz = _efl_canvas_image_internal_stretch_region_iterate(horizontal); + if (!fhsz) goto on_error; + fvsz = _efl_canvas_image_internal_stretch_region_iterate(vertical); + if (!fvsz) goto on_error; + + for (walk = fhsz; *walk; walk++) + { + if ((*walk & 0x80)) hstretch += *walk & 0x7F; + htotal += *walk & 0x7F; + } + + for (walk = fvsz; *walk; walk++) + { + if ((*walk & 0x80)) vstretch += *walk & 0x7F; + vtotal += *walk & 0x7F; + } + + eina_iterator_free(horizontal); + eina_iterator_free(vertical); + + EINA_COW_IMAGE_STATE_WRITE_BEGIN(pd, state_write) + { + state_write->stretch.horizontal.region = fhsz; + state_write->stretch.horizontal.stretchable = hstretch; + state_write->stretch.horizontal.total = htotal; + state_write->stretch.vertical.region = fvsz; + state_write->stretch.vertical.stretchable = vstretch; + state_write->stretch.vertical.total = vtotal; + } + EINA_COW_IMAGE_STATE_WRITE_END(pd, state_write); + + return 0; + + on_error: + eina_iterator_free(horizontal); + eina_iterator_free(vertical); + free(fhsz); + free(fvsz); + + return err; +} + +typedef struct _Efl_Gfx_Image_Stretch_Region_Iterator Efl_Gfx_Image_Stretch_Region_Iterator; +struct _Efl_Gfx_Image_Stretch_Region_Iterator +{ + Eina_Iterator iterator; + + Efl_Gfx_Image_Stretch_Region sz; + + uint8_t *stretch_region; + unsigned int next; +}; + +static Eina_Bool +_efl_gfx_image_stretch_region_iterator_next(Eina_Iterator *iterator, void **data) +{ + Efl_Gfx_Image_Stretch_Region_Iterator *it = (Efl_Gfx_Image_Stretch_Region_Iterator*) iterator; + + *data = &it->sz; + if (!it->stretch_region[it->next]) return EINA_FALSE; + + it->sz.offset = 0; + it->sz.length = 0; + + // Count offset before next stretch region + while (!(it->stretch_region[it->next] & 0x80) && it->stretch_region[it->next]) + { + it->sz.offset += it->stretch_region[it->next] & 0x7F; + it->next++; + } + + // Count length of the stretch region + while ((it->stretch_region[it->next] & 0x80) && it->stretch_region[it->next]) + { + it->sz.length += it->stretch_region[it->next] & 0x7F; + it->next++; + } + + return EINA_TRUE; +} + +static void * +_efl_gfx_image_stretch_region_iterator_container(Eina_Iterator *it EINA_UNUSED) +{ + return NULL; +} + +static void +_efl_gfx_image_stretch_region_iterator_free(Eina_Iterator *it) +{ + free(it); +} + +static void +_efl_canvas_image_internal_efl_gfx_image_stretch_region_get(const Eo *eo_obj, + Evas_Image_Data *pd, + Eina_Iterator **horizontal, + Eina_Iterator **vertical) +{ + Efl_Gfx_Image_Stretch_Region_Iterator *it; + + if (!horizontal) goto vertical_only; + if (!pd->cur->stretch.horizontal.region) + { + *horizontal = NULL; + goto vertical_only; + } + + it = calloc(1, sizeof (Efl_Gfx_Image_Stretch_Region_Iterator)); + if (!it) return; + + EINA_MAGIC_SET(&it->iterator, EINA_MAGIC_ITERATOR); + + it->stretch_region = pd->cur->stretch.horizontal.region; + + it->iterator.version = EINA_ITERATOR_VERSION; + it->iterator.next = FUNC_ITERATOR_NEXT(_efl_gfx_image_stretch_region_iterator_next); + it->iterator.get_container = FUNC_ITERATOR_GET_CONTAINER( + _efl_gfx_image_stretch_region_iterator_container); + it->iterator.free = FUNC_ITERATOR_FREE(_efl_gfx_image_stretch_region_iterator_free); + + *horizontal = &it->iterator; + + vertical_only: + if (!vertical) return; + if (!pd->cur->stretch.vertical.region) + { + *vertical = NULL; + return; + } + + it = calloc(1, sizeof (Efl_Gfx_Image_Stretch_Region_Iterator)); + if (!it) return; + + EINA_MAGIC_SET(&it->iterator, EINA_MAGIC_ITERATOR); + + it->stretch_region = pd->cur->stretch.vertical.region; + + it->iterator.version = EINA_ITERATOR_VERSION; + it->iterator.next = FUNC_ITERATOR_NEXT(_efl_gfx_image_stretch_region_iterator_next); + it->iterator.get_container = FUNC_ITERATOR_GET_CONTAINER( + _efl_gfx_image_stretch_region_iterator_container); + it->iterator.free = FUNC_ITERATOR_FREE(_efl_gfx_image_stretch_region_iterator_free); + + *vertical = &it->iterator; +} + EOLIAN static void _efl_canvas_image_internal_efl_gfx_fill_fill_auto_set(Eo *eo_obj, Evas_Image_Data *o, Eina_Bool setting) { @@ -1980,6 +2237,84 @@ _evas_image_pixels_get(Eo *eo_obj, Evas_Object_Protected_Data *obj, return pixels; } +static inline uint32_t +_stretch_region_accumulate(uint8_t *stretch_region, Eina_Bool mask, uint32_t *i) +{ + uint32_t acc; + + for (acc = 0; + ((stretch_region[*i] & 0x80) == mask) && stretch_region[*i]; + (*i)++) + acc += stretch_region[*i] & 0x7F; + + return acc; +} + +static void +_evas_image_render_hband(Evas_Object_Protected_Data *obj, Evas_Image_Data *o, + void *engine, void *output, void *context, + void *surface, void *pixels, + const int *imw, const int *imh EINA_UNUSED, + int stretchw, int stretchh EINA_UNUSED, + int *inx, int *iny, int *inw, int *inh, + int *outx, int *outy, int *outw, int *outh, + Eina_Bool do_async) +{ + uint32_t hacc; + uint32_t hi; + + if (*inh == 0 || *outh == 0) goto end; + + hi = 0; + while (o->cur->stretch.horizontal.region[hi]) + { + // Not stretched horizontal + hacc = _stretch_region_accumulate(o->cur->stretch.horizontal.region, + 0, &hi); + *inw = hacc; + *outw = hacc; + + if (*inw && *outw) + _draw_image(obj, engine, output, context, surface, pixels, + *inx, *iny, *inw, *inh, + *outx, *outy, *outw, *outh, + o->cur->smooth_scale, do_async); + + // We always step before starting the new region + *inx += *inw; + *outx += *outw; + + // Horizontal stretched + hacc = _stretch_region_accumulate(o->cur->stretch.horizontal.region, + 0x80, &hi); + *inw = hacc; + *outw = hacc * stretchw / o->cur->stretch.horizontal.stretchable; + + if (*inw) + _draw_image(obj, engine, output, context, surface, pixels, + *inx, *iny, *inw, *inh, + *outx, *outy, *outw, *outh, + o->cur->smooth_scale, do_async); + + // We always step before starting the new region + *inx += *inw; + *outx += *outw; + } + // Finish end of image, not horizontally stretched + *inw = *imw - *inx; + *outw = *inw; // If my math are correct, this should be equal + + if (*inw) + _draw_image(obj, engine, output, context, surface, pixels, + *inx, *iny, *inw, *inh, + *outx, *outy, *outw, *outh, + o->cur->smooth_scale, do_async); + + end: + *iny += *inh; // We always step before starting the next region + *outy += *outh; +} + static void _evas_image_render(Eo *eo_obj, Evas_Object_Protected_Data *obj, void *engine, void *output, void *context, void *surface, int x, int y, @@ -2064,7 +2399,82 @@ _evas_image_render(Eo *eo_obj, Evas_Object_Protected_Data *obj, if (ih <= 0) break; } - if ((o->cur->border.l == 0) && (o->cur->border.r == 0) && + if (o->cur->stretch.horizontal.region && + o->cur->stretch.vertical.region) + { + int inx, iny, inw, inh, outx, outy, outw, outh; + int ox, oy; + int imw, imh; + int stretchh, stretchw; + uint32_t vacc; + uint32_t vi; + + imw = imagew; + imh = imageh; + ox = offx + ix; + oy = offy + iy; + iny = 0; + outy = oy; + + // Check that the image is something we can deal with + if (imw < (int) o->cur->stretch.horizontal.total || + imh < (int) o->cur->stretch.vertical.total) + break; + + // Calculate the amount to stretch, by removing from the targetted size + // the amount that should not stretch from the source image + stretchw = iw - (imw - o->cur->stretch.horizontal.stretchable); + stretchh = ih - (imh - o->cur->stretch.vertical.stretchable); + + // Check we have room to stretch + if (stretchh < 0) stretchh = 0; + if (stretchw < 0) stretchw = 0; + + for (vi = 0; o->cur->stretch.vertical.region[vi]; ) + { + vacc = _stretch_region_accumulate(o->cur->stretch.vertical.region, 0, &vi); + inx = 0; + outx = ox; + + // Not stretching vertically step + inh = vacc; + outh = vacc; + + _evas_image_render_hband(obj, o, engine, output, context, + surface, pixels, + &imw, &imh, stretchw, stretchh, + &inx, &iny, &inw, &inh, + &outx, &outy, &outw, &outh, + do_async); + + // Stretching vertically step + vacc = _stretch_region_accumulate(o->cur->stretch.vertical.region, 0x80, &vi); + inx = 0; + outx = ox; + inh = vacc; + outh = vacc * stretchh / o->cur->stretch.vertical.stretchable; + + _evas_image_render_hband(obj, o, engine, output, context, + surface, pixels, + &imw, &imh, stretchw, stretchh, + &inx, &iny, &inw, &inh, + &outx, &outy, &outw, &outh, + do_async); + } + // Finish end of image, not stretched vertical, not stretched horizontal + inx = 0; + outx = ox; + inh = imh - iny; + outh = inh; // Again, if my math are correct, this should be the same + + _evas_image_render_hband(obj, o, engine, output, context, + surface, pixels, + &imw, &imh, stretchw, stretchh, + &inx, &iny, &inw, &inh, + &outx, &outy, &outw, &outh, + do_async); + } + else if ((o->cur->border.l == 0) && (o->cur->border.r == 0) && (o->cur->border.t == 0) && (o->cur->border.b == 0) && (o->cur->border.fill != 0)) {