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 41a7dcae4987d1784864a51f81ac95b5b55bd402
Author: [email protected] <[email protected]>
AuthorDate: Wed Apr 29 15:20:15 2026 -0600
edje: fix materializer's fill-vs-node-color ordering (Task 6.4)
Spec §3.2 defines node-level color as multiplicative: the effective fill is
(node × fill / 255) per channel, premultiplied by alpha. The Phase 3.1
materializer called _apply_color_binding twice on the same shape — first
for fill RGBA, then for node-level color — causing the second write to
clobber the first. Phase 4.6 and 5.4 tests worked around this by asserting
on stroke properties instead of fill.
Split _apply_common into _apply_common_no_color (name + visibility +
transform) and the unchanged _apply_common. Add _apply_shape_combined_color
helper that computes the multiplicative combine in 8-bit arithmetic,
premultiplies alpha, and makes a single efl_gfx_color_set call. Flat-fill
shapes now use the combined helper; gradient-fill shapes apply node-level
color directly to the gradient paint object (the gradient-rendered shape
itself, no flat fill RGBA to combine).
edje_vg_render_box_a_hover now asserts both fill color (default 200,0,0,255,
hover 200,200,0,255) and stroke width. Add new end-to-end test
edje_vg_render_box_classed_fill that proves color-class pipeline works:
class registered → resolved at cache rebuild → combined with node-level
color → correct fill RGBA readable. No effect on existing render output —
all pre-existing tests use default white (255,255,255,255) for node-level
color, so multiplicative combine yields the fill unchanged.
Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
---
src/lib/edje/edje_vg_tree.c | 57 +++++++++++++++--
src/tests/edje/edje_test_vector_states.c | 105 ++++++++++++++++++++++++++++++-
2 files changed, 155 insertions(+), 7 deletions(-)
diff --git a/src/lib/edje/edje_vg_tree.c b/src/lib/edje/edje_vg_tree.c
index 8f4eaf3064..ca941164cc 100644
--- a/src/lib/edje/edje_vg_tree.c
+++ b/src/lib/edje/edje_vg_tree.c
@@ -1409,19 +1409,57 @@ _apply_transform(Efl_VG *vg, const Edje_Vg_Transform *xf)
}
/* --------------------------------------------------------------------------
- * Common node properties: name, visibility, color, transform.
+ * Common node properties without color: name, visibility, transform.
+ * Used by the shape materializer which applies a combined color separately.
* -------------------------------------------------------------------------- */
static void
-_apply_common(Efl_VG *vg, const Edje_Vg_Node *n)
+_apply_common_no_color(Efl_VG *vg, const Edje_Vg_Node *n)
{
if (n->name)
efl_name_set(vg, n->name);
if (!n->visible)
efl_gfx_entity_visible_set(vg, EINA_FALSE);
- _apply_color_binding(vg, &n->color);
_apply_transform(vg, &n->xform);
}
+/* --------------------------------------------------------------------------
+ * Common node properties: name, visibility, color, transform.
+ * -------------------------------------------------------------------------- */
+static void
+_apply_common(Efl_VG *vg, const Edje_Vg_Node *n)
+{
+ _apply_common_no_color(vg, n);
+ _apply_color_binding(vg, &n->color);
+}
+
+/* --------------------------------------------------------------------------
+ * Apply the multiplicative combination of node-level color × fill color on a
+ * shape object. Spec §3.2: the node-level color is "multiplicative" — for
+ * shapes the effective fill is node × fill / 255 per channel.
+ *
+ * After Phase 6.2 both bindings carry already-resolved RGBA (no color_class
+ * refs remain), so this is a straightforward 8-bit multiply followed by the
+ * same premul step that _apply_color_binding uses.
+ * -------------------------------------------------------------------------- */
+static void
+_apply_shape_combined_color(Efl_Canvas_Vg_Shape *s,
+ const Edje_Vg_Color_Binding *node,
+ const Edje_Vg_Color_Binding *fill)
+{
+ int r = (node->r * fill->r) / 255;
+ int g = (node->g * fill->g) / 255;
+ int b = (node->b * fill->b) / 255;
+ int a = (node->a * fill->a) / 255;
+ /* Pre-multiply alpha, matching the existing _apply_color_binding contract. */
+ if (a < 255)
+ {
+ r = (r * a) / 255;
+ g = (g * a) / 255;
+ b = (b * a) / 255;
+ }
+ efl_gfx_color_set(s, r, g, b, a);
+}
+
/* --------------------------------------------------------------------------
* Pass 1 helper — collect all named gradient data-nodes into the hash.
* Instantiates the Efl_VG gradient objects (no parent) so that shapes can
@@ -1528,10 +1566,17 @@ _shape_to_efl_vg(const Edje_Vg_Node *n, Efl_VG *parent,
else
WRN("gradient_ref '%s' not found in tree — fill left unset",
n->shape.gradient_ref);
+ /* For gradient-filled shapes apply the node-level color directly
+ * (no flat fill binding to combine against). */
+ _apply_color_binding(s, &n->color);
}
else
{
- _apply_color_binding(s, &n->shape.fill);
+ /* Flat fill: combine node-level color × fill color per spec §3.2.
+ * Both are pre-resolved (Phase 6.2) so no class refs remain here.
+ * A single efl_gfx_color_set call replaces the old two-call pattern
+ * (fill first, then node-level overwrote it). */
+ _apply_shape_combined_color(s, &n->color, &n->shape.fill);
}
/* Stroke */
@@ -1563,8 +1608,8 @@ _shape_to_efl_vg(const Edje_Vg_Node *n, Efl_VG *parent,
/* Fill rule */
efl_gfx_shape_fill_rule_set(s, n->shape.fill_rule);
- /* Common: name, visibility, node-level color, transform */
- _apply_common(s, n);
+ /* Common: name, visibility, transform — color already handled above. */
+ _apply_common_no_color(s, n);
return s;
}
diff --git a/src/tests/edje/edje_test_vector_states.c b/src/tests/edje/edje_test_vector_states.c
index 6b058f8aad..23c51969af 100644
--- a/src/tests/edje/edje_test_vector_states.c
+++ b/src/tests/edje/edje_test_vector_states.c
@@ -916,6 +916,7 @@ EFL_START_TEST(edje_vg_render_box_a_hover)
Efl_Canvas_Vg_Node *vg_root;
Efl_Canvas_Vg_Node *body_node;
double sw;
+ int r, g, b, a;
/* --- Setup: buffer canvas, edje object, load group --- */
ee = ecore_evas_buffer_new(VG_RENDER_W, VG_RENDER_H);
@@ -956,13 +957,24 @@ EFL_START_TEST(edje_vg_render_box_a_hover)
ck_assert_msg(sw >= 0.5 && sw <= 1.5,
"Default state stroke width %.2f != expected ~1.0", sw);
+ /* Default fill: tile_a body fill.color = (200,0,0,255); node-level color is
+ * default white (255,255,255,255). Combined per spec §3.2:
+ * r = 200*255/255 = 200, g = 0, b = 0, a = 255.
+ * Task 6.4 fix: single efl_gfx_color_set call with combined value — no
+ * longer clobbered by the node-level color write. */
+ efl_gfx_color_get(body_node, &r, &g, &b, &a);
+ ck_assert_msg(r == 200 && g == 0 && b == 0 && a == 255,
+ "Default state fill (%d,%d,%d,%d) != expected (200,0,0,255)",
+ r, g, b, a);
+
/* --- Switch to hover state via signal --- */
edje_object_signal_emit(obj, "test,hover", "test");
edje_object_message_signal_process(obj);
/* Recalc drives _edje_vector_recalc_apply with the new chosen_desc
* (hover), which has overrides. The override path dups the base tree
- * and applies stroke.width: 4 before rematerializing. */
+ * and applies fill.color: (200,200,0,255) and stroke.width: 4 before
+ * rematerializing. */
edje_object_calc_force(obj);
ecore_evas_manual_render(ee);
@@ -987,6 +999,14 @@ EFL_START_TEST(edje_vg_render_box_a_hover)
"Hover state stroke width %.2f != expected ~4.0 (override not applied)",
sw);
+ /* The hover override also sets fill.color: (200,200,0,255). With the
+ * Task 6.4 fix the combined color is node white × fill = (200,200,0,255).
+ * Assert fill color is now readable correctly. */
+ efl_gfx_color_get(body_node, &r, &g, &b, &a);
+ ck_assert_msg(r == 200 && g == 200 && b == 0 && a == 255,
+ "Hover state fill (%d,%d,%d,%d) != expected (200,200,0,255)",
+ r, g, b, a);
+
/* --- Cleanup --- */
evas_object_del(obj);
ecore_evas_free(ee);
@@ -1768,6 +1788,87 @@ EFL_START_TEST(edje_vg_override_class_clear)
}
EFL_END_TEST
+/*
+ * edje_vg_render_box_classed_fill (Task 6.4)
+ *
+ * End-to-end test for fill color class resolution combined with the Task 6.4
+ * materializer fix.
+ *
+ * Fixture: ui/box_classed
+ * Uses tile_classed: body fill.color_class = "vg_accent" (80,140,255,255).
+ * Default node-level color is white (255,255,255,255).
+ *
+ * Expected fill RGBA after Task 6.2 resolve + Task 6.4 combine:
+ * resolved fill = vg_accent = (80,140,255,255) (class resolved to raw RGBA)
+ * node-level = white = (255,255,255,255)
+ * combined = (80*255/255, 140*255/255, 255*255/255, 255*255/255)
+ * = (80, 140, 255, 255)
+ * premul step: alpha = 255, so no change to rgb.
+ * Final efl_gfx_color_set value: (80, 140, 255, 255).
+ *
+ * Before Task 6.4: _apply_common clobbered the fill RGBA with the node-level
+ * white, making efl_gfx_color_get return (255,255,255,255) instead. This
+ * test would have failed against (80,140,255,255) before the fix.
+ */
+EFL_START_TEST(edje_vg_render_box_classed_fill)
+{
+ const char *edj = TESTS_BUILD_DIR "/data/test_vector_states.edj";
+ Ecore_Evas *ee;
+ Evas *evas;
+ Evas_Object *obj;
+ Evas_Object *vg_part;
+ Efl_Canvas_Vg_Node *vg_root;
+ Efl_Canvas_Vg_Node *body_node;
+ int r, g, b, a;
+
+ /* --- Setup: buffer canvas, edje object, load group --- */
+ ee = ecore_evas_buffer_new(VG_RENDER_W, VG_RENDER_H);
+ fail_if(!ee);
+ ecore_evas_show(ee);
+ ecore_evas_manual_render_set(ee, EINA_TRUE);
+
+ evas = ecore_evas_get(ee);
+ fail_if(!evas);
+
+ obj = edje_object_add(evas);
+ fail_if(!obj);
+
+ fail_unless(edje_object_file_set(obj, edj, "ui/box_classed"));
+
+ evas_object_resize(obj, VG_RENDER_W, VG_RENDER_H);
+ evas_object_move(obj, 0, 0);
+ evas_object_show(obj);
+
+ edje_object_calc_force(obj);
+ ecore_evas_manual_render(ee);
+
+ /* --- Fetch the materialized body shape --- */
+ vg_part = (Evas_Object *)edje_object_part_object_get(obj, "vg");
+ fail_if(!vg_part);
+
+ vg_root = efl_canvas_vg_object_root_node_get(vg_part);
+ fail_if(vg_root == NULL);
+ fail_if(!efl_isa(vg_root, EFL_CANVAS_VG_CONTAINER_CLASS));
+
+ body_node = efl_canvas_vg_container_child_get(vg_root, "body");
+ fail_if(body_node == NULL);
+ fail_if(!efl_isa(body_node, EFL_CANVAS_VG_SHAPE_CLASS));
+
+ /* Phase 6.2 resolved vg_accent → raw (80,140,255,255) before materializing.
+ * Task 6.4 combined with node-level white: (80,140,255,255).
+ * Alpha == 255 so premul is a no-op. Assert the fill color. */
+ efl_gfx_color_get(body_node, &r, &g, &b, &a);
+ ck_assert_msg(r == 80 && g == 140 && b == 255 && a == 255,
+ "Classed fill (%d,%d,%d,%d) != expected vg_accent (80,140,255,255) — "
+ "class resolve or fill+node combine broken",
+ r, g, b, a);
+
+ /* --- Cleanup --- */
+ evas_object_del(obj);
+ ecore_evas_free(ee);
+}
+EFL_END_TEST
+
void
edje_test_vector_states(TCase *tc)
{
@@ -1803,4 +1904,6 @@ edje_test_vector_states(TCase *tc)
tcase_add_test(tc, edje_vg_runtime_class_retint);
/* Phase 6.3 override-side empty-string class clear */
tcase_add_test(tc, edje_vg_override_class_clear);
+ /* Phase 6.4 fill × node-level combined color + class resolution end-to-end */
+ tcase_add_test(tc, edje_vg_render_box_classed_fill);
}
--
To stop receiving notification emails like this one, please contact
the administrator of this repository.