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 |
63 | gtk_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 | |
104 | static void |
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 (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 | |
153 | static gboolean |
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 (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 | |
265 | static int |
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 (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 | |
293 | static void |
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 (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 */ |
307 | static gboolean |
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 (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 | |
366 | static GtkCssValue * |
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 (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 | |
403 | static gboolean |
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 (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 | |
449 | static gboolean |
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 (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 | |
483 | static void |
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: 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 | |
541 | static GtkCssValue * |
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 (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 | |
619 | static void |
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, 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 | |
692 | static void |
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, 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 | |
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 = { >K_CSS_VALUE_FILTER, 1, TRUE, 0, { { GTK_CSS_FILTER_NONE } } }; |
725 | |
726 | static GtkCssValue * |
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 (klass: >K_CSS_VALUE_FILTER, size: sizeof (GtkCssValue) + sizeof (GtkCssFilter) * (n_filters - 1)); |
734 | result->n_filters = n_filters; |
735 | |
736 | return result; |
737 | } |
738 | |
739 | GtkCssValue * |
740 | gtk_css_filter_value_new_none (void) |
741 | { |
742 | return _gtk_css_value_ref (value: &filter_none_singleton); |
743 | } |
744 | |
745 | static gboolean |
746 | gtk_css_filter_value_is_none (const GtkCssValue *value) |
747 | { |
748 | return value->n_filters == 0; |
749 | } |
750 | |
751 | static guint |
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, 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 | |
765 | static guint |
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, flags: 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 |
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, flags: GTK_CSS_PARSE_ANGLE); |
787 | if (values[n] == NULL) |
788 | return 0; |
789 | |
790 | return 1; |
791 | } |
792 | |
793 | static guint |
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 * |
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 (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 | |
926 | fail: |
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 | |
935 | void |
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 (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 | |
972 | void |
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 (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 | |