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
29typedef union _GtkCssFilter GtkCssFilter;
30
31typedef 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
45union _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
53struct _GtkCssValue {
54 GTK_CSS_VALUE_BASE
55 guint n_filters;
56 GtkCssFilter filters[1];
57};
58
59static GtkCssValue * gtk_css_filter_value_alloc (guint n_values);
60static gboolean gtk_css_filter_value_is_none (const GtkCssValue *value);
61
62static void
63gtk_css_filter_clear (GtkCssFilter *filter)
64{
65 switch (filter->type)
66 {
67 case GTK_CSS_FILTER_BRIGHTNESS:
68 _gtk_css_value_unref (value: filter->brightness.value);
69 break;
70 case GTK_CSS_FILTER_CONTRAST:
71 _gtk_css_value_unref (value: filter->contrast.value);
72 break;
73 case GTK_CSS_FILTER_GRAYSCALE:
74 _gtk_css_value_unref (value: filter->grayscale.value);
75 break;
76 case GTK_CSS_FILTER_HUE_ROTATE:
77 _gtk_css_value_unref (value: filter->hue_rotate.value);
78 break;
79 case GTK_CSS_FILTER_INVERT:
80 _gtk_css_value_unref (value: filter->invert.value);
81 break;
82 case GTK_CSS_FILTER_OPACITY:
83 _gtk_css_value_unref (value: filter->opacity.value);
84 break;
85 case GTK_CSS_FILTER_SATURATE:
86 _gtk_css_value_unref (value: filter->saturate.value);
87 break;
88 case GTK_CSS_FILTER_SEPIA:
89 _gtk_css_value_unref (value: filter->sepia.value);
90 break;
91 case GTK_CSS_FILTER_BLUR:
92 _gtk_css_value_unref (value: filter->blur.value);
93 break;
94 case GTK_CSS_FILTER_DROP_SHADOW:
95 _gtk_css_value_unref (value: filter->drop_shadow.value);
96 break;
97 case GTK_CSS_FILTER_NONE:
98 default:
99 g_assert_not_reached ();
100 break;
101 }
102}
103
104static void
105gtk_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 (value: 1, unit: GTK_CSS_NUMBER);
112 break;
113 case GTK_CSS_FILTER_CONTRAST:
114 filter->contrast.value = _gtk_css_number_value_new (value: 1, unit: GTK_CSS_NUMBER);
115 break;
116 case GTK_CSS_FILTER_GRAYSCALE:
117 filter->grayscale.value = _gtk_css_number_value_new (value: 0, unit: GTK_CSS_NUMBER);
118 break;
119 case GTK_CSS_FILTER_HUE_ROTATE:
120 filter->hue_rotate.value = _gtk_css_number_value_new (value: 0, unit: GTK_CSS_DEG);
121 break;
122 case GTK_CSS_FILTER_INVERT:
123 filter->invert.value = _gtk_css_number_value_new (value: 0, unit: GTK_CSS_NUMBER);
124 break;
125 case GTK_CSS_FILTER_OPACITY:
126 filter->opacity.value = _gtk_css_number_value_new (value: 1, unit: GTK_CSS_NUMBER);
127 break;
128 case GTK_CSS_FILTER_SATURATE:
129 filter->saturate.value = _gtk_css_number_value_new (value: 1, unit: GTK_CSS_NUMBER);
130 break;
131 case GTK_CSS_FILTER_SEPIA:
132 filter->sepia.value = _gtk_css_number_value_new (value: 0, unit: GTK_CSS_NUMBER);
133 break;
134 case GTK_CSS_FILTER_BLUR:
135 filter->blur.value = _gtk_css_number_value_new (value: 0, unit: 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
153static gboolean
154gtk_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 (number: filter->brightness.value, one_hundred_percent: 1.0);
164 graphene_matrix_init_scale (m: matrix, x: value, y: value, z: value);
165 graphene_vec4_init (v: offset, x: 0.0, y: 0.0, z: 0.0, w: 0.0);
166 break;
167
168 case GTK_CSS_FILTER_CONTRAST:
169 value = _gtk_css_number_value_get (number: filter->contrast.value, one_hundred_percent: 1.0);
170 graphene_matrix_init_scale (m: matrix, x: value, y: value, z: value);
171 graphene_vec4_init (v: offset, x: 0.5 - 0.5 * value, y: 0.5 - 0.5 * value, z: 0.5 - 0.5 * value, w: 0.0);
172 break;
173
174 case GTK_CSS_FILTER_GRAYSCALE:
175 value = _gtk_css_number_value_get (number: filter->grayscale.value, one_hundred_percent: 1.0);
176 graphene_matrix_init_from_float (m: matrix, v: (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 (v: offset, x: 0.0, y: 0.0, z: 0.0, w: 0.0);
183 break;
184
185 case GTK_CSS_FILTER_HUE_ROTATE:
186 {
187 double c, s;
188 value = _gtk_css_number_value_get (number: filter->grayscale.value, one_hundred_percent: 1.0) * G_PI / 180.0;
189 c = cos (x: value);
190 s = sin (x: value);
191 graphene_matrix_init_from_float (m: matrix, v: (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 (v: offset, x: 0.0, y: 0.0, z: 0.0, w: 0.0);
207 }
208 break;
209
210 case GTK_CSS_FILTER_INVERT:
211 value = _gtk_css_number_value_get (number: filter->invert.value, one_hundred_percent: 1.0);
212 graphene_matrix_init_scale (m: matrix, x: 1.0 - 2 * value, y: 1.0 - 2 * value, z: 1.0 - 2 * value);
213 graphene_vec4_init (v: offset, x: value, y: value, z: value, w: 0.0);
214 break;
215
216 case GTK_CSS_FILTER_OPACITY:
217 value = _gtk_css_number_value_get (number: filter->invert.value, one_hundred_percent: 1.0);
218 graphene_matrix_init_from_float (m: matrix, v: (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 (v: offset, x: 0.0, y: 0.0, z: 0.0, w: 0.0);
225 break;
226
227 case GTK_CSS_FILTER_SATURATE:
228 value = _gtk_css_number_value_get (number: filter->saturate.value, one_hundred_percent: 1.0);
229 graphene_matrix_init_from_float (m: matrix, v: (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 (v: offset, x: 0.0, y: 0.0, z: 0.0, w: 0.0);
236 break;
237
238 case GTK_CSS_FILTER_SEPIA:
239 value = _gtk_css_number_value_get (number: filter->sepia.value, one_hundred_percent: 1.0);
240 graphene_matrix_init_from_float (m: matrix, v: (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 (v: offset, x: 0.0, y: 0.0, z: 0.0, w: 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
265static int
266gtk_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 (filter: &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 (filter: &value->filters[i], matrix: &m, offset: &o))
281 return i;
282
283 graphene_matrix_multiply (a: matrix, b: &m, res: &m2);
284 graphene_matrix_transform_vec4 (m: &m, v: offset, res: &o2);
285
286 graphene_matrix_init_from_matrix (m: matrix, src: &m2);
287 graphene_vec4_add (a: &o, b: &o2, res: offset);
288 }
289
290 return value->n_filters;
291}
292
293static void
294gtk_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 (filter: &value->filters[i]);
301 }
302
303 g_slice_free1 (block_size: sizeof (GtkCssValue) + sizeof (GtkCssFilter) * (value->n_filters - 1), mem_block: value);
304}
305
306/* returns TRUE if dest == src */
307static gboolean
308gtk_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 (value: 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 (value: 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 (value: 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 (value: 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 (value: 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 (value: 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 (value: 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 (value: 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 (value: 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 (value: 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
366static GtkCssValue *
367gtk_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 (n_values: value->n_filters);
383
384 for (i = 0; i < value->n_filters; i++)
385 {
386 changes |= !gtk_css_filter_compute (dest: &result->filters[i],
387 src: &value->filters[i],
388 property_id,
389 provider,
390 style,
391 parent_style);
392 }
393
394 if (!changes)
395 {
396 _gtk_css_value_unref (value: result);
397 result = _gtk_css_value_ref (value);
398 }
399
400 return result;
401}
402
403static gboolean
404gtk_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 (value1: filter1->brightness.value, value2: filter2->brightness.value);
414
415 case GTK_CSS_FILTER_CONTRAST:
416 return _gtk_css_value_equal (value1: filter1->contrast.value, value2: filter2->contrast.value);
417
418 case GTK_CSS_FILTER_GRAYSCALE:
419 return _gtk_css_value_equal (value1: filter1->grayscale.value, value2: filter2->grayscale.value);
420
421 case GTK_CSS_FILTER_HUE_ROTATE:
422 return _gtk_css_value_equal (value1: filter1->hue_rotate.value, value2: filter2->hue_rotate.value);
423
424 case GTK_CSS_FILTER_INVERT:
425 return _gtk_css_value_equal (value1: filter1->invert.value, value2: filter2->invert.value);
426
427 case GTK_CSS_FILTER_OPACITY:
428 return _gtk_css_value_equal (value1: filter1->opacity.value, value2: filter2->opacity.value);
429
430 case GTK_CSS_FILTER_SATURATE:
431 return _gtk_css_value_equal (value1: filter1->saturate.value, value2: filter2->saturate.value);
432
433 case GTK_CSS_FILTER_SEPIA:
434 return _gtk_css_value_equal (value1: filter1->sepia.value, value2: filter2->sepia.value);
435
436 case GTK_CSS_FILTER_BLUR:
437 return _gtk_css_value_equal (value1: filter1->blur.value, value2: filter2->blur.value);
438
439 case GTK_CSS_FILTER_DROP_SHADOW:
440 return _gtk_css_value_equal (value1: filter1->drop_shadow.value, value2: filter2->drop_shadow.value);
441
442 case GTK_CSS_FILTER_NONE:
443 default:
444 g_assert_not_reached ();
445 return FALSE;
446 }
447}
448
449static gboolean
450gtk_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 (filter1: &value1->filters[i], filter2: &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: &filter, type: larger->filters[i].type);
470
471 if (!gtk_css_filter_equal (filter1: &larger->filters[i], filter2: &filter))
472 {
473 gtk_css_filter_clear (filter: &filter);
474 return FALSE;
475 }
476
477 gtk_css_filter_clear (filter: &filter);
478 }
479
480 return TRUE;
481}
482
483static void
484gtk_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: start->brightness.value, end: end->brightness.value, property_id, progress);
496 break;
497
498 case GTK_CSS_FILTER_CONTRAST:
499 result->contrast.value = _gtk_css_value_transition (start: start->contrast.value, end: end->contrast.value, property_id, progress);
500 break;
501
502 case GTK_CSS_FILTER_GRAYSCALE:
503 result->grayscale.value = _gtk_css_value_transition (start: start->grayscale.value, end: 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: start->hue_rotate.value, end: 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: start->invert.value, end: end->invert.value, property_id, progress);
512 break;
513
514 case GTK_CSS_FILTER_OPACITY:
515 result->opacity.value = _gtk_css_value_transition (start: start->opacity.value, end: end->opacity.value, property_id, progress);
516 break;
517
518 case GTK_CSS_FILTER_SATURATE:
519 result->saturate.value = _gtk_css_value_transition (start: start->saturate.value, end: end->saturate.value, property_id, progress);
520 break;
521
522 case GTK_CSS_FILTER_SEPIA:
523 result->sepia.value = _gtk_css_value_transition (start: start->sepia.value, end: end->sepia.value, property_id, progress);
524 break;
525
526 case GTK_CSS_FILTER_BLUR:
527 result->blur.value = _gtk_css_value_transition (start: start->blur.value, end: 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: start->drop_shadow.value, end: 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
541static GtkCssValue *
542gtk_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 (value: start))
551 {
552 if (gtk_css_filter_value_is_none (value: end))
553 return _gtk_css_value_ref (value: start);
554
555 n = 0;
556 }
557 else if (gtk_css_filter_value_is_none (value: 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: &result->filters[i],
583 start: &start->filters[i],
584 end: &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: &filter, type: start->filters[i].type);
594 gtk_css_filter_transition (result: &result->filters[i],
595 start: &start->filters[i],
596 end: &filter,
597 property_id,
598 progress);
599 gtk_css_filter_clear (filter: &filter);
600 }
601 for (; i < end->n_filters; i++)
602 {
603 GtkCssFilter filter;
604
605 gtk_css_filter_init_identity (filter: &filter, type: end->filters[i].type);
606 gtk_css_filter_transition (result: &result->filters[i],
607 start: &filter,
608 end: &end->filters[i],
609 property_id,
610 progress);
611 gtk_css_filter_clear (filter: &filter);
612 }
613
614 g_assert (i == MAX (start->n_filters, end->n_filters));
615
616 return result;
617}
618
619static void
620gtk_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, val: "brightness(");
627 _gtk_css_value_print (value: filter->brightness.value, string);
628 g_string_append (string, val: ")");
629 break;
630
631 case GTK_CSS_FILTER_CONTRAST:
632 g_string_append (string, val: "contrast(");
633 _gtk_css_value_print (value: filter->contrast.value, string);
634 g_string_append (string, val: ")");
635 break;
636
637 case GTK_CSS_FILTER_GRAYSCALE:
638 g_string_append (string, val: "grayscale(");
639 _gtk_css_value_print (value: filter->grayscale.value, string);
640 g_string_append (string, val: ")");
641 break;
642
643 case GTK_CSS_FILTER_HUE_ROTATE:
644 g_string_append (string, val: "hue-rotate(");
645 _gtk_css_value_print (value: filter->hue_rotate.value, string);
646 g_string_append (string, val: ")");
647 break;
648
649 case GTK_CSS_FILTER_INVERT:
650 g_string_append (string, val: "invert(");
651 _gtk_css_value_print (value: filter->invert.value, string);
652 g_string_append (string, val: ")");
653 break;
654
655 case GTK_CSS_FILTER_OPACITY:
656 g_string_append (string, val: "opacity(");
657 _gtk_css_value_print (value: filter->opacity.value, string);
658 g_string_append (string, val: ")");
659 break;
660
661 case GTK_CSS_FILTER_SATURATE:
662 g_string_append (string, val: "saturate(");
663 _gtk_css_value_print (value: filter->saturate.value, string);
664 g_string_append (string, val: ")");
665 break;
666
667 case GTK_CSS_FILTER_SEPIA:
668 g_string_append (string, val: "sepia(");
669 _gtk_css_value_print (value: filter->sepia.value, string);
670 g_string_append (string, val: ")");
671 break;
672
673 case GTK_CSS_FILTER_BLUR:
674 g_string_append (string, val: "blur(");
675 _gtk_css_value_print (value: filter->blur.value, string);
676 g_string_append (string, val: ")");
677 break;
678
679 case GTK_CSS_FILTER_DROP_SHADOW:
680 g_string_append (string, val: "drop-shadow(");
681 _gtk_css_value_print (value: filter->drop_shadow.value, string);
682 g_string_append (string, val: ")");
683 break;
684
685 case GTK_CSS_FILTER_NONE:
686 default:
687 g_assert_not_reached ();
688 break;
689 }
690}
691
692static void
693gtk_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, val: "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 (filter: &value->filters[i], string);
710 }
711}
712
713static 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
724static GtkCssValue filter_none_singleton = { &GTK_CSS_VALUE_FILTER, 1, TRUE, 0, { { GTK_CSS_FILTER_NONE } } };
725
726static GtkCssValue *
727gtk_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 (klass: &GTK_CSS_VALUE_FILTER, size: sizeof (GtkCssValue) + sizeof (GtkCssFilter) * (n_filters - 1));
734 result->n_filters = n_filters;
735
736 return result;
737}
738
739GtkCssValue *
740gtk_css_filter_value_new_none (void)
741{
742 return _gtk_css_value_ref (value: &filter_none_singleton);
743}
744
745static gboolean
746gtk_css_filter_value_is_none (const GtkCssValue *value)
747{
748 return value->n_filters == 0;
749}
750
751static guint
752gtk_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, flags: 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
765static guint
766gtk_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, flags: GTK_CSS_PARSE_LENGTH | GTK_CSS_POSITIVE_ONLY);
773 if (values[n] == NULL)
774 return 0;
775
776 return 1;
777}
778
779static guint
780gtk_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, flags: GTK_CSS_PARSE_ANGLE);
787 if (values[n] == NULL)
788 return 0;
789
790 return 1;
791}
792
793static guint
794gtk_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
807GtkCssValue *
808gtk_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 (self: parser, ident: "none"))
816 return gtk_css_filter_value_new_none ();
817
818 array = g_array_new (FALSE, FALSE, element_size: sizeof (GtkCssFilter));
819
820 while (TRUE)
821 {
822 GtkCssFilter filter;
823
824 if (gtk_css_parser_has_function (self: parser, name: "blur"))
825 {
826 if (!gtk_css_parser_consume_function (self: parser, min_args: 1, max_args: 1, parse_func: gtk_css_filter_parse_length, data: &filter.blur.value))
827 goto fail;
828
829 filter.type = GTK_CSS_FILTER_BLUR;
830 computed = computed && gtk_css_value_is_computed (value: filter.blur.value);
831 }
832 else if (gtk_css_parser_has_function (self: parser, name: "brightness"))
833 {
834 if (!gtk_css_parser_consume_function (self: parser, min_args: 1, max_args: 1, parse_func: gtk_css_filter_parse_number, data: &filter.brightness.value))
835 goto fail;
836
837 filter.type = GTK_CSS_FILTER_BRIGHTNESS;
838 computed = computed && gtk_css_value_is_computed (value: filter.brightness.value);
839 }
840 else if (gtk_css_parser_has_function (self: parser, name: "contrast"))
841 {
842 if (!gtk_css_parser_consume_function (self: parser, min_args: 1, max_args: 1, parse_func: gtk_css_filter_parse_number, data: &filter.contrast.value))
843 goto fail;
844
845 filter.type = GTK_CSS_FILTER_CONTRAST;
846 computed = computed && gtk_css_value_is_computed (value: filter.contrast.value);
847 }
848 else if (gtk_css_parser_has_function (self: parser, name: "grayscale"))
849 {
850 if (!gtk_css_parser_consume_function (self: parser, min_args: 1, max_args: 1, parse_func: gtk_css_filter_parse_number, data: &filter.grayscale.value))
851 goto fail;
852
853 filter.type = GTK_CSS_FILTER_GRAYSCALE;
854 computed = computed && gtk_css_value_is_computed (value: filter.grayscale.value);
855 }
856 else if (gtk_css_parser_has_function (self: parser, name: "hue-rotate"))
857 {
858 if (!gtk_css_parser_consume_function (self: parser, min_args: 1, max_args: 1, parse_func: gtk_css_filter_parse_angle, data: &filter.hue_rotate.value))
859 goto fail;
860
861 filter.type = GTK_CSS_FILTER_HUE_ROTATE;
862 computed = computed && gtk_css_value_is_computed (value: filter.hue_rotate.value);
863 }
864 else if (gtk_css_parser_has_function (self: parser, name: "invert"))
865 {
866 if (!gtk_css_parser_consume_function (self: parser, min_args: 1, max_args: 1, parse_func: gtk_css_filter_parse_number, data: &filter.invert.value))
867 goto fail;
868
869 filter.type = GTK_CSS_FILTER_INVERT;
870 computed = computed && gtk_css_value_is_computed (value: filter.invert.value);
871 }
872 else if (gtk_css_parser_has_function (self: parser, name: "opacity"))
873 {
874 if (!gtk_css_parser_consume_function (self: parser, min_args: 1, max_args: 1, parse_func: gtk_css_filter_parse_number, data: &filter.opacity.value))
875 goto fail;
876
877 filter.type = GTK_CSS_FILTER_OPACITY;
878 computed = computed && gtk_css_value_is_computed (value: filter.opacity.value);
879 }
880 else if (gtk_css_parser_has_function (self: parser, name: "saturate"))
881 {
882 if (!gtk_css_parser_consume_function (self: parser, min_args: 1, max_args: 1, parse_func: gtk_css_filter_parse_number, data: &filter.saturate.value))
883 goto fail;
884
885 filter.type = GTK_CSS_FILTER_SATURATE;
886 computed = computed && gtk_css_value_is_computed (value: filter.saturate.value);
887 }
888 else if (gtk_css_parser_has_function (self: parser, name: "sepia"))
889 {
890 if (!gtk_css_parser_consume_function (self: parser, min_args: 1, max_args: 1, parse_func: gtk_css_filter_parse_number, data: &filter.sepia.value))
891 goto fail;
892
893 filter.type = GTK_CSS_FILTER_SEPIA;
894 computed = computed && gtk_css_value_is_computed (value: filter.sepia.value);
895 }
896 else if (gtk_css_parser_has_function (self: parser, name: "drop-shadow"))
897 {
898 if (!gtk_css_parser_consume_function (self: parser, min_args: 1, max_args: 1, parse_func: gtk_css_filter_parse_shadow, data: &filter.drop_shadow.value))
899 goto fail;
900
901 filter.type = GTK_CSS_FILTER_DROP_SHADOW;
902 computed = computed && gtk_css_value_is_computed (value: 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 (self: parser, format: "Expected a filter");
915 goto fail;
916 }
917
918 value = gtk_css_filter_value_alloc (n_filters: array->len);
919 memcpy (dest: value->filters, src: array->data, n: sizeof (GtkCssFilter) * array->len);
920 value->is_computed = computed;
921
922 g_array_free (array, TRUE);
923
924 return value;
925
926fail:
927 for (i = 0; i < array->len; i++)
928 {
929 gtk_css_filter_clear (filter: &g_array_index (array, GtkCssFilter, i));
930 }
931 g_array_free (array, TRUE);
932 return NULL;
933}
934
935void
936gtk_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 (value: filter))
944 return;
945
946 i = 0;
947 while (i < filter->n_filters)
948 {
949 j = gtk_css_filter_value_compute_matrix (value: filter, first: i, matrix: &matrix, offset: &offset);
950 if (i < j)
951 gtk_snapshot_push_color_matrix (snapshot, color_matrix: &matrix, color_offset: &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 (number: filter->filters[j].blur.value, one_hundred_percent: 100.0);
958 gtk_snapshot_push_blur (snapshot, radius: 2 * std_dev);
959 }
960 else if (filter->filters[j].type == GTK_CSS_FILTER_DROP_SHADOW)
961 {
962 gtk_css_shadow_value_push_snapshot (value: 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
972void
973gtk_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 (value: 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 (value: filter->filters[j].drop_shadow.value, snapshot);
1000 }
1001
1002 i = j + 1;
1003 }
1004}
1005

source code of gtk/gtk/gtkcssfiltervalue.c