]> Pileus Git - ~andy/gtk/blob - gtk/gtkborderimage.c
cf9da9cf0a804b4b32a63b9fa82344e81a9363b0
[~andy/gtk] / gtk / gtkborderimage.c
1 /* GTK - The GIMP Toolkit
2  * Copyright (C) 2010 Carlos Garnacho <carlosg@gnome.org>
3  * Copyright (C) 2011 Red Hat, Inc.
4  *
5  * Authors: Carlos Garnacho <carlosg@gnome.org>
6  *          Cosimo Cecchi <cosimoc@gnome.org>
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Lesser General Public
10  * License as published by the Free Software Foundation; either
11  * version 2 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with this library; if not, write to the
20  * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
21  * Boston, MA 02111-1307, USA.
22  */
23
24 #include <config.h>
25 #include <cairo-gobject.h>
26
27 #include <math.h>
28
29 #include "gtkborderimageprivate.h"
30
31 G_DEFINE_BOXED_TYPE (GtkBorderImage, _gtk_border_image,
32                      _gtk_border_image_ref, _gtk_border_image_unref)
33
34 enum {
35   BORDER_LEFT,
36   BORDER_MIDDLE,
37   BORDER_RIGHT,
38   BORDER_LAST,
39   BORDER_TOP = BORDER_LEFT,
40   BORDER_BOTTOM = BORDER_RIGHT
41 };
42
43 enum {
44   SIDE_TOP,
45   SIDE_RIGHT,
46   SIDE_BOTTOM,
47   SIDE_LEFT
48 };
49
50 struct _GtkBorderImage {
51   cairo_pattern_t *source;
52   GtkGradient *source_gradient;
53
54   GtkBorder slice;
55   GtkCssBorderImageRepeat repeat;
56
57   gint ref_count;
58 };
59
60 GtkBorderImage *
61 _gtk_border_image_new (cairo_pattern_t      *pattern,
62                        GtkBorder            *slice,
63                        GtkCssBorderImageRepeat *repeat)
64 {
65   GtkBorderImage *image;
66
67   image = g_slice_new0 (GtkBorderImage);
68   image->ref_count = 1;
69
70   if (pattern != NULL)
71     image->source = cairo_pattern_reference (pattern);
72
73   if (slice != NULL)
74     image->slice = *slice;
75
76   if (repeat != NULL)
77     image->repeat = *repeat;
78
79   return image;
80 }
81
82 GtkBorderImage *
83 _gtk_border_image_new_for_gradient (GtkGradient          *gradient,
84                                     GtkBorder            *slice,
85                                     GtkCssBorderImageRepeat *repeat)
86 {
87   GtkBorderImage *image;
88
89   image = g_slice_new0 (GtkBorderImage);
90   image->ref_count = 1;
91
92   if (gradient != NULL)
93     image->source_gradient = gtk_gradient_ref (gradient);
94
95   if (slice != NULL)
96     image->slice = *slice;
97
98   if (repeat != NULL)
99     image->repeat = *repeat;
100
101   return image;  
102 }
103
104 GtkBorderImage *
105 _gtk_border_image_ref (GtkBorderImage *image)
106 {
107   g_return_val_if_fail (image != NULL, NULL);
108
109   image->ref_count++;
110
111   return image;
112 }
113
114 void
115 _gtk_border_image_unref (GtkBorderImage *image)
116 {
117   g_return_if_fail (image != NULL);
118
119   image->ref_count--;
120
121   if (image->ref_count == 0)
122     {
123       if (image->source != NULL)
124         cairo_pattern_destroy (image->source);
125
126       if (image->source_gradient != NULL)
127         gtk_gradient_unref (image->source_gradient);
128
129       g_slice_free (GtkBorderImage, image);
130     }
131 }
132
133 GParameter *
134 _gtk_border_image_unpack (const GValue *value,
135                           guint        *n_params)
136 {
137   GParameter *parameter = g_new0 (GParameter, 3);
138   GtkBorderImage *image = g_value_get_boxed (value);
139
140   parameter[0].name = "border-image-source";
141   g_value_init (&parameter[0].value, CAIRO_GOBJECT_TYPE_PATTERN);
142   g_value_set_boxed (&parameter[0].value, image->source);
143
144   parameter[1].name = "border-image-slice";
145   g_value_init (&parameter[1].value, GTK_TYPE_BORDER);
146   g_value_set_boxed (&parameter[1].value, &image->slice);
147
148   parameter[2].name = "border-image-repeat";
149   g_value_init (&parameter[2].value, GTK_TYPE_CSS_BORDER_IMAGE_REPEAT);
150   g_value_set_boxed (&parameter[2].value, &image->repeat);
151
152   *n_params = 3;
153   return parameter;
154 }
155
156 void
157 _gtk_border_image_pack (GValue             *value,
158                         GtkStyleProperties *props,
159                         GtkStateFlags       state)
160 {
161   GtkBorderImage *image;
162   cairo_pattern_t *source;
163   GtkBorder *slice;
164   GtkCssBorderImageRepeat *repeat;
165
166   gtk_style_properties_get (props, state,
167                             "border-image-source", &source,
168                             "border-image-slice", &slice,
169                             "border-image-repeat", &repeat,
170                             NULL);
171
172   if (source == NULL)
173     {
174       g_value_take_boxed (value, NULL);
175     }
176   else
177     {
178       image = _gtk_border_image_new (source, slice, repeat);
179       g_value_take_boxed (value, image);
180
181       cairo_pattern_destroy (source);
182     }
183
184   if (slice != NULL)
185     gtk_border_free (slice);
186
187   if (repeat != NULL)
188     g_free (repeat);
189 }
190
191 static void
192 render_corner (cairo_t         *cr,
193                gdouble          corner_x,
194                gdouble          corner_y,
195                gdouble          corner_width,
196                gdouble          corner_height,
197                cairo_surface_t *surface,
198                gdouble          image_width,
199                gdouble          image_height)
200 {
201   if (corner_width == 0 || corner_height == 0)
202     return;
203
204   cairo_save (cr);
205
206   cairo_translate (cr, corner_x, corner_y);
207   cairo_scale (cr,
208                corner_width / image_width,
209                corner_height / image_height);
210   cairo_set_source_surface (cr, surface, 0, 0);
211
212   /* use the nearest filter for scaling, to avoid color blending */
213   cairo_pattern_set_filter (cairo_get_source (cr), CAIRO_FILTER_NEAREST);
214
215   cairo_paint (cr);
216
217   cairo_restore (cr);
218 }
219
220 static cairo_surface_t *
221 create_spaced_surface (cairo_surface_t *tile,
222                        gdouble          tile_width,
223                        gdouble          tile_height,
224                        gdouble          width,
225                        gdouble          height,
226                        GtkOrientation   orientation)
227 {
228   gint n_repeats, idx;
229   gdouble avail_space, step;
230   cairo_surface_t *retval;
231   cairo_t *cr;
232
233   n_repeats = (orientation == GTK_ORIENTATION_HORIZONTAL) ?
234     (gint) floor (width / tile_width) :
235     (gint) floor (height / tile_height);
236
237   avail_space = (orientation == GTK_ORIENTATION_HORIZONTAL) ?
238     width - (n_repeats * tile_width) :
239     height - (n_repeats * tile_height);
240   step = avail_space / (n_repeats + 2);
241
242   retval = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
243                                        width, height);
244   cr = cairo_create (retval);
245   idx = 0;
246
247   while (idx < n_repeats)
248     {
249       cairo_save (cr);
250
251       if (orientation == GTK_ORIENTATION_HORIZONTAL)
252         cairo_set_source_surface (cr, tile,
253                                   ((idx + 1) * step) + (idx * tile_width), 0);
254       else
255         cairo_set_source_surface (cr, tile,
256                                   0, ((idx + 1 ) * step) + (idx * tile_height));
257
258       cairo_paint (cr);
259       cairo_restore (cr);
260
261       idx++;
262     }
263
264   cairo_destroy (cr);
265
266   return retval;
267 }
268
269 static void
270 render_border (cairo_t              *cr,
271                gdouble               total_width,
272                gdouble               total_height,
273                cairo_surface_t      *surface,
274                gdouble               surface_width,
275                gdouble               surface_height,
276                guint                 side,
277                GtkBorder            *border_area,
278                GtkCssBorderImageRepeat *repeat)
279 {
280   gdouble target_x, target_y;
281   gdouble target_width, target_height;
282   GdkRectangle image_area;
283   cairo_pattern_t *pattern;
284   gboolean repeat_pattern;
285
286   if (surface == NULL)
287     return;
288
289   cairo_surface_reference (surface);
290   repeat_pattern = FALSE;
291
292   if (side == SIDE_TOP || side == SIDE_BOTTOM)
293     {
294       target_height = (side == SIDE_TOP) ? (border_area->top) : (border_area->bottom);
295       target_width = surface_width * (target_height / surface_height);
296     }
297   else
298     {
299       target_width = (side == SIDE_LEFT) ? (border_area->left) : (border_area->right);
300       target_height = surface_height * (target_width / surface_width);
301     }
302
303   if (side == SIDE_TOP || side == SIDE_BOTTOM)
304     {
305       image_area.x = border_area->left;
306       image_area.y = (side == SIDE_TOP) ? 0 : (total_height - border_area->bottom);
307       image_area.width = total_width - border_area->left - border_area->right;
308       image_area.height = (side == SIDE_TOP) ? border_area->top : border_area->bottom;
309
310       target_x = border_area->left;
311       target_y = (side == SIDE_TOP) ? 0 : (total_height - border_area->bottom);
312
313       if (repeat->vrepeat == GTK_CSS_REPEAT_STYLE_NONE)
314         {
315           target_width = image_area.width;
316         }
317       else if (repeat->vrepeat == GTK_CSS_REPEAT_STYLE_REPEAT)
318         {
319           repeat_pattern = TRUE;
320
321           target_x = border_area->left + (total_width - border_area->left - border_area->right) / 2;
322           target_y = ((side == SIDE_TOP) ? 0 : (total_height - border_area->bottom)) / 2;
323         }
324       else if (repeat->vrepeat == GTK_CSS_REPEAT_STYLE_ROUND)
325         {
326           gint n_repeats;
327
328           repeat_pattern = TRUE;
329
330           n_repeats = (gint) floor (image_area.width / surface_width);
331           target_width = image_area.width / n_repeats;
332         }
333       else if (repeat->vrepeat == GTK_CSS_REPEAT_STYLE_SPACE)
334         {
335           cairo_surface_t *spaced_surface;
336
337           spaced_surface = create_spaced_surface (surface,
338                                                   surface_width, surface_height,
339                                                   image_area.width, surface_height,
340                                                   GTK_ORIENTATION_HORIZONTAL);
341           cairo_surface_destroy (surface);
342           surface = spaced_surface;
343
344           /* short-circuit hscaling */
345           target_width = surface_width = cairo_image_surface_get_width (spaced_surface);
346         }
347     }
348   else
349     {
350       image_area.x = (side == SIDE_LEFT) ? 0 : (total_width - border_area->right);
351       image_area.y = border_area->top;
352       image_area.width = (side == SIDE_LEFT) ? border_area->left : border_area->right;
353       image_area.height = total_height - border_area->top - border_area->bottom;
354
355       target_x = (side == SIDE_LEFT) ? 0 : (total_width - border_area->right);
356       target_y = border_area->top;
357
358       if (repeat->hrepeat == GTK_CSS_REPEAT_STYLE_NONE)
359         {
360           target_height = total_height - border_area->top - border_area->bottom;
361         }
362       else if (repeat->hrepeat == GTK_CSS_REPEAT_STYLE_REPEAT)
363         {
364           repeat_pattern = TRUE;
365
366           target_height = total_height - border_area->top - border_area->bottom;
367           target_x = (side == SIDE_LEFT) ? 0 : (total_width - border_area->right) / 2;
368           target_y = border_area->top + (total_height - border_area->top - border_area->bottom) / 2;
369         }
370       else if (repeat->hrepeat == GTK_CSS_REPEAT_STYLE_ROUND)
371         {
372           gint n_repeats;
373
374           repeat_pattern = TRUE;
375
376           n_repeats = (gint) floor (image_area.height / surface_height);
377           target_height = image_area.height / n_repeats;
378         }
379       else if (repeat->hrepeat == GTK_CSS_REPEAT_STYLE_SPACE)
380         {
381           cairo_surface_t *spaced_surface;
382
383           spaced_surface = create_spaced_surface (surface,
384                                                   surface_width, surface_height,
385                                                   surface_width, image_area.height,
386                                                   GTK_ORIENTATION_VERTICAL);
387           cairo_surface_destroy (surface);
388           surface = spaced_surface;
389
390           /* short-circuit vscaling */
391           target_height = surface_height = cairo_image_surface_get_height (spaced_surface);
392         }
393     }
394
395   if (target_width == 0 || target_height == 0)
396     return;
397
398   cairo_save (cr);
399
400   pattern = cairo_pattern_create_for_surface (surface);
401
402   gdk_cairo_rectangle (cr, &image_area);
403   cairo_clip (cr);
404
405   cairo_translate (cr,
406                    target_x, target_y);
407
408   /* use the nearest filter for scaling, to avoid color blending */
409   cairo_pattern_set_filter (pattern, CAIRO_FILTER_NEAREST);
410   
411   if (repeat_pattern)
412     cairo_pattern_set_extend (pattern, CAIRO_EXTEND_REPEAT);
413
414   cairo_scale (cr,
415                target_width / surface_width,
416                target_height / surface_height);
417
418   cairo_set_source (cr, pattern);
419   cairo_paint (cr);
420
421   cairo_restore (cr);
422
423   cairo_pattern_destroy (pattern);
424   cairo_surface_destroy (surface);
425 }
426
427 void
428 _gtk_border_image_render (GtkBorderImage   *image,
429                           GtkBorder        *border_width,
430                           cairo_t          *cr,
431                           gdouble           x,
432                           gdouble           y,
433                           gdouble           width,
434                           gdouble           height)
435 {
436   cairo_surface_t *surface, *slice;
437   gdouble slice_width, slice_height, surface_width, surface_height;
438
439   if (cairo_pattern_get_type (image->source) != CAIRO_PATTERN_TYPE_SURFACE)
440     {
441       cairo_matrix_t matrix;
442       cairo_t *surface_cr;
443
444       surface_width = width;
445       surface_height = height;
446
447       cairo_matrix_init_scale (&matrix, 1 / width, 1 / height);
448       cairo_pattern_set_matrix (image->source, &matrix);
449
450       surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height);
451       surface_cr = cairo_create (surface);
452       cairo_set_source (surface_cr, image->source);
453       cairo_paint (surface_cr);
454
455       cairo_destroy (surface_cr);
456     }
457   else
458     {
459       cairo_pattern_get_surface (image->source, &surface);
460       cairo_surface_reference (surface);
461
462       surface_width = cairo_image_surface_get_width (surface);
463       surface_height = cairo_image_surface_get_height (surface);
464     }
465
466   cairo_save (cr);
467   cairo_translate (cr, x, y);
468
469   if ((image->slice.left + image->slice.right) < surface_width)
470     {
471       /* Top side */
472       slice_width = surface_width - image->slice.left - image->slice.right;
473       slice_height = image->slice.top;
474       slice = cairo_surface_create_for_rectangle
475         (surface,
476          image->slice.left, 0,
477          slice_width, slice_height);
478
479       render_border (cr,
480                      width, height,
481                      slice,
482                      slice_width, slice_height,
483                      SIDE_TOP,
484                      border_width,
485                      &image->repeat);
486
487       cairo_surface_destroy (slice);
488
489       /* Bottom side */
490       slice_height = image->slice.bottom;
491       slice = cairo_surface_create_for_rectangle
492         (surface,
493          image->slice.left, surface_height - image->slice.bottom,
494          slice_width, slice_height);
495
496       render_border (cr,
497                      width, height,
498                      slice,
499                      slice_width, slice_height,
500                      SIDE_BOTTOM,
501                      border_width,
502                      &image->repeat);
503
504       cairo_surface_destroy (slice);
505     }
506
507   if ((image->slice.top + image->slice.bottom) < surface_height)
508     {
509       /* Left side */
510       slice_width = image->slice.left;
511       slice_height = surface_height - image->slice.top - image->slice.bottom;
512       slice = cairo_surface_create_for_rectangle
513         (surface,
514          0, image->slice.top,
515          slice_width, slice_height);
516
517       render_border (cr,
518                      width, height,
519                      slice,
520                      slice_width, slice_height,
521                      SIDE_LEFT,
522                      border_width,
523                      &image->repeat);
524
525       cairo_surface_destroy (slice);
526
527       /* Right side */
528       slice_width = image->slice.right;
529       slice = cairo_surface_create_for_rectangle
530         (surface, 
531          surface_width - image->slice.right, image->slice.top,
532          slice_width, slice_height);
533
534       render_border (cr,
535                      width, height,
536                      slice,
537                      slice_width, slice_height,
538                      SIDE_RIGHT,
539                      border_width,
540                      &image->repeat);
541
542       cairo_surface_destroy (slice);
543     }
544
545   /* Top/left corner */
546   slice_width = image->slice.left;
547   slice_height = image->slice.top;
548   slice = cairo_surface_create_for_rectangle
549     (surface, 
550      0, 0,
551      slice_width, slice_height);
552
553   render_corner (cr,
554                  0, 0,
555                  border_width->left, border_width->top,
556                  slice,
557                  slice_width, slice_height);
558
559   cairo_surface_destroy (slice);
560
561   /* Top/right corner */
562   slice_width = image->slice.right;
563   slice = cairo_surface_create_for_rectangle
564     (surface,
565      surface_width - image->slice.right, 0,
566      slice_width, slice_height);
567
568   render_corner (cr,
569                  width - border_width->right, 0,
570                  border_width->right, border_width->top,
571                  slice,
572                  slice_width, slice_height);
573
574   cairo_surface_destroy (slice);
575
576   /* Bottom/left corner */
577   slice_width = image->slice.left;
578   slice_height = image->slice.bottom;
579   slice = cairo_surface_create_for_rectangle
580     (surface,
581      0, surface_height - image->slice.bottom,
582      slice_width, slice_height);
583
584   render_corner (cr,
585                  0, height - border_width->bottom,
586                  border_width->left, border_width->bottom,
587                  slice,
588                  slice_width, slice_height);
589
590   cairo_surface_destroy (slice);
591
592   /* Bottom/right corner */
593   slice_width = image->slice.right;
594   slice = cairo_surface_create_for_rectangle
595     (surface,
596      surface_width - image->slice.right,
597      surface_height - image->slice.bottom,
598      slice_width, slice_height);
599
600   render_corner (cr,
601                  width - border_width->right, height - border_width->bottom,
602                  border_width->right, border_width->bottom,
603                  slice,
604                  slice_width, slice_height);
605
606   cairo_surface_destroy (slice);
607
608   cairo_restore (cr);
609 }