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.

Reply via email to