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 b660205cbc22a4efbb7504565d7dcadb2a5e6aee
Author: [email protected] <[email protected]>
AuthorDate: Wed Apr 29 10:10:11 2026 -0600
edje: add per-property lerp helpers (Task 5.1 Phase 5)
Implement five componentwise lerp functions to serve as substrate for
Phase 5's name-aligned tree walker (_edje_vg_tree_lerp). Each helper
handles one property family from the vector state animation spec §5.2:
- _edje_vg_color_lerp: RGBA interpolation with rounding; color_class
field is stepped (borrowed pointer) since binding-name doesn't
participate in lerp, only the resolved color does. This becomes
relevant in Phase 6 when materializer re-applies bindings.
- _edje_vg_xform_lerp: Handles both decomposed (tx, ty, sx, sy, angle)
and matrix-form (3x3 row-major) representations. When either side is
matrix, both are normalized to matrix space via T·R·S composition
and lerped element-wise. Otherwise decomposed lerp uses shortest-arc
rotation (delta normalized to [-180, 180]) to prevent wrap-around
through 180°. Uses no premultiplication (matrix form inherently
non-premul due to affine structure).
- _edje_vg_stops_lerp / _edje_vg_dash_lerp: Return false on count
mismatch, leaving step/hold semantics to the caller. Per-element
lerp on match.
- _edje_vg_viewbox_lerp: Componentwise on 4 doubles with NULL guards.
These helpers currently have no consumers; Task 5.2 will call them
from _edje_vg_tree_lerp to build the interpolated property tree.
Includes 8 unit tests covering endpoints (pos=0/1), midpoints, shortest-
arc edge case, count-mismatch paths, and viewbox composition.
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
---
src/lib/edje/edje_vg_tree.c | 130 ++++++++++++++++
src/lib/edje/edje_vg_tree.h | 40 +++++
src/tests/edje/edje_test_vector_states.c | 253 +++++++++++++++++++++++++++++++
3 files changed, 423 insertions(+)
diff --git a/src/lib/edje/edje_vg_tree.c b/src/lib/edje/edje_vg_tree.c
index 9ee8850d57..0b1fc738a1 100644
--- a/src/lib/edje/edje_vg_tree.c
+++ b/src/lib/edje/edje_vg_tree.c
@@ -967,6 +967,136 @@ _edje_vg_override_free(Edje_Vg_Override *ovr)
free(ovr);
}
+/* =========================================================================
+ * Phase 5.1 — Per-property lerp helpers
+ * ========================================================================= */
+
+/* Helper: materialise a decomposed transform to a row-major 3×3 matrix.
+ * Composition order: T · R · S.
+ *
+ * [ sx*c -sy*s tx ]
+ * [ sx*s sy*c ty ]
+ * [ 0 0 1 ]
+ *
+ * where c = cos(angle_rad), s = sin(angle_rad). */
+static void
+_decomposed_to_matrix(const Edje_Vg_Transform *t, double m[9])
+{
+ double rad = t->angle * M_PI / 180.0;
+ double c = cos(rad), s = sin(rad);
+ m[0] = t->sx * c; m[1] = -t->sy * s; m[2] = t->tx;
+ m[3] = t->sx * s; m[4] = t->sy * c; m[5] = t->ty;
+ m[6] = 0.0; m[7] = 0.0; m[8] = 1.0;
+}
+
+EAPI void
+_edje_vg_color_lerp(Edje_Vg_Color_Binding *out,
+ const Edje_Vg_Color_Binding *a,
+ const Edje_Vg_Color_Binding *b,
+ double pos)
+{
+ double q;
+ if (!out || !a || !b) return;
+ q = 1.0 - pos;
+ out->r = (unsigned char)(q * a->r + pos * b->r + 0.5);
+ out->g = (unsigned char)(q * a->g + pos * b->g + 0.5);
+ out->b = (unsigned char)(q * a->b + pos * b->b + 0.5);
+ out->a = (unsigned char)(q * a->a + pos * b->a + 0.5);
+ /* color_class is stepped: use a's name for the first half, b's after. */
+ out->color_class = (pos < 0.5) ? a->color_class : b->color_class;
+}
+
+EAPI void
+_edje_vg_xform_lerp(Edje_Vg_Transform *out,
+ const Edje_Vg_Transform *a,
+ const Edje_Vg_Transform *b,
+ double pos)
+{
+ double q;
+ if (!out || !a || !b) return;
+ q = 1.0 - pos;
+
+ if (a->has_matrix || b->has_matrix)
+ {
+ /* At least one side is matrix form — lerp element-wise in matrix space.
+ * Materialise the decomposed side using T·R·S composition. */
+ const double *am = a->m;
+ const double *bm = b->m;
+ double a_mat[9], b_mat[9];
+ int i;
+ if (!a->has_matrix) { _decomposed_to_matrix(a, a_mat); am = a_mat; }
+ if (!b->has_matrix) { _decomposed_to_matrix(b, b_mat); bm = b_mat; }
+ out->has_matrix = 1;
+ for (i = 0; i < 9; i++)
+ out->m[i] = q * am[i] + pos * bm[i];
+ }
+ else
+ {
+ double delta;
+ out->has_matrix = 0;
+ out->tx = q * a->tx + pos * b->tx;
+ out->ty = q * a->ty + pos * b->ty;
+ out->sx = q * a->sx + pos * b->sx;
+ out->sy = q * a->sy + pos * b->sy;
+ /* Shortest signed arc: normalise delta to [-180, 180]. */
+ delta = b->angle - a->angle;
+ while (delta > 180.0) delta -= 360.0;
+ while (delta < -180.0) delta += 360.0;
+ out->angle = a->angle + pos * delta;
+ }
+}
+
+EAPI Eina_Bool
+_edje_vg_stops_lerp(Edje_Vg_Stop *out, unsigned int out_count,
+ const Edje_Vg_Stop *a, unsigned int a_count,
+ const Edje_Vg_Stop *b, unsigned int b_count,
+ double pos)
+{
+ double q;
+ unsigned int i;
+ if (a_count != b_count) return EINA_FALSE;
+ if (out_count != a_count) return EINA_FALSE;
+ if (!out || !a || !b) return EINA_FALSE;
+ q = 1.0 - pos;
+ for (i = 0; i < a_count; i++)
+ {
+ out[i].offset = q * a[i].offset + pos * b[i].offset;
+ _edje_vg_color_lerp(&out[i].color, &a[i].color, &b[i].color, pos);
+ }
+ return EINA_TRUE;
+}
+
+EAPI Eina_Bool
+_edje_vg_dash_lerp(double *out, unsigned int out_count,
+ const double *a, unsigned int a_count,
+ const double *b, unsigned int b_count,
+ double pos)
+{
+ double q;
+ unsigned int i;
+ if (a_count != b_count) return EINA_FALSE;
+ if (out_count != a_count) return EINA_FALSE;
+ if (!out || !a || !b) return EINA_FALSE;
+ q = 1.0 - pos;
+ for (i = 0; i < a_count; i++)
+ out[i] = q * a[i] + pos * b[i];
+ return EINA_TRUE;
+}
+
+EAPI void
+_edje_vg_viewbox_lerp(double *out_x, double *out_y,
+ double *out_w, double *out_h,
+ double ax, double ay, double aw, double ah,
+ double bx, double by, double bw, double bh,
+ double pos)
+{
+ double q = 1.0 - pos;
+ if (out_x) *out_x = q * ax + pos * bx;
+ if (out_y) *out_y = q * ay + pos * by;
+ if (out_w) *out_w = q * aw + pos * bw;
+ if (out_h) *out_h = q * ah + pos * bh;
+}
+
/* =========================================================================
* 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 3e30a199c3..f8d799281b 100644
--- a/src/lib/edje/edje_vg_tree.h
+++ b/src/lib/edje/edje_vg_tree.h
@@ -97,6 +97,46 @@ EAPI Edje_Vg_Override *_edje_vg_override_dup(const Edje_Vg_Override *src);
* Safe to call with NULL. */
EAPI void _edje_vg_override_free(Edje_Vg_Override *ovr);
+/* --- Phase 5.1: Per-property lerp helpers --------------------------------- */
+
+/* Componentwise RGBA lerp on Edje_Vg_Color_Binding (raw RGBA, no premul;
+ * class resolution arrives in Phase 6 BEFORE the lerp). color_class field
+ * is stepped: a's at pos < 0.5, else b's. Strings are not ref-counted in
+ * the output (caller copies if persisting). */
+EAPI void _edje_vg_color_lerp(Edje_Vg_Color_Binding *out,
+ const Edje_Vg_Color_Binding *a,
+ const Edje_Vg_Color_Binding *b,
+ double pos);
+
+/* Decomposed transform: linear lerp on tx/ty/sx/sy; rotate via shortest
+ * signed arc. If either side has_matrix, lerp in matrix space (materialise
+ * the decomposed side via T·R·S composition first). */
+EAPI void _edje_vg_xform_lerp(Edje_Vg_Transform *out,
+ const Edje_Vg_Transform *a,
+ const Edje_Vg_Transform *b,
+ double pos);
+
+/* Per-stop lerp if counts match. Returns EINA_TRUE if lerped, EINA_FALSE if
+ * counts differ (caller should step to a or b based on pos). */
+EAPI Eina_Bool _edje_vg_stops_lerp(Edje_Vg_Stop *out, unsigned int out_count,
+ const Edje_Vg_Stop *a, unsigned int a_count,
+ const Edje_Vg_Stop *b, unsigned int b_count,
+ double pos);
+
+/* Dash pattern lerp if pair counts match. Returns EINA_TRUE if lerped,
+ * EINA_FALSE if counts differ. */
+EAPI Eina_Bool _edje_vg_dash_lerp(double *out, unsigned int out_count,
+ const double *a, unsigned int a_count,
+ const double *b, unsigned int b_count,
+ double pos);
+
+/* Componentwise viewbox lerp. Each output pointer may be NULL (skipped). */
+EAPI void _edje_vg_viewbox_lerp(double *out_x, double *out_y,
+ double *out_w, double *out_h,
+ double ax, double ay, double aw, double ah,
+ double bx, double by, double bw, double bh,
+ 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 a7f3f5c913..670f5c1838 100644
--- a/src/tests/edje/edje_test_vector_states.c
+++ b/src/tests/edje/edje_test_vector_states.c
@@ -2,6 +2,7 @@
# include <config.h>
#endif
+#include <math.h>
#include <Eina.h>
#include <Eet.h>
#include <Evas.h>
@@ -60,6 +61,56 @@ Edje_Vg_Tree *_edje_vg_tree_dup(const Edje_Vg_Tree *t);
void _edje_vg_tree_apply_override(Edje_Vg_Tree *working,
const void *ovr);
+/* Phase 5.1 lerp helpers — forward declarations.
+ *
+ * We redeclare the value-type structs used by these helpers because the test
+ * file cannot include edje_private.h. The layouts below match edje_private.h
+ * exactly:
+ *
+ * Edje_Vg_Color_Binding: unsigned char r,g,b,a; const char *color_class;
+ * Edje_Vg_Stop: double offset; Edje_Vg_Color_Binding color;
+ * Edje_Vg_Transform: double tx,ty,angle,sx,sy,m[9]; unsigned char has_matrix;
+ */
+typedef struct {
+ unsigned char r, g, b, a;
+ const char *color_class;
+} Edje_Vg_Color_Binding;
+
+typedef struct {
+ double offset;
+ Edje_Vg_Color_Binding color;
+} Edje_Vg_Stop;
+
+typedef struct {
+ double tx, ty;
+ double angle;
+ double sx, sy;
+ double m[9];
+ unsigned char has_matrix;
+} Edje_Vg_Transform;
+
+void _edje_vg_color_lerp(Edje_Vg_Color_Binding *out,
+ const Edje_Vg_Color_Binding *a,
+ const Edje_Vg_Color_Binding *b,
+ double pos);
+void _edje_vg_xform_lerp(Edje_Vg_Transform *out,
+ const Edje_Vg_Transform *a,
+ const Edje_Vg_Transform *b,
+ double pos);
+Eina_Bool _edje_vg_stops_lerp(Edje_Vg_Stop *out, unsigned int out_count,
+ const Edje_Vg_Stop *a, unsigned int a_count,
+ const Edje_Vg_Stop *b, unsigned int b_count,
+ double pos);
+Eina_Bool _edje_vg_dash_lerp(double *out, unsigned int out_count,
+ const double *a, unsigned int a_count,
+ const double *b, unsigned int b_count,
+ double pos);
+void _edje_vg_viewbox_lerp(double *out_x, double *out_y,
+ double *out_w, double *out_h,
+ double ax, double ay, double aw, double ah,
+ double bx, double by, double bw, double bh,
+ 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
@@ -878,6 +929,199 @@ EFL_START_TEST(edje_vg_render_box_a_hover)
}
EFL_END_TEST
+/* =========================================================================
+ * Phase 5.1 — Lerp helper unit tests
+ * ========================================================================= */
+
+/*
+ * edje_vg_color_lerp_basic
+ *
+ * Verify that mid-lerp (pos=0.5) produces the correct rounded average RGBA.
+ */
+EFL_START_TEST(edje_vg_color_lerp_basic)
+{
+ Edje_Vg_Color_Binding a = { 100, 200, 50, 255, NULL };
+ Edje_Vg_Color_Binding b = { 200, 100, 150, 255, NULL };
+ Edje_Vg_Color_Binding out;
+
+ memset(&out, 0, sizeof(out));
+ _edje_vg_color_lerp(&out, &a, &b, 0.5);
+ ck_assert_int_eq(out.r, 150);
+ ck_assert_int_eq(out.g, 150);
+ ck_assert_int_eq(out.b, 100);
+ ck_assert_int_eq(out.a, 255);
+}
+EFL_END_TEST
+
+/*
+ * edje_vg_color_lerp_endpoints
+ *
+ * At pos=0 the output must equal a; at pos=1 it must equal b.
+ */
+EFL_START_TEST(edje_vg_color_lerp_endpoints)
+{
+ Edje_Vg_Color_Binding a = { 0, 0, 0, 255, NULL };
+ Edje_Vg_Color_Binding b = { 255, 255, 255, 255, NULL };
+ Edje_Vg_Color_Binding out;
+
+ memset(&out, 0, sizeof(out));
+ _edje_vg_color_lerp(&out, &a, &b, 0.0);
+ fail_if(out.r != 0 || out.g != 0 || out.b != 0);
+
+ _edje_vg_color_lerp(&out, &a, &b, 1.0);
+ fail_if(out.r != 255 || out.g != 255 || out.b != 255);
+}
+EFL_END_TEST
+
+/*
+ * edje_vg_xform_lerp_decomposed
+ *
+ * Lerp two decomposed transforms; verify tx/ty/sx/sy/angle mid-values.
+ */
+EFL_START_TEST(edje_vg_xform_lerp_decomposed)
+{
+ /* identity */
+ Edje_Vg_Transform a;
+ memset(&a, 0, sizeof(a));
+ a.sx = 1.0; a.sy = 1.0;
+ a.tx = 0.0; a.ty = 0.0;
+ a.angle = 0.0;
+ a.has_matrix = 0;
+
+ /* rotated + scaled + translated */
+ Edje_Vg_Transform b;
+ memset(&b, 0, sizeof(b));
+ b.sx = 2.0; b.sy = 2.0;
+ b.tx = 100.0; b.ty = 200.0;
+ b.angle = 90.0;
+ b.has_matrix = 0;
+
+ Edje_Vg_Transform out;
+ memset(&out, 0, sizeof(out));
+ _edje_vg_xform_lerp(&out, &a, &b, 0.5);
+
+ fail_if(out.tx != 50.0 || out.ty != 100.0);
+ fail_if(out.sx != 1.5 || out.sy != 1.5);
+ fail_if(out.angle < 44.9 || out.angle > 45.1);
+ fail_if(out.has_matrix);
+}
+EFL_END_TEST
+
+/*
+ * edje_vg_xform_lerp_shortest_arc
+ *
+ * 350° → 10° must lerp through 0° (shortest arc = 20°), not through 180°.
+ * Mid-point result should be near 0° (or 360°, equivalent).
+ */
+EFL_START_TEST(edje_vg_xform_lerp_shortest_arc)
+{
+ Edje_Vg_Transform a;
+ memset(&a, 0, sizeof(a));
+ a.sx = 1.0; a.sy = 1.0;
+ a.angle = 350.0;
+
+ Edje_Vg_Transform b;
+ memset(&b, 0, sizeof(b));
+ b.sx = 1.0; b.sy = 1.0;
+ b.angle = 10.0;
+
+ Edje_Vg_Transform out;
+ memset(&out, 0, sizeof(out));
+ _edje_vg_xform_lerp(&out, &a, &b, 0.5);
+
+ /* Normalise to [0, 360) and verify we're near 0° (not near 180°). */
+ double angle_norm = fmod(out.angle + 720.0, 360.0);
+ fail_if(angle_norm > 1.0 && angle_norm < 359.0);
+}
+EFL_END_TEST
+
+/*
+ * edje_vg_stops_lerp_count_mismatch_steps
+ *
+ * Count mismatch (2 vs 3) must return EINA_FALSE (step semantics).
+ */
+EFL_START_TEST(edje_vg_stops_lerp_count_mismatch_steps)
+{
+ Edje_Vg_Stop a[2];
+ Edje_Vg_Stop b[3];
+ Edje_Vg_Stop out[2];
+ Eina_Bool lerped;
+
+ memset(a, 0, sizeof(a));
+ memset(b, 0, sizeof(b));
+ memset(out, 0, sizeof(out));
+
+ lerped = _edje_vg_stops_lerp(out, 2, a, 2, b, 3, 0.5);
+ fail_if(lerped);
+}
+EFL_END_TEST
+
+/*
+ * edje_vg_stops_lerp_count_match
+ *
+ * When counts match the function must lerp offsets and colors, returning true.
+ */
+EFL_START_TEST(edje_vg_stops_lerp_count_match)
+{
+ Edje_Vg_Stop a[2] = {
+ { 0.0, { 0, 0, 0, 255, NULL } },
+ { 1.0, { 255, 0, 0, 255, NULL } },
+ };
+ Edje_Vg_Stop b[2] = {
+ { 0.0, { 255, 255, 255, 255, NULL } },
+ { 0.5, { 0, 0, 255, 255, NULL } },
+ };
+ Edje_Vg_Stop out[2];
+ Eina_Bool lerped;
+
+ memset(out, 0, sizeof(out));
+ lerped = _edje_vg_stops_lerp(out, 2, a, 2, b, 2, 0.5);
+ fail_if(!lerped);
+
+ /* stop 0: offset 0→0 = 0; color (0,0,0,255)→(255,255,255,255) at 0.5.
+ * 0.5*255 = 127.5 rounds to 128 with +0.5 integer rounding. */
+ ck_assert_int_eq(out[0].color.r, 128);
+
+ /* stop 1: offset 1.0→0.5 at 0.5 = 0.75 */
+ fail_if(out[1].offset < 0.74 || out[1].offset > 0.76);
+}
+EFL_END_TEST
+
+/*
+ * edje_vg_dash_lerp_match
+ *
+ * Dash arrays of equal count must be lerped componentwise.
+ */
+EFL_START_TEST(edje_vg_dash_lerp_match)
+{
+ double a[4] = { 5.0, 3.0, 2.0, 2.0 };
+ double b[4] = { 10.0, 6.0, 4.0, 4.0 };
+ double out[4];
+ Eina_Bool lerped;
+
+ memset(out, 0, sizeof(out));
+ lerped = _edje_vg_dash_lerp(out, 4, a, 4, b, 4, 0.5);
+ fail_if(!lerped);
+ fail_if(out[0] != 7.5 || out[2] != 3.0);
+}
+EFL_END_TEST
+
+/*
+ * edje_vg_viewbox_lerp_componentwise
+ *
+ * All four viewbox components lerped independently.
+ */
+EFL_START_TEST(edje_vg_viewbox_lerp_componentwise)
+{
+ double ox, oy, ow, oh;
+ _edje_vg_viewbox_lerp(&ox, &oy, &ow, &oh,
+ 0, 0, 100, 100,
+ 50, 50, 200, 200,
+ 0.5);
+ fail_if(ox != 25.0 || oy != 25.0 || ow != 150.0 || oh != 150.0);
+}
+EFL_END_TEST
+
void
edje_test_vector_states(TCase *tc)
{
@@ -892,4 +1136,13 @@ edje_test_vector_states(TCase *tc)
tcase_add_test(tc, edje_vg_tree_apply_override_visible);
tcase_add_test(tc, edje_vg_tree_apply_override_unresolved_noop);
tcase_add_test(tc, edje_vg_render_box_a_hover);
+ /* Phase 5.1 lerp helpers */
+ tcase_add_test(tc, edje_vg_color_lerp_basic);
+ tcase_add_test(tc, edje_vg_color_lerp_endpoints);
+ tcase_add_test(tc, edje_vg_xform_lerp_decomposed);
+ tcase_add_test(tc, edje_vg_xform_lerp_shortest_arc);
+ tcase_add_test(tc, edje_vg_stops_lerp_count_mismatch_steps);
+ 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);
}
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.