This is an automated email from the git hooks/post-receive script.
git pushed a commit to branch devs/cedric/wl/evas-source-region
in repository efl.
View the commit online.
commit 9e5c5a43aa3b305162946b78d3b0430df71df210
Author: Cedric BAIL <[email protected]>
AuthorDate: Sat Aug 8 09:23:31 2026 -0600
evas/tests: cover the image buffer crop property
The source image is a 4x4 buffer of four solid 2x2 quadrants, so a crop
pinned to one quadrant and scaled over the object must paint it in
exactly one colour. That makes every assertion an exact pixel comparison
against ecore_evas_buffer_pixels_get() rather than a tolerance, and it
holds regardless of how the scaler interpolates.
Covers the property itself, including that any zero or negative extent
normalises to the same unset value; the four quadrants rendering; the
clamping cases (oversized crop, negative origin, and a crop entirely
outside the buffer, which must show the background and so also proves
the opacity path); damage inside the crop being painted while damage
outside it does not leak onto the canvas; and the bordered nine-cell
path reading from inside the crop.
Hit testing is driven with real pointer events rather than
evas_object_top_at_xy_get(), which is purely geometric and never
consults precise_is_inside. With the crop on the transparent quadrant
the centre of the object must stop receiving events, which it would not
if evas_object_image_is_inside() still sampled the whole buffer.
Gated on BUILD_ENGINE_BUFFER like evas_test_mask.c.
Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01Ke8zkumHXWeJHNDMzEUqsH
---
src/tests/evas/evas_suite.c | 1 +
src/tests/evas/evas_suite.h | 1 +
src/tests/evas/evas_test_buffer_crop.c | 372 +++++++++++++++++++++++++++++++++
src/tests/evas/meson.build | 1 +
4 files changed, 375 insertions(+)
diff --git a/src/tests/evas/evas_suite.c b/src/tests/evas/evas_suite.c
index 05faf08ff8..a5abb8613b 100644
--- a/src/tests/evas/evas_suite.c
+++ b/src/tests/evas/evas_suite.c
@@ -20,6 +20,7 @@ static const Efl_Test_Case etc[] = {
{ "Filters", evas_test_filters },
{ "Images", evas_test_image_object },
{ "Images", evas_test_image_object2 },
+ { "Buffer Crop", evas_test_image_buffer_crop },
{ "Masking", evas_test_mask },
{ "Evas GL", evas_test_evasgl },
{ "Object Smart", evas_test_object_smart },
diff --git a/src/tests/evas/evas_suite.h b/src/tests/evas/evas_suite.h
index 76a10a19b5..a25d9f70a0 100644
--- a/src/tests/evas/evas_suite.h
+++ b/src/tests/evas/evas_suite.h
@@ -13,6 +13,7 @@ void evas_test_callbacks(TCase *tc);
void evas_test_render_engines(TCase *tc);
void evas_test_filters(TCase *tc);
void evas_test_image_object(TCase *tc);
+void evas_test_image_buffer_crop(TCase *tc);
void evas_test_image_object2(TCase *tc);
void evas_test_mask(TCase *tc);
void evas_test_evasgl(TCase *tc);
diff --git a/src/tests/evas/evas_test_buffer_crop.c b/src/tests/evas/evas_test_buffer_crop.c
new file mode 100644
index 0000000000..c46041ed7c
--- /dev/null
+++ b/src/tests/evas/evas_test_buffer_crop.c
@@ -0,0 +1,372 @@
+#ifdef HAVE_CONFIG_H
+# include "config.h"
+#endif
+
+#ifdef BUILD_ENGINE_BUFFER
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <Eina.h>
+#include <Evas.h>
+#include <Ecore_Evas.h>
+
+#include "evas_suite.h"
+#include "evas_tests_helpers.h"
+
+/* The source image is a 4x4 buffer made of four solid 2x2 quadrants. Cropping
+ * to one quadrant and scaling it over the whole object must therefore paint
+ * the object in exactly one colour, which makes every assertion below an exact
+ * pixel comparison rather than a tolerance. */
+#define IMG_W 4
+#define IMG_H 4
+#define OBJ_W 20
+#define OBJ_H 20
+
+#define C_TL 0xffff0000 /* red */
+#define C_TR 0xff00ff00 /* green */
+#define C_BL 0xff0000ff /* blue */
+#define C_BR 0xffffff00 /* yellow */
+#define C_BG 0xff123456 /* canvas background */
+
+#define START_CROP_TEST() \
+ Ecore_Evas *ee; \
+ Evas *e; \
+ Evas_Object *bg, *o; \
+ ee = ecore_evas_buffer_new(OBJ_W, OBJ_H); \
+ fail_if(!ee); \
+ ecore_evas_show(ee); \
+ ecore_evas_manual_render_set(ee, EINA_TRUE); \
+ e = ecore_evas_get(ee); \
+ bg = _bg_add(e); \
+ o = _img_add(e); \
+ do {} while (0)
+
+#define END_CROP_TEST() \
+ do { \
+ evas_object_del(o); \
+ evas_object_del(bg); \
+ ecore_evas_free(ee); \
+ } while (0)
+
+static Evas_Object *
+_bg_add(Evas *e)
+{
+ Evas_Object *bg = evas_object_rectangle_add(e);
+
+ evas_object_color_set(bg, 0x12, 0x34, 0x56, 0xff);
+ evas_object_move(bg, 0, 0);
+ evas_object_resize(bg, OBJ_W, OBJ_H);
+ evas_object_show(bg);
+ return bg;
+}
+
+static Evas_Object *
+_img_add(Evas *e)
+{
+ Evas_Object *o = evas_object_image_add(e);
+ unsigned int *data;
+ int x, y;
+
+ evas_object_image_colorspace_set(o, EVAS_COLORSPACE_ARGB8888);
+ evas_object_image_alpha_set(o, EINA_FALSE);
+ evas_object_image_size_set(o, IMG_W, IMG_H);
+ evas_object_image_smooth_scale_set(o, EINA_FALSE);
+
+ data = "" EINA_TRUE);
+ fail_if(!data);
+ for (y = 0; y < IMG_H; y++)
+ for (x = 0; x < IMG_W; x++)
+ {
+ unsigned int c;
+
+ if (y < IMG_H / 2) c = (x < IMG_W / 2) ? C_TL : C_TR;
+ else c = (x < IMG_W / 2) ? C_BL : C_BR;
+ data[(y * IMG_W) + x] = c;
+ }
+ evas_object_image_data_set(o, data);
+ evas_object_image_data_update_add(o, 0, 0, IMG_W, IMG_H);
+
+ evas_object_image_filled_set(o, EINA_TRUE);
+ evas_object_move(o, 0, 0);
+ evas_object_resize(o, OBJ_W, OBJ_H);
+ evas_object_show(o);
+ return o;
+}
+
+/* Returns the number of pixels that are not @c expected. */
+static int
+_count_not(const unsigned int *pix, unsigned int expected)
+{
+ int i, bad = 0;
+
+ for (i = 0; i < OBJ_W * OBJ_H; i++)
+ if (pix[i] != expected) bad++;
+ return bad;
+}
+
+static Eina_Bool
+_has_colour(const unsigned int *pix, unsigned int c)
+{
+ int i;
+
+ for (i = 0; i < OBJ_W * OBJ_H; i++)
+ if (pix[i] == c) return EINA_TRUE;
+ return EINA_FALSE;
+}
+
+EFL_START_TEST(evas_object_image_buffer_crop_property)
+{
+ Evas *e = EVAS_TEST_INIT_EVAS();
+ Evas_Object *o = evas_object_image_add(e);
+ int x, y, w, h;
+
+ /* unset by default */
+ evas_object_image_buffer_crop_get(o, &x, &y, &w, &h);
+ fail_if((x != 0) || (y != 0) || (w != 0) || (h != 0));
+
+ evas_object_image_buffer_crop_set(o, 3, 5, 7, 11);
+ evas_object_image_buffer_crop_get(o, &x, &y, &w, &h);
+ fail_if((x != 3) || (y != 5) || (w != 7) || (h != 11));
+
+ /* partial gets must work */
+ x = y = w = h = -1;
+ evas_object_image_buffer_crop_get(o, NULL, NULL, &w, NULL);
+ fail_if(w != 7);
+
+ /* a zero or negative extent unsets, and normalises the origin away so that
+ * "unset" has exactly one representation */
+ evas_object_image_buffer_crop_set(o, 3, 5, 0, 11);
+ evas_object_image_buffer_crop_get(o, &x, &y, &w, &h);
+ fail_if((x != 0) || (y != 0) || (w != 0) || (h != 0));
+
+ evas_object_image_buffer_crop_set(o, 3, 5, 7, -1);
+ evas_object_image_buffer_crop_get(o, &x, &y, &w, &h);
+ fail_if((x != 0) || (y != 0) || (w != 0) || (h != 0));
+
+ evas_object_del(o);
+ evas_free(e);
+}
+EFL_END_TEST
+
+EFL_START_TEST(evas_object_image_buffer_crop_render)
+{
+ const unsigned int *pix;
+ struct {
+ int x, y;
+ unsigned int colour;
+ const char *name;
+ } quad[] = {
+ { 0, 0, C_TL, "top-left" },
+ { 2, 0, C_TR, "top-right" },
+ { 0, 2, C_BL, "bottom-left" },
+ { 2, 2, C_BR, "bottom-right" },
+ };
+ unsigned int i;
+
+ START_CROP_TEST();
+
+ for (i = 0; i < EINA_C_ARRAY_LENGTH(quad); i++)
+ {
+ evas_object_image_buffer_crop_set(o, quad[i].x, quad[i].y, 2, 2);
+ ecore_evas_manual_render(ee);
+ pix = ecore_evas_buffer_pixels_get(ee);
+ fail_if(!pix);
+ ck_assert_msg(_count_not(pix, quad[i].colour) == 0,
+ "crop of the %s quadrant did not fill the object with %#x",
+ quad[i].name, quad[i].colour);
+ }
+
+ /* unsetting brings the whole buffer back */
+ evas_object_image_buffer_crop_set(o, 0, 0, 0, 0);
+ ecore_evas_manual_render(ee);
+ pix = ecore_evas_buffer_pixels_get(ee);
+ fail_if(!pix);
+ ck_assert_msg(_has_colour(pix, C_TL) && _has_colour(pix, C_TR) &&
+ _has_colour(pix, C_BL) && _has_colour(pix, C_BR),
+ "unsetting the crop did not restore the whole buffer");
+
+ END_CROP_TEST();
+}
+EFL_END_TEST
+
+EFL_START_TEST(evas_object_image_buffer_crop_clamp)
+{
+ const unsigned int *pix;
+
+ START_CROP_TEST();
+
+ /* Reaches past the right and bottom edges: the intersection is the
+ * bottom-right quadrant only. */
+ evas_object_image_buffer_crop_set(o, 2, 2, 100, 100);
+ ecore_evas_manual_render(ee);
+ pix = ecore_evas_buffer_pixels_get(ee);
+ fail_if(!pix);
+ ck_assert_msg(_count_not(pix, C_BR) == 0,
+ "an oversized crop did not clamp to the buffer");
+
+ /* A negative origin is clamped to 0, shrinking the extent with it, so this
+ * still selects exactly the top-left quadrant. */
+ evas_object_image_buffer_crop_set(o, -2, -2, 4, 4);
+ ecore_evas_manual_render(ee);
+ pix = ecore_evas_buffer_pixels_get(ee);
+ fail_if(!pix);
+ ck_assert_msg(_count_not(pix, C_TL) == 0,
+ "a negative crop origin was not clamped to the buffer");
+
+ /* Entirely outside: nothing is drawn at all and the background shows
+ * through. This also exercises the opacity path -- if the object were still
+ * reported opaque the background would have been optimised away. */
+ evas_object_image_buffer_crop_set(o, IMG_W + 4, 0, 2, 2);
+ ecore_evas_manual_render(ee);
+ pix = ecore_evas_buffer_pixels_get(ee);
+ fail_if(!pix);
+ ck_assert_msg(_count_not(pix, C_BG) == 0,
+ "a crop fully outside the buffer still drew something");
+
+ END_CROP_TEST();
+}
+EFL_END_TEST
+
+EFL_START_TEST(evas_object_image_buffer_crop_damage)
+{
+ const unsigned int *pix;
+
+ START_CROP_TEST();
+
+ evas_object_image_buffer_crop_set(o, 0, 0, 2, 2);
+ ecore_evas_manual_render(ee);
+
+ /* Repaint the cropped-in quadrant only, and report damage for it. The
+ * object must pick the change up. */
+ {
+ unsigned int *data = "" EINA_TRUE);
+ int x, y;
+
+ fail_if(!data);
+ for (y = 0; y < IMG_H / 2; y++)
+ for (x = 0; x < IMG_W / 2; x++)
+ data[(y * IMG_W) + x] = C_BR;
+ evas_object_image_data_set(o, data);
+ evas_object_image_data_update_add(o, 0, 0, IMG_W / 2, IMG_H / 2);
+ }
+ ecore_evas_manual_render(ee);
+ pix = ecore_evas_buffer_pixels_get(ee);
+ fail_if(!pix);
+ ck_assert_msg(_count_not(pix, C_BR) == 0,
+ "damage inside the crop was not painted");
+
+ /* Damage that falls entirely outside the crop must not change what is on
+ * screen: those pixels are never displayed. */
+ {
+ unsigned int *data = "" EINA_TRUE);
+ int x, y;
+
+ fail_if(!data);
+ for (y = IMG_H / 2; y < IMG_H; y++)
+ for (x = IMG_W / 2; x < IMG_W; x++)
+ data[(y * IMG_W) + x] = C_TR;
+ evas_object_image_data_set(o, data);
+ evas_object_image_data_update_add(o, IMG_W / 2, IMG_H / 2,
+ IMG_W / 2, IMG_H / 2);
+ }
+ ecore_evas_manual_render(ee);
+ pix = ecore_evas_buffer_pixels_get(ee);
+ fail_if(!pix);
+ ck_assert_msg(_count_not(pix, C_BR) == 0,
+ "damage outside the crop leaked onto the canvas");
+
+ END_CROP_TEST();
+}
+EFL_END_TEST
+
+static void
+_mouse_move_cb(void *data, Evas *e EINA_UNUSED, Evas_Object *obj EINA_UNUSED,
+ void *event_info EINA_UNUSED)
+{
+ int *hit = data;
+
+ *hit = 1;
+}
+
+EFL_START_TEST(evas_object_image_buffer_crop_hittest)
+{
+ unsigned int *data;
+ int hit;
+ int x, y;
+
+ START_CROP_TEST();
+
+ /* Precise hit testing is only consulted when events are actually routed --
+ * evas_object_top_at_xy_get() is purely geometric -- so feed real pointer
+ * events and watch whether the object receives them. */
+ evas_object_image_alpha_set(o, EINA_TRUE);
+ data = "" EINA_TRUE);
+ fail_if(!data);
+ for (y = 0; y < IMG_H / 2; y++)
+ for (x = 0; x < IMG_W / 2; x++)
+ data[(y * IMG_W) + x] = 0x00000000; /* transparent top-left quadrant */
+ evas_object_image_data_set(o, data);
+ evas_object_image_data_update_add(o, 0, 0, IMG_W, IMG_H);
+ evas_object_precise_is_inside_set(o, EINA_TRUE);
+ evas_object_event_callback_add(o, EVAS_CALLBACK_MOUSE_MOVE, _mouse_move_cb, &hit);
+ evas_event_feed_mouse_in(e, 0, NULL);
+
+ /* Cropped to the opaque bottom-right quadrant: the centre is a hit. */
+ evas_object_image_buffer_crop_set(o, 2, 2, 2, 2);
+ ecore_evas_manual_render(ee);
+ hit = 0;
+ evas_event_feed_mouse_move(e, (OBJ_W / 2) + 1, (OBJ_H / 2) + 1, 0, NULL);
+ ck_assert_msg(hit == 1, "hit testing missed an opaque cropped region");
+
+ /* Cropped to the transparent top-left quadrant: the same point is not. */
+ evas_object_image_buffer_crop_set(o, 0, 0, 2, 2);
+ ecore_evas_manual_render(ee);
+ hit = 0;
+ evas_event_feed_mouse_move(e, OBJ_W / 2, OBJ_H / 2, 0, NULL);
+ ck_assert_msg(hit == 0,
+ "hit testing did not follow the crop into a transparent region");
+
+ END_CROP_TEST();
+}
+EFL_END_TEST
+
+EFL_START_TEST(evas_object_image_buffer_crop_border)
+{
+ const unsigned int *pix;
+
+ START_CROP_TEST();
+
+ /* A border splits the source into nine cells. Those cells must be taken
+ * from inside the crop, not from the whole buffer: with the crop pinned to
+ * a solid quadrant every cell is the same colour, so the result stays
+ * solid however the border divides it. */
+ evas_object_image_border_set(o, 1, 1, 1, 1);
+ evas_object_image_buffer_crop_set(o, 2, 2, 2, 2);
+ ecore_evas_manual_render(ee);
+ pix = ecore_evas_buffer_pixels_get(ee);
+ fail_if(!pix);
+ ck_assert_msg(_count_not(pix, C_BR) == 0,
+ "the bordered draw path read outside the crop");
+
+ END_CROP_TEST();
+}
+EFL_END_TEST
+
+void evas_test_image_buffer_crop(TCase *tc)
+{
+ tcase_add_test(tc, evas_object_image_buffer_crop_property);
+ tcase_add_test(tc, evas_object_image_buffer_crop_render);
+ tcase_add_test(tc, evas_object_image_buffer_crop_clamp);
+ tcase_add_test(tc, evas_object_image_buffer_crop_damage);
+ tcase_add_test(tc, evas_object_image_buffer_crop_hittest);
+ tcase_add_test(tc, evas_object_image_buffer_crop_border);
+}
+
+#else
+
+void evas_test_image_buffer_crop(TCase *tc EINA_UNUSED)
+{
+}
+
+#endif
diff --git a/src/tests/evas/meson.build b/src/tests/evas/meson.build
index 63670c7986..fcbf3a31b2 100644
--- a/src/tests/evas/meson.build
+++ b/src/tests/evas/meson.build
@@ -11,6 +11,7 @@ evas_suite_src = [
'evas_test_render_engines.c',
'evas_test_filters.c',
'evas_test_image.c',
+ 'evas_test_buffer_crop.c',
'evas_test_mask.c',
'evas_test_evasgl.c',
'evas_test_focus.c',
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.