1 /* GTK - The GIMP Toolkit
2  * Copyright (C) 2017 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 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 
18 #include "config.h"
19 
20 #include "gtkcssbgsizevalueprivate.h"
21 
22 #include <math.h>
23 #include <string.h>
24 
25 #include "gtkcssfiltervalueprivate.h"
26 #include "gtkcssnumbervalueprivate.h"
27 #include "gtkcssshadowvalueprivate.h"
28 
29 typedef union _GtkCssFilter GtkCssFilter;
30 
31 typedef enum {
32   GTK_CSS_FILTER_NONE,
33   GTK_CSS_FILTER_BLUR,
34   GTK_CSS_FILTER_BRIGHTNESS,
35   GTK_CSS_FILTER_CONTRAST,
36   GTK_CSS_FILTER_DROP_SHADOW,
37   GTK_CSS_FILTER_GRAYSCALE,
38   GTK_CSS_FILTER_HUE_ROTATE,
39   GTK_CSS_FILTER_INVERT,
40   GTK_CSS_FILTER_OPACITY,
41   GTK_CSS_FILTER_SATURATE,
42   GTK_CSS_FILTER_SEPIA
43 } GtkCssFilterType;
44 
45 union _GtkCssFilter {
46   GtkCssFilterType       type;
47   struct {
48     GtkCssFilterType     type;
49     GtkCssValue         *value;
50   }            blur, brightness, contrast, drop_shadow, grayscale, hue_rotate, invert, opacity, saturate, sepia;
51 };
52 
53 struct _GtkCssValue {
54   GTK_CSS_VALUE_BASE
55   guint                 n_filters;
56   GtkCssFilter          filters[1];
57 };
58 
59 static GtkCssValue *    gtk_css_filter_value_alloc           (guint                  n_values);
60 static gboolean         gtk_css_filter_value_is_none         (const GtkCssValue     *value);
61 
62 static void
gtk_css_filter_clear(GtkCssFilter * filter)63 gtk_css_filter_clear (GtkCssFilter *filter)
64 {
65   switch (filter->type)
66     {
67     case GTK_CSS_FILTER_BRIGHTNESS:
68       _gtk_css_value_unref (filter->brightness.value);
69       break;
70     case GTK_CSS_FILTER_CONTRAST:
71       _gtk_css_value_unref (filter->contrast.value);
72       break;
73     case GTK_CSS_FILTER_GRAYSCALE:
74       _gtk_css_value_unref (filter->grayscale.value);
75       break;
76     case GTK_CSS_FILTER_HUE_ROTATE:
77       _gtk_css_value_unref (filter->hue_rotate.value);
78       break;
79     case GTK_CSS_FILTER_INVERT:
80       _gtk_css_value_unref (filter->invert.value);
81       break;
82     case GTK_CSS_FILTER_OPACITY:
83       _gtk_css_value_unref (filter->opacity.value);
84       break;
85     case GTK_CSS_FILTER_SATURATE:
86       _gtk_css_value_unref (filter->saturate.value);
87       break;
88     case GTK_CSS_FILTER_SEPIA:
89       _gtk_css_value_unref (filter->sepia.value);
90       break;
91     case GTK_CSS_FILTER_BLUR:
92       _gtk_css_value_unref (filter->blur.value);
93       break;
94     case GTK_CSS_FILTER_DROP_SHADOW:
95       _gtk_css_value_unref (filter->drop_shadow.value);
96       break;
97     case GTK_CSS_FILTER_NONE:
98     default:
99       g_assert_not_reached ();
100       break;
101     }
102 }
103 
104 static void
gtk_css_filter_init_identity(GtkCssFilter * filter,GtkCssFilterType type)105 gtk_css_filter_init_identity (GtkCssFilter     *filter,
106                               GtkCssFilterType  type)
107 {
108   switch (type)
109     {
110     case GTK_CSS_FILTER_BRIGHTNESS:
111       filter->brightness.value = _gtk_css_number_value_new (1, GTK_CSS_NUMBER);
112       break;
113     case GTK_CSS_FILTER_CONTRAST:
114       filter->contrast.value = _gtk_css_number_value_new (1, GTK_CSS_NUMBER);
115       break;
116     case GTK_CSS_FILTER_GRAYSCALE:
117       filter->grayscale.value = _gtk_css_number_value_new (0, GTK_CSS_NUMBER);
118       break;
119     case GTK_CSS_FILTER_HUE_ROTATE:
120       filter->hue_rotate.value = _gtk_css_number_value_new (0, GTK_CSS_DEG);
121       break;
122     case GTK_CSS_FILTER_INVERT:
123       filter->invert.value = _gtk_css_number_value_new (0, GTK_CSS_NUMBER);
124       break;
125     case GTK_CSS_FILTER_OPACITY:
126       filter->opacity.value = _gtk_css_number_value_new (1, GTK_CSS_NUMBER);
127       break;
128     case GTK_CSS_FILTER_SATURATE:
129       filter->saturate.value = _gtk_css_number_value_new (1, GTK_CSS_NUMBER);
130       break;
131     case GTK_CSS_FILTER_SEPIA:
132       filter->sepia.value = _gtk_css_number_value_new (0, GTK_CSS_NUMBER);
133       break;
134     case GTK_CSS_FILTER_BLUR:
135       filter->blur.value = _gtk_css_number_value_new (0, GTK_CSS_PX);
136       break;
137     case GTK_CSS_FILTER_DROP_SHADOW:
138       filter->drop_shadow.value = gtk_css_shadow_value_new_filter ();
139       break;
140     case GTK_CSS_FILTER_NONE:
141     default:
142       g_assert_not_reached ();
143       break;
144     }
145 
146   filter->type = type;
147 }
148 
149 #define R 0.2126
150 #define G 0.7152
151 #define B 0.0722
152 
153 static gboolean
gtk_css_filter_get_matrix(const GtkCssFilter * filter,graphene_matrix_t * matrix,graphene_vec4_t * offset)154 gtk_css_filter_get_matrix (const GtkCssFilter *filter,
155                            graphene_matrix_t  *matrix,
156                            graphene_vec4_t    *offset)
157 {
158   double value;
159 
160   switch (filter->type)
161     {
162     case GTK_CSS_FILTER_BRIGHTNESS:
163       value = _gtk_css_number_value_get (filter->brightness.value, 1.0);
164       graphene_matrix_init_scale (matrix, value, value, value);
165       graphene_vec4_init (offset, 0.0, 0.0, 0.0, 0.0);
166       break;
167 
168     case GTK_CSS_FILTER_CONTRAST:
169       value = _gtk_css_number_value_get (filter->contrast.value, 1.0);
170       graphene_matrix_init_scale (matrix, value, value, value);
171       graphene_vec4_init (offset, 0.5 - 0.5 * value, 0.5 - 0.5 * value, 0.5 - 0.5 * value, 0.0);
172       break;
173 
174     case GTK_CSS_FILTER_GRAYSCALE:
175       value = _gtk_css_number_value_get (filter->grayscale.value, 1.0);
176       graphene_matrix_init_from_float (matrix, (float[16]) {
177                                            1.0 - (1.0 - R) * value, R * value, R * value, 0.0,
178                                            G * value, 1.0 - (1.0 - G) * value, G * value, 0.0,
179                                            B * value, B * value, 1.0 - (1.0 - B) * value, 0.0,
180                                            0.0, 0.0, 0.0, 1.0
181                                        });
182       graphene_vec4_init (offset, 0.0, 0.0, 0.0, 0.0);
183       break;
184 
185     case GTK_CSS_FILTER_HUE_ROTATE:
186       {
187         double c, s;
188         value = _gtk_css_number_value_get (filter->grayscale.value, 1.0) * G_PI / 180.0;
189         c = cos (value);
190         s = sin (value);
191         graphene_matrix_init_from_float (matrix, (float[16]) {
192                                              0.213 + 0.787 * c - 0.213 * s,
193                                              0.213 - 0.213 * c + 0.143 * s,
194                                              0.213 - 0.213 * c - 0.787 * s,
195                                              0,
196                                              0.715 - 0.715 * c - 0.715 * s,
197                                              0.715 + 0.285 * c + 0.140 * s,
198                                              0.715 - 0.715 * c + 0.715 * s,
199                                              0,
200                                              0.072 - 0.072 * c + 0.928 * s,
201                                              0.072 - 0.072 * c - 0.283 * s,
202                                              0.072 + 0.928 * c + 0.072 * s,
203                                              0,
204                                              0, 0, 0, 1
205                                          });
206         graphene_vec4_init (offset, 0.0, 0.0, 0.0, 0.0);
207       }
208       break;
209 
210     case GTK_CSS_FILTER_INVERT:
211       value = _gtk_css_number_value_get (filter->invert.value, 1.0);
212       graphene_matrix_init_scale (matrix, 1.0 - 2 * value, 1.0 - 2 * value, 1.0 - 2 * value);
213       graphene_vec4_init (offset, value, value, value, 0.0);
214       break;
215 
216     case GTK_CSS_FILTER_OPACITY:
217       value = _gtk_css_number_value_get (filter->invert.value, 1.0);
218       graphene_matrix_init_from_float (matrix, (float[16]) {
219                                            1.0, 0.0, 0.0, 0.0,
220                                            0.0, 1.0, 0.0, 0.0,
221                                            0.0, 0.0, 1.0, 0.0,
222                                            0.0, 0.0, 0.0, value
223                                        });
224       graphene_vec4_init (offset, 0.0, 0.0, 0.0, 0.0);
225       break;
226 
227     case GTK_CSS_FILTER_SATURATE:
228       value = _gtk_css_number_value_get (filter->saturate.value, 1.0);
229       graphene_matrix_init_from_float (matrix, (float[16]) {
230                                            R + (1.0 - R) * value, R - R * value, R - R * value, 0.0,
231                                            G - G * value, G + (1.0 - G) * value, G - G * value, 0.0,
232                                            B - B * value, B - B * value, B + (1.0 - B) * value, 0.0,
233                                            0.0, 0.0, 0.0, 1.0
234                                        });
235       graphene_vec4_init (offset, 0.0, 0.0, 0.0, 0.0);
236       break;
237 
238     case GTK_CSS_FILTER_SEPIA:
239       value = _gtk_css_number_value_get (filter->sepia.value, 1.0);
240       graphene_matrix_init_from_float (matrix, (float[16]) {
241                                            1.0 - 0.607 * value, 0.349 * value, 0.272 * value, 0.0,
242                                            0.769 * value, 1.0 - 0.314 * value, 0.534 * value, 0.0,
243                                            0.189 * value, 0.168 * value, 1.0 - 0.869 * value, 0.0,
244                                            0.0, 0.0, 0.0, 1.0
245                                        });
246       graphene_vec4_init (offset, 0.0, 0.0, 0.0, 0.0);
247       break;
248 
249     case GTK_CSS_FILTER_NONE:
250     case GTK_CSS_FILTER_BLUR:
251     case GTK_CSS_FILTER_DROP_SHADOW:
252       return FALSE;
253     default:
254       g_assert_not_reached ();
255       break;
256     }
257 
258   return TRUE;
259 }
260 
261 #undef R
262 #undef G
263 #undef B
264 
265 static int
gtk_css_filter_value_compute_matrix(const GtkCssValue * value,int first,graphene_matrix_t * matrix,graphene_vec4_t * offset)266 gtk_css_filter_value_compute_matrix (const GtkCssValue *value,
267                                      int                first,
268                                      graphene_matrix_t *matrix,
269                                      graphene_vec4_t   *offset)
270 {
271   graphene_matrix_t m, m2;
272   graphene_vec4_t o, o2;
273   int i;
274 
275   if (!gtk_css_filter_get_matrix (&value->filters[first], matrix, offset))
276     return first;
277 
278   for (i = first + 1; i < value->n_filters; i++)
279     {
280       if (!gtk_css_filter_get_matrix (&value->filters[i], &m, &o))
281         return i;
282 
283       graphene_matrix_multiply (matrix, &m, &m2);
284       graphene_matrix_transform_vec4 (&m, offset, &o2);
285 
286       graphene_matrix_init_from_matrix (matrix, &m2);
287       graphene_vec4_add (&o, &o2, offset);
288     }
289 
290   return value->n_filters;
291 }
292 
293 static void
gtk_css_value_filter_free(GtkCssValue * value)294 gtk_css_value_filter_free (GtkCssValue *value)
295 {
296   guint i;
297 
298   for (i = 0; i < value->n_filters; i++)
299     {
300       gtk_css_filter_clear (&value->filters[i]);
301     }
302 
303   g_slice_free1 (sizeof (GtkCssValue) + sizeof (GtkCssFilter) * (value->n_filters - 1), value);
304 }
305 
306 /* returns TRUE if dest == src */
307 static gboolean
gtk_css_filter_compute(GtkCssFilter * dest,GtkCssFilter * src,guint property_id,GtkStyleProvider * provider,GtkCssStyle * style,GtkCssStyle * parent_style)308 gtk_css_filter_compute (GtkCssFilter     *dest,
309                         GtkCssFilter     *src,
310                         guint             property_id,
311                         GtkStyleProvider *provider,
312                         GtkCssStyle      *style,
313                         GtkCssStyle      *parent_style)
314 {
315   dest->type = src->type;
316 
317   switch (src->type)
318     {
319     case GTK_CSS_FILTER_BRIGHTNESS:
320       dest->brightness.value = _gtk_css_value_compute (src->brightness.value, property_id, provider, style, parent_style);
321       return dest->brightness.value == src->brightness.value;
322 
323     case GTK_CSS_FILTER_CONTRAST:
324       dest->contrast.value = _gtk_css_value_compute (src->contrast.value, property_id, provider, style, parent_style);
325       return dest->contrast.value == src->contrast.value;
326 
327     case GTK_CSS_FILTER_GRAYSCALE:
328       dest->grayscale.value = _gtk_css_value_compute (src->grayscale.value, property_id, provider, style, parent_style);
329       return dest->grayscale.value == src->grayscale.value;
330 
331     case GTK_CSS_FILTER_HUE_ROTATE:
332       dest->hue_rotate.value = _gtk_css_value_compute (src->hue_rotate.value, property_id, provider, style, parent_style);
333       return dest->hue_rotate.value == src->hue_rotate.value;
334 
335     case GTK_CSS_FILTER_INVERT:
336       dest->invert.value = _gtk_css_value_compute (src->invert.value, property_id, provider, style, parent_style);
337       return dest->invert.value == src->invert.value;
338 
339     case GTK_CSS_FILTER_OPACITY:
340       dest->opacity.value = _gtk_css_value_compute (src->opacity.value, property_id, provider, style, parent_style);
341       return dest->opacity.value == src->opacity.value;
342 
343     case GTK_CSS_FILTER_SATURATE:
344       dest->saturate.value = _gtk_css_value_compute (src->saturate.value, property_id, provider, style, parent_style);
345       return dest->saturate.value == src->saturate.value;
346 
347     case GTK_CSS_FILTER_SEPIA:
348       dest->sepia.value = _gtk_css_value_compute (src->sepia.value, property_id, provider, style, parent_style);
349       return dest->sepia.value == src->sepia.value;
350 
351     case GTK_CSS_FILTER_BLUR:
352       dest->blur.value = _gtk_css_value_compute (src->blur.value, property_id, provider, style, parent_style);
353       return dest->blur.value == src->blur.value;
354 
355     case GTK_CSS_FILTER_DROP_SHADOW:
356       dest->drop_shadow.value = _gtk_css_value_compute (src->drop_shadow.value, property_id, provider, style, parent_style);
357       return dest->drop_shadow.value == src->drop_shadow.value;
358 
359     case GTK_CSS_FILTER_NONE:
360     default:
361       g_assert_not_reached ();
362       return FALSE;
363     }
364 }
365 
366 static GtkCssValue *
gtk_css_value_filter_compute(GtkCssValue * value,guint property_id,GtkStyleProvider * provider,GtkCssStyle * style,GtkCssStyle * parent_style)367 gtk_css_value_filter_compute (GtkCssValue      *value,
368                               guint             property_id,
369                               GtkStyleProvider *provider,
370                               GtkCssStyle      *style,
371                               GtkCssStyle      *parent_style)
372 {
373   GtkCssValue *result;
374   gboolean changes;
375   guint i;
376 
377   /* Special case the 99% case of "none" */
378   if (gtk_css_filter_value_is_none (value))
379     return _gtk_css_value_ref (value);
380 
381   changes = FALSE;
382   result = gtk_css_filter_value_alloc (value->n_filters);
383 
384   for (i = 0; i < value->n_filters; i++)
385     {
386       changes |= !gtk_css_filter_compute (&result->filters[i],
387                                           &value->filters[i],
388                                           property_id,
389                                           provider,
390                                           style,
391                                           parent_style);
392     }
393 
394   if (!changes)
395     {
396       _gtk_css_value_unref (result);
397       result = _gtk_css_value_ref (value);
398     }
399 
400   return result;
401 }
402 
403 static gboolean
gtk_css_filter_equal(const GtkCssFilter * filter1,const GtkCssFilter * filter2)404 gtk_css_filter_equal (const GtkCssFilter *filter1,
405                       const GtkCssFilter *filter2)
406 {
407   if (filter1->type != filter2->type)
408     return FALSE;
409 
410   switch (filter1->type)
411     {
412     case GTK_CSS_FILTER_BRIGHTNESS:
413       return _gtk_css_value_equal (filter1->brightness.value, filter2->brightness.value);
414 
415     case GTK_CSS_FILTER_CONTRAST:
416       return _gtk_css_value_equal (filter1->contrast.value, filter2->contrast.value);
417 
418     case GTK_CSS_FILTER_GRAYSCALE:
419       return _gtk_css_value_equal (filter1->grayscale.value, filter2->grayscale.value);
420 
421     case GTK_CSS_FILTER_HUE_ROTATE:
422       return _gtk_css_value_equal (filter1->hue_rotate.value, filter2->hue_rotate.value);
423 
424     case GTK_CSS_FILTER_INVERT:
425       return _gtk_css_value_equal (filter1->invert.value, filter2->invert.value);
426 
427     case GTK_CSS_FILTER_OPACITY:
428       return _gtk_css_value_equal (filter1->opacity.value, filter2->opacity.value);
429 
430     case GTK_CSS_FILTER_SATURATE:
431       return _gtk_css_value_equal (filter1->saturate.value, filter2->saturate.value);
432 
433     case GTK_CSS_FILTER_SEPIA:
434       return _gtk_css_value_equal (filter1->sepia.value, filter2->sepia.value);
435 
436     case GTK_CSS_FILTER_BLUR:
437       return _gtk_css_value_equal (filter1->blur.value, filter2->blur.value);
438 
439     case GTK_CSS_FILTER_DROP_SHADOW:
440       return _gtk_css_value_equal (filter1->drop_shadow.value, filter2->drop_shadow.value);
441 
442     case GTK_CSS_FILTER_NONE:
443     default:
444       g_assert_not_reached ();
445       return FALSE;
446     }
447 }
448 
449 static gboolean
gtk_css_value_filter_equal(const GtkCssValue * value1,const GtkCssValue * value2)450 gtk_css_value_filter_equal (const GtkCssValue *value1,
451                             const GtkCssValue *value2)
452 {
453   const GtkCssValue *larger;
454   guint i, n;
455 
456   n = MIN (value1->n_filters, value2->n_filters);
457   for (i = 0; i < n; i++)
458     {
459       if (!gtk_css_filter_equal (&value1->filters[i], &value2->filters[i]))
460         return FALSE;
461     }
462 
463   larger = value1->n_filters > value2->n_filters ? value1 : value2;
464 
465   for (; i < larger->n_filters; i++)
466     {
467       GtkCssFilter filter;
468 
469       gtk_css_filter_init_identity (&filter, larger->filters[i].type);
470 
471       if (!gtk_css_filter_equal (&larger->filters[i], &filter))
472         {
473           gtk_css_filter_clear (&filter);
474           return FALSE;
475         }
476 
477       gtk_css_filter_clear (&filter);
478     }
479 
480   return TRUE;
481 }
482 
483 static void
gtk_css_filter_transition(GtkCssFilter * result,const GtkCssFilter * start,const GtkCssFilter * end,guint property_id,double progress)484 gtk_css_filter_transition (GtkCssFilter       *result,
485                            const GtkCssFilter *start,
486                            const GtkCssFilter *end,
487                            guint               property_id,
488                            double              progress)
489 {
490   result->type = start->type;
491 
492   switch (start->type)
493     {
494     case GTK_CSS_FILTER_BRIGHTNESS:
495       result->brightness.value = _gtk_css_value_transition (start->brightness.value, end->brightness.value, property_id, progress);
496       break;
497 
498     case GTK_CSS_FILTER_CONTRAST:
499       result->contrast.value = _gtk_css_value_transition (start->contrast.value, end->contrast.value, property_id, progress);
500       break;
501 
502     case GTK_CSS_FILTER_GRAYSCALE:
503       result->grayscale.value = _gtk_css_value_transition (start->grayscale.value, end->grayscale.value, property_id, progress);
504       break;
505 
506     case GTK_CSS_FILTER_HUE_ROTATE:
507       result->hue_rotate.value = _gtk_css_value_transition (start->hue_rotate.value, end->hue_rotate.value, property_id, progress);
508       break;
509 
510     case GTK_CSS_FILTER_INVERT:
511       result->invert.value = _gtk_css_value_transition (start->invert.value, end->invert.value, property_id, progress);
512       break;
513 
514     case GTK_CSS_FILTER_OPACITY:
515       result->opacity.value = _gtk_css_value_transition (start->opacity.value, end->opacity.value, property_id, progress);
516       break;
517 
518     case GTK_CSS_FILTER_SATURATE:
519       result->saturate.value = _gtk_css_value_transition (start->saturate.value, end->saturate.value, property_id, progress);
520       break;
521 
522     case GTK_CSS_FILTER_SEPIA:
523       result->sepia.value = _gtk_css_value_transition (start->sepia.value, end->sepia.value, property_id, progress);
524       break;
525 
526     case GTK_CSS_FILTER_BLUR:
527       result->blur.value = _gtk_css_value_transition (start->blur.value, end->blur.value, property_id, progress);
528       break;
529 
530     case GTK_CSS_FILTER_DROP_SHADOW:
531       result->drop_shadow.value = _gtk_css_value_transition (start->drop_shadow.value, end->drop_shadow.value, property_id, progress);
532       break;
533 
534     case GTK_CSS_FILTER_NONE:
535     default:
536       g_assert_not_reached ();
537       break;
538     }
539 }
540 
541 static GtkCssValue *
gtk_css_value_filter_transition(GtkCssValue * start,GtkCssValue * end,guint property_id,double progress)542 gtk_css_value_filter_transition (GtkCssValue *start,
543                                  GtkCssValue *end,
544                                  guint        property_id,
545                                  double       progress)
546 {
547   GtkCssValue *result;
548   guint i, n;
549 
550   if (gtk_css_filter_value_is_none (start))
551     {
552       if (gtk_css_filter_value_is_none (end))
553         return _gtk_css_value_ref (start);
554 
555       n = 0;
556     }
557   else if (gtk_css_filter_value_is_none (end))
558     {
559       n = 0;
560     }
561   else
562     {
563       n = MIN (start->n_filters, end->n_filters);
564     }
565 
566   /* Check filters are compatible. If not, transition between
567    * their result matrices.
568    */
569   for (i = 0; i < n; i++)
570     {
571       if (start->filters[i].type != end->filters[i].type)
572         {
573           /* XXX: can we improve this? */
574           return NULL;
575         }
576     }
577 
578   result = gtk_css_filter_value_alloc (MAX (start->n_filters, end->n_filters));
579 
580   for (i = 0; i < n; i++)
581     {
582       gtk_css_filter_transition (&result->filters[i],
583                                  &start->filters[i],
584                                  &end->filters[i],
585                                  property_id,
586                                  progress);
587     }
588 
589   for (; i < start->n_filters; i++)
590     {
591       GtkCssFilter filter;
592 
593       gtk_css_filter_init_identity (&filter, start->filters[i].type);
594       gtk_css_filter_transition (&result->filters[i],
595                                  &start->filters[i],
596                                  &filter,
597                                  property_id,
598                                  progress);
599       gtk_css_filter_clear (&filter);
600     }
601   for (; i < end->n_filters; i++)
602     {
603       GtkCssFilter filter;
604 
605       gtk_css_filter_init_identity (&filter, end->filters[i].type);
606       gtk_css_filter_transition (&result->filters[i],
607                                  &filter,
608                                  &end->filters[i],
609                                  property_id,
610                                  progress);
611       gtk_css_filter_clear (&filter);
612     }
613 
614   g_assert (i == MAX (start->n_filters, end->n_filters));
615 
616   return result;
617 }
618 
619 static void
gtk_css_filter_print(const GtkCssFilter * filter,GString * string)620 gtk_css_filter_print (const GtkCssFilter *filter,
621                       GString            *string)
622 {
623   switch (filter->type)
624     {
625     case GTK_CSS_FILTER_BRIGHTNESS:
626       g_string_append (string, "brightness(");
627       _gtk_css_value_print (filter->brightness.value, string);
628       g_string_append (string, ")");
629       break;
630 
631     case GTK_CSS_FILTER_CONTRAST:
632       g_string_append (string, "contrast(");
633       _gtk_css_value_print (filter->contrast.value, string);
634       g_string_append (string, ")");
635       break;
636 
637     case GTK_CSS_FILTER_GRAYSCALE:
638       g_string_append (string, "grayscale(");
639       _gtk_css_value_print (filter->grayscale.value, string);
640       g_string_append (string, ")");
641       break;
642 
643     case GTK_CSS_FILTER_HUE_ROTATE:
644       g_string_append (string, "hue-rotate(");
645       _gtk_css_value_print (filter->hue_rotate.value, string);
646       g_string_append (string, ")");
647       break;
648 
649     case GTK_CSS_FILTER_INVERT:
650       g_string_append (string, "invert(");
651       _gtk_css_value_print (filter->invert.value, string);
652       g_string_append (string, ")");
653       break;
654 
655     case GTK_CSS_FILTER_OPACITY:
656       g_string_append (string, "opacity(");
657       _gtk_css_value_print (filter->opacity.value, string);
658       g_string_append (string, ")");
659       break;
660 
661     case GTK_CSS_FILTER_SATURATE:
662       g_string_append (string, "saturate(");
663       _gtk_css_value_print (filter->saturate.value, string);
664       g_string_append (string, ")");
665       break;
666 
667     case GTK_CSS_FILTER_SEPIA:
668       g_string_append (string, "sepia(");
669       _gtk_css_value_print (filter->sepia.value, string);
670       g_string_append (string, ")");
671       break;
672 
673     case GTK_CSS_FILTER_BLUR:
674       g_string_append (string, "blur(");
675       _gtk_css_value_print (filter->blur.value, string);
676       g_string_append (string, ")");
677       break;
678 
679     case GTK_CSS_FILTER_DROP_SHADOW:
680       g_string_append (string, "drop-shadow(");
681       _gtk_css_value_print (filter->drop_shadow.value, string);
682       g_string_append (string, ")");
683       break;
684 
685     case GTK_CSS_FILTER_NONE:
686     default:
687       g_assert_not_reached ();
688       break;
689     }
690 }
691 
692 static void
gtk_css_value_filter_print(const GtkCssValue * value,GString * string)693 gtk_css_value_filter_print (const GtkCssValue *value,
694                             GString           *string)
695 {
696   guint i;
697 
698   if (gtk_css_filter_value_is_none (value))
699     {
700       g_string_append (string, "none");
701       return;
702     }
703 
704   for (i = 0; i < value->n_filters; i++)
705     {
706       if (i > 0)
707         g_string_append_c (string, ' ');
708 
709       gtk_css_filter_print (&value->filters[i], string);
710     }
711 }
712 
713 static const GtkCssValueClass GTK_CSS_VALUE_FILTER = {
714   "GtkCssFilterValue",
715   gtk_css_value_filter_free,
716   gtk_css_value_filter_compute,
717   gtk_css_value_filter_equal,
718   gtk_css_value_filter_transition,
719   NULL,
720   NULL,
721   gtk_css_value_filter_print
722 };
723 
724 static GtkCssValue filter_none_singleton = { &GTK_CSS_VALUE_FILTER, 1, TRUE, 0, {  { GTK_CSS_FILTER_NONE } } };
725 
726 static GtkCssValue *
gtk_css_filter_value_alloc(guint n_filters)727 gtk_css_filter_value_alloc (guint n_filters)
728 {
729   GtkCssValue *result;
730 
731   g_return_val_if_fail (n_filters > 0, NULL);
732 
733   result = _gtk_css_value_alloc (&GTK_CSS_VALUE_FILTER, sizeof (GtkCssValue) + sizeof (GtkCssFilter) * (n_filters - 1));
734   result->n_filters = n_filters;
735 
736   return result;
737 }
738 
739 GtkCssValue *
gtk_css_filter_value_new_none(void)740 gtk_css_filter_value_new_none (void)
741 {
742   return _gtk_css_value_ref (&filter_none_singleton);
743 }
744 
745 static gboolean
gtk_css_filter_value_is_none(const GtkCssValue * value)746 gtk_css_filter_value_is_none (const GtkCssValue *value)
747 {
748   return value->n_filters == 0;
749 }
750 
751 static guint
gtk_css_filter_parse_number(GtkCssParser * parser,guint n,gpointer data)752 gtk_css_filter_parse_number (GtkCssParser *parser,
753                              guint         n,
754                              gpointer      data)
755 {
756   GtkCssValue **values = data;
757 
758   values[n] = _gtk_css_number_value_parse (parser, GTK_CSS_PARSE_NUMBER | GTK_CSS_PARSE_PERCENT | GTK_CSS_POSITIVE_ONLY);
759   if (values[n] == NULL)
760     return 0;
761 
762   return 1;
763 }
764 
765 static guint
gtk_css_filter_parse_length(GtkCssParser * parser,guint n,gpointer data)766 gtk_css_filter_parse_length (GtkCssParser *parser,
767                              guint         n,
768                              gpointer      data)
769 {
770   GtkCssValue **values = data;
771 
772   values[n] = _gtk_css_number_value_parse (parser, GTK_CSS_PARSE_LENGTH | GTK_CSS_POSITIVE_ONLY);
773   if (values[n] == NULL)
774     return 0;
775 
776   return 1;
777 }
778 
779 static guint
gtk_css_filter_parse_angle(GtkCssParser * parser,guint n,gpointer data)780 gtk_css_filter_parse_angle (GtkCssParser *parser,
781                             guint         n,
782                             gpointer      data)
783 {
784   GtkCssValue **values = data;
785 
786   values[n] = _gtk_css_number_value_parse (parser, GTK_CSS_PARSE_ANGLE);
787   if (values[n] == NULL)
788     return 0;
789 
790   return 1;
791 }
792 
793 static guint
gtk_css_filter_parse_shadow(GtkCssParser * parser,guint n,gpointer data)794 gtk_css_filter_parse_shadow (GtkCssParser *parser,
795                              guint         n,
796                              gpointer      data)
797 {
798   GtkCssValue **values = data;
799 
800   values[n] = gtk_css_shadow_value_parse_filter (parser);
801   if (values[n] == NULL)
802     return 0;
803 
804   return 1;
805 }
806 
807 GtkCssValue *
gtk_css_filter_value_parse(GtkCssParser * parser)808 gtk_css_filter_value_parse (GtkCssParser *parser)
809 {
810   GtkCssValue *value;
811   GArray *array;
812   guint i;
813   gboolean computed = TRUE;
814 
815   if (gtk_css_parser_try_ident (parser, "none"))
816     return gtk_css_filter_value_new_none ();
817 
818   array = g_array_new (FALSE, FALSE, sizeof (GtkCssFilter));
819 
820   while (TRUE)
821     {
822       GtkCssFilter filter;
823 
824       if (gtk_css_parser_has_function (parser, "blur"))
825         {
826           if (!gtk_css_parser_consume_function (parser, 1, 1, gtk_css_filter_parse_length, &filter.blur.value))
827             goto fail;
828 
829           filter.type = GTK_CSS_FILTER_BLUR;
830           computed = computed && gtk_css_value_is_computed (filter.blur.value);
831         }
832       else if (gtk_css_parser_has_function (parser, "brightness"))
833         {
834           if (!gtk_css_parser_consume_function (parser, 1, 1, gtk_css_filter_parse_number, &filter.brightness.value))
835             goto fail;
836 
837           filter.type = GTK_CSS_FILTER_BRIGHTNESS;
838           computed = computed && gtk_css_value_is_computed (filter.brightness.value);
839         }
840       else if (gtk_css_parser_has_function (parser, "contrast"))
841         {
842           if (!gtk_css_parser_consume_function (parser, 1, 1, gtk_css_filter_parse_number, &filter.contrast.value))
843             goto fail;
844 
845           filter.type = GTK_CSS_FILTER_CONTRAST;
846           computed = computed && gtk_css_value_is_computed (filter.contrast.value);
847         }
848       else if (gtk_css_parser_has_function (parser, "grayscale"))
849         {
850           if (!gtk_css_parser_consume_function (parser, 1, 1, gtk_css_filter_parse_number, &filter.grayscale.value))
851             goto fail;
852 
853           filter.type = GTK_CSS_FILTER_GRAYSCALE;
854           computed = computed && gtk_css_value_is_computed (filter.grayscale.value);
855         }
856       else if (gtk_css_parser_has_function (parser, "hue-rotate"))
857         {
858           if (!gtk_css_parser_consume_function (parser, 1, 1, gtk_css_filter_parse_angle, &filter.hue_rotate.value))
859             goto fail;
860 
861           filter.type = GTK_CSS_FILTER_HUE_ROTATE;
862           computed = computed && gtk_css_value_is_computed (filter.hue_rotate.value);
863         }
864       else if (gtk_css_parser_has_function (parser, "invert"))
865         {
866           if (!gtk_css_parser_consume_function (parser, 1, 1, gtk_css_filter_parse_number, &filter.invert.value))
867             goto fail;
868 
869           filter.type = GTK_CSS_FILTER_INVERT;
870           computed = computed && gtk_css_value_is_computed (filter.invert.value);
871         }
872       else if (gtk_css_parser_has_function (parser, "opacity"))
873         {
874           if (!gtk_css_parser_consume_function (parser, 1, 1, gtk_css_filter_parse_number, &filter.opacity.value))
875             goto fail;
876 
877           filter.type = GTK_CSS_FILTER_OPACITY;
878           computed = computed && gtk_css_value_is_computed (filter.opacity.value);
879         }
880       else if (gtk_css_parser_has_function (parser, "saturate"))
881         {
882           if (!gtk_css_parser_consume_function (parser, 1, 1, gtk_css_filter_parse_number, &filter.saturate.value))
883             goto fail;
884 
885           filter.type = GTK_CSS_FILTER_SATURATE;
886           computed = computed && gtk_css_value_is_computed (filter.saturate.value);
887         }
888       else if (gtk_css_parser_has_function (parser, "sepia"))
889         {
890           if (!gtk_css_parser_consume_function (parser, 1, 1, gtk_css_filter_parse_number, &filter.sepia.value))
891             goto fail;
892 
893           filter.type = GTK_CSS_FILTER_SEPIA;
894           computed = computed && gtk_css_value_is_computed (filter.sepia.value);
895         }
896       else if (gtk_css_parser_has_function (parser, "drop-shadow"))
897         {
898           if (!gtk_css_parser_consume_function (parser, 1, 1, gtk_css_filter_parse_shadow, &filter.drop_shadow.value))
899             goto fail;
900 
901           filter.type = GTK_CSS_FILTER_DROP_SHADOW;
902           computed = computed && gtk_css_value_is_computed (filter.drop_shadow.value);
903         }
904       else
905         {
906           break;
907         }
908 
909       g_array_append_val (array, filter);
910     }
911 
912   if (array->len == 0)
913     {
914       gtk_css_parser_error_syntax (parser, "Expected a filter");
915       goto fail;
916     }
917 
918   value = gtk_css_filter_value_alloc (array->len);
919   memcpy (value->filters, array->data, sizeof (GtkCssFilter) * array->len);
920   value->is_computed = computed;
921 
922   g_array_free (array, TRUE);
923 
924   return value;
925 
926 fail:
927   for (i = 0; i < array->len; i++)
928     {
929       gtk_css_filter_clear (&g_array_index (array, GtkCssFilter, i));
930     }
931   g_array_free (array, TRUE);
932   return NULL;
933 }
934 
935 void
gtk_css_filter_value_push_snapshot(const GtkCssValue * filter,GtkSnapshot * snapshot)936 gtk_css_filter_value_push_snapshot (const GtkCssValue *filter,
937                                     GtkSnapshot       *snapshot)
938 {
939   graphene_matrix_t matrix;
940   graphene_vec4_t offset;
941   int i, j;
942 
943   if (gtk_css_filter_value_is_none (filter))
944     return;
945 
946   i = 0;
947   while (i < filter->n_filters)
948     {
949       j = gtk_css_filter_value_compute_matrix (filter, i, &matrix, &offset);
950       if (i < j)
951         gtk_snapshot_push_color_matrix (snapshot, &matrix, &offset);
952 
953       if (j < filter->n_filters)
954         {
955           if (filter->filters[j].type == GTK_CSS_FILTER_BLUR)
956             {
957               double std_dev = _gtk_css_number_value_get (filter->filters[j].blur.value, 100.0);
958               gtk_snapshot_push_blur (snapshot, 2 * std_dev);
959             }
960           else if (filter->filters[j].type == GTK_CSS_FILTER_DROP_SHADOW)
961             {
962               gtk_css_shadow_value_push_snapshot (filter->filters[j].drop_shadow.value, snapshot);
963             }
964           else
965             g_warning ("Don't know how to handle filter type %d", filter->filters[j].type);
966         }
967 
968       i = j + 1;
969     }
970 }
971 
972 void
gtk_css_filter_value_pop_snapshot(const GtkCssValue * filter,GtkSnapshot * snapshot)973 gtk_css_filter_value_pop_snapshot (const GtkCssValue *filter,
974                                    GtkSnapshot       *snapshot)
975 {
976   int i, j;
977 
978   if (gtk_css_filter_value_is_none (filter))
979     return;
980 
981   i = 0;
982   while (i < filter->n_filters)
983     {
984       for (j = i; j < filter->n_filters; j++)
985         {
986           if (filter->filters[j].type == GTK_CSS_FILTER_BLUR ||
987               filter->filters[j].type == GTK_CSS_FILTER_DROP_SHADOW)
988             break;
989         }
990 
991       if (i < j)
992         gtk_snapshot_pop (snapshot);
993 
994       if (j < filter->n_filters)
995         {
996           if (filter->filters[j].type == GTK_CSS_FILTER_BLUR)
997             gtk_snapshot_pop (snapshot);
998           else if (filter->filters[j].type == GTK_CSS_FILTER_DROP_SHADOW)
999             gtk_css_shadow_value_pop_snapshot (filter->filters[j].drop_shadow.value, snapshot);
1000         }
1001 
1002       i = j + 1;
1003     }
1004 }
1005