[gtk+] border-image: Redo border-image rendering



commit 3ef4a3d46fad3cff1d44e72e49379cf1aa0e038c
Author: Benjamin Otte <otte redhat com>
Date:   Thu Jun 9 01:41:18 2011 +0200

    border-image: Redo border-image rendering
    
    The new code is smaller, less crashy and correct(er), but arguably more
    complex. I'd have liked to make it simpler, but this border image
    algorithm is complex...

 gtk/gtkborderimage.c |  536 ++++++++++++++++++--------------------------------
 1 files changed, 189 insertions(+), 347 deletions(-)
---
diff --git a/gtk/gtkborderimage.c b/gtk/gtkborderimage.c
index 499bbe4..2770078 100644
--- a/gtk/gtkborderimage.c
+++ b/gtk/gtkborderimage.c
@@ -188,240 +188,167 @@ _gtk_border_image_pack (GValue             *value,
     g_free (repeat);
 }
 
-static void
-render_corner (cairo_t         *cr,
-               gdouble          corner_x,
-               gdouble          corner_y,
-               gdouble          corner_width,
-               gdouble          corner_height,
-               cairo_surface_t *surface,
-               gdouble          image_width,
-               gdouble          image_height)
-{
-  if (corner_width == 0 || corner_height == 0)
-    return;
-
-  cairo_save (cr);
-
-  cairo_rectangle (cr, corner_x, corner_y, corner_width, corner_height);
-
-  cairo_translate (cr, corner_x, corner_y);
-  cairo_scale (cr,
-               corner_width / image_width,
-               corner_height / image_height);
-
-  cairo_set_source_surface (cr, surface, 0, 0);
-  cairo_pattern_set_extend (cairo_get_source (cr), CAIRO_EXTEND_PAD);
-
-  cairo_fill (cr);
-
-  cairo_restore (cr);
-}
+typedef struct _GtkBorderImageSliceSize GtkBorderImageSliceSize;
+struct _GtkBorderImageSliceSize {
+  double offset;
+  double size;
+};
 
-static cairo_surface_t *
-create_spaced_surface (cairo_surface_t *tile,
-                       gdouble          tile_width,
-                       gdouble          tile_height,
-                       gdouble          width,
-                       gdouble          height,
-                       GtkOrientation   orientation)
+static void
+gtk_border_image_compute_border_size (GtkBorderImageSliceSize sizes[3],
+                                      double                  offset,
+                                      double                  area_size,
+                                      int                     start_border,
+                                      int                     end_border)
 {
-  gint n_repeats, idx;
-  gdouble avail_space, step;
-  cairo_surface_t *retval;
-  cairo_t *cr;
-
-  n_repeats = (orientation == GTK_ORIENTATION_HORIZONTAL) ?
-    (gint) floor (width / tile_width) :
-    (gint) floor (height / tile_height);
-
-  avail_space = (orientation == GTK_ORIENTATION_HORIZONTAL) ?
-    width - (n_repeats * tile_width) :
-    height - (n_repeats * tile_height);
-  step = avail_space / (n_repeats + 2);
-
-  retval = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
-                                       width, height);
-  cr = cairo_create (retval);
-  idx = 0;
-
-  while (idx < n_repeats)
-    {
-      cairo_save (cr);
-
-      if (orientation == GTK_ORIENTATION_HORIZONTAL)
-        cairo_set_source_surface (cr, tile,
-                                  ((idx + 1) * step) + (idx * tile_width), 0);
-      else
-        cairo_set_source_surface (cr, tile,
-                                  0, ((idx + 1 ) * step) + (idx * tile_height));
-
-      cairo_paint (cr);
-      cairo_restore (cr);
-
-      idx++;
-    }
-
-  cairo_destroy (cr);
-
-  return retval;
+  /* This code assumes area_size >= start_border + end_border */
+
+  sizes[0].offset = offset;
+  sizes[0].size = start_border;
+  sizes[1].offset = offset + start_border;
+  sizes[1].size = area_size - start_border - end_border;
+  sizes[2].offset = offset + area_size - end_border;
+  sizes[2].size = end_border;
 }
 
 static void
-render_border (cairo_t              *cr,
-               gdouble               total_width,
-               gdouble               total_height,
-               cairo_surface_t      *surface,
-               gdouble               surface_width,
-               gdouble               surface_height,
-               guint                 side,
-               GtkBorder            *border_area,
-               GtkCssBorderImageRepeat *repeat)
+gtk_border_image_render_slice (cairo_t           *cr,
+                               cairo_surface_t   *slice,
+                               double             slice_width,
+                               double             slice_height,
+                               double             x,
+                               double             y,
+                               double             width,
+                               double             height,
+                               GtkCssRepeatStyle  hrepeat,
+                               GtkCssRepeatStyle  vrepeat)
 {
-  gdouble target_x, target_y;
-  gdouble target_width, target_height;
-  GdkRectangle image_area;
+  double hscale, vscale;
+  double xstep, ystep;
+  cairo_extend_t extend = CAIRO_EXTEND_PAD;
+  cairo_matrix_t matrix;
   cairo_pattern_t *pattern;
-  gboolean repeat_pattern;
 
-  if (surface == NULL)
-    return;
+  /* We can't draw center tiles yet */
+  g_assert (hrepeat == GTK_CSS_REPEAT_STYLE_NONE || vrepeat == GTK_CSS_REPEAT_STYLE_NONE);
 
-  cairo_surface_reference (surface);
-  repeat_pattern = FALSE;
+  hscale = width / slice_width;
+  vscale = height / slice_height;
+  xstep = width;
+  ystep = height;
 
-  if (side == SIDE_TOP || side == SIDE_BOTTOM)
+  switch (hrepeat)
     {
-      target_height = (side == SIDE_TOP) ? (border_area->top) : (border_area->bottom);
-      target_width = surface_width * (target_height / surface_height);
-    }
-  else
-    {
-      target_width = (side == SIDE_LEFT) ? (border_area->left) : (border_area->right);
-      target_height = surface_height * (target_width / surface_width);
+    case GTK_CSS_REPEAT_STYLE_REPEAT:
+      extend = CAIRO_EXTEND_REPEAT;
+      hscale = vscale;
+      break;
+    case GTK_CSS_REPEAT_STYLE_SPACE:
+      {
+        double space, n;
+
+        extend = CAIRO_EXTEND_NONE;
+        hscale = vscale;
+
+        xstep = hscale * slice_width;
+        n = floor (width / xstep);
+        space = (width - n * xstep) / (n + 1);
+        xstep += space;
+        x += space;
+        width -= 2 * space;
+      }
+      break;
+    case GTK_CSS_REPEAT_STYLE_NONE:
+      break;
+    case GTK_CSS_REPEAT_STYLE_ROUND:
+      extend = CAIRO_EXTEND_REPEAT;
+      hscale = width / (slice_width * MAX (round (width / (slice_width * vscale)), 1));
+      break;
+    default:
+      g_assert_not_reached ();
+      break;
     }
 
-  if (side == SIDE_TOP || side == SIDE_BOTTOM)
+  switch (vrepeat)
     {
-      image_area.x = border_area->left;
-      image_area.y = (side == SIDE_TOP) ? 0 : (total_height - border_area->bottom);
-      image_area.width = total_width - border_area->left - border_area->right;
-      image_area.height = (side == SIDE_TOP) ? border_area->top : border_area->bottom;
-
-      target_x = border_area->left;
-      target_y = (side == SIDE_TOP) ? 0 : (total_height - border_area->bottom);
-
-      if (repeat->hrepeat == GTK_CSS_REPEAT_STYLE_NONE)
-        {
-          target_width = image_area.width;
-        }
-      else if (repeat->hrepeat == GTK_CSS_REPEAT_STYLE_REPEAT)
-        {
-          repeat_pattern = TRUE;
-
-          target_x = border_area->left + (total_width - border_area->left - border_area->right) / 2;
-          target_y = ((side == SIDE_TOP) ? 0 : (total_height - border_area->bottom)) / 2;
-        }
-      else if (repeat->hrepeat == GTK_CSS_REPEAT_STYLE_ROUND)
-        {
-          gint n_repeats;
-
-          repeat_pattern = TRUE;
-
-          n_repeats = (gint) floor (image_area.width / surface_width);
-          target_width = image_area.width / n_repeats;
-        }
-      else if (repeat->hrepeat == GTK_CSS_REPEAT_STYLE_SPACE)
-        {
-          cairo_surface_t *spaced_surface;
-
-          spaced_surface = create_spaced_surface (surface,
-                                                  surface_width, surface_height,
-                                                  image_area.width, surface_height,
-                                                  GTK_ORIENTATION_HORIZONTAL);
-          cairo_surface_destroy (surface);
-          surface = spaced_surface;
-
-          /* short-circuit hscaling */
-          target_width = surface_width = cairo_image_surface_get_width (spaced_surface);
-        }
+    case GTK_CSS_REPEAT_STYLE_REPEAT:
+      extend = CAIRO_EXTEND_REPEAT;
+      vscale = hscale;
+      break;
+    case GTK_CSS_REPEAT_STYLE_SPACE:
+      {
+        double space, n;
+
+        extend = CAIRO_EXTEND_NONE;
+        vscale = hscale;
+
+        ystep = vscale * slice_height;
+        n = floor (height / ystep);
+        space = (height - n * ystep) / (n + 1);
+        ystep += space;
+        y += space;
+        height -= 2 * space;
+      }
+      break;
+    case GTK_CSS_REPEAT_STYLE_NONE:
+      break;
+    case GTK_CSS_REPEAT_STYLE_ROUND:
+      extend = CAIRO_EXTEND_REPEAT;
+      vscale = height / (slice_height * MAX (round (height / (slice_height * hscale)), 1));
+      break;
+    default:
+      g_assert_not_reached ();
+      break;
     }
-  else
-    {
-      image_area.x = (side == SIDE_LEFT) ? 0 : (total_width - border_area->right);
-      image_area.y = border_area->top;
-      image_area.width = (side == SIDE_LEFT) ? border_area->left : border_area->right;
-      image_area.height = total_height - border_area->top - border_area->bottom;
 
-      target_x = (side == SIDE_LEFT) ? 0 : (total_width - border_area->right);
-      target_y = border_area->top;
+  pattern = cairo_pattern_create_for_surface (slice);
 
-      if (repeat->vrepeat == GTK_CSS_REPEAT_STYLE_NONE)
-        {
-          target_height = total_height - border_area->top - border_area->bottom;
-        }
-      else if (repeat->vrepeat == GTK_CSS_REPEAT_STYLE_REPEAT)
-        {
-          repeat_pattern = TRUE;
+  cairo_matrix_init_translate (&matrix,
+                               hrepeat == GTK_CSS_REPEAT_STYLE_REPEAT ? slice_width / 2 : 0,
+                               vrepeat == GTK_CSS_REPEAT_STYLE_REPEAT ? slice_height / 2 : 0);
+  cairo_matrix_scale (&matrix, 1 / hscale, 1 / vscale);
+  cairo_matrix_translate (&matrix,
+                          hrepeat == GTK_CSS_REPEAT_STYLE_REPEAT ? - width / 2 : 0,
+                          vrepeat == GTK_CSS_REPEAT_STYLE_REPEAT ? - height / 2 : 0);
 
-          target_height = total_height - border_area->top - border_area->bottom;
-          target_x = (side == SIDE_LEFT) ? 0 : (total_width - border_area->right) / 2;
-          target_y = border_area->top + (total_height - border_area->top - border_area->bottom) / 2;
-        }
-      else if (repeat->vrepeat == GTK_CSS_REPEAT_STYLE_ROUND)
-        {
-          gint n_repeats;
+  cairo_pattern_set_matrix (pattern, &matrix);
+  cairo_pattern_set_extend (pattern, extend);
 
-          repeat_pattern = TRUE;
+  cairo_save (cr);
+  cairo_translate (cr, x, y);
 
-          n_repeats = (gint) floor (image_area.height / surface_height);
-          target_height = image_area.height / n_repeats;
-        }
-      else if (repeat->vrepeat == GTK_CSS_REPEAT_STYLE_SPACE)
+  for (y = 0; y < height; y += ystep)
+    {
+      for (x = 0; x < width; x += xstep)
         {
-          cairo_surface_t *spaced_surface;
-
-          spaced_surface = create_spaced_surface (surface,
-                                                  surface_width, surface_height,
-                                                  surface_width, image_area.height,
-                                                  GTK_ORIENTATION_VERTICAL);
-          cairo_surface_destroy (surface);
-          surface = spaced_surface;
-
-          /* short-circuit vscaling */
-          target_height = surface_height = cairo_image_surface_get_height (spaced_surface);
+          cairo_save (cr);
+          cairo_translate (cr, x, y);
+          cairo_set_source (cr, pattern);
+          cairo_rectangle (cr, 0, 0, xstep, ystep);
+          cairo_fill (cr);
+          cairo_restore (cr);
         }
     }
 
-  if (target_width == 0 || target_height == 0)
-    return;
-
-  cairo_save (cr);
-
-  pattern = cairo_pattern_create_for_surface (surface);
-
-  gdk_cairo_rectangle (cr, &image_area);
-  cairo_clip (cr);
-
-  cairo_translate (cr,
-                   target_x, target_y);
-
-  if (repeat_pattern)
-    cairo_pattern_set_extend (pattern, CAIRO_EXTEND_REPEAT);
-  else
-    cairo_pattern_set_extend (pattern, CAIRO_EXTEND_PAD);
+  cairo_restore (cr);
 
-  cairo_scale (cr,
-               target_width / surface_width,
-               target_height / surface_height);
+  cairo_pattern_destroy (pattern);
+}
 
-  cairo_set_source (cr, pattern);
-  cairo_paint (cr);
+static void
+gtk_border_image_compute_slice_size (GtkBorderImageSliceSize sizes[3],
+                                     int                     surface_size,
+                                     int                     start_size,
+                                     int                     end_size)
+{
+  sizes[0].size = MIN (start_size, surface_size);
+  sizes[0].offset = 0;
 
-  cairo_restore (cr);
+  sizes[2].size = MIN (end_size, surface_size);
+  sizes[2].offset = surface_size - sizes[2].size;
 
-  cairo_pattern_destroy (pattern);
-  cairo_surface_destroy (surface);
+  sizes[1].size = MAX (0, surface_size - sizes[0].size - sizes[2].size);
+  sizes[1].offset = sizes[0].size;
 }
 
 void
@@ -434,7 +361,10 @@ _gtk_border_image_render (GtkBorderImage   *image,
                           gdouble           height)
 {
   cairo_surface_t *surface, *slice;
-  gdouble slice_width, slice_height, surface_width, surface_height;
+  GtkBorderImageSliceSize vertical_slice[3], horizontal_slice[3];
+  GtkBorderImageSliceSize vertical_border[3], horizontal_border[3];
+  int surface_width, surface_height;
+  int h, v;
 
   if (cairo_pattern_get_type (image->source) != CAIRO_PATTERN_TYPE_SURFACE)
     {
@@ -463,147 +393,59 @@ _gtk_border_image_render (GtkBorderImage   *image,
       surface_height = cairo_image_surface_get_height (surface);
     }
 
-  cairo_save (cr);
-  cairo_translate (cr, x, y);
-
-  if ((image->slice.left + image->slice.right) < surface_width)
+  gtk_border_image_compute_slice_size (horizontal_slice,
+                                       surface_width, 
+                                       image->slice.left,
+                                       image->slice.right);
+  gtk_border_image_compute_slice_size (vertical_slice,
+                                       surface_height, 
+                                       image->slice.top,
+                                       image->slice.bottom);
+  gtk_border_image_compute_border_size (horizontal_border,
+                                        x,
+                                        width,
+                                        border_width->left,
+                                        border_width->right);
+  gtk_border_image_compute_border_size (vertical_border,
+                                        y,
+                                        height,
+                                        border_width->top,
+                                        border_width->bottom);
+  
+  for (v = 0; v < 3; v++)
     {
-      /* Top side */
-      slice_width = surface_width - image->slice.left - image->slice.right;
-      slice_height = image->slice.top;
-      slice = cairo_surface_create_for_rectangle
-        (surface,
-         image->slice.left, 0,
-         slice_width, slice_height);
-
-      render_border (cr,
-                     width, height,
-                     slice,
-                     slice_width, slice_height,
-                     SIDE_TOP,
-                     border_width,
-                     &image->repeat);
-
-      cairo_surface_destroy (slice);
-
-      /* Bottom side */
-      slice_height = image->slice.bottom;
-      slice = cairo_surface_create_for_rectangle
-        (surface,
-         image->slice.left, surface_height - image->slice.bottom,
-         slice_width, slice_height);
-
-      render_border (cr,
-                     width, height,
-                     slice,
-                     slice_width, slice_height,
-                     SIDE_BOTTOM,
-                     border_width,
-                     &image->repeat);
-
-      cairo_surface_destroy (slice);
-    }
+      if (vertical_slice[v].size == 0 ||
+          vertical_border[v].size == 0)
+        continue;
 
-  if ((image->slice.top + image->slice.bottom) < surface_height)
-    {
-      /* Left side */
-      slice_width = image->slice.left;
-      slice_height = surface_height - image->slice.top - image->slice.bottom;
-      slice = cairo_surface_create_for_rectangle
-        (surface,
-         0, image->slice.top,
-         slice_width, slice_height);
-
-      render_border (cr,
-                     width, height,
-                     slice,
-                     slice_width, slice_height,
-                     SIDE_LEFT,
-                     border_width,
-                     &image->repeat);
-
-      cairo_surface_destroy (slice);
-
-      /* Right side */
-      slice_width = image->slice.right;
-      slice = cairo_surface_create_for_rectangle
-        (surface, 
-         surface_width - image->slice.right, image->slice.top,
-         slice_width, slice_height);
-
-      render_border (cr,
-                     width, height,
-                     slice,
-                     slice_width, slice_height,
-                     SIDE_RIGHT,
-                     border_width,
-                     &image->repeat);
-
-      cairo_surface_destroy (slice);
+      for (h = 0; h < 3; h++)
+        {
+          if (horizontal_slice[h].size == 0 ||
+              horizontal_border[v].size == 0)
+            continue;
+
+          if (h == 1 && v == 1)
+            continue;
+
+          slice = cairo_surface_create_for_rectangle (surface,
+                                                      horizontal_slice[h].offset,
+                                                      vertical_slice[v].offset,
+                                                      horizontal_slice[h].size,
+                                                      vertical_slice[v].size);
+
+          /* xxx: we scale to border-width here, that's wrong, isn't it? */
+          gtk_border_image_render_slice (cr,
+                                         slice,
+                                         horizontal_slice[h].size,
+                                         vertical_slice[v].size,
+                                         horizontal_border[h].offset,
+                                         vertical_border[v].offset,
+                                         horizontal_border[h].size,
+                                         vertical_border[v].size,
+                                         h == 1 ? image->repeat.hrepeat : GTK_CSS_REPEAT_STYLE_NONE,
+                                         v == 1 ? image->repeat.vrepeat : GTK_CSS_REPEAT_STYLE_NONE);
+
+          cairo_surface_destroy (slice);
+        }
     }
-
-  /* Top/left corner */
-  slice_width = image->slice.left;
-  slice_height = image->slice.top;
-  slice = cairo_surface_create_for_rectangle
-    (surface, 
-     0, 0,
-     slice_width, slice_height);
-
-  render_corner (cr,
-                 0, 0,
-                 border_width->left, border_width->top,
-                 slice,
-                 slice_width, slice_height);
-
-  cairo_surface_destroy (slice);
-
-  /* Top/right corner */
-  slice_width = image->slice.right;
-  slice = cairo_surface_create_for_rectangle
-    (surface,
-     surface_width - image->slice.right, 0,
-     slice_width, slice_height);
-
-  render_corner (cr,
-                 width - border_width->right, 0,
-                 border_width->right, border_width->top,
-                 slice,
-                 slice_width, slice_height);
-
-  cairo_surface_destroy (slice);
-
-  /* Bottom/left corner */
-  slice_width = image->slice.left;
-  slice_height = image->slice.bottom;
-  slice = cairo_surface_create_for_rectangle
-    (surface,
-     0, surface_height - image->slice.bottom,
-     slice_width, slice_height);
-
-  render_corner (cr,
-                 0, height - border_width->bottom,
-                 border_width->left, border_width->bottom,
-                 slice,
-                 slice_width, slice_height);
-
-  cairo_surface_destroy (slice);
-
-  /* Bottom/right corner */
-  slice_width = image->slice.right;
-  slice = cairo_surface_create_for_rectangle
-    (surface,
-     surface_width - image->slice.right,
-     surface_height - image->slice.bottom,
-     slice_width, slice_height);
-
-  render_corner (cr,
-                 width - border_width->right, height - border_width->bottom,
-                 border_width->right, border_width->bottom,
-                 slice,
-                 slice_width, slice_height);
-
-  cairo_surface_destroy (slice);
-
-  cairo_restore (cr);
 }



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]