This is an automated email from the git hooks/post-receive script.
git pushed a commit to branch edje-vector-intergration
in repository efl.
View the commit online.
commit 29eb56a2cfbed00e7f45493f0389e4fc32c633c4
Author: [email protected] <[email protected]>
AuthorDate: Wed Apr 29 10:28:39 2026 -0600
edje: implement name-aligned tree lerp (Task 5.2)
Adds _edje_vg_tree_lerp(a, b, pos) to build a third tree by walking A and B
in parallel, lerping non-path properties where named nodes match in both sides.
The output structure mirrors B (post-transition state), with step semantics
for mismatches: a-only nodes effectively snap out at pos>0 in v1 (alpha fade
deferred to a future phase).
Key design points:
- Output mirrors B's structure from _edje_vg_tree_dup, then walks to lookup
each named node in both A and B. For type matches, non-path properties are
lerp'd via Task 5.1 helpers; otherwise B's values remain (step semantics).
- Stringshare-aware color_class handling via new _color_lerp_with_ref()
wrapper. The dup'd output already owns a ref to B's color_class for every
node. Overwriting with a borrowed pointer (written by _edje_vg_color_lerp)
would leak the dup ref and cause double-free at cleanup. The wrapper
releases the dup ref, calls the lerp (writes borrowed stepped pointer),
then re-refs the result. Applied to node->color, shape.fill,
shape.stroke_color, and per-stop color.
- Path lerping deferred to Task 5.3 materialization via
efl_gfx_path_interpolate, leveraging EFL's existing topology-checking
rather than reimplementing SVG path morphing in our model.
- Type-specific coverage: shapes lerp fill, stroke_color, stroke_width,
stroke_dash (conditional on count match); gradients lerp geometry
(x0/y0/x1/y1/radius) and stops (conditional on count match); step semantics
for cap/join/fill_rule/spread/visible (visible flips at pos=1.0).
Unit test edje_vg_tree_lerp_basic builds two trees with different body colors,
lerps at 0.5, asserts resolved body RGBA is halfway. All 62 tests pass, no
valgrind leaks.
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
---
src/lib/edje/edje_vg_tree.c | 233 +++++++++++++++++++++++++++++++
src/lib/edje/edje_vg_tree.h | 15 ++
src/tests/edje/edje_test_vector_states.c | 151 +++++++++++++++++++-
3 files changed, 398 insertions(+), 1 deletion(-)
diff --git a/src/lib/edje/edje_vg_tree.c b/src/lib/edje/edje_vg_tree.c
index 0b1fc738a1..a4781a3350 100644
--- a/src/lib/edje/edje_vg_tree.c
+++ b/src/lib/edje/edje_vg_tree.c
@@ -1097,6 +1097,239 @@ _edje_vg_viewbox_lerp(double *out_x, double *out_y,
if (out_h) *out_h = q * ah + pos * bh;
}
+/* =========================================================================
+ * Phase 5.2 — Name-aligned tree lerp
+ *
+ * _edje_vg_tree_lerp builds a third tree whose structure mirrors B. For
+ * every named node present in both A and B with a matching type we lerp
+ * non-path properties from A and B into the output node. Nodes present
+ * only in one side keep B's values (step semantics). Path lerping is
+ * deferred to materialize-time via efl_gfx_path_interpolate (Task 5.3).
+ *
+ * Stringshare ownership rule for color_class:
+ * _edje_vg_color_lerp() writes a non-ref-counted borrow into out.color_class.
+ * The dup'd out_node already owns B's stringshare ref for that field.
+ * After the lerp we call eina_stringshare_replace() to correctly release
+ * B's ref and acquire the chosen side's ref. This avoids both leaks and
+ * double-frees when _edje_vg_node_free() later calls eina_stringshare_del.
+ * ========================================================================= */
+
+/* Helper: lerp a color binding AND fix up the color_class stringshare ref.
+ *
+ * out_cb is the destination in the dup'd out_node; it already owns a
+ * stringshare ref (B's class, from the dup). _edje_vg_color_lerp will
+ * overwrite color_class with a borrowed pointer without releasing the old
+ * ref. To avoid a leak (and to avoid del'ing a borrowed pointer later),
+ * we:
+ * 1. Release the dup'd ref first.
+ * 2. Call _edje_vg_color_lerp (writes borrowed stepped pointer).
+ * 3. Acquire a proper ref for the chosen class. */
+static void
+_color_lerp_with_ref(Edje_Vg_Color_Binding *out_cb,
+ const Edje_Vg_Color_Binding *a_cb,
+ const Edje_Vg_Color_Binding *b_cb,
+ double pos)
+{
+ /* Release the dup-owned ref before the lerp overwrites the pointer. */
+ eina_stringshare_del(out_cb->color_class);
+ out_cb->color_class = NULL;
+
+ _edje_vg_color_lerp(out_cb, a_cb, b_cb, pos);
+
+ /* _edje_vg_color_lerp left a borrowed stepped pointer; acquire a ref. */
+ if (out_cb->color_class)
+ out_cb->color_class = eina_stringshare_ref(out_cb->color_class);
+}
+
+static void
+_node_lerp_recursive(Edje_Vg_Node *out_node,
+ const Edje_Vg_Tree *a, const Edje_Vg_Tree *b,
+ double pos)
+{
+ if (!out_node) return;
+
+ if (out_node->name)
+ {
+ Edje_Vg_Node *a_node =
+ _edje_vg_tree_node_by_name(a, out_node->name);
+ Edje_Vg_Node *b_node =
+ _edje_vg_tree_node_by_name(b, out_node->name);
+
+ if (a_node && b_node &&
+ a_node->type == b_node->type &&
+ a_node->type == out_node->type)
+ {
+ /* Both sides found with matching types — lerp into out_node.
+ *
+ * TODO(Phase 5.x): when a_node exists but b_node does not (a-only
+ * node, present in A but missing from B's tree structure), spec
+ * §5.1 says fade via the node's color alpha if the parent
+ * container exists in both trees; otherwise step. v1 omits this
+ * since out is structurally B and a-only nodes simply aren't
+ * present — they effectively step out at pos>0. Implementing
+ * alpha fade requires either pre-merging the structure (so a-only
+ * nodes appear in out and fade out) or a post-process pass that
+ * synthesises faded versions of a's missing nodes into out. */
+
+ /* Common: color (with stringshare fix-up) */
+ _color_lerp_with_ref(&out_node->color,
+ &a_node->color, &b_node->color, pos);
+
+ /* Common: transform (no heap allocations inside) */
+ _edje_vg_xform_lerp(&out_node->xform,
+ &a_node->xform, &b_node->xform, pos);
+
+ /* Common: visible — step at pos < 1.0 uses a's value */
+ out_node->visible = (pos < 1.0) ? a_node->visible
+ : b_node->visible;
+
+ switch (out_node->type)
+ {
+ case EDJE_VG_NODE_SHAPE:
+ /* fill color */
+ _color_lerp_with_ref(&out_node->shape.fill,
+ &a_node->shape.fill,
+ &b_node->shape.fill, pos);
+ /* stroke color */
+ _color_lerp_with_ref(&out_node->shape.stroke_color,
+ &a_node->shape.stroke_color,
+ &b_node->shape.stroke_color, pos);
+ /* stroke width — scalar lerp */
+ out_node->shape.stroke_width =
+ (1.0 - pos) * a_node->shape.stroke_width +
+ pos * b_node->shape.stroke_width;
+
+ /* Dash: lerp if counts match; else step (out already has B's) */
+ if (a_node->shape.stroke_dash_count ==
+ b_node->shape.stroke_dash_count &&
+ a_node->shape.stroke_dash_count > 0 &&
+ a_node->shape.stroke_dash &&
+ b_node->shape.stroke_dash)
+ {
+ unsigned int dc = a_node->shape.stroke_dash_count;
+ double *new_dash = malloc(dc * sizeof(double));
+ if (new_dash)
+ {
+ free(out_node->shape.stroke_dash);
+ out_node->shape.stroke_dash = new_dash;
+ out_node->shape.stroke_dash_count = dc;
+ _edje_vg_dash_lerp(new_dash, dc,
+ a_node->shape.stroke_dash, dc,
+ b_node->shape.stroke_dash, dc,
+ pos);
+ }
+ /* On OOM: leave out's dash as B's (already correct). */
+ }
+
+ /* Cap, join, fill_rule: step — out already holds B's values */
+
+ /* Path: NOT lerped here — efl_gfx_path_interpolate at
+ materialize-time (Task 5.3). out already holds B's path
+ string from the dup; this is the fallback step value. */
+ break;
+
+ case EDJE_VG_NODE_GRADIENT_LINEAR:
+ case EDJE_VG_NODE_GRADIENT_RADIAL:
+ /* Geometry: componentwise lerp */
+ out_node->gradient.x0 =
+ (1.0 - pos) * a_node->gradient.x0 +
+ pos * b_node->gradient.x0;
+ out_node->gradient.y0 =
+ (1.0 - pos) * a_node->gradient.y0 +
+ pos * b_node->gradient.y0;
+ out_node->gradient.x1 =
+ (1.0 - pos) * a_node->gradient.x1 +
+ pos * b_node->gradient.x1;
+ out_node->gradient.y1 =
+ (1.0 - pos) * a_node->gradient.y1 +
+ pos * b_node->gradient.y1;
+ out_node->gradient.radius =
+ (1.0 - pos) * a_node->gradient.radius +
+ pos * b_node->gradient.radius;
+
+ /* Stops: lerp if counts match; out already holds B's stops */
+ if (a_node->gradient.stops_count ==
+ b_node->gradient.stops_count &&
+ a_node->gradient.stops_count > 0 &&
+ a_node->gradient.stops && b_node->gradient.stops &&
+ out_node->gradient.stops)
+ {
+ unsigned int sc = a_node->gradient.stops_count;
+ unsigned int i;
+ /* Release dup'd refs before _edje_vg_color_lerp
+ overwrites the color_class pointers; otherwise
+ those refs leak and the borrowed pointers written
+ by the lerp would be double-del'd on free. */
+ for (i = 0; i < sc; i++)
+ {
+ eina_stringshare_del(
+ out_node->gradient.stops[i].color.color_class);
+ out_node->gradient.stops[i].color.color_class =
+ NULL;
+ }
+ _edje_vg_stops_lerp(out_node->gradient.stops, sc,
+ a_node->gradient.stops, sc,
+ b_node->gradient.stops, sc,
+ pos);
+ /* _edje_vg_color_lerp writes a borrowed stepped
+ pointer into color_class. Acquire a proper ref
+ for each stop so _edje_vg_node_free can del it. */
+ for (i = 0; i < sc; i++)
+ {
+ const char *cc =
+ out_node->gradient.stops[i].color.color_class;
+ if (cc)
+ out_node->gradient.stops[i].color.color_class =
+ eina_stringshare_ref(cc);
+ }
+ }
+ /* Spread: step — out holds B's value */
+ break;
+
+ case EDJE_VG_NODE_CONTAINER:
+ /* No leaf properties beyond common; recurse below. */
+ break;
+
+ default:
+ break;
+ }
+ }
+ /* else: type/parent-path mismatch or missing node — step (B's values). */
+ }
+
+ /* Recurse into containers regardless of whether we lerped this level. */
+ if (out_node->type == EDJE_VG_NODE_CONTAINER)
+ {
+ Eina_List *l;
+ Edje_Vg_Node *c;
+ EINA_LIST_FOREACH(out_node->container.children, l, c)
+ _node_lerp_recursive(c, a, b, pos);
+ }
+}
+
+EAPI Edje_Vg_Tree *
+_edje_vg_tree_lerp(const Edje_Vg_Tree *a, const Edje_Vg_Tree *b, double pos)
+{
+ Edje_Vg_Tree *out;
+
+ if (!a || !b) return NULL;
+
+ /* Output structure: dup B (later side dominates structure and values). */
+ out = _edje_vg_tree_dup(b);
+ if (!out) return NULL;
+
+ /* Lerp the viewbox. */
+ _edje_vg_viewbox_lerp(&out->vbx, &out->vby, &out->vbw, &out->vbh,
+ a->vbx, a->vby, a->vbw, a->vbh,
+ b->vbx, b->vby, b->vbw, b->vbh,
+ pos);
+
+ /* Walk out's hierarchy, reading from a and b, writing into out. */
+ _node_lerp_recursive(out->root, a, b, pos);
+
+ return out;
+}
+
/* =========================================================================
* Phase 3.1 — Materializer: Edje_Vg_Tree → Efl_VG object tree
*
diff --git a/src/lib/edje/edje_vg_tree.h b/src/lib/edje/edje_vg_tree.h
index f8d799281b..5db65173ba 100644
--- a/src/lib/edje/edje_vg_tree.h
+++ b/src/lib/edje/edje_vg_tree.h
@@ -137,6 +137,21 @@ EAPI void _edje_vg_viewbox_lerp(double *out_x, double *out_y,
double bx, double by, double bw, double bh,
double pos);
+/* --- Phase 5.2: Name-aligned tree lerp ------------------------------------ */
+
+/* Build a third tree by walking A and B in parallel. The output's structure
+ * mirrors B (post-pos description). For each named node found in BOTH a and b
+ * with matching type, lerp non-path properties (path is handled at
+ * materialize-time via efl_gfx_path_interpolate). For nodes only in one side,
+ * step to b's value (the structural copy already provides this). For
+ * type/parent-path mismatches, step. The viewbox is componentwise lerp'd.
+ *
+ * Returns a freshly-allocated tree owned by the caller; free with
+ * _edje_vg_tree_free(). Returns NULL on OOM or invalid input. */
+EAPI Edje_Vg_Tree *_edje_vg_tree_lerp(const Edje_Vg_Tree *a,
+ const Edje_Vg_Tree *b,
+ double pos);
+
/* --- Eet descriptor accessor -------------------------------------------- */
/* Returns the static Eet_Data_Descriptor for Edje_Vg_Tree.
diff --git a/src/tests/edje/edje_test_vector_states.c b/src/tests/edje/edje_test_vector_states.c
index 670f5c1838..0dac610f67 100644
--- a/src/tests/edje/edje_test_vector_states.c
+++ b/src/tests/edje/edje_test_vector_states.c
@@ -111,6 +111,11 @@ void _edje_vg_viewbox_lerp(double *out_x, double *out_y,
double bx, double by, double bw, double bh,
double pos);
+/* Phase 5.2 tree-lerp helper */
+Edje_Vg_Tree *_edje_vg_tree_lerp(const Edje_Vg_Tree *a,
+ const Edje_Vg_Tree *b,
+ double pos);
+
/* Partial view of Edje_Vg_Override: all fields, inline payload shape sub-struct.
* We replicate only the fields needed by the three Task 4.1 tests.
* The payload is a full Edje_Vg_Node embedded (not a pointer), so we mirror
@@ -151,7 +156,40 @@ typedef struct {
/* Replicate the EDJE_VG_OVR_* bitmask constants so the test file does not
* need to include edje_private.h. Values must stay in sync with the header. */
-#define _TEST_VG_OVR_VISIBLE (1ULL << 0)
+#define _TEST_VG_OVR_VISIBLE (1ULL << 0)
+#define _TEST_VG_OVR_COLOR_RAW (1ULL << 1)
+
+/*
+ * Extended override struct for tests that need to set the node-level color
+ * (Edje_Vg_OVR_COLOR_RAW). The real Edje_Vg_Override.payload is a full
+ * Edje_Vg_Node embedded inline; we expose the layout up through the color
+ * field so we can write RGBA values into it.
+ *
+ * Edje_Vg_Node field layout (from edje_private.h + alignment verification):
+ * const char *name; // +0 (8 bytes)
+ * Edje_Vg_Node_Type type; // +8 (4 bytes, int-sized enum)
+ * unsigned char visible; // +12 (1 byte)
+ * (3 bytes padding)
+ * Edje_Vg_Color_Binding color; // +16 (16 bytes: 4 uchars + ptr)
+ * (opaque tail with xform, shape, container, gradient...)
+ *
+ * Edje_Vg_Color_Binding layout:
+ * unsigned char r, g, b, a; // +0..+3
+ * const char *color_class; // +8 (with alignment padding after a)
+ */
+typedef struct {
+ const char *target_name;
+ Edje_Vg_Node_Type expected_type;
+ uint64_t field_mask;
+ struct {
+ const char *name; /* ptr, 8 bytes */
+ Edje_Vg_Node_Type type; /* int-enum, 4 bytes at +8 */
+ unsigned char visible; /* uchar at +12 */
+ unsigned char _pad[3]; /* explicit pad to reach +16 */
+ Edje_Vg_Color_Binding color; /* color binding at +16 */
+ /* opaque tail — zeroed by memset in tests */
+ } payload;
+} _Test_Edje_Vg_Override_Color;
/*
* Partial-view forward declarations for the Task 3.0 round-trip test.
@@ -1122,6 +1160,115 @@ EFL_START_TEST(edje_vg_viewbox_lerp_componentwise)
}
EFL_END_TEST
+/* =========================================================================
+ * Phase 5.2 — Tree lerp unit test
+ * ========================================================================= */
+
+/*
+ * edje_vg_tree_lerp_basic (Task 5.2)
+ *
+ * Verifies that _edje_vg_tree_lerp produces a third tree whose named nodes
+ * carry values that are correctly lerp'd from A and B.
+ *
+ * Setup:
+ * A = _edje_vg_tree_new_for_test() → "body" shape, node color white (255,255,255,255)
+ * B = dup(A), then override "body" node color to red (255, 0, 0, 255)
+ *
+ * At pos=0.5 the output "body" node color must be the midpoint:
+ * r = 255 (unchanged), g = 127, b = 127, a = 255.
+ *
+ * NOTE: _edje_vg_tree_new_for_test() sets the "body" node's fill and stroke
+ * to specific values (fill rgb 64,128,32; stroke 0,0,0), but the NODE-LEVEL
+ * color (color multiplicative) is NOT explicitly set — so it defaults to
+ * (255,255,255,255) from _edje_vg_node_new via _node_new_shape which uses
+ * calloc + explicit field init. Checking _node_new_shape shows it does NOT
+ * set n->color — calloc zeros it, but then _edje_vg_node_new does set
+ * color.r/g/b/a = 255. However _node_new_shape uses a private helper that
+ * doesn't call _edje_vg_node_new — we verify what the initial value is.
+ *
+ * Actually _node_new_shape uses calloc, which zeros color to (0,0,0,0).
+ * The EDJE_VG_OVR_COLOR_RAW override writes (255,0,0,255) into B's body.
+ * So the lerp at pos=0.5: (0,0,0,255)→(255,0,0,255) gives r=127, g=0, b=0,
+ * a=255. Wait — we need to check the actual initial color from the fixture.
+ *
+ * From _node_new_shape in edje_vg_tree.c: it uses calloc, so color starts at
+ * (0,0,0,0), and only shape.fill and shape.stroke_color are set explicitly.
+ * The node-level n->color is NOT set by _node_new_shape (only _edje_vg_node_new
+ * sets it to 255,255,255,255). So A's body.color = (0,0,0,0), B's after
+ * override = (255,0,0,255). At pos=0.5: r=127, g=0, b=0, a=127.
+ *
+ * To simplify, we set A's body.color explicitly via an override too, making
+ * A's color = (255,255,255,255) and B's color = (255,0,0,255).
+ * Mid-lerp (pos=0.5): r=255, g=127, b=127, a=255.
+ *
+ * Partial view for reading the node-level color field:
+ * name (ptr), type (int), visible (uchar), pad(3), color (16 bytes)
+ */
+EFL_START_TEST(edje_vg_tree_lerp_basic)
+{
+ /* Partial view extending _NodeHead to include the color binding. */
+ typedef struct {
+ const char *name;
+ int type;
+ unsigned char visible;
+ unsigned char _pad[3];
+ Edje_Vg_Color_Binding color;
+ } _NodeWithColor;
+
+ Edje_Vg_Tree *a = _edje_vg_tree_new_for_test();
+ fail_if(!a);
+
+ /* Set A's "body" node-level color to white (255,255,255,255). */
+ _Test_Edje_Vg_Override_Color ovr_a;
+ memset(&ovr_a, 0, sizeof(ovr_a));
+ ovr_a.target_name = "body";
+ ovr_a.expected_type = EDJE_VG_NODE_SHAPE;
+ ovr_a.field_mask = _TEST_VG_OVR_COLOR_RAW;
+ ovr_a.payload.color.r = 255;
+ ovr_a.payload.color.g = 255;
+ ovr_a.payload.color.b = 255;
+ ovr_a.payload.color.a = 255;
+ _edje_vg_tree_apply_override(a, &ovr_a);
+
+ /* B is a dup of A (body color already white), then set body to red. */
+ Edje_Vg_Tree *b = _edje_vg_tree_dup(a);
+ fail_if(!b);
+
+ _Test_Edje_Vg_Override_Color ovr_b;
+ memset(&ovr_b, 0, sizeof(ovr_b));
+ ovr_b.target_name = "body";
+ ovr_b.expected_type = EDJE_VG_NODE_SHAPE;
+ ovr_b.field_mask = _TEST_VG_OVR_COLOR_RAW;
+ ovr_b.payload.color.r = 255;
+ ovr_b.payload.color.g = 0;
+ ovr_b.payload.color.b = 0;
+ ovr_b.payload.color.a = 255;
+ _edje_vg_tree_apply_override(b, &ovr_b);
+
+ /* Lerp at pos=0.5: body color must be midpoint of (255,255,255,255)
+ and (255,0,0,255): r=255, g=127, b=127, a=255. */
+ Edje_Vg_Tree *out = _edje_vg_tree_lerp(a, b, 0.5);
+ fail_if(!out);
+
+ Edje_Vg_Node *out_body = _edje_vg_tree_node_by_name(out, "body");
+ fail_if(!out_body);
+
+ _NodeWithColor *nc = (_NodeWithColor *)(void *)out_body;
+ ck_assert_msg(nc->color.r == 255,
+ "lerp body color.r expected 255, got %d", nc->color.r);
+ ck_assert_msg(nc->color.g == 127 || nc->color.g == 128,
+ "lerp body color.g expected ~127, got %d", nc->color.g);
+ ck_assert_msg(nc->color.b == 127 || nc->color.b == 128,
+ "lerp body color.b expected ~127, got %d", nc->color.b);
+ ck_assert_msg(nc->color.a == 255,
+ "lerp body color.a expected 255, got %d", nc->color.a);
+
+ _edje_vg_tree_free(a);
+ _edje_vg_tree_free(b);
+ _edje_vg_tree_free(out);
+}
+EFL_END_TEST
+
void
edje_test_vector_states(TCase *tc)
{
@@ -1145,4 +1292,6 @@ edje_test_vector_states(TCase *tc)
tcase_add_test(tc, edje_vg_stops_lerp_count_match);
tcase_add_test(tc, edje_vg_dash_lerp_match);
tcase_add_test(tc, edje_vg_viewbox_lerp_componentwise);
+ /* Phase 5.2 tree lerp */
+ tcase_add_test(tc, edje_vg_tree_lerp_basic);
}
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.