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)) {