rendernode: Fix Cairo rendering of repeating gradients
authorBenjamin Otte <otte@redhat.com>
Thu, 7 Sep 2023 14:02:09 +0000 (16:02 +0200)
committerMatthias Clasen <mclasen@redhat.com>
Tue, 19 Sep 2023 19:23:24 +0000 (15:23 -0400)
Cairo and the GL renderer have a different idea of how to handle
transitioning of colors outside the defined range.

Consider these stops:
  black 50%, white 50%

What color is at 0%?

Cairo would transition between the last and first stop, ie it'd do a
white-to-black transition and end up at rgb(0.5,0.5,0.5) at 0%.
GL would behave as it would for non-repeating gradients and use black
for the range [0%..50%] and white for [50%..100%].
The web would rescale the range so the first stop would be at 0% and
the last stop would be at 100%, so this gradient would be illegal.

Considering that it's possible for code to transition between the
different behaviors by adding explicit stops at 0%/100%, I could choose
any method.
So I chose the simplest one, which is what the GL renderer does and
which treats repeating and non-repeating gradients the same.

Tests attached.

gsk/gskrendernodeimpl.c
testsuite/gsk/compare/repeating-linear-gradient-edge-colors.node [new file with mode: 0644]
testsuite/gsk/compare/repeating-linear-gradient-edge-colors.png [new file with mode: 0644]
testsuite/gsk/compare/repeating-radial-gradient-edge-colors.node [new file with mode: 0644]
testsuite/gsk/compare/repeating-radial-gradient-edge-colors.png [new file with mode: 0644]
testsuite/gsk/meson.build

index 2758dff2a6a99c09626a60faa40e59d838ca33d1..08bdc58e4711832bd7a513483bd528bc28816c93 100644 (file)
@@ -245,6 +245,13 @@ gsk_linear_gradient_node_draw (GskRenderNode *node,
   if (gsk_render_node_get_node_type (node) == GSK_REPEATING_LINEAR_GRADIENT_NODE)
     cairo_pattern_set_extend (pattern, CAIRO_EXTEND_REPEAT);
 
+  if (self->stops[0].offset > 0.0)
+    cairo_pattern_add_color_stop_rgba (pattern,
+                                       0.0,
+                                       self->stops[0].color.red,
+                                       self->stops[0].color.green,
+                                       self->stops[0].color.blue,
+                                       self->stops[0].color.alpha);
   for (i = 0; i < self->n_stops; i++)
     {
       cairo_pattern_add_color_stop_rgba (pattern,
@@ -254,6 +261,13 @@ gsk_linear_gradient_node_draw (GskRenderNode *node,
                                          self->stops[i].color.blue,
                                          self->stops[i].color.alpha);
     }
+  if (self->stops[self->n_stops-1].offset < 1.0)
+    cairo_pattern_add_color_stop_rgba (pattern,
+                                       1.0,
+                                       self->stops[self->n_stops-1].color.red,
+                                       self->stops[self->n_stops-1].color.green,
+                                       self->stops[self->n_stops-1].color.blue,
+                                       self->stops[self->n_stops-1].color.alpha);
 
   cairo_set_source (cr, pattern);
   cairo_pattern_destroy (pattern);
@@ -559,13 +573,29 @@ gsk_radial_gradient_node_draw (GskRenderNode *node,
   else
     cairo_pattern_set_extend (pattern, CAIRO_EXTEND_PAD);
 
+  if (self->stops[0].offset > 0.0)
+    cairo_pattern_add_color_stop_rgba (pattern,
+                                       0.0,
+                                       self->stops[0].color.red,
+                                       self->stops[0].color.green,
+                                       self->stops[0].color.blue,
+                                       self->stops[0].color.alpha);
   for (i = 0; i < self->n_stops; i++)
+    {
+      cairo_pattern_add_color_stop_rgba (pattern,
+                                         self->stops[i].offset,
+                                         self->stops[i].color.red,
+                                         self->stops[i].color.green,
+                                         self->stops[i].color.blue,
+                                         self->stops[i].color.alpha);
+    }
+  if (self->stops[self->n_stops-1].offset < 1.0)
     cairo_pattern_add_color_stop_rgba (pattern,
-                                       self->stops[i].offset,
-                                       self->stops[i].color.red,
-                                       self->stops[i].color.green,
-                                       self->stops[i].color.blue,
-                                       self->stops[i].color.alpha);
+                                       1.0,
+                                       self->stops[self->n_stops-1].color.red,
+                                       self->stops[self->n_stops-1].color.green,
+                                       self->stops[self->n_stops-1].color.blue,
+                                       self->stops[self->n_stops-1].color.alpha);
 
   gsk_cairo_rectangle (cr, &node->bounds);
   cairo_translate (cr, self->center.x, self->center.y);
diff --git a/testsuite/gsk/compare/repeating-linear-gradient-edge-colors.node b/testsuite/gsk/compare/repeating-linear-gradient-edge-colors.node
new file mode 100644 (file)
index 0000000..06ffe1b
--- /dev/null
@@ -0,0 +1,6 @@
+repeating-linear-gradient {
+  bounds: 0 0 50 50;
+  start: 0 0;
+  end: 0 50;
+  stops: 0.5 rgb(255,0,0), 0.5 rgb(0,255,0);
+}
diff --git a/testsuite/gsk/compare/repeating-linear-gradient-edge-colors.png b/testsuite/gsk/compare/repeating-linear-gradient-edge-colors.png
new file mode 100644 (file)
index 0000000..2ac3fd1
Binary files /dev/null and b/testsuite/gsk/compare/repeating-linear-gradient-edge-colors.png differ
diff --git a/testsuite/gsk/compare/repeating-radial-gradient-edge-colors.node b/testsuite/gsk/compare/repeating-radial-gradient-edge-colors.node
new file mode 100644 (file)
index 0000000..10db7dc
--- /dev/null
@@ -0,0 +1,10 @@
+clip {
+  clip: -25 -25 50 50;
+  child: repeating-radial-gradient {
+    bounds: -100 -100 200 200;
+    center: 0 0;
+    hradius: 100;
+    vradius: 100;
+    stops: 0.5 rgb(255,0,0), 0.5 rgb(0,255,0);
+  }
+}
diff --git a/testsuite/gsk/compare/repeating-radial-gradient-edge-colors.png b/testsuite/gsk/compare/repeating-radial-gradient-edge-colors.png
new file mode 100644 (file)
index 0000000..9a5764a
Binary files /dev/null and b/testsuite/gsk/compare/repeating-radial-gradient-edge-colors.png differ
index e48e9a3a45d9ae8904508002a68120a664231f2d..c0facb5a12faca35c3ad511cae8b13b15eb639a1 100644 (file)
@@ -78,6 +78,8 @@ compare_render_tests = [
   'outset_shadow_rounded_top',
   'outset_shadow_simple',
   'repeat',
+  'repeating-linear-gradient-edge-colors',
+  'repeating-radial-gradient-edge-colors',
   'repeat-no-repeat',
   'repeat-empty-child-bounds',
   'repeat-negative-coords',