gsk: Add (private) gsk_rounded_rect_intersection()
authorBenjamin Otte <otte@redhat.com>
Sun, 14 May 2023 21:13:23 +0000 (23:13 +0200)
committerBenjamin Otte <otte@redhat.com>
Sun, 4 Jun 2023 17:42:01 +0000 (19:42 +0200)
The idea is that for a rectangle intersection, each corner of the
result is either entirely part of one original rectangle or it is
an intersection point.

By detecting those 2 cases and treating them differently, we can
simplify the code to compare rounded rectangles.

gsk/gskroundedrect.c
gsk/gskroundedrectprivate.h
testsuite/gsk/rounded-rect.c

index ed1131de83f3caeb94942c712863f9c78ad21856..76efe4d9aefbef1e0ebb3404907d65d0ebb88d42 100644 (file)
@@ -689,6 +689,134 @@ gsk_rounded_rect_intersect_with_rect (const GskRoundedRect  *self,
   return GSK_INTERSECTION_NONEMPTY;
 }
 
+static gboolean
+check_nonintersecting_corner (const GskRoundedRect *out,
+                              const GskRoundedRect *in,
+                              GskCorner             corner,
+                              float                 diff_x,
+                              float                 diff_y,
+                              GskRoundedRect       *result)
+{
+  g_assert (diff_x >= 0);
+  g_assert (diff_y >= 0);
+
+  if (out->corner[corner].width < diff_x ||
+      out->corner[corner].height < diff_y ||
+      (out->corner[corner].width <= in->corner[corner].width + diff_x &&
+       out->corner[corner].height <= in->corner[corner].height + diff_y))
+    {
+      result->corner[corner] = in->corner[corner];
+      return TRUE;
+    }
+
+  if (diff_x > 0 || diff_y > 0)
+    return FALSE;
+
+  if (out->corner[corner].width > in->corner[corner].width &&
+      out->corner[corner].height > in->corner[corner].height)
+    {
+      result->corner[corner] = out->corner[corner];
+      return TRUE;
+    }
+
+  return FALSE;
+}
+
+/* a is outside in x direction, b is outside in y direction */
+static gboolean
+check_intersecting_corner (const GskRoundedRect *a,
+                           const GskRoundedRect *b,
+                           GskCorner             corner,
+                           float                 diff_x,
+                           float                 diff_y,
+                           GskRoundedRect       *result)
+{
+  g_assert (diff_x > 0);
+  g_assert (diff_y > 0);
+
+  if (diff_x < a->corner[corner].width ||
+      diff_x > a->bounds.size.width - a->corner[corner].width - a->corner[OPPOSITE_CORNER_X (corner)].width ||
+      diff_y < b->corner[corner].height ||
+      diff_y > b->bounds.size.height - b->corner[corner].height - b->corner[OPPOSITE_CORNER_Y (corner)].height)
+    return FALSE;
+
+  result->corner[corner] = GRAPHENE_SIZE_INIT (0, 0);
+  return TRUE;
+}
+
+static gboolean
+check_corner (const GskRoundedRect *a,
+              const GskRoundedRect *b,
+              GskCorner             corner,
+              float                 diff_x,
+              float                 diff_y,
+              GskRoundedRect       *result)
+{
+  if (diff_x >= 0)
+    {
+      if (diff_y >= 0)
+        {
+          return check_nonintersecting_corner (a, b, corner, diff_x, diff_y, result);
+        }
+      else if (diff_x == 0)
+        {
+          return check_nonintersecting_corner (b, a, corner, 0, - diff_y, result);
+        }
+      else
+        {
+          return check_intersecting_corner (a, b, corner, diff_x, - diff_y, result);
+        }
+    }
+  else
+    {
+      if (diff_y <= 0)
+        {
+          return check_nonintersecting_corner (b, a, corner, - diff_x, - diff_y, result);
+        }
+      else
+        {
+          return check_intersecting_corner (b, a, corner, - diff_x, diff_y, result);
+        }
+    }
+                    
+}
+
+GskRoundedRectIntersection
+gsk_rounded_rect_intersection (const GskRoundedRect *a,
+                               const GskRoundedRect *b,
+                               GskRoundedRect       *result)
+{
+  float top, left, bottom, right;
+
+  if (!graphene_rect_intersection (&a->bounds, &b->bounds, &result->bounds))
+    return GSK_INTERSECTION_EMPTY;
+
+  left = b->bounds.origin.x - a->bounds.origin.x;
+  top = b->bounds.origin.y - a->bounds.origin.y;
+  right = a->bounds.origin.x + a->bounds.size.width - b->bounds.origin.x - b->bounds.size.width;
+  bottom = a->bounds.origin.y + a->bounds.size.height - b->bounds.origin.y - b->bounds.size.height;
+
+  if (check_corner (a, b,
+                    GSK_CORNER_TOP_LEFT,
+                    left, top,
+                    result) &&
+      check_corner (a, b,
+                    GSK_CORNER_TOP_RIGHT,
+                    right, top,
+                    result) &&
+      check_corner (a, b,
+                    GSK_CORNER_BOTTOM_LEFT,
+                    left, bottom,
+                    result) &&
+      check_corner (a, b,
+                    GSK_CORNER_BOTTOM_RIGHT,
+                    right, bottom,
+                    result))
+    return GSK_INTERSECTION_NONEMPTY;
+
+  return GSK_INTERSECTION_NOT_REPRESENTABLE;
+}
+
 static void
 append_arc (cairo_t *cr, double angle1, double angle2, gboolean negative)
 {
index c42906d53ab24f3c370d5774b73663a7019da76f..64c3469df94fe7f84af645f74ff79266784056cf 100644 (file)
@@ -6,6 +6,24 @@
 
 G_BEGIN_DECLS
 
+#define OPPOSITE_CORNER(corner) ((corner) ^ 2)
+G_STATIC_ASSERT (OPPOSITE_CORNER (GSK_CORNER_TOP_LEFT) == GSK_CORNER_BOTTOM_RIGHT);
+G_STATIC_ASSERT (OPPOSITE_CORNER (GSK_CORNER_TOP_RIGHT) == GSK_CORNER_BOTTOM_LEFT);
+G_STATIC_ASSERT (OPPOSITE_CORNER (GSK_CORNER_BOTTOM_LEFT) == GSK_CORNER_TOP_RIGHT);
+G_STATIC_ASSERT (OPPOSITE_CORNER (GSK_CORNER_BOTTOM_RIGHT) == GSK_CORNER_TOP_LEFT);
+
+#define OPPOSITE_CORNER_X(corner) ((corner) ^ 1)
+G_STATIC_ASSERT (OPPOSITE_CORNER_X (GSK_CORNER_TOP_LEFT) == GSK_CORNER_TOP_RIGHT);
+G_STATIC_ASSERT (OPPOSITE_CORNER_X (GSK_CORNER_TOP_RIGHT) == GSK_CORNER_TOP_LEFT);
+G_STATIC_ASSERT (OPPOSITE_CORNER_X (GSK_CORNER_BOTTOM_LEFT) == GSK_CORNER_BOTTOM_RIGHT);
+G_STATIC_ASSERT (OPPOSITE_CORNER_X (GSK_CORNER_BOTTOM_RIGHT) == GSK_CORNER_BOTTOM_LEFT);
+
+#define OPPOSITE_CORNER_Y(corner) ((corner) ^ 3)
+G_STATIC_ASSERT (OPPOSITE_CORNER_Y (GSK_CORNER_TOP_LEFT) == GSK_CORNER_BOTTOM_LEFT);
+G_STATIC_ASSERT (OPPOSITE_CORNER_Y (GSK_CORNER_TOP_RIGHT) == GSK_CORNER_BOTTOM_RIGHT);
+G_STATIC_ASSERT (OPPOSITE_CORNER_Y (GSK_CORNER_BOTTOM_LEFT) == GSK_CORNER_TOP_LEFT);
+G_STATIC_ASSERT (OPPOSITE_CORNER_Y (GSK_CORNER_BOTTOM_RIGHT) == GSK_CORNER_TOP_RIGHT);
+
 #define GSK_ROUNDED_RECT_INIT_FROM_RECT(_r)   \
   (GskRoundedRect) { .bounds = _r, \
                      .corner = { \
@@ -44,6 +62,9 @@ typedef enum {
 GskRoundedRectIntersection gsk_rounded_rect_intersect_with_rect   (const GskRoundedRect     *self,
                                                                    const graphene_rect_t    *rect,
                                                                    GskRoundedRect           *result) G_GNUC_PURE;
+GskRoundedRectIntersection gsk_rounded_rect_intersection          (const GskRoundedRect     *a,
+                                                                   const GskRoundedRect     *b,
+                                                                   GskRoundedRect           *result);
 
 
 G_END_DECLS
index 5a72928257d5fc6a53b74f41be50365f0755917a..552bed49d9904b1eb65b34586f008870227c338c 100644 (file)
@@ -241,6 +241,145 @@ test_intersect_with_rect (void)
     }
 }
 
+static void
+test_intersect (void)
+{
+  struct {
+    GskRoundedRect a;
+    GskRoundedRect b;
+    GskRoundedRectIntersection result;
+    GskRoundedRect expected;
+  } test[] = {
+    {
+      ROUNDED_RECT_INIT(0, 0, 100, 100, 0),
+      ROUNDED_RECT_INIT(0, 0, 100, 100, 20),
+      GSK_INTERSECTION_NONEMPTY,
+      ROUNDED_RECT_INIT(0, 0, 100, 100, 20),
+    },
+    {
+      ROUNDED_RECT_INIT(0, 0, 100, 100, 20),
+      ROUNDED_RECT_INIT(50, 50, 100, 100, 20),
+      GSK_INTERSECTION_NONEMPTY,
+      ROUNDED_RECT_INIT_UNIFORM(50, 50, 50, 50, 20, 0, 20, 0),
+    },
+    {
+      ROUNDED_RECT_INIT(0, 0, 100, 100, 20),
+      ROUNDED_RECT_INIT(50, 0, 100, 100, 20),
+      GSK_INTERSECTION_NONEMPTY,
+      ROUNDED_RECT_INIT(50, 0, 50, 100, 20),
+    },
+    {
+      ROUNDED_RECT_INIT(0, 0, 100, 100, 20),
+      ROUNDED_RECT_INIT(0, 50, 100, 100, 20),
+      GSK_INTERSECTION_NONEMPTY,
+      ROUNDED_RECT_INIT(0, 50, 100, 50, 20),
+    },
+    {
+      ROUNDED_RECT_INIT(0, 0, 100, 100, 20),
+      ROUNDED_RECT_INIT(-50, -50, 100, 100, 20),
+      GSK_INTERSECTION_NONEMPTY,
+      ROUNDED_RECT_INIT_UNIFORM(0, 0, 50, 50, 20, 0, 20, 0),
+    },
+    {
+      ROUNDED_RECT_INIT(0, 0, 100, 100, 20),
+      ROUNDED_RECT_INIT(0, -50, 100, 100, 20),
+      GSK_INTERSECTION_NONEMPTY,
+      ROUNDED_RECT_INIT(0, 0, 100, 50, 20),
+    },
+    {
+      ROUNDED_RECT_INIT(0, 0, 100, 100, 20),
+      ROUNDED_RECT_INIT(-50, 0, 100, 100, 20),
+      GSK_INTERSECTION_NONEMPTY,
+      ROUNDED_RECT_INIT(0, 0, 50, 100, 20),
+    },
+    {
+      ROUNDED_RECT_INIT(0, 0, 100, 100, 20),
+      ROUNDED_RECT_INIT(10, 10, 80, 80, 20),
+      GSK_INTERSECTION_NONEMPTY,
+      ROUNDED_RECT_INIT(10, 10, 80, 80, 20),
+    },
+    {
+      ROUNDED_RECT_INIT(0, 0, 100, 100, 20),
+      ROUNDED_RECT_INIT(10, 10, 80, 80, 10),
+      GSK_INTERSECTION_NONEMPTY,
+      ROUNDED_RECT_INIT(10, 10, 80, 80, 10),
+    },
+    {
+      ROUNDED_RECT_INIT(0, 0, 100, 100, 40),
+      ROUNDED_RECT_INIT(10, 10, 80, 80, 0),
+      GSK_INTERSECTION_NOT_REPRESENTABLE,
+    },
+    {
+      ROUNDED_RECT_INIT(10, 10, 100, 100, 40),
+      ROUNDED_RECT_INIT(30, 0, 40, 40, 0),
+      GSK_INTERSECTION_NOT_REPRESENTABLE,
+    },
+    {
+      ROUNDED_RECT_INIT(10, 10, 100, 100, 40),
+      ROUNDED_RECT_INIT(0, 0, 100, 20, 0),
+      GSK_INTERSECTION_NOT_REPRESENTABLE,
+    },
+    {
+      ROUNDED_RECT_INIT_UNIFORM(647, 18, 133, 35, 5, 0, 0, 5),
+      ROUNDED_RECT_INIT_UNIFORM(14, 12, 1666, 889, 8, 8, 0, 0),
+      GSK_INTERSECTION_NONEMPTY,
+      ROUNDED_RECT_INIT_UNIFORM(647, 18, 133, 35, 5, 0, 0, 5),
+    },
+    {
+      ROUNDED_RECT_INIT_UNIFORM(0, 0, 100, 100, 100, 0, 0, 0),
+      ROUNDED_RECT_INIT_UNIFORM(0, 0, 100, 100, 0, 0, 100, 0),
+      GSK_INTERSECTION_NONEMPTY,
+      ROUNDED_RECT_INIT_UNIFORM(0, 0, 100, 100, 100, 0, 100, 0),
+    },
+    {
+      ROUNDED_RECT_INIT_UNIFORM(0, 0, 100, 100, 100, 0, 0, 0),
+      ROUNDED_RECT_INIT_UNIFORM(-20, -20, 100, 100, 0, 0, 100, 0),
+      GSK_INTERSECTION_NOT_REPRESENTABLE,
+    },
+    {
+      ROUNDED_RECT_INIT_UNIFORM(0, 0, 50, 50, 0, 0, 50, 0),
+      ROUNDED_RECT_INIT_UNIFORM(0, 0, 20, 20, 20, 0, 0, 0),
+      GSK_INTERSECTION_NOT_REPRESENTABLE, /* FIXME: should be empty */
+    },
+    {
+      ROUNDED_RECT_INIT_UNIFORM(0, 0, 50, 50, 0, 0, 50, 0),
+      ROUNDED_RECT_INIT_UNIFORM(0, 0, 21, 21, 21, 0, 0, 0),
+      GSK_INTERSECTION_NOT_REPRESENTABLE,
+    },
+  };
+  gsize i;
+
+  for (i = 0; i < G_N_ELEMENTS (test); i++)
+    {
+      GskRoundedRect out;
+      GskRoundedRectIntersection res;
+
+      if (g_test_verbose ())
+        g_test_message ("intersection test %zu", i);
+
+      memset (&out, 0, sizeof (GskRoundedRect));
+
+      res = gsk_rounded_rect_intersection (&test[i].a, &test[i].b, &out);
+      g_assert_cmpint (res, ==, test[i].result);
+      if (res == GSK_INTERSECTION_NONEMPTY)
+        {
+          if (!gsk_rounded_rect_equal (&out, &test[i].expected))
+            {
+              char *a = gsk_rounded_rect_to_string (&test[i].a);
+              char *b = gsk_rounded_rect_to_string (&test[i].b);
+              char *expected = gsk_rounded_rect_to_string (&test[i].expected);
+              char *result = gsk_rounded_rect_to_string (&out);
+              g_test_message ("     A = %s\n"
+                              "     B = %s\n"
+                              "expected %s\n"
+                              "     got %s\n",
+                              a, b, expected, result);
+            }
+          g_assert_true (gsk_rounded_rect_equal (&out, &test[i].expected));
+        }
+    }
+}
+
 int
 main (int   argc,
       char *argv[])
@@ -253,6 +392,7 @@ main (int   argc,
   g_test_add_func ("/rounded-rect/is-circular", test_is_circular);
   g_test_add_func ("/rounded-rect/to-float", test_to_float);
   g_test_add_func ("/rounded-rect/intersect-with-rect", test_intersect_with_rect);
+  g_test_add_func ("/rounded-rect/intersect", test_intersect);
 
   return g_test_run ();
 }