]> Pileus Git - ~andy/gtk/blob - gtk/gtkcssimagegradient.c
cssimage: Fix huge memleak
[~andy/gtk] / gtk / gtkcssimagegradient.c
1 /*
2  * Copyright © 2011 Red Hat Inc.
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2.1 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library. If not, see <http://www.gnu.org/licenses/>.
16  *
17  * Authors: Benjamin Otte <otte@gnome.org>
18  */
19
20 #include "config.h"
21
22 #include "gtkcssimagegradientprivate.h"
23
24 #include "gtkcssprovider.h"
25 #include "gtkgradientprivate.h"
26 #include "gtksymboliccolorprivate.h"
27
28 G_DEFINE_TYPE (GtkCssImageGradient, _gtk_css_image_gradient, GTK_TYPE_CSS_IMAGE)
29
30 static GtkCssImage *
31 gtk_css_image_gradient_compute (GtkCssImage             *image,
32                                 guint                    property_id,
33                                 GtkStyleProviderPrivate *provider,
34                                 GtkCssComputedValues    *values,
35                                 GtkCssComputedValues    *parent_values,
36                                 GtkCssDependencies      *dependencies)
37 {
38   GtkCssImageGradient *gradient = GTK_CSS_IMAGE_GRADIENT (image);
39   GtkCssImageGradient *copy;
40
41   if (gradient->pattern)
42     return g_object_ref (gradient);
43
44   copy = g_object_new (GTK_TYPE_CSS_IMAGE_GRADIENT, NULL);
45   copy->gradient = gtk_gradient_ref (gradient->gradient);
46   copy->pattern = _gtk_gradient_resolve_full (copy->gradient, provider, values, parent_values, dependencies);
47
48   return GTK_CSS_IMAGE (copy);
49 }
50
51 static cairo_pattern_t *
52 fade_pattern (cairo_pattern_t *pattern,
53               double           opacity)
54 {
55   double x0, y0, x1, y1, r0, r1;
56   cairo_pattern_t *result;
57   int i, n;
58
59   switch (cairo_pattern_get_type (pattern))
60     {
61     case CAIRO_PATTERN_TYPE_LINEAR:
62       cairo_pattern_get_linear_points (pattern, &x0, &y0, &x1, &y1);
63       result = cairo_pattern_create_linear (x0, y0, x1, y1);
64       break;
65     case CAIRO_PATTERN_TYPE_RADIAL:
66       cairo_pattern_get_radial_circles (pattern, &x0, &y0, &r0, &x1, &y1, &r1);
67       result = cairo_pattern_create_radial (x0, y0, r0, x1, y1, r1);
68       break;
69     default:
70       g_return_val_if_reached (NULL);
71     }
72
73   cairo_pattern_get_color_stop_count (pattern, &n);
74   for (i = 0; i < n; i++)
75     {
76       double o, r, g, b, a;
77
78       cairo_pattern_get_color_stop_rgba (pattern, i, &o, &r, &g, &b, &a);
79       cairo_pattern_add_color_stop_rgba (result, o, r, g, b, a * opacity);
80     }
81
82   return result;
83 }
84
85 static cairo_pattern_t *
86 transition_pattern (cairo_pattern_t *start,
87                     cairo_pattern_t *end,
88                     double           progress)
89 {
90   double sx0, sy0, sx1, sy1, sr0, sr1, ex0, ey0, ex1, ey1, er0, er1;
91   cairo_pattern_t *result;
92   int i, n;
93
94   progress = CLAMP (progress, 0.0, 1.0);
95
96   if (end == NULL)
97     return fade_pattern (start, 1.0 - progress);
98
99   g_assert (cairo_pattern_get_type (start) == cairo_pattern_get_type (end));
100
101   switch (cairo_pattern_get_type (start))
102     {
103     case CAIRO_PATTERN_TYPE_LINEAR:
104       cairo_pattern_get_linear_points (start, &sx0, &sy0, &sx1, &sy1);
105       cairo_pattern_get_linear_points (end, &ex0, &ey0, &ex1, &ey1);
106       result = cairo_pattern_create_linear ((1 - progress) * sx0 + progress * ex0,
107                                             (1 - progress) * sx1 + progress * ex1,
108                                             (1 - progress) * sy0 + progress * ey0,
109                                             (1 - progress) * sy1 + progress * ey1);
110       break;
111     case CAIRO_PATTERN_TYPE_RADIAL:
112       cairo_pattern_get_radial_circles (start, &sx0, &sy0, &sr0, &sx1, &sy1, &sr1);
113       cairo_pattern_get_radial_circles (end, &ex0, &ey0, &er0, &ex1, &ey1, &er1);
114       result = cairo_pattern_create_radial ((1 - progress) * sx0 + progress * ex0,
115                                             (1 - progress) * sy0 + progress * ey0,
116                                             (1 - progress) * sr0 + progress * er0,
117                                             (1 - progress) * sx1 + progress * ex1,
118                                             (1 - progress) * sy1 + progress * ey1,
119                                             (1 - progress) * sr1 + progress * er1);
120       break;
121     default:
122       g_return_val_if_reached (NULL);
123     }
124
125   cairo_pattern_get_color_stop_count (start, &n);
126   for (i = 0; i < n; i++)
127     {
128       double so, sr, sg, sb, sa, eo, er, eg, eb, ea;
129
130       cairo_pattern_get_color_stop_rgba (start, i, &so, &sr, &sg, &sb, &sa);
131       cairo_pattern_get_color_stop_rgba (end, i, &eo, &er, &eg, &eb, &ea);
132
133       cairo_pattern_add_color_stop_rgba (result,
134                                          (1 - progress) * so + progress * eo,
135                                          (1 - progress) * sr + progress * er,
136                                          (1 - progress) * sg + progress * eg,
137                                          (1 - progress) * sb + progress * eb,
138                                          (1 - progress) * sa + progress * ea);
139     }
140
141   return result;
142 }
143
144 static GtkCssImage *
145 gtk_css_image_gradient_transition (GtkCssImage *start_image,
146                                    GtkCssImage *end_image,
147                                    guint        property_id,
148                                    double       progress)
149 {
150   GtkGradient *start_gradient, *end_gradient, *gradient;
151   cairo_pattern_t *start_pattern, *end_pattern;
152   GtkCssImageGradient *result;
153
154   start_gradient = GTK_CSS_IMAGE_GRADIENT (start_image)->gradient;
155   start_pattern = GTK_CSS_IMAGE_GRADIENT (start_image)->pattern;
156   if (end_image == NULL)
157     {
158       end_gradient = NULL;
159       end_pattern = NULL;
160     }
161   else
162     {
163       if (!GTK_IS_CSS_IMAGE_GRADIENT (end_image))
164         return GTK_CSS_IMAGE_CLASS (_gtk_css_image_gradient_parent_class)->transition (start_image, end_image, property_id, progress);
165
166       end_gradient = GTK_CSS_IMAGE_GRADIENT (end_image)->gradient;
167       end_pattern = GTK_CSS_IMAGE_GRADIENT (end_image)->pattern;
168     }
169
170   gradient = _gtk_gradient_transition (start_gradient, end_gradient, property_id, progress);
171   if (gradient == NULL)
172     return GTK_CSS_IMAGE_CLASS (_gtk_css_image_gradient_parent_class)->transition (start_image, end_image, property_id, progress);
173
174   result = g_object_new (GTK_TYPE_CSS_IMAGE_GRADIENT, NULL);
175   result->gradient = gradient;
176   result->pattern = transition_pattern (start_pattern, end_pattern, progress);
177
178   return GTK_CSS_IMAGE (result);
179 }
180
181 static gboolean
182 gtk_css_image_gradient_draw_circle (GtkCssImageGradient *image,
183                                     cairo_t              *cr,
184                                     double               width,
185                                     double               height)
186 {
187   cairo_pattern_t *pattern = image->pattern;
188   double x0, y0, x1, y1, r0, r1;
189   GdkRGBA color0, color1;
190   double offset0, offset1;
191   int n_stops;
192
193   if (cairo_pattern_get_type (pattern) != CAIRO_PATTERN_TYPE_RADIAL)
194     return FALSE;
195   if (cairo_pattern_get_extend (pattern) != CAIRO_EXTEND_PAD)
196     return FALSE;
197
198   cairo_pattern_get_radial_circles (pattern, &x0, &y0, &r0, &x1, &y1, &r1);
199
200   if (x0 != x1 ||
201       y0 != y1 ||
202       r0 != 0.0)
203     return FALSE;
204
205   cairo_pattern_get_color_stop_count (pattern, &n_stops);
206   if (n_stops != 2)
207     return FALSE;
208
209   cairo_pattern_get_color_stop_rgba (pattern, 0, &offset0, &color0.red, &color0.green, &color0.blue, &color0.alpha);
210   cairo_pattern_get_color_stop_rgba (pattern, 1, &offset1, &color1.red, &color1.green, &color1.blue, &color1.alpha);
211   if (offset0 != offset1)
212     return FALSE;
213
214   cairo_scale (cr, width, height);
215
216   cairo_rectangle (cr, 0, 0, 1, 1);
217   cairo_clip (cr);
218
219   gdk_cairo_set_source_rgba (cr, &color1);
220   cairo_paint (cr);
221
222   gdk_cairo_set_source_rgba (cr, &color0);
223   cairo_arc (cr, x1, y1, r1 * offset1, 0, 2 * G_PI);
224   cairo_fill (cr);
225
226   return TRUE;
227 }
228
229 static void
230 gtk_css_image_gradient_draw (GtkCssImage        *image,
231                              cairo_t            *cr,
232                              double              width,
233                              double              height)
234 {
235   GtkCssImageGradient *gradient = GTK_CSS_IMAGE_GRADIENT (image);
236
237   if (!gradient->pattern)
238     {
239       g_warning ("trying to paint unresolved gradient");
240       return;
241     }
242
243   if (gtk_css_image_gradient_draw_circle (gradient, cr, width, height))
244     return;
245
246   cairo_scale (cr, width, height);
247
248   cairo_rectangle (cr, 0, 0, 1, 1);
249   cairo_set_source (cr, gradient->pattern);
250   cairo_fill (cr);
251 }
252
253 static gboolean
254 gtk_css_image_gradient_parse (GtkCssImage  *image,
255                               GtkCssParser *parser)
256 {
257   GtkCssImageGradient *gradient = GTK_CSS_IMAGE_GRADIENT (image);
258
259   gradient->gradient = _gtk_gradient_parse (parser);
260
261   return gradient->gradient != NULL;
262 }
263
264 static void
265 gtk_css_image_gradient_print (GtkCssImage *image,
266                               GString     *string)
267 {
268   GtkCssImageGradient *gradient = GTK_CSS_IMAGE_GRADIENT (image);
269   char *s;
270
271   s = gtk_gradient_to_string (gradient->gradient);
272   g_string_append (string, s);
273   g_free (s);
274 }
275
276 static void
277 gtk_css_image_gradient_dispose (GObject *object)
278 {
279   GtkCssImageGradient *gradient = GTK_CSS_IMAGE_GRADIENT (object);
280
281   if (gradient->gradient)
282     {
283       gtk_gradient_unref (gradient->gradient);
284       gradient->gradient = NULL;
285     }
286   if (gradient->pattern)
287     {
288       cairo_pattern_destroy (gradient->pattern);
289       gradient->pattern = NULL;
290     }
291
292   G_OBJECT_CLASS (_gtk_css_image_gradient_parent_class)->dispose (object);
293 }
294
295 static void
296 _gtk_css_image_gradient_class_init (GtkCssImageGradientClass *klass)
297 {
298   GtkCssImageClass *image_class = GTK_CSS_IMAGE_CLASS (klass);
299   GObjectClass *object_class = G_OBJECT_CLASS (klass);
300
301   image_class->compute = gtk_css_image_gradient_compute;
302   image_class->transition = gtk_css_image_gradient_transition;
303   image_class->draw = gtk_css_image_gradient_draw;
304   image_class->parse = gtk_css_image_gradient_parse;
305   image_class->print = gtk_css_image_gradient_print;
306
307   object_class->dispose = gtk_css_image_gradient_dispose;
308 }
309
310 static void
311 _gtk_css_image_gradient_init (GtkCssImageGradient *image_gradient)
312 {
313 }
314
315 GtkGradient *
316 _gtk_gradient_parse (GtkCssParser *parser)
317 {
318   GtkGradient *gradient;
319   cairo_pattern_type_t type;
320   gdouble coords[6];
321   guint i;
322
323   g_return_val_if_fail (parser != NULL, NULL);
324
325   if (!_gtk_css_parser_try (parser, "-gtk-gradient", TRUE))
326     {
327       _gtk_css_parser_error (parser,
328                              "Expected '-gtk-gradient'");
329       return NULL;
330     }
331
332   if (!_gtk_css_parser_try (parser, "(", TRUE))
333     {
334       _gtk_css_parser_error (parser,
335                              "Expected '(' after '-gtk-gradient'");
336       return NULL;
337     }
338
339   /* Parse gradient type */
340   if (_gtk_css_parser_try (parser, "linear", TRUE))
341     type = CAIRO_PATTERN_TYPE_LINEAR;
342   else if (_gtk_css_parser_try (parser, "radial", TRUE))
343     type = CAIRO_PATTERN_TYPE_RADIAL;
344   else
345     {
346       _gtk_css_parser_error (parser,
347                              "Gradient type must be 'radial' or 'linear'");
348       return NULL;
349     }
350
351   /* Parse start/stop position parameters */
352   for (i = 0; i < 2; i++)
353     {
354       if (! _gtk_css_parser_try (parser, ",", TRUE))
355         {
356           _gtk_css_parser_error (parser,
357                                  "Expected ','");
358           return NULL;
359         }
360
361       if (_gtk_css_parser_try (parser, "left", TRUE))
362         coords[i * 3] = 0;
363       else if (_gtk_css_parser_try (parser, "right", TRUE))
364         coords[i * 3] = 1;
365       else if (_gtk_css_parser_try (parser, "center", TRUE))
366         coords[i * 3] = 0.5;
367       else if (!_gtk_css_parser_try_double (parser, &coords[i * 3]))
368         {
369           _gtk_css_parser_error (parser,
370                                  "Expected a valid X coordinate");
371           return NULL;
372         }
373
374       if (_gtk_css_parser_try (parser, "top", TRUE))
375         coords[i * 3 + 1] = 0;
376       else if (_gtk_css_parser_try (parser, "bottom", TRUE))
377         coords[i * 3 + 1] = 1;
378       else if (_gtk_css_parser_try (parser, "center", TRUE))
379         coords[i * 3 + 1] = 0.5;
380       else if (!_gtk_css_parser_try_double (parser, &coords[i * 3 + 1]))
381         {
382           _gtk_css_parser_error (parser,
383                                  "Expected a valid Y coordinate");
384           return NULL;
385         }
386
387       if (type == CAIRO_PATTERN_TYPE_RADIAL)
388         {
389           /* Parse radius */
390           if (! _gtk_css_parser_try (parser, ",", TRUE))
391             {
392               _gtk_css_parser_error (parser,
393                                      "Expected ','");
394               return NULL;
395             }
396
397           if (! _gtk_css_parser_try_double (parser, &coords[(i * 3) + 2]))
398             {
399               _gtk_css_parser_error (parser,
400                                      "Expected a numer for the radius");
401               return NULL;
402             }
403         }
404     }
405
406   if (type == CAIRO_PATTERN_TYPE_LINEAR)
407     gradient = gtk_gradient_new_linear (coords[0], coords[1], coords[3], coords[4]);
408   else
409     gradient = gtk_gradient_new_radial (coords[0], coords[1], coords[2],
410                                         coords[3], coords[4], coords[5]);
411
412   while (_gtk_css_parser_try (parser, ",", TRUE))
413     {
414       GtkSymbolicColor *color;
415       gdouble position;
416
417       if (_gtk_css_parser_try (parser, "from", TRUE))
418         {
419           position = 0;
420
421           if (!_gtk_css_parser_try (parser, "(", TRUE))
422             {
423               gtk_gradient_unref (gradient);
424               _gtk_css_parser_error (parser,
425                                      "Expected '('");
426               return NULL;
427             }
428
429         }
430       else if (_gtk_css_parser_try (parser, "to", TRUE))
431         {
432           position = 1;
433
434           if (!_gtk_css_parser_try (parser, "(", TRUE))
435             {
436               gtk_gradient_unref (gradient);
437               _gtk_css_parser_error (parser,
438                                      "Expected '('");
439               return NULL;
440             }
441
442         }
443       else if (_gtk_css_parser_try (parser, "color-stop", TRUE))
444         {
445           if (!_gtk_css_parser_try (parser, "(", TRUE))
446             {
447               gtk_gradient_unref (gradient);
448               _gtk_css_parser_error (parser,
449                                      "Expected '('");
450               return NULL;
451             }
452
453           if (!_gtk_css_parser_try_double (parser, &position))
454             {
455               gtk_gradient_unref (gradient);
456               _gtk_css_parser_error (parser,
457                                      "Expected a valid number");
458               return NULL;
459             }
460
461           if (!_gtk_css_parser_try (parser, ",", TRUE))
462             {
463               gtk_gradient_unref (gradient);
464               _gtk_css_parser_error (parser,
465                                      "Expected a comma");
466               return NULL;
467             }
468         }
469       else
470         {
471           gtk_gradient_unref (gradient);
472           _gtk_css_parser_error (parser,
473                                  "Not a valid color-stop definition");
474           return NULL;
475         }
476
477       color = _gtk_symbolic_color_new_take_value (_gtk_css_symbolic_value_new (parser));
478       if (color == NULL)
479         {
480           gtk_gradient_unref (gradient);
481           return NULL;
482         }
483
484       gtk_gradient_add_color_stop (gradient, position, color);
485       gtk_symbolic_color_unref (color);
486
487       if (!_gtk_css_parser_try (parser, ")", TRUE))
488         {
489           gtk_gradient_unref (gradient);
490           _gtk_css_parser_error (parser,
491                                  "Expected ')'");
492           return NULL;
493         }
494     }
495
496   if (!_gtk_css_parser_try (parser, ")", TRUE))
497     {
498       gtk_gradient_unref (gradient);
499       _gtk_css_parser_error (parser,
500                              "Expected ')'");
501       return NULL;
502     }
503
504   return gradient;
505 }