1/* GSK - The GTK Scene Kit
2 *
3 * Copyright 2016 Endless
4 *
5 * This library is free software; you can redistribute it and/or
6 * modify it under the terms of the GNU Lesser General Public
7 * License as published by the Free Software Foundation; either
8 * version 2 of the License, or (at your option) any later version.
9 *
10 * This library is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 * Lesser General Public License for more details.
14 *
15 * You should have received a copy of the GNU Lesser General Public
16 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19#include "config.h"
20
21#include "gskrendernodeprivate.h"
22
23#include "gskcairoblurprivate.h"
24#include "gskdebugprivate.h"
25#include "gskdiffprivate.h"
26#include "gskrendererprivate.h"
27#include "gskroundedrectprivate.h"
28#include "gsktransformprivate.h"
29
30#include "gdk/gdktextureprivate.h"
31#include "gdk/gdkmemoryformatprivate.h"
32#include "gdk/gdk-private.h"
33
34#include <hb-ot.h>
35
36/* maximal number of rectangles we keep in a diff region before we throw
37 * the towel and just use the bounding box of the parent node.
38 * Meant to avoid performance corner cases.
39 */
40#define MAX_RECTS_IN_DIFF 30
41
42static inline void
43gsk_cairo_rectangle (cairo_t *cr,
44 const graphene_rect_t *rect)
45{
46 cairo_rectangle (cr,
47 x: rect->origin.x, y: rect->origin.y,
48 width: rect->size.width, height: rect->size.height);
49}
50
51static void
52rectangle_init_from_graphene (cairo_rectangle_int_t *cairo,
53 const graphene_rect_t *graphene)
54{
55 cairo->x = floorf (x: graphene->origin.x);
56 cairo->y = floorf (x: graphene->origin.y);
57 cairo->width = ceilf (x: graphene->origin.x + graphene->size.width) - cairo->x;
58 cairo->height = ceilf (x: graphene->origin.y + graphene->size.height) - cairo->y;
59}
60
61/*** GSK_COLOR_NODE ***/
62
63/**
64 * GskColorNode:
65 *
66 * A render node for a solid color.
67 */
68struct _GskColorNode
69{
70 GskRenderNode render_node;
71
72 GdkRGBA color;
73};
74
75static void
76gsk_color_node_draw (GskRenderNode *node,
77 cairo_t *cr)
78{
79 GskColorNode *self = (GskColorNode *) node;
80
81 gdk_cairo_set_source_rgba (cr, rgba: &self->color);
82
83 gsk_cairo_rectangle (cr, rect: &node->bounds);
84 cairo_fill (cr);
85}
86
87static void
88gsk_color_node_diff (GskRenderNode *node1,
89 GskRenderNode *node2,
90 cairo_region_t *region)
91{
92 GskColorNode *self1 = (GskColorNode *) node1;
93 GskColorNode *self2 = (GskColorNode *) node2;
94
95 if (graphene_rect_equal (a: &node1->bounds, b: &node2->bounds) &&
96 gdk_rgba_equal (p1: &self1->color, p2: &self2->color))
97 return;
98
99 gsk_render_node_diff_impossible (node1, node2, region);
100}
101
102/**
103 * gsk_color_node_get_color:
104 * @node: (type GskColorNode): a `GskRenderNode`
105 *
106 * Retrieves the color of the given @node.
107 *
108 * Returns: (transfer none): the color of the node
109 */
110const GdkRGBA *
111gsk_color_node_get_color (const GskRenderNode *node)
112{
113 GskColorNode *self = (GskColorNode *) node;
114
115 g_return_val_if_fail (GSK_IS_RENDER_NODE_TYPE (node, GSK_COLOR_NODE), NULL);
116
117 return &self->color;
118}
119
120/**
121 * gsk_color_node_new:
122 * @rgba: a `GdkRGBA` specifying a color
123 * @bounds: the rectangle to render the color into
124 *
125 * Creates a `GskRenderNode` that will render the color specified by @rgba into
126 * the area given by @bounds.
127 *
128 * Returns: (transfer full) (type GskColorNode): A new `GskRenderNode`
129 */
130GskRenderNode *
131gsk_color_node_new (const GdkRGBA *rgba,
132 const graphene_rect_t *bounds)
133{
134 GskColorNode *self;
135 GskRenderNode *node;
136
137 g_return_val_if_fail (rgba != NULL, NULL);
138 g_return_val_if_fail (bounds != NULL, NULL);
139
140 self = gsk_render_node_alloc (node_type: GSK_COLOR_NODE);
141 node = (GskRenderNode *) self;
142
143 self->color = *rgba;
144 graphene_rect_init_from_rect (r: &node->bounds, src: bounds);
145
146 return node;
147}
148
149/*** GSK_LINEAR_GRADIENT_NODE ***/
150
151/**
152 * GskRepeatingLinearGradientNode:
153 *
154 * A render node for a repeating linear gradient.
155 */
156
157/**
158 * GskLinearGradientNode:
159 *
160 * A render node for a linear gradient.
161 */
162struct _GskLinearGradientNode
163{
164 GskRenderNode render_node;
165
166 graphene_point_t start;
167 graphene_point_t end;
168
169 gsize n_stops;
170 GskColorStop *stops;
171};
172
173static void
174gsk_linear_gradient_node_finalize (GskRenderNode *node)
175{
176 GskLinearGradientNode *self = (GskLinearGradientNode *) node;
177 GskRenderNodeClass *parent_class = g_type_class_peek (type: g_type_parent (GSK_TYPE_LINEAR_GRADIENT_NODE));
178
179 g_free (mem: self->stops);
180
181 parent_class->finalize (node);
182}
183
184static void
185gsk_linear_gradient_node_draw (GskRenderNode *node,
186 cairo_t *cr)
187{
188 GskLinearGradientNode *self = (GskLinearGradientNode *) node;
189 cairo_pattern_t *pattern;
190 gsize i;
191
192 pattern = cairo_pattern_create_linear (x0: self->start.x, y0: self->start.y,
193 x1: self->end.x, y1: self->end.y);
194
195 if (gsk_render_node_get_node_type (node) == GSK_REPEATING_LINEAR_GRADIENT_NODE)
196 cairo_pattern_set_extend (pattern, extend: CAIRO_EXTEND_REPEAT);
197
198 for (i = 0; i < self->n_stops; i++)
199 {
200 cairo_pattern_add_color_stop_rgba (pattern,
201 offset: self->stops[i].offset,
202 red: self->stops[i].color.red,
203 green: self->stops[i].color.green,
204 blue: self->stops[i].color.blue,
205 alpha: self->stops[i].color.alpha);
206 }
207
208 cairo_set_source (cr, source: pattern);
209 cairo_pattern_destroy (pattern);
210
211 gsk_cairo_rectangle (cr, rect: &node->bounds);
212 cairo_fill (cr);
213}
214
215static void
216gsk_linear_gradient_node_diff (GskRenderNode *node1,
217 GskRenderNode *node2,
218 cairo_region_t *region)
219{
220 GskLinearGradientNode *self1 = (GskLinearGradientNode *) node1;
221 GskLinearGradientNode *self2 = (GskLinearGradientNode *) node2;
222
223 if (graphene_point_equal (a: &self1->start, b: &self2->start) &&
224 graphene_point_equal (a: &self1->end, b: &self2->end) &&
225 self1->n_stops == self2->n_stops)
226 {
227 gsize i;
228
229 for (i = 0; i < self1->n_stops; i++)
230 {
231 GskColorStop *stop1 = &self1->stops[i];
232 GskColorStop *stop2 = &self2->stops[i];
233
234 if (stop1->offset == stop2->offset &&
235 gdk_rgba_equal (p1: &stop1->color, p2: &stop2->color))
236 continue;
237
238 gsk_render_node_diff_impossible (node1, node2, region);
239 return;
240 }
241
242 return;
243 }
244
245 gsk_render_node_diff_impossible (node1, node2, region);
246}
247
248/**
249 * gsk_linear_gradient_node_new:
250 * @bounds: the rectangle to render the linear gradient into
251 * @start: the point at which the linear gradient will begin
252 * @end: the point at which the linear gradient will finish
253 * @color_stops: (array length=n_color_stops): a pointer to an array of
254 * `GskColorStop` defining the gradient. The offsets of all color stops
255 * must be increasing. The first stop's offset must be >= 0 and the last
256 * stop's offset must be <= 1.
257 * @n_color_stops: the number of elements in @color_stops
258 *
259 * Creates a `GskRenderNode` that will create a linear gradient from the given
260 * points and color stops, and render that into the area given by @bounds.
261 *
262 * Returns: (transfer full) (type GskLinearGradientNode): A new `GskRenderNode`
263 */
264GskRenderNode *
265gsk_linear_gradient_node_new (const graphene_rect_t *bounds,
266 const graphene_point_t *start,
267 const graphene_point_t *end,
268 const GskColorStop *color_stops,
269 gsize n_color_stops)
270{
271 GskLinearGradientNode *self;
272 GskRenderNode *node;
273 gsize i;
274
275 g_return_val_if_fail (bounds != NULL, NULL);
276 g_return_val_if_fail (start != NULL, NULL);
277 g_return_val_if_fail (end != NULL, NULL);
278 g_return_val_if_fail (color_stops != NULL, NULL);
279 g_return_val_if_fail (n_color_stops >= 2, NULL);
280 g_return_val_if_fail (color_stops[0].offset >= 0, NULL);
281 for (i = 1; i < n_color_stops; i++)
282 g_return_val_if_fail (color_stops[i].offset >= color_stops[i - 1].offset, NULL);
283 g_return_val_if_fail (color_stops[n_color_stops - 1].offset <= 1, NULL);
284
285 self = gsk_render_node_alloc (node_type: GSK_LINEAR_GRADIENT_NODE);
286 node = (GskRenderNode *) self;
287
288 graphene_rect_init_from_rect (r: &node->bounds, src: bounds);
289 graphene_point_init_from_point (p: &self->start, src: start);
290 graphene_point_init_from_point (p: &self->end, src: end);
291
292 self->n_stops = n_color_stops;
293 self->stops = g_malloc_n (n_blocks: n_color_stops, n_block_bytes: sizeof (GskColorStop));
294 memcpy (dest: self->stops, src: color_stops, n: n_color_stops * sizeof (GskColorStop));
295
296 return node;
297}
298
299/**
300 * gsk_repeating_linear_gradient_node_new:
301 * @bounds: the rectangle to render the linear gradient into
302 * @start: the point at which the linear gradient will begin
303 * @end: the point at which the linear gradient will finish
304 * @color_stops: (array length=n_color_stops): a pointer to an array of
305 * `GskColorStop` defining the gradient. The offsets of all color stops
306 * must be increasing. The first stop's offset must be >= 0 and the last
307 * stop's offset must be <= 1.
308 * @n_color_stops: the number of elements in @color_stops
309 *
310 * Creates a `GskRenderNode` that will create a repeating linear gradient
311 * from the given points and color stops, and render that into the area
312 * given by @bounds.
313 *
314 * Returns: (transfer full) (type GskRepeatingLinearGradientNode): A new `GskRenderNode`
315 */
316GskRenderNode *
317gsk_repeating_linear_gradient_node_new (const graphene_rect_t *bounds,
318 const graphene_point_t *start,
319 const graphene_point_t *end,
320 const GskColorStop *color_stops,
321 gsize n_color_stops)
322{
323 GskLinearGradientNode *self;
324 GskRenderNode *node;
325 gsize i;
326
327 g_return_val_if_fail (bounds != NULL, NULL);
328 g_return_val_if_fail (start != NULL, NULL);
329 g_return_val_if_fail (end != NULL, NULL);
330 g_return_val_if_fail (color_stops != NULL, NULL);
331 g_return_val_if_fail (n_color_stops >= 2, NULL);
332 g_return_val_if_fail (color_stops[0].offset >= 0, NULL);
333 for (i = 1; i < n_color_stops; i++)
334 g_return_val_if_fail (color_stops[i].offset >= color_stops[i - 1].offset, NULL);
335 g_return_val_if_fail (color_stops[n_color_stops - 1].offset <= 1, NULL);
336
337 self = gsk_render_node_alloc (node_type: GSK_REPEATING_LINEAR_GRADIENT_NODE);
338 node = (GskRenderNode *) self;
339
340 graphene_rect_init_from_rect (r: &node->bounds, src: bounds);
341 graphene_point_init_from_point (p: &self->start, src: start);
342 graphene_point_init_from_point (p: &self->end, src: end);
343
344 self->stops = g_malloc_n (n_blocks: n_color_stops, n_block_bytes: sizeof (GskColorStop));
345 memcpy (dest: self->stops, src: color_stops, n: n_color_stops * sizeof (GskColorStop));
346 self->n_stops = n_color_stops;
347
348 return node;
349}
350
351/**
352 * gsk_linear_gradient_node_get_start:
353 * @node: (type GskLinearGradientNode): a `GskRenderNode` for a linear gradient
354 *
355 * Retrieves the initial point of the linear gradient.
356 *
357 * Returns: (transfer none): the initial point
358 */
359const graphene_point_t *
360gsk_linear_gradient_node_get_start (const GskRenderNode *node)
361{
362 const GskLinearGradientNode *self = (const GskLinearGradientNode *) node;
363
364 return &self->start;
365}
366
367/**
368 * gsk_linear_gradient_node_get_end:
369 * @node: (type GskLinearGradientNode): a `GskRenderNode` for a linear gradient
370 *
371 * Retrieves the final point of the linear gradient.
372 *
373 * Returns: (transfer none): the final point
374 */
375const graphene_point_t *
376gsk_linear_gradient_node_get_end (const GskRenderNode *node)
377{
378 const GskLinearGradientNode *self = (const GskLinearGradientNode *) node;
379
380 return &self->end;
381}
382
383/**
384 * gsk_linear_gradient_node_get_n_color_stops:
385 * @node: (type GskLinearGradientNode): a `GskRenderNode` for a linear gradient
386 *
387 * Retrieves the number of color stops in the gradient.
388 *
389 * Returns: the number of color stops
390 */
391gsize
392gsk_linear_gradient_node_get_n_color_stops (const GskRenderNode *node)
393{
394 const GskLinearGradientNode *self = (const GskLinearGradientNode *) node;
395
396 return self->n_stops;
397}
398
399/**
400 * gsk_linear_gradient_node_get_color_stops:
401 * @node: (type GskLinearGradientNode): a `GskRenderNode` for a linear gradient
402 * @n_stops: (out) (optional): the number of color stops in the returned array
403 *
404 * Retrieves the color stops in the gradient.
405 *
406 * Returns: (array length=n_stops): the color stops in the gradient
407 */
408const GskColorStop *
409gsk_linear_gradient_node_get_color_stops (const GskRenderNode *node,
410 gsize *n_stops)
411{
412 const GskLinearGradientNode *self = (const GskLinearGradientNode *) node;
413
414 if (n_stops != NULL)
415 *n_stops = self->n_stops;
416
417 return self->stops;
418}
419
420/*** GSK_RADIAL_GRADIENT_NODE ***/
421
422/**
423 * GskRepeatingRadialGradientNode:
424 *
425 * A render node for a repeating radial gradient.
426 */
427
428/**
429 * GskRadialGradientNode:
430 *
431 * A render node for a radial gradient.
432 */
433struct _GskRadialGradientNode
434{
435 GskRenderNode render_node;
436
437 graphene_point_t center;
438
439 float hradius;
440 float vradius;
441 float start;
442 float end;
443
444 gsize n_stops;
445 GskColorStop *stops;
446};
447
448static void
449gsk_radial_gradient_node_finalize (GskRenderNode *node)
450{
451 GskRadialGradientNode *self = (GskRadialGradientNode *) node;
452 GskRenderNodeClass *parent_class = g_type_class_peek (type: g_type_parent (GSK_TYPE_RADIAL_GRADIENT_NODE));
453
454 g_free (mem: self->stops);
455
456 parent_class->finalize (node);
457}
458
459static void
460gsk_radial_gradient_node_draw (GskRenderNode *node,
461 cairo_t *cr)
462{
463 GskRadialGradientNode *self = (GskRadialGradientNode *) node;
464 cairo_pattern_t *pattern;
465 gsize i;
466
467 pattern = cairo_pattern_create_radial (cx0: 0, cy0: 0, radius0: self->hradius * self->start,
468 cx1: 0, cy1: 0, radius1: self->hradius * self->end);
469
470 if (self->hradius != self->vradius)
471 {
472 cairo_matrix_t matrix;
473
474 cairo_matrix_init_scale (matrix: &matrix, sx: 1.0, sy: self->hradius / self->vradius);
475 cairo_pattern_set_matrix (pattern, matrix: &matrix);
476 }
477
478 if (gsk_render_node_get_node_type (node) == GSK_REPEATING_RADIAL_GRADIENT_NODE)
479 cairo_pattern_set_extend (pattern, extend: CAIRO_EXTEND_REPEAT);
480 else
481 cairo_pattern_set_extend (pattern, extend: CAIRO_EXTEND_PAD);
482
483 for (i = 0; i < self->n_stops; i++)
484 cairo_pattern_add_color_stop_rgba (pattern,
485 offset: self->stops[i].offset,
486 red: self->stops[i].color.red,
487 green: self->stops[i].color.green,
488 blue: self->stops[i].color.blue,
489 alpha: self->stops[i].color.alpha);
490
491 gsk_cairo_rectangle (cr, rect: &node->bounds);
492 cairo_translate (cr, tx: self->center.x, ty: self->center.y);
493 cairo_set_source (cr, source: pattern);
494 cairo_fill (cr);
495
496 cairo_pattern_destroy (pattern);
497}
498
499static void
500gsk_radial_gradient_node_diff (GskRenderNode *node1,
501 GskRenderNode *node2,
502 cairo_region_t *region)
503{
504 GskRadialGradientNode *self1 = (GskRadialGradientNode *) node1;
505 GskRadialGradientNode *self2 = (GskRadialGradientNode *) node2;
506
507 if (graphene_point_equal (a: &self1->center, b: &self2->center) &&
508 self1->hradius == self2->hradius &&
509 self1->vradius == self2->vradius &&
510 self1->start == self2->start &&
511 self1->end == self2->end &&
512 self1->n_stops == self2->n_stops)
513 {
514 gsize i;
515
516 for (i = 0; i < self1->n_stops; i++)
517 {
518 GskColorStop *stop1 = &self1->stops[i];
519 GskColorStop *stop2 = &self2->stops[i];
520
521 if (stop1->offset == stop2->offset &&
522 gdk_rgba_equal (p1: &stop1->color, p2: &stop2->color))
523 continue;
524
525 gsk_render_node_diff_impossible (node1, node2, region);
526 return;
527 }
528
529 return;
530 }
531
532 gsk_render_node_diff_impossible (node1, node2, region);
533}
534
535/**
536 * gsk_radial_gradient_node_new:
537 * @bounds: the bounds of the node
538 * @center: the center of the gradient
539 * @hradius: the horizontal radius
540 * @vradius: the vertical radius
541 * @start: a percentage >= 0 that defines the start of the gradient around @center
542 * @end: a percentage >= 0 that defines the end of the gradient around @center
543 * @color_stops: (array length=n_color_stops): a pointer to an array of
544 * `GskColorStop` defining the gradient. The offsets of all color stops
545 * must be increasing. The first stop's offset must be >= 0 and the last
546 * stop's offset must be <= 1.
547 * @n_color_stops: the number of elements in @color_stops
548 *
549 * Creates a `GskRenderNode` that draws a radial gradient.
550 *
551 * The radial gradient
552 * starts around @center. The size of the gradient is dictated by @hradius
553 * in horizontal orientation and by @vradius in vertial orientation.
554 *
555 * Returns: (transfer full) (type GskRadialGradientNode): A new `GskRenderNode`
556 */
557GskRenderNode *
558gsk_radial_gradient_node_new (const graphene_rect_t *bounds,
559 const graphene_point_t *center,
560 float hradius,
561 float vradius,
562 float start,
563 float end,
564 const GskColorStop *color_stops,
565 gsize n_color_stops)
566{
567 GskRadialGradientNode *self;
568 GskRenderNode *node;
569 gsize i;
570
571 g_return_val_if_fail (bounds != NULL, NULL);
572 g_return_val_if_fail (center != NULL, NULL);
573 g_return_val_if_fail (hradius > 0., NULL);
574 g_return_val_if_fail (vradius > 0., NULL);
575 g_return_val_if_fail (start >= 0., NULL);
576 g_return_val_if_fail (end >= 0., NULL);
577 g_return_val_if_fail (end > start, NULL);
578 g_return_val_if_fail (color_stops != NULL, NULL);
579 g_return_val_if_fail (n_color_stops >= 2, NULL);
580 g_return_val_if_fail (color_stops[0].offset >= 0, NULL);
581 for (i = 1; i < n_color_stops; i++)
582 g_return_val_if_fail (color_stops[i].offset >= color_stops[i - 1].offset, NULL);
583 g_return_val_if_fail (color_stops[n_color_stops - 1].offset <= 1, NULL);
584
585 self = gsk_render_node_alloc (node_type: GSK_RADIAL_GRADIENT_NODE);
586 node = (GskRenderNode *) self;
587
588 graphene_rect_init_from_rect (r: &node->bounds, src: bounds);
589 graphene_point_init_from_point (p: &self->center, src: center);
590
591 self->hradius = hradius;
592 self->vradius = vradius;
593 self->start = start;
594 self->end = end;
595
596 self->n_stops = n_color_stops;
597 self->stops = g_malloc_n (n_blocks: n_color_stops, n_block_bytes: sizeof (GskColorStop));
598 memcpy (dest: self->stops, src: color_stops, n: n_color_stops * sizeof (GskColorStop));
599
600 return node;
601}
602
603/**
604 * gsk_repeating_radial_gradient_node_new:
605 * @bounds: the bounds of the node
606 * @center: the center of the gradient
607 * @hradius: the horizontal radius
608 * @vradius: the vertical radius
609 * @start: a percentage >= 0 that defines the start of the gradient around @center
610 * @end: a percentage >= 0 that defines the end of the gradient around @center
611 * @color_stops: (array length=n_color_stops): a pointer to an array of
612 * `GskColorStop` defining the gradient. The offsets of all color stops
613 * must be increasing. The first stop's offset must be >= 0 and the last
614 * stop's offset must be <= 1.
615 * @n_color_stops: the number of elements in @color_stops
616 *
617 * Creates a `GskRenderNode` that draws a repeating radial gradient.
618 *
619 * The radial gradient starts around @center. The size of the gradient
620 * is dictated by @hradius in horizontal orientation and by @vradius
621 * in vertial orientation.
622 *
623 * Returns: (transfer full) (type GskRepeatingRadialGradientNode): A new `GskRenderNode`
624 */
625GskRenderNode *
626gsk_repeating_radial_gradient_node_new (const graphene_rect_t *bounds,
627 const graphene_point_t *center,
628 float hradius,
629 float vradius,
630 float start,
631 float end,
632 const GskColorStop *color_stops,
633 gsize n_color_stops)
634{
635 GskRadialGradientNode *self;
636 GskRenderNode *node;
637 gsize i;
638
639 g_return_val_if_fail (bounds != NULL, NULL);
640 g_return_val_if_fail (center != NULL, NULL);
641 g_return_val_if_fail (hradius > 0., NULL);
642 g_return_val_if_fail (vradius > 0., NULL);
643 g_return_val_if_fail (start >= 0., NULL);
644 g_return_val_if_fail (end >= 0., NULL);
645 g_return_val_if_fail (end > start, NULL);
646 g_return_val_if_fail (color_stops != NULL, NULL);
647 g_return_val_if_fail (n_color_stops >= 2, NULL);
648 g_return_val_if_fail (color_stops[0].offset >= 0, NULL);
649 for (i = 1; i < n_color_stops; i++)
650 g_return_val_if_fail (color_stops[i].offset >= color_stops[i - 1].offset, NULL);
651 g_return_val_if_fail (color_stops[n_color_stops - 1].offset <= 1, NULL);
652
653 self = gsk_render_node_alloc (node_type: GSK_REPEATING_RADIAL_GRADIENT_NODE);
654 node = (GskRenderNode *) self;
655
656 graphene_rect_init_from_rect (r: &node->bounds, src: bounds);
657 graphene_point_init_from_point (p: &self->center, src: center);
658
659 self->hradius = hradius;
660 self->vradius = vradius;
661 self->start = start;
662 self->end = end;
663
664 self->n_stops = n_color_stops;
665 self->stops = g_malloc_n (n_blocks: n_color_stops, n_block_bytes: sizeof (GskColorStop));
666 memcpy (dest: self->stops, src: color_stops, n: n_color_stops * sizeof (GskColorStop));
667
668 return node;
669}
670
671/**
672 * gsk_radial_gradient_node_get_n_color_stops:
673 * @node: (type GskRadialGradientNode): a `GskRenderNode` for a radial gradient
674 *
675 * Retrieves the number of color stops in the gradient.
676 *
677 * Returns: the number of color stops
678 */
679gsize
680gsk_radial_gradient_node_get_n_color_stops (const GskRenderNode *node)
681{
682 const GskRadialGradientNode *self = (const GskRadialGradientNode *) node;
683
684 return self->n_stops;
685}
686
687/**
688 * gsk_radial_gradient_node_get_color_stops:
689 * @node: (type GskRadialGradientNode): a `GskRenderNode` for a radial gradient
690 * @n_stops: (out) (optional): the number of color stops in the returned array
691 *
692 * Retrieves the color stops in the gradient.
693 *
694 * Returns: (array length=n_stops): the color stops in the gradient
695 */
696const GskColorStop *
697gsk_radial_gradient_node_get_color_stops (const GskRenderNode *node,
698 gsize *n_stops)
699{
700 const GskRadialGradientNode *self = (const GskRadialGradientNode *) node;
701
702 if (n_stops != NULL)
703 *n_stops = self->n_stops;
704
705 return self->stops;
706}
707
708/**
709 * gsk_radial_gradient_node_get_center:
710 * @node: (type GskRadialGradientNode): a `GskRenderNode` for a radial gradient
711 *
712 * Retrieves the center pointer for the gradient.
713 *
714 * Returns: the center point for the gradient
715 */
716const graphene_point_t *
717gsk_radial_gradient_node_get_center (const GskRenderNode *node)
718{
719 const GskRadialGradientNode *self = (const GskRadialGradientNode *) node;
720
721 return &self->center;
722}
723
724/**
725 * gsk_radial_gradient_node_get_hradius:
726 * @node: (type GskRadialGradientNode): a `GskRenderNode` for a radial gradient
727 *
728 * Retrieves the horizonal radius for the gradient.
729 *
730 * Returns: the horizontal radius for the gradient
731 */
732float
733gsk_radial_gradient_node_get_hradius (const GskRenderNode *node)
734{
735 const GskRadialGradientNode *self = (const GskRadialGradientNode *) node;
736
737 return self->hradius;
738}
739
740/**
741 * gsk_radial_gradient_node_get_vradius:
742 * @node: (type GskRadialGradientNode): a `GskRenderNode` for a radial gradient
743 *
744 * Retrieves the vertical radius for the gradient.
745 *
746 * Returns: the vertical radius for the gradient
747 */
748float
749gsk_radial_gradient_node_get_vradius (const GskRenderNode *node)
750{
751 const GskRadialGradientNode *self = (const GskRadialGradientNode *) node;
752
753 return self->vradius;
754}
755
756/**
757 * gsk_radial_gradient_node_get_start:
758 * @node: (type GskRadialGradientNode): a `GskRenderNode` for a radial gradient
759 *
760 * Retrieves the start value for the gradient.
761 *
762 * Returns: the start value for the gradient
763 */
764float
765gsk_radial_gradient_node_get_start (const GskRenderNode *node)
766{
767 const GskRadialGradientNode *self = (const GskRadialGradientNode *) node;
768
769 return self->start;
770}
771
772/**
773 * gsk_radial_gradient_node_get_end:
774 * @node: (type GskRadialGradientNode): a `GskRenderNode` for a radial gradient
775 *
776 * Retrieves the end value for the gradient.
777 *
778 * Returns: the end value for the gradient
779 */
780float
781gsk_radial_gradient_node_get_end (const GskRenderNode *node)
782{
783 const GskRadialGradientNode *self = (const GskRadialGradientNode *) node;
784
785 return self->end;
786}
787
788/*** GSK_CONIC_GRADIENT_NODE ***/
789
790/**
791 * GskConicGradientNode:
792 *
793 * A render node for a conic gradient.
794 */
795
796struct _GskConicGradientNode
797{
798 GskRenderNode render_node;
799
800 graphene_point_t center;
801 float rotation;
802 float angle;
803
804 gsize n_stops;
805 GskColorStop *stops;
806};
807
808static void
809gsk_conic_gradient_node_finalize (GskRenderNode *node)
810{
811 GskConicGradientNode *self = (GskConicGradientNode *) node;
812 GskRenderNodeClass *parent_class = g_type_class_peek (type: g_type_parent (GSK_TYPE_CONIC_GRADIENT_NODE));
813
814 g_free (mem: self->stops);
815
816 parent_class->finalize (node);
817}
818
819#define DEG_TO_RAD(x) ((x) * (G_PI / 180.f))
820
821static void
822_cairo_mesh_pattern_set_corner_rgba (cairo_pattern_t *pattern,
823 guint corner_num,
824 const GdkRGBA *rgba)
825{
826 cairo_mesh_pattern_set_corner_color_rgba (pattern, corner_num, red: rgba->red, green: rgba->green, blue: rgba->blue, alpha: rgba->alpha);
827}
828
829static void
830project (double angle,
831 double radius,
832 double *x_out,
833 double *y_out)
834{
835 double x, y;
836
837#ifdef HAVE_SINCOS
838 sincos (x: angle, sinx: &y, cosx: &x);
839#else
840 x = cos (angle);
841 y = sin (angle);
842#endif
843 *x_out = radius * x;
844 *y_out = radius * y;
845}
846
847static void
848gsk_conic_gradient_node_add_patch (cairo_pattern_t *pattern,
849 float radius,
850 float start_angle,
851 const GdkRGBA *start_color,
852 float end_angle,
853 const GdkRGBA *end_color)
854{
855 double x, y;
856
857 cairo_mesh_pattern_begin_patch (pattern);
858
859 cairo_mesh_pattern_move_to (pattern, x: 0, y: 0);
860 project (angle: start_angle, radius, x_out: &x, y_out: &y);
861 cairo_mesh_pattern_line_to (pattern, x, y);
862 project (angle: end_angle, radius, x_out: &x, y_out: &y);
863 cairo_mesh_pattern_line_to (pattern, x, y);
864 cairo_mesh_pattern_line_to (pattern, x: 0, y: 0);
865
866 _cairo_mesh_pattern_set_corner_rgba (pattern, corner_num: 0, rgba: start_color);
867 _cairo_mesh_pattern_set_corner_rgba (pattern, corner_num: 1, rgba: start_color);
868 _cairo_mesh_pattern_set_corner_rgba (pattern, corner_num: 2, rgba: end_color);
869 _cairo_mesh_pattern_set_corner_rgba (pattern, corner_num: 3, rgba: end_color);
870
871 cairo_mesh_pattern_end_patch (pattern);
872}
873
874static void
875gdk_rgba_color_interpolate (GdkRGBA *dest,
876 const GdkRGBA *src1,
877 const GdkRGBA *src2,
878 double progress)
879{
880 double alpha = src1->alpha * (1.0 - progress) + src2->alpha * progress;
881
882 dest->alpha = alpha;
883 if (alpha == 0)
884 {
885 dest->red = src1->red * (1.0 - progress) + src2->red * progress;
886 dest->green = src1->green * (1.0 - progress) + src2->green * progress;
887 dest->blue = src1->blue * (1.0 - progress) + src2->blue * progress;
888 }
889 else
890 {
891 dest->red = (src1->red * src1->alpha * (1.0 - progress) + src2->red * src2->alpha * progress) / alpha;
892 dest->green = (src1->green * src1->alpha * (1.0 - progress) + src2->green * src2->alpha * progress) / alpha;
893 dest->blue = (src1->blue * src1->alpha * (1.0 - progress) + src2->blue * src2->alpha * progress) / alpha;
894 }
895}
896
897static void
898gsk_conic_gradient_node_draw (GskRenderNode *node,
899 cairo_t *cr)
900{
901 GskConicGradientNode *self = (GskConicGradientNode *) node;
902 cairo_pattern_t *pattern;
903 graphene_point_t corner;
904 float radius;
905 gsize i;
906
907 pattern = cairo_pattern_create_mesh ();
908 graphene_rect_get_top_right (r: &node->bounds, p: &corner);
909 radius = graphene_point_distance (a: &self->center, b: &corner, NULL, NULL);
910 graphene_rect_get_bottom_right (r: &node->bounds, p: &corner);
911 radius = MAX (radius, graphene_point_distance (&self->center, &corner, NULL, NULL));
912 graphene_rect_get_bottom_left (r: &node->bounds, p: &corner);
913 radius = MAX (radius, graphene_point_distance (&self->center, &corner, NULL, NULL));
914 graphene_rect_get_top_left (r: &node->bounds, p: &corner);
915 radius = MAX (radius, graphene_point_distance (&self->center, &corner, NULL, NULL));
916
917 for (i = 0; i <= self->n_stops; i++)
918 {
919 GskColorStop *stop1 = &self->stops[MAX (i, 1) - 1];
920 GskColorStop *stop2 = &self->stops[MIN (i, self->n_stops - 1)];
921 double offset1 = i > 0 ? stop1->offset : 0;
922 double offset2 = i < self->n_stops ? stop2->offset : 1;
923 double start_angle, end_angle;
924
925 offset1 = offset1 * 360 + self->rotation - 90;
926 offset2 = offset2 * 360 + self->rotation - 90;
927
928 for (start_angle = offset1; start_angle < offset2; start_angle = end_angle)
929 {
930 GdkRGBA start_color, end_color;
931 end_angle = (floor (x: start_angle / 45) + 1) * 45;
932 end_angle = MIN (end_angle, offset2);
933 gdk_rgba_color_interpolate (dest: &start_color,
934 src1: &stop1->color,
935 src2: &stop2->color,
936 progress: (start_angle - offset1) / (offset2 - offset1));
937 gdk_rgba_color_interpolate (dest: &end_color,
938 src1: &stop1->color,
939 src2: &stop2->color,
940 progress: (end_angle - offset1) / (offset2 - offset1));
941
942 gsk_conic_gradient_node_add_patch (pattern,
943 radius,
944 DEG_TO_RAD (start_angle),
945 start_color: &start_color,
946 DEG_TO_RAD (end_angle),
947 end_color: &end_color);
948 }
949 }
950
951 cairo_pattern_set_extend (pattern, extend: CAIRO_EXTEND_PAD);
952
953 gsk_cairo_rectangle (cr, rect: &node->bounds);
954 cairo_translate (cr, tx: self->center.x, ty: self->center.y);
955 cairo_set_source (cr, source: pattern);
956 cairo_fill (cr);
957
958 cairo_pattern_destroy (pattern);
959}
960
961static void
962gsk_conic_gradient_node_diff (GskRenderNode *node1,
963 GskRenderNode *node2,
964 cairo_region_t *region)
965{
966 GskConicGradientNode *self1 = (GskConicGradientNode *) node1;
967 GskConicGradientNode *self2 = (GskConicGradientNode *) node2;
968 gsize i;
969
970 if (!graphene_point_equal (a: &self1->center, b: &self2->center) ||
971 self1->rotation != self2->rotation ||
972 self1->n_stops != self2->n_stops)
973 {
974 gsk_render_node_diff_impossible (node1, node2, region);
975 return;
976 }
977
978 for (i = 0; i < self1->n_stops; i++)
979 {
980 GskColorStop *stop1 = &self1->stops[i];
981 GskColorStop *stop2 = &self2->stops[i];
982
983 if (stop1->offset != stop2->offset ||
984 !gdk_rgba_equal (p1: &stop1->color, p2: &stop2->color))
985 {
986 gsk_render_node_diff_impossible (node1, node2, region);
987 return;
988 }
989 }
990}
991
992/**
993 * gsk_conic_gradient_node_new:
994 * @bounds: the bounds of the node
995 * @center: the center of the gradient
996 * @rotation: the rotation of the gradient in degrees
997 * @color_stops: (array length=n_color_stops): a pointer to an array of
998 * `GskColorStop` defining the gradient. The offsets of all color stops
999 * must be increasing. The first stop's offset must be >= 0 and the last
1000 * stop's offset must be <= 1.
1001 * @n_color_stops: the number of elements in @color_stops
1002 *
1003 * Creates a `GskRenderNode` that draws a conic gradient.
1004 *
1005 * The conic gradient
1006 * starts around @center in the direction of @rotation. A rotation of 0 means
1007 * that the gradient points up. Color stops are then added clockwise.
1008 *
1009 * Returns: (transfer full) (type GskConicGradientNode): A new `GskRenderNode`
1010 */
1011GskRenderNode *
1012gsk_conic_gradient_node_new (const graphene_rect_t *bounds,
1013 const graphene_point_t *center,
1014 float rotation,
1015 const GskColorStop *color_stops,
1016 gsize n_color_stops)
1017{
1018 GskConicGradientNode *self;
1019 GskRenderNode *node;
1020 gsize i;
1021
1022 g_return_val_if_fail (bounds != NULL, NULL);
1023 g_return_val_if_fail (center != NULL, NULL);
1024 g_return_val_if_fail (color_stops != NULL, NULL);
1025 g_return_val_if_fail (n_color_stops >= 2, NULL);
1026 g_return_val_if_fail (color_stops[0].offset >= 0, NULL);
1027 for (i = 1; i < n_color_stops; i++)
1028 g_return_val_if_fail (color_stops[i].offset >= color_stops[i - 1].offset, NULL);
1029 g_return_val_if_fail (color_stops[n_color_stops - 1].offset <= 1, NULL);
1030
1031 self = gsk_render_node_alloc (node_type: GSK_CONIC_GRADIENT_NODE);
1032 node = (GskRenderNode *) self;
1033
1034 graphene_rect_init_from_rect (r: &node->bounds, src: bounds);
1035 graphene_point_init_from_point (p: &self->center, src: center);
1036
1037 self->rotation = rotation;
1038
1039 self->n_stops = n_color_stops;
1040 self->stops = g_malloc_n (n_blocks: n_color_stops, n_block_bytes: sizeof (GskColorStop));
1041 memcpy (dest: self->stops, src: color_stops, n: n_color_stops * sizeof (GskColorStop));
1042
1043 self->angle = 90.f - self->rotation;
1044 self->angle = G_PI * self->angle / 180.f;
1045 self->angle = fmodf (x: self->angle, y: 2.f * G_PI);
1046 if (self->angle < 0.f)
1047 self->angle += 2.f * G_PI;
1048
1049 return node;
1050}
1051
1052/**
1053 * gsk_conic_gradient_node_get_n_color_stops:
1054 * @node: (type GskConicGradientNode): a `GskRenderNode` for a conic gradient
1055 *
1056 * Retrieves the number of color stops in the gradient.
1057 *
1058 * Returns: the number of color stops
1059 */
1060gsize
1061gsk_conic_gradient_node_get_n_color_stops (const GskRenderNode *node)
1062{
1063 const GskConicGradientNode *self = (const GskConicGradientNode *) node;
1064
1065 return self->n_stops;
1066}
1067
1068/**
1069 * gsk_conic_gradient_node_get_color_stops:
1070 * @node: (type GskConicGradientNode): a `GskRenderNode` for a conic gradient
1071 * @n_stops: (out) (optional): the number of color stops in the returned array
1072 *
1073 * Retrieves the color stops in the gradient.
1074 *
1075 * Returns: (array length=n_stops): the color stops in the gradient
1076 */
1077const GskColorStop *
1078gsk_conic_gradient_node_get_color_stops (const GskRenderNode *node,
1079 gsize *n_stops)
1080{
1081 const GskConicGradientNode *self = (const GskConicGradientNode *) node;
1082
1083 if (n_stops != NULL)
1084 *n_stops = self->n_stops;
1085
1086 return self->stops;
1087}
1088
1089/**
1090 * gsk_conic_gradient_node_get_center:
1091 * @node: (type GskConicGradientNode): a `GskRenderNode` for a conic gradient
1092 *
1093 * Retrieves the center pointer for the gradient.
1094 *
1095 * Returns: the center point for the gradient
1096 */
1097const graphene_point_t *
1098gsk_conic_gradient_node_get_center (const GskRenderNode *node)
1099{
1100 const GskConicGradientNode *self = (const GskConicGradientNode *) node;
1101
1102 return &self->center;
1103}
1104
1105/**
1106 * gsk_conic_gradient_node_get_rotation:
1107 * @node: (type GskConicGradientNode): a `GskRenderNode` for a conic gradient
1108 *
1109 * Retrieves the rotation for the gradient in degrees.
1110 *
1111 * Returns: the rotation for the gradient
1112 */
1113float
1114gsk_conic_gradient_node_get_rotation (const GskRenderNode *node)
1115{
1116 const GskConicGradientNode *self = (const GskConicGradientNode *) node;
1117
1118 return self->rotation;
1119}
1120
1121/**
1122 * gsk_conic_gradient_node_get_angle:
1123 * @node: (type GskConicGradientNode): a `GskRenderNode` for a conic gradient
1124 *
1125 * Retrieves the angle for the gradient in radians, normalized in [0, 2 * PI].
1126 *
1127 * The angle is starting at the top and going clockwise, as expressed
1128 * in the css specification:
1129 *
1130 * angle = 90 - gsk_conic_gradient_node_get_rotation()
1131 *
1132 * Returns: the angle for the gradient
1133 *
1134 * Since: 4.2
1135 */
1136float
1137gsk_conic_gradient_node_get_angle (const GskRenderNode *node)
1138{
1139 const GskConicGradientNode *self = (const GskConicGradientNode *) node;
1140
1141 return self->angle;
1142}
1143
1144/*** GSK_BORDER_NODE ***/
1145
1146/**
1147 * GskBorderNode:
1148 *
1149 * A render node for a border.
1150 */
1151struct _GskBorderNode
1152{
1153 GskRenderNode render_node;
1154
1155 bool uniform_width: 1;
1156 bool uniform_color: 1;
1157 GskRoundedRect outline;
1158 float border_width[4];
1159 GdkRGBA border_color[4];
1160};
1161
1162static void
1163gsk_border_node_mesh_add_patch (cairo_pattern_t *pattern,
1164 const GdkRGBA *color,
1165 double x0,
1166 double y0,
1167 double x1,
1168 double y1,
1169 double x2,
1170 double y2,
1171 double x3,
1172 double y3)
1173{
1174 cairo_mesh_pattern_begin_patch (pattern);
1175 cairo_mesh_pattern_move_to (pattern, x: x0, y: y0);
1176 cairo_mesh_pattern_line_to (pattern, x: x1, y: y1);
1177 cairo_mesh_pattern_line_to (pattern, x: x2, y: y2);
1178 cairo_mesh_pattern_line_to (pattern, x: x3, y: y3);
1179 cairo_mesh_pattern_set_corner_color_rgba (pattern, corner_num: 0, red: color->red, green: color->green, blue: color->blue, alpha: color->alpha);
1180 cairo_mesh_pattern_set_corner_color_rgba (pattern, corner_num: 1, red: color->red, green: color->green, blue: color->blue, alpha: color->alpha);
1181 cairo_mesh_pattern_set_corner_color_rgba (pattern, corner_num: 2, red: color->red, green: color->green, blue: color->blue, alpha: color->alpha);
1182 cairo_mesh_pattern_set_corner_color_rgba (pattern, corner_num: 3, red: color->red, green: color->green, blue: color->blue, alpha: color->alpha);
1183 cairo_mesh_pattern_end_patch (pattern);
1184}
1185
1186static void
1187gsk_border_node_draw (GskRenderNode *node,
1188 cairo_t *cr)
1189{
1190 GskBorderNode *self = (GskBorderNode *) node;
1191 GskRoundedRect inside;
1192
1193 cairo_save (cr);
1194
1195 gsk_rounded_rect_init_copy (self: &inside, src: &self->outline);
1196 gsk_rounded_rect_shrink (self: &inside,
1197 top: self->border_width[0], right: self->border_width[1],
1198 bottom: self->border_width[2], left: self->border_width[3]);
1199
1200 cairo_set_fill_rule (cr, fill_rule: CAIRO_FILL_RULE_EVEN_ODD);
1201 gsk_rounded_rect_path (self: &self->outline, cr);
1202 gsk_rounded_rect_path (self: &inside, cr);
1203
1204 if (gdk_rgba_equal (p1: &self->border_color[0], p2: &self->border_color[1]) &&
1205 gdk_rgba_equal (p1: &self->border_color[0], p2: &self->border_color[2]) &&
1206 gdk_rgba_equal (p1: &self->border_color[0], p2: &self->border_color[3]))
1207 {
1208 gdk_cairo_set_source_rgba (cr, rgba: &self->border_color[0]);
1209 }
1210 else
1211 {
1212 const graphene_rect_t *bounds = &self->outline.bounds;
1213 /* distance to center "line":
1214 * +-------------------------+
1215 * | |
1216 * | |
1217 * | ---this-line--- |
1218 * | |
1219 * | |
1220 * +-------------------------+
1221 * That line is equidistant from all sides. It's either horizontal
1222 * or vertical, depending on if the rect is wider or taller.
1223 * We use the 4 sides spanned up by connecting the line to the corner
1224 * points to color the regions of the rectangle differently.
1225 * Note that the call to cairo_fill() will add the potential final
1226 * segment by closing the path, so we don't have to care.
1227 */
1228 cairo_pattern_t *mesh;
1229 cairo_matrix_t mat;
1230 graphene_point_t tl, br;
1231 float scale;
1232
1233 mesh = cairo_pattern_create_mesh ();
1234 cairo_matrix_init_translate (matrix: &mat, tx: -bounds->origin.x, ty: -bounds->origin.y);
1235 cairo_pattern_set_matrix (pattern: mesh, matrix: &mat);
1236
1237 scale = MIN (bounds->size.width / (self->border_width[1] + self->border_width[3]),
1238 bounds->size.height / (self->border_width[0] + self->border_width[2]));
1239 graphene_point_init (p: &tl,
1240 x: self->border_width[3] * scale,
1241 y: self->border_width[0] * scale);
1242 graphene_point_init (p: &br,
1243 x: bounds->size.width - self->border_width[1] * scale,
1244 y: bounds->size.height - self->border_width[2] * scale);
1245
1246 /* Top */
1247 if (self->border_width[0] > 0)
1248 {
1249 gsk_border_node_mesh_add_patch (pattern: mesh,
1250 color: &self->border_color[0],
1251 x0: 0, y0: 0,
1252 x1: tl.x, y1: tl.y,
1253 x2: br.x, y2: tl.y,
1254 x3: bounds->size.width, y3: 0);
1255 }
1256
1257 /* Right */
1258 if (self->border_width[1] > 0)
1259 {
1260 gsk_border_node_mesh_add_patch (pattern: mesh,
1261 color: &self->border_color[1],
1262 x0: bounds->size.width, y0: 0,
1263 x1: br.x, y1: tl.y,
1264 x2: br.x, y2: br.y,
1265 x3: bounds->size.width, y3: bounds->size.height);
1266 }
1267
1268 /* Bottom */
1269 if (self->border_width[2] > 0)
1270 {
1271 gsk_border_node_mesh_add_patch (pattern: mesh,
1272 color: &self->border_color[2],
1273 x0: 0, y0: bounds->size.height,
1274 x1: tl.x, y1: br.y,
1275 x2: br.x, y2: br.y,
1276 x3: bounds->size.width, y3: bounds->size.height);
1277 }
1278
1279 /* Left */
1280 if (self->border_width[3] > 0)
1281 {
1282 gsk_border_node_mesh_add_patch (pattern: mesh,
1283 color: &self->border_color[3],
1284 x0: 0, y0: 0,
1285 x1: tl.x, y1: tl.y,
1286 x2: tl.x, y2: br.y,
1287 x3: 0, y3: bounds->size.height);
1288 }
1289
1290 cairo_set_source (cr, source: mesh);
1291 cairo_pattern_destroy (pattern: mesh);
1292 }
1293
1294 cairo_fill (cr);
1295 cairo_restore (cr);
1296}
1297
1298static void
1299gsk_border_node_diff (GskRenderNode *node1,
1300 GskRenderNode *node2,
1301 cairo_region_t *region)
1302{
1303 GskBorderNode *self1 = (GskBorderNode *) node1;
1304 GskBorderNode *self2 = (GskBorderNode *) node2;
1305 gboolean uniform1 = self1->uniform_width && self1->uniform_color;
1306 gboolean uniform2 = self2->uniform_width && self2->uniform_color;
1307
1308 if (uniform1 &&
1309 uniform2 &&
1310 self1->border_width[0] == self2->border_width[0] &&
1311 gsk_rounded_rect_equal (rect1: &self1->outline, rect2: &self2->outline) &&
1312 gdk_rgba_equal (p1: &self1->border_color[0], p2: &self2->border_color[0]))
1313 return;
1314
1315 /* Different uniformity -> diff impossible */
1316 if (uniform1 ^ uniform2)
1317 {
1318 gsk_render_node_diff_impossible (node1, node2, region);
1319 return;
1320 }
1321
1322 if (self1->border_width[0] == self2->border_width[0] &&
1323 self1->border_width[1] == self2->border_width[1] &&
1324 self1->border_width[2] == self2->border_width[2] &&
1325 self1->border_width[3] == self2->border_width[3] &&
1326 gdk_rgba_equal (p1: &self1->border_color[0], p2: &self2->border_color[0]) &&
1327 gdk_rgba_equal (p1: &self1->border_color[1], p2: &self2->border_color[1]) &&
1328 gdk_rgba_equal (p1: &self1->border_color[2], p2: &self2->border_color[2]) &&
1329 gdk_rgba_equal (p1: &self1->border_color[3], p2: &self2->border_color[3]) &&
1330 gsk_rounded_rect_equal (rect1: &self1->outline, rect2: &self2->outline))
1331 return;
1332
1333 gsk_render_node_diff_impossible (node1, node2, region);
1334}
1335
1336/**
1337 * gsk_border_node_get_outline:
1338 * @node: (type GskBorderNode): a `GskRenderNode` for a border
1339 *
1340 * Retrieves the outline of the border.
1341 *
1342 * Returns: the outline of the border
1343 */
1344const GskRoundedRect *
1345gsk_border_node_get_outline (const GskRenderNode *node)
1346{
1347 const GskBorderNode *self = (const GskBorderNode *) node;
1348
1349 return &self->outline;
1350}
1351
1352/**
1353 * gsk_border_node_get_widths:
1354 * @node: (type GskBorderNode): a `GskRenderNode` for a border
1355 *
1356 * Retrieves the stroke widths of the border.
1357 *
1358 * Returns: (transfer none) (array fixed-size=4): an array of 4 floats
1359 * for the top, right, bottom and left stroke width of the border,
1360 * respectively
1361 */
1362const float *
1363gsk_border_node_get_widths (const GskRenderNode *node)
1364{
1365 const GskBorderNode *self = (const GskBorderNode *) node;
1366
1367 return self->border_width;
1368}
1369
1370/**
1371 * gsk_border_node_get_colors:
1372 * @node: (type GskBorderNode): a `GskRenderNode` for a border
1373 *
1374 * Retrieves the colors of the border.
1375 *
1376 * Returns: (transfer none): an array of 4 `GdkRGBA` structs
1377 * for the top, right, bottom and left color of the border
1378 */
1379const GdkRGBA *
1380gsk_border_node_get_colors (const GskRenderNode *node)
1381{
1382 const GskBorderNode *self = (const GskBorderNode *) node;
1383
1384 return self->border_color;
1385}
1386
1387/**
1388 * gsk_border_node_new:
1389 * @outline: a `GskRoundedRect` describing the outline of the border
1390 * @border_width: (array fixed-size=4): the stroke width of the border on
1391 * the top, right, bottom and left side respectively.
1392 * @border_color: (array fixed-size=4): the color used on the top, right,
1393 * bottom and left side.
1394 *
1395 * Creates a `GskRenderNode` that will stroke a border rectangle inside the
1396 * given @outline.
1397 *
1398 * The 4 sides of the border can have different widths and colors.
1399 *
1400 * Returns: (transfer full) (type GskBorderNode): A new `GskRenderNode`
1401 */
1402GskRenderNode *
1403gsk_border_node_new (const GskRoundedRect *outline,
1404 const float border_width[4],
1405 const GdkRGBA border_color[4])
1406{
1407 GskBorderNode *self;
1408 GskRenderNode *node;
1409
1410 g_return_val_if_fail (outline != NULL, NULL);
1411 g_return_val_if_fail (border_width != NULL, NULL);
1412 g_return_val_if_fail (border_color != NULL, NULL);
1413
1414 self = gsk_render_node_alloc (node_type: GSK_BORDER_NODE);
1415 node = (GskRenderNode *) self;
1416
1417 gsk_rounded_rect_init_copy (self: &self->outline, src: outline);
1418 memcpy (dest: self->border_width, src: border_width, n: sizeof (self->border_width));
1419 memcpy (dest: self->border_color, src: border_color, n: sizeof (self->border_color));
1420
1421 if (border_width[0] == border_width[1] &&
1422 border_width[0] == border_width[2] &&
1423 border_width[0] == border_width[3])
1424 self->uniform_width = TRUE;
1425 else
1426 self->uniform_width = FALSE;
1427
1428 if (gdk_rgba_equal (p1: &border_color[0], p2: &border_color[1]) &&
1429 gdk_rgba_equal (p1: &border_color[0], p2: &border_color[2]) &&
1430 gdk_rgba_equal (p1: &border_color[0], p2: &border_color[3]))
1431 self->uniform_color = TRUE;
1432 else
1433 self->uniform_color = FALSE;
1434
1435 graphene_rect_init_from_rect (r: &node->bounds, src: &self->outline.bounds);
1436
1437 return node;
1438}
1439
1440/* Private */
1441bool
1442gsk_border_node_get_uniform (const GskRenderNode *self)
1443{
1444 const GskBorderNode *node = (const GskBorderNode *)self;
1445 return node->uniform_width && node->uniform_color;
1446}
1447
1448bool
1449gsk_border_node_get_uniform_color (const GskRenderNode *self)
1450{
1451 const GskBorderNode *node = (const GskBorderNode *)self;
1452 return node->uniform_color;
1453}
1454
1455/*** GSK_TEXTURE_NODE ***/
1456
1457/**
1458 * GskTextureNode:
1459 *
1460 * A render node for a `GdkTexture`.
1461 */
1462struct _GskTextureNode
1463{
1464 GskRenderNode render_node;
1465
1466 GdkTexture *texture;
1467};
1468
1469static void
1470gsk_texture_node_finalize (GskRenderNode *node)
1471{
1472 GskTextureNode *self = (GskTextureNode *) node;
1473 GskRenderNodeClass *parent_class = g_type_class_peek (type: g_type_parent (GSK_TYPE_TEXTURE_NODE));
1474
1475 g_clear_object (&self->texture);
1476
1477 parent_class->finalize (node);
1478}
1479
1480static void
1481gsk_texture_node_draw (GskRenderNode *node,
1482 cairo_t *cr)
1483{
1484 GskTextureNode *self = (GskTextureNode *) node;
1485 cairo_surface_t *surface;
1486 cairo_pattern_t *pattern;
1487 cairo_matrix_t matrix;
1488
1489 surface = gdk_texture_download_surface (texture: self->texture);
1490 pattern = cairo_pattern_create_for_surface (surface);
1491 cairo_pattern_set_extend (pattern, extend: CAIRO_EXTEND_PAD);
1492
1493 cairo_matrix_init_scale (matrix: &matrix,
1494 sx: gdk_texture_get_width (texture: self->texture) / node->bounds.size.width,
1495 sy: gdk_texture_get_height (texture: self->texture) / node->bounds.size.height);
1496 cairo_matrix_translate (matrix: &matrix,
1497 tx: -node->bounds.origin.x,
1498 ty: -node->bounds.origin.y);
1499 cairo_pattern_set_matrix (pattern, matrix: &matrix);
1500
1501 cairo_set_source (cr, source: pattern);
1502 cairo_pattern_destroy (pattern);
1503 cairo_surface_destroy (surface);
1504
1505 gsk_cairo_rectangle (cr, rect: &node->bounds);
1506 cairo_fill (cr);
1507}
1508
1509static void
1510gsk_texture_node_diff (GskRenderNode *node1,
1511 GskRenderNode *node2,
1512 cairo_region_t *region)
1513{
1514 GskTextureNode *self1 = (GskTextureNode *) node1;
1515 GskTextureNode *self2 = (GskTextureNode *) node2;
1516
1517 if (graphene_rect_equal (a: &node1->bounds, b: &node2->bounds) &&
1518 self1->texture == self2->texture)
1519 return;
1520
1521 gsk_render_node_diff_impossible (node1, node2, region);
1522}
1523
1524/**
1525 * gsk_texture_node_get_texture:
1526 * @node: (type GskTextureNode): a `GskRenderNode` of type %GSK_TEXTURE_NODE
1527 *
1528 * Retrieves the `GdkTexture` used when creating this `GskRenderNode`.
1529 *
1530 * Returns: (transfer none): the `GdkTexture`
1531 */
1532GdkTexture *
1533gsk_texture_node_get_texture (const GskRenderNode *node)
1534{
1535 const GskTextureNode *self = (const GskTextureNode *) node;
1536
1537 return self->texture;
1538}
1539
1540/**
1541 * gsk_texture_node_new:
1542 * @texture: the `GdkTexture`
1543 * @bounds: the rectangle to render the texture into
1544 *
1545 * Creates a `GskRenderNode` that will render the given
1546 * @texture into the area given by @bounds.
1547 *
1548 * Returns: (transfer full) (type GskTextureNode): A new `GskRenderNode`
1549 */
1550GskRenderNode *
1551gsk_texture_node_new (GdkTexture *texture,
1552 const graphene_rect_t *bounds)
1553{
1554 GskTextureNode *self;
1555 GskRenderNode *node;
1556
1557 g_return_val_if_fail (GDK_IS_TEXTURE (texture), NULL);
1558 g_return_val_if_fail (bounds != NULL, NULL);
1559
1560 self = gsk_render_node_alloc (node_type: GSK_TEXTURE_NODE);
1561 node = (GskRenderNode *) self;
1562
1563 self->texture = g_object_ref (texture);
1564 graphene_rect_init_from_rect (r: &node->bounds, src: bounds);
1565
1566 node->prefers_high_depth = gdk_memory_format_prefers_high_depth (format: gdk_texture_get_format (self: texture));
1567
1568 return node;
1569}
1570
1571/*** GSK_INSET_SHADOW_NODE ***/
1572
1573/**
1574 * GskInsetShadowNode:
1575 *
1576 * A render node for an inset shadow.
1577 */
1578struct _GskInsetShadowNode
1579{
1580 GskRenderNode render_node;
1581
1582 GskRoundedRect outline;
1583 GdkRGBA color;
1584 float dx;
1585 float dy;
1586 float spread;
1587 float blur_radius;
1588};
1589
1590static gboolean
1591has_empty_clip (cairo_t *cr)
1592{
1593 double x1, y1, x2, y2;
1594
1595 cairo_clip_extents (cr, x1: &x1, y1: &y1, x2: &x2, y2: &y2);
1596 return x1 == x2 && y1 == y2;
1597}
1598
1599static void
1600draw_shadow (cairo_t *cr,
1601 gboolean inset,
1602 const GskRoundedRect *box,
1603 const GskRoundedRect *clip_box,
1604 float radius,
1605 const GdkRGBA *color,
1606 GskBlurFlags blur_flags)
1607{
1608 cairo_t *shadow_cr;
1609
1610 if (has_empty_clip (cr))
1611 return;
1612
1613 gdk_cairo_set_source_rgba (cr, rgba: color);
1614 shadow_cr = gsk_cairo_blur_start_drawing (cr, radius, blur_flags);
1615
1616 cairo_set_fill_rule (cr: shadow_cr, fill_rule: CAIRO_FILL_RULE_EVEN_ODD);
1617 gsk_rounded_rect_path (self: box, cr: shadow_cr);
1618 if (inset)
1619 gsk_cairo_rectangle (cr: shadow_cr, rect: &clip_box->bounds);
1620
1621 cairo_fill (cr: shadow_cr);
1622
1623 gsk_cairo_blur_finish_drawing (cr: shadow_cr, radius, color, blur_flags);
1624}
1625
1626typedef struct {
1627 float radius;
1628 graphene_size_t corner;
1629} CornerMask;
1630
1631typedef enum {
1632 TOP,
1633 RIGHT,
1634 BOTTOM,
1635 LEFT
1636} Side;
1637
1638static guint
1639corner_mask_hash (CornerMask *mask)
1640{
1641 return ((guint)mask->radius << 24) ^
1642 ((guint)(mask->corner.width*4)) << 12 ^
1643 ((guint)(mask->corner.height*4)) << 0;
1644}
1645
1646static gboolean
1647corner_mask_equal (CornerMask *mask1,
1648 CornerMask *mask2)
1649{
1650 return
1651 mask1->radius == mask2->radius &&
1652 mask1->corner.width == mask2->corner.width &&
1653 mask1->corner.height == mask2->corner.height;
1654}
1655
1656static void
1657draw_shadow_corner (cairo_t *cr,
1658 gboolean inset,
1659 const GskRoundedRect *box,
1660 const GskRoundedRect *clip_box,
1661 float radius,
1662 const GdkRGBA *color,
1663 GskCorner corner,
1664 cairo_rectangle_int_t *drawn_rect)
1665{
1666 float clip_radius;
1667 int x1, x2, x3, y1, y2, y3, x, y;
1668 GskRoundedRect corner_box;
1669 cairo_t *mask_cr;
1670 cairo_surface_t *mask;
1671 cairo_pattern_t *pattern;
1672 cairo_matrix_t matrix;
1673 float sx, sy;
1674 static GHashTable *corner_mask_cache = NULL;
1675 float max_other;
1676 CornerMask key;
1677 gboolean overlapped;
1678
1679 clip_radius = gsk_cairo_blur_compute_pixels (radius);
1680
1681 overlapped = FALSE;
1682 if (corner == GSK_CORNER_TOP_LEFT || corner == GSK_CORNER_BOTTOM_LEFT)
1683 {
1684 x1 = floor (x: box->bounds.origin.x - clip_radius);
1685 x2 = ceil (x: box->bounds.origin.x + box->corner[corner].width + clip_radius);
1686 x = x1;
1687 sx = 1;
1688 max_other = MAX(box->corner[GSK_CORNER_TOP_RIGHT].width, box->corner[GSK_CORNER_BOTTOM_RIGHT].width);
1689 x3 = floor (x: box->bounds.origin.x + box->bounds.size.width - max_other - clip_radius);
1690 if (x2 > x3)
1691 overlapped = TRUE;
1692 }
1693 else
1694 {
1695 x1 = floor (x: box->bounds.origin.x + box->bounds.size.width - box->corner[corner].width - clip_radius);
1696 x2 = ceil (x: box->bounds.origin.x + box->bounds.size.width + clip_radius);
1697 x = x2;
1698 sx = -1;
1699 max_other = MAX(box->corner[GSK_CORNER_TOP_LEFT].width, box->corner[GSK_CORNER_BOTTOM_LEFT].width);
1700 x3 = ceil (x: box->bounds.origin.x + max_other + clip_radius);
1701 if (x3 > x1)
1702 overlapped = TRUE;
1703 }
1704
1705 if (corner == GSK_CORNER_TOP_LEFT || corner == GSK_CORNER_TOP_RIGHT)
1706 {
1707 y1 = floor (x: box->bounds.origin.y - clip_radius);
1708 y2 = ceil (x: box->bounds.origin.y + box->corner[corner].height + clip_radius);
1709 y = y1;
1710 sy = 1;
1711 max_other = MAX(box->corner[GSK_CORNER_BOTTOM_LEFT].height, box->corner[GSK_CORNER_BOTTOM_RIGHT].height);
1712 y3 = floor (x: box->bounds.origin.y + box->bounds.size.height - max_other - clip_radius);
1713 if (y2 > y3)
1714 overlapped = TRUE;
1715 }
1716 else
1717 {
1718 y1 = floor (x: box->bounds.origin.y + box->bounds.size.height - box->corner[corner].height - clip_radius);
1719 y2 = ceil (x: box->bounds.origin.y + box->bounds.size.height + clip_radius);
1720 y = y2;
1721 sy = -1;
1722 max_other = MAX(box->corner[GSK_CORNER_TOP_LEFT].height, box->corner[GSK_CORNER_TOP_RIGHT].height);
1723 y3 = ceil (x: box->bounds.origin.y + max_other + clip_radius);
1724 if (y3 > y1)
1725 overlapped = TRUE;
1726 }
1727
1728 drawn_rect->x = x1;
1729 drawn_rect->y = y1;
1730 drawn_rect->width = x2 - x1;
1731 drawn_rect->height = y2 - y1;
1732
1733 cairo_rectangle (cr, x: x1, y: y1, width: x2 - x1, height: y2 - y1);
1734 cairo_clip (cr);
1735
1736 if (inset || overlapped)
1737 {
1738 /* Fall back to generic path if inset or if the corner radius
1739 runs into each other */
1740 draw_shadow (cr, inset, box, clip_box, radius, color, blur_flags: GSK_BLUR_X | GSK_BLUR_Y);
1741 return;
1742 }
1743
1744 if (has_empty_clip (cr))
1745 return;
1746
1747 /* At this point we're drawing a blurred outset corner. The only
1748 * things that affect the output of the blurred mask in this case
1749 * is:
1750 *
1751 * What corner this is, which defines the orientation (sx,sy)
1752 * and position (x,y)
1753 *
1754 * The blur radius (which also defines the clip_radius)
1755 *
1756 * The horizontal and vertical corner radius
1757 *
1758 * We apply the first position and orientation when drawing the
1759 * mask, so we cache rendered masks based on the blur radius and the
1760 * corner radius.
1761 */
1762 if (corner_mask_cache == NULL)
1763 corner_mask_cache = g_hash_table_new_full (hash_func: (GHashFunc)corner_mask_hash,
1764 key_equal_func: (GEqualFunc)corner_mask_equal,
1765 key_destroy_func: g_free, value_destroy_func: (GDestroyNotify)cairo_surface_destroy);
1766
1767 key.radius = radius;
1768 key.corner = box->corner[corner];
1769
1770 mask = g_hash_table_lookup (hash_table: corner_mask_cache, key: &key);
1771 if (mask == NULL)
1772 {
1773 mask = cairo_surface_create_similar_image (other: cairo_get_target (cr), format: CAIRO_FORMAT_A8,
1774 width: drawn_rect->width + clip_radius,
1775 height: drawn_rect->height + clip_radius);
1776 mask_cr = cairo_create (target: mask);
1777 gsk_rounded_rect_init_from_rect (self: &corner_box, bounds: &GRAPHENE_RECT_INIT (clip_radius, clip_radius, 2*drawn_rect->width, 2*drawn_rect->height), radius: 0);
1778 corner_box.corner[0] = box->corner[corner];
1779 gsk_rounded_rect_path (self: &corner_box, cr: mask_cr);
1780 cairo_fill (cr: mask_cr);
1781 gsk_cairo_blur_surface (surface: mask, radius, flags: GSK_BLUR_X | GSK_BLUR_Y);
1782 cairo_destroy (cr: mask_cr);
1783 g_hash_table_insert (hash_table: corner_mask_cache, key: g_memdup2 (mem: &key, byte_size: sizeof (key)), value: mask);
1784 }
1785
1786 gdk_cairo_set_source_rgba (cr, rgba: color);
1787 pattern = cairo_pattern_create_for_surface (surface: mask);
1788 cairo_matrix_init_identity (matrix: &matrix);
1789 cairo_matrix_scale (matrix: &matrix, sx, sy);
1790 cairo_matrix_translate (matrix: &matrix, tx: -x, ty: -y);
1791 cairo_pattern_set_matrix (pattern, matrix: &matrix);
1792 cairo_mask (cr, pattern);
1793 cairo_pattern_destroy (pattern);
1794}
1795
1796static void
1797draw_shadow_side (cairo_t *cr,
1798 gboolean inset,
1799 const GskRoundedRect *box,
1800 const GskRoundedRect *clip_box,
1801 float radius,
1802 const GdkRGBA *color,
1803 Side side,
1804 cairo_rectangle_int_t *drawn_rect)
1805{
1806 GskBlurFlags blur_flags = GSK_BLUR_REPEAT;
1807 double clip_radius;
1808 int x1, x2, y1, y2;
1809
1810 clip_radius = gsk_cairo_blur_compute_pixels (radius);
1811
1812 if (side == TOP || side == BOTTOM)
1813 {
1814 blur_flags |= GSK_BLUR_Y;
1815 x1 = floor (x: box->bounds.origin.x - clip_radius);
1816 x2 = ceil (x: box->bounds.origin.x + box->bounds.size.width + clip_radius);
1817 }
1818 else if (side == LEFT)
1819 {
1820 x1 = floor (x: box->bounds.origin.x -clip_radius);
1821 x2 = ceil (x: box->bounds.origin.x + clip_radius);
1822 }
1823 else
1824 {
1825 x1 = floor (x: box->bounds.origin.x + box->bounds.size.width -clip_radius);
1826 x2 = ceil (x: box->bounds.origin.x + box->bounds.size.width + clip_radius);
1827 }
1828
1829 if (side == LEFT || side == RIGHT)
1830 {
1831 blur_flags |= GSK_BLUR_X;
1832 y1 = floor (x: box->bounds.origin.y - clip_radius);
1833 y2 = ceil (x: box->bounds.origin.y + box->bounds.size.height + clip_radius);
1834 }
1835 else if (side == TOP)
1836 {
1837 y1 = floor (x: box->bounds.origin.y -clip_radius);
1838 y2 = ceil (x: box->bounds.origin.y + clip_radius);
1839 }
1840 else
1841 {
1842 y1 = floor (x: box->bounds.origin.y + box->bounds.size.height -clip_radius);
1843 y2 = ceil (x: box->bounds.origin.y + box->bounds.size.height + clip_radius);
1844 }
1845
1846 drawn_rect->x = x1;
1847 drawn_rect->y = y1;
1848 drawn_rect->width = x2 - x1;
1849 drawn_rect->height = y2 - y1;
1850
1851 cairo_rectangle (cr, x: x1, y: y1, width: x2 - x1, height: y2 - y1);
1852 cairo_clip (cr);
1853 draw_shadow (cr, inset, box, clip_box, radius, color, blur_flags);
1854}
1855
1856static gboolean
1857needs_blur (double radius)
1858{
1859 /* The code doesn't actually do any blurring for radius 1, as it
1860 * ends up with box filter size 1 */
1861 if (radius <= 1.0)
1862 return FALSE;
1863
1864 return TRUE;
1865}
1866
1867static void
1868gsk_inset_shadow_node_draw (GskRenderNode *node,
1869 cairo_t *cr)
1870{
1871 GskInsetShadowNode *self = (GskInsetShadowNode *) node;
1872 GskRoundedRect box, clip_box;
1873 int clip_radius;
1874 double x1c, y1c, x2c, y2c;
1875 double blur_radius;
1876
1877 /* We don't need to draw invisible shadows */
1878 if (gdk_rgba_is_clear (rgba: &self->color))
1879 return;
1880
1881 cairo_clip_extents (cr, x1: &x1c, y1: &y1c, x2: &x2c, y2: &y2c);
1882 if (!gsk_rounded_rect_intersects_rect (self: &self->outline, rect: &GRAPHENE_RECT_INIT (x1c, y1c, x2c - x1c, y2c - y1c)))
1883 return;
1884
1885 blur_radius = self->blur_radius / 2;
1886
1887 clip_radius = gsk_cairo_blur_compute_pixels (radius: blur_radius);
1888
1889 cairo_save (cr);
1890
1891 gsk_rounded_rect_path (self: &self->outline, cr);
1892 cairo_clip (cr);
1893
1894 gsk_rounded_rect_init_copy (self: &box, src: &self->outline);
1895 gsk_rounded_rect_offset (self: &box, dx: self->dx, dy: self->dy);
1896 gsk_rounded_rect_shrink (self: &box, top: self->spread, right: self->spread, bottom: self->spread, left: self->spread);
1897
1898 gsk_rounded_rect_init_copy (self: &clip_box, src: &self->outline);
1899 gsk_rounded_rect_shrink (self: &clip_box, top: -clip_radius, right: -clip_radius, bottom: -clip_radius, left: -clip_radius);
1900
1901 if (!needs_blur (radius: blur_radius))
1902 draw_shadow (cr, TRUE, box: &box, clip_box: &clip_box, radius: blur_radius, color: &self->color, blur_flags: GSK_BLUR_NONE);
1903 else
1904 {
1905 cairo_region_t *remaining;
1906 cairo_rectangle_int_t r;
1907 int i;
1908
1909 /* For the blurred case we divide the rendering into 9 parts,
1910 * 4 of the corners, 4 for the horizonat/vertical lines and
1911 * one for the interior. We make the non-interior parts
1912 * large enough to fit the full radius of the blur, so that
1913 * the interior part can be drawn solidly.
1914 */
1915
1916 /* In the inset case we want to paint the whole clip-box.
1917 * We could remove the part of "box" where the blur doesn't
1918 * reach, but computing that is a bit tricky since the
1919 * rounded corners are on the "inside" of it. */
1920 r.x = floor (x: clip_box.bounds.origin.x);
1921 r.y = floor (x: clip_box.bounds.origin.y);
1922 r.width = ceil (x: clip_box.bounds.origin.x + clip_box.bounds.size.width) - r.x;
1923 r.height = ceil (x: clip_box.bounds.origin.y + clip_box.bounds.size.height) - r.y;
1924 remaining = cairo_region_create_rectangle (rectangle: &r);
1925
1926 /* First do the corners of box */
1927 for (i = 0; i < 4; i++)
1928 {
1929 cairo_save (cr);
1930 /* Always clip with remaining to ensure we never draw any area twice */
1931 gdk_cairo_region (cr, region: remaining);
1932 cairo_clip (cr);
1933 draw_shadow_corner (cr, TRUE, box: &box, clip_box: &clip_box, radius: blur_radius, color: &self->color, corner: i, drawn_rect: &r);
1934 cairo_restore (cr);
1935
1936 /* We drew the region, remove it from remaining */
1937 cairo_region_subtract_rectangle (dst: remaining, rectangle: &r);
1938 }
1939
1940 /* Then the sides */
1941 for (i = 0; i < 4; i++)
1942 {
1943 cairo_save (cr);
1944 /* Always clip with remaining to ensure we never draw any area twice */
1945 gdk_cairo_region (cr, region: remaining);
1946 cairo_clip (cr);
1947 draw_shadow_side (cr, TRUE, box: &box, clip_box: &clip_box, radius: blur_radius, color: &self->color, side: i, drawn_rect: &r);
1948 cairo_restore (cr);
1949
1950 /* We drew the region, remove it from remaining */
1951 cairo_region_subtract_rectangle (dst: remaining, rectangle: &r);
1952 }
1953
1954 /* Then the rest, which needs no blurring */
1955
1956 cairo_save (cr);
1957 gdk_cairo_region (cr, region: remaining);
1958 cairo_clip (cr);
1959 draw_shadow (cr, TRUE, box: &box, clip_box: &clip_box, radius: blur_radius, color: &self->color, blur_flags: GSK_BLUR_NONE);
1960 cairo_restore (cr);
1961
1962 cairo_region_destroy (region: remaining);
1963 }
1964
1965 cairo_restore (cr);
1966}
1967
1968static void
1969gsk_inset_shadow_node_diff (GskRenderNode *node1,
1970 GskRenderNode *node2,
1971 cairo_region_t *region)
1972{
1973 GskInsetShadowNode *self1 = (GskInsetShadowNode *) node1;
1974 GskInsetShadowNode *self2 = (GskInsetShadowNode *) node2;
1975
1976 if (gsk_rounded_rect_equal (rect1: &self1->outline, rect2: &self2->outline) &&
1977 gdk_rgba_equal (p1: &self1->color, p2: &self2->color) &&
1978 self1->dx == self2->dx &&
1979 self1->dy == self2->dy &&
1980 self1->spread == self2->spread &&
1981 self1->blur_radius == self2->blur_radius)
1982 return;
1983
1984 gsk_render_node_diff_impossible (node1, node2, region);
1985}
1986
1987/**
1988 * gsk_inset_shadow_node_new:
1989 * @outline: outline of the region containing the shadow
1990 * @color: color of the shadow
1991 * @dx: horizontal offset of shadow
1992 * @dy: vertical offset of shadow
1993 * @spread: how far the shadow spreads towards the inside
1994 * @blur_radius: how much blur to apply to the shadow
1995 *
1996 * Creates a `GskRenderNode` that will render an inset shadow
1997 * into the box given by @outline.
1998 *
1999 * Returns: (transfer full) (type GskInsetShadowNode): A new `GskRenderNode`
2000 */
2001GskRenderNode *
2002gsk_inset_shadow_node_new (const GskRoundedRect *outline,
2003 const GdkRGBA *color,
2004 float dx,
2005 float dy,
2006 float spread,
2007 float blur_radius)
2008{
2009 GskInsetShadowNode *self;
2010 GskRenderNode *node;
2011
2012 g_return_val_if_fail (outline != NULL, NULL);
2013 g_return_val_if_fail (color != NULL, NULL);
2014
2015 self = gsk_render_node_alloc (node_type: GSK_INSET_SHADOW_NODE);
2016 node = (GskRenderNode *) self;
2017
2018 gsk_rounded_rect_init_copy (self: &self->outline, src: outline);
2019 self->color = *color;
2020 self->dx = dx;
2021 self->dy = dy;
2022 self->spread = spread;
2023 self->blur_radius = blur_radius;
2024
2025 graphene_rect_init_from_rect (r: &node->bounds, src: &self->outline.bounds);
2026
2027 return node;
2028}
2029
2030/**
2031 * gsk_inset_shadow_node_get_outline:
2032 * @node: (type GskInsetShadowNode): a `GskRenderNode` for an inset shadow
2033 *
2034 * Retrieves the outline rectangle of the inset shadow.
2035 *
2036 * Returns: (transfer none): a rounded rectangle
2037 */
2038const GskRoundedRect *
2039gsk_inset_shadow_node_get_outline (const GskRenderNode *node)
2040{
2041 const GskInsetShadowNode *self = (const GskInsetShadowNode *) node;
2042
2043 return &self->outline;
2044}
2045
2046/**
2047 * gsk_inset_shadow_node_get_color:
2048 * @node: (type GskInsetShadowNode): a `GskRenderNode` for an inset shadow
2049 *
2050 * Retrieves the color of the inset shadow.
2051 *
2052 * Returns: (transfer none): the color of the shadow
2053 */
2054const GdkRGBA *
2055gsk_inset_shadow_node_get_color (const GskRenderNode *node)
2056{
2057 const GskInsetShadowNode *self = (const GskInsetShadowNode *) node;
2058
2059 return &self->color;
2060}
2061
2062/**
2063 * gsk_inset_shadow_node_get_dx:
2064 * @node: (type GskInsetShadowNode): a `GskRenderNode` for an inset shadow
2065 *
2066 * Retrieves the horizontal offset of the inset shadow.
2067 *
2068 * Returns: an offset, in pixels
2069 */
2070float
2071gsk_inset_shadow_node_get_dx (const GskRenderNode *node)
2072{
2073 const GskInsetShadowNode *self = (const GskInsetShadowNode *) node;
2074
2075 return self->dx;
2076}
2077
2078/**
2079 * gsk_inset_shadow_node_get_dy:
2080 * @node: (type GskInsetShadowNode): a `GskRenderNode` for an inset shadow
2081 *
2082 * Retrieves the vertical offset of the inset shadow.
2083 *
2084 * Returns: an offset, in pixels
2085 */
2086float
2087gsk_inset_shadow_node_get_dy (const GskRenderNode *node)
2088{
2089 const GskInsetShadowNode *self = (const GskInsetShadowNode *) node;
2090
2091 return self->dy;
2092}
2093
2094/**
2095 * gsk_inset_shadow_node_get_spread:
2096 * @node: (type GskInsetShadowNode): a `GskRenderNode` for an inset shadow
2097 *
2098 * Retrieves how much the shadow spreads inwards.
2099 *
2100 * Returns: the size of the shadow, in pixels
2101 */
2102float
2103gsk_inset_shadow_node_get_spread (const GskRenderNode *node)
2104{
2105 const GskInsetShadowNode *self = (const GskInsetShadowNode *) node;
2106
2107 return self->spread;
2108}
2109
2110/**
2111 * gsk_inset_shadow_node_get_blur_radius:
2112 * @node: (type GskInsetShadowNode): a `GskRenderNode` for an inset shadow
2113 *
2114 * Retrieves the blur radius to apply to the shadow.
2115 *
2116 * Returns: the blur radius, in pixels
2117 */
2118float
2119gsk_inset_shadow_node_get_blur_radius (const GskRenderNode *node)
2120{
2121 const GskInsetShadowNode *self = (const GskInsetShadowNode *) node;
2122
2123 return self->blur_radius;
2124}
2125
2126/*** GSK_OUTSET_SHADOW_NODE ***/
2127
2128/**
2129 * GskOutsetShadowNode:
2130 *
2131 * A render node for an outset shadow.
2132 */
2133struct _GskOutsetShadowNode
2134{
2135 GskRenderNode render_node;
2136
2137 GskRoundedRect outline;
2138 GdkRGBA color;
2139 float dx;
2140 float dy;
2141 float spread;
2142 float blur_radius;
2143};
2144
2145static void
2146gsk_outset_shadow_get_extents (GskOutsetShadowNode *self,
2147 float *top,
2148 float *right,
2149 float *bottom,
2150 float *left)
2151{
2152 float clip_radius;
2153
2154 clip_radius = gsk_cairo_blur_compute_pixels (radius: self->blur_radius / 2.0);
2155 *top = MAX (0, clip_radius + self->spread - self->dy);
2156 *right = MAX (0, ceil (clip_radius + self->spread + self->dx));
2157 *bottom = MAX (0, ceil (clip_radius + self->spread + self->dy));
2158 *left = MAX (0, ceil (clip_radius + self->spread - self->dx));
2159}
2160
2161static void
2162gsk_outset_shadow_node_draw (GskRenderNode *node,
2163 cairo_t *cr)
2164{
2165 GskOutsetShadowNode *self = (GskOutsetShadowNode *) node;
2166 GskRoundedRect box, clip_box;
2167 int clip_radius;
2168 double x1c, y1c, x2c, y2c;
2169 float top, right, bottom, left;
2170 double blur_radius;
2171
2172 /* We don't need to draw invisible shadows */
2173 if (gdk_rgba_is_clear (rgba: &self->color))
2174 return;
2175
2176 cairo_clip_extents (cr, x1: &x1c, y1: &y1c, x2: &x2c, y2: &y2c);
2177 if (gsk_rounded_rect_contains_rect (self: &self->outline, rect: &GRAPHENE_RECT_INIT (x1c, y1c, x2c - x1c, y2c - y1c)))
2178 return;
2179
2180 blur_radius = self->blur_radius / 2;
2181
2182 clip_radius = gsk_cairo_blur_compute_pixels (radius: blur_radius);
2183
2184 cairo_save (cr);
2185
2186 gsk_rounded_rect_init_copy (self: &clip_box, src: &self->outline);
2187 gsk_outset_shadow_get_extents (self, top: &top, right: &right, bottom: &bottom, left: &left);
2188 gsk_rounded_rect_shrink (self: &clip_box, top: -top, right: -right, bottom: -bottom, left: -left);
2189
2190 cairo_set_fill_rule (cr, fill_rule: CAIRO_FILL_RULE_EVEN_ODD);
2191 gsk_rounded_rect_path (self: &self->outline, cr);
2192 gsk_cairo_rectangle (cr, rect: &clip_box.bounds);
2193
2194 cairo_clip (cr);
2195
2196 gsk_rounded_rect_init_copy (self: &box, src: &self->outline);
2197 gsk_rounded_rect_offset (self: &box, dx: self->dx, dy: self->dy);
2198 gsk_rounded_rect_shrink (self: &box, top: -self->spread, right: -self->spread, bottom: -self->spread, left: -self->spread);
2199
2200 if (!needs_blur (radius: blur_radius))
2201 draw_shadow (cr, FALSE, box: &box, clip_box: &clip_box, radius: blur_radius, color: &self->color, blur_flags: GSK_BLUR_NONE);
2202 else
2203 {
2204 int i;
2205 cairo_region_t *remaining;
2206 cairo_rectangle_int_t r;
2207
2208 /* For the blurred case we divide the rendering into 9 parts,
2209 * 4 of the corners, 4 for the horizonat/vertical lines and
2210 * one for the interior. We make the non-interior parts
2211 * large enough to fit the full radius of the blur, so that
2212 * the interior part can be drawn solidly.
2213 */
2214
2215 /* In the outset case we want to paint the entire box, plus as far
2216 * as the radius reaches from it */
2217 r.x = floor (x: box.bounds.origin.x - clip_radius);
2218 r.y = floor (x: box.bounds.origin.y - clip_radius);
2219 r.width = ceil (x: box.bounds.origin.x + box.bounds.size.width + clip_radius) - r.x;
2220 r.height = ceil (x: box.bounds.origin.y + box.bounds.size.height + clip_radius) - r.y;
2221
2222 remaining = cairo_region_create_rectangle (rectangle: &r);
2223
2224 /* First do the corners of box */
2225 for (i = 0; i < 4; i++)
2226 {
2227 cairo_save (cr);
2228 /* Always clip with remaining to ensure we never draw any area twice */
2229 gdk_cairo_region (cr, region: remaining);
2230 cairo_clip (cr);
2231 draw_shadow_corner (cr, FALSE, box: &box, clip_box: &clip_box, radius: blur_radius, color: &self->color, corner: i, drawn_rect: &r);
2232 cairo_restore (cr);
2233
2234 /* We drew the region, remove it from remaining */
2235 cairo_region_subtract_rectangle (dst: remaining, rectangle: &r);
2236 }
2237
2238 /* Then the sides */
2239 for (i = 0; i < 4; i++)
2240 {
2241 cairo_save (cr);
2242 /* Always clip with remaining to ensure we never draw any area twice */
2243 gdk_cairo_region (cr, region: remaining);
2244 cairo_clip (cr);
2245 draw_shadow_side (cr, FALSE, box: &box, clip_box: &clip_box, radius: blur_radius, color: &self->color, side: i, drawn_rect: &r);
2246 cairo_restore (cr);
2247
2248 /* We drew the region, remove it from remaining */
2249 cairo_region_subtract_rectangle (dst: remaining, rectangle: &r);
2250 }
2251
2252 /* Then the rest, which needs no blurring */
2253
2254 cairo_save (cr);
2255 gdk_cairo_region (cr, region: remaining);
2256 cairo_clip (cr);
2257 draw_shadow (cr, FALSE, box: &box, clip_box: &clip_box, radius: blur_radius, color: &self->color, blur_flags: GSK_BLUR_NONE);
2258 cairo_restore (cr);
2259
2260 cairo_region_destroy (region: remaining);
2261 }
2262
2263 cairo_restore (cr);
2264}
2265
2266static void
2267gsk_outset_shadow_node_diff (GskRenderNode *node1,
2268 GskRenderNode *node2,
2269 cairo_region_t *region)
2270{
2271 GskOutsetShadowNode *self1 = (GskOutsetShadowNode *) node1;
2272 GskOutsetShadowNode *self2 = (GskOutsetShadowNode *) node2;
2273
2274 if (gsk_rounded_rect_equal (rect1: &self1->outline, rect2: &self2->outline) &&
2275 gdk_rgba_equal (p1: &self1->color, p2: &self2->color) &&
2276 self1->dx == self2->dx &&
2277 self1->dy == self2->dy &&
2278 self1->spread == self2->spread &&
2279 self1->blur_radius == self2->blur_radius)
2280 return;
2281
2282 gsk_render_node_diff_impossible (node1, node2, region);
2283}
2284
2285/**
2286 * gsk_outset_shadow_node_new:
2287 * @outline: outline of the region surrounded by shadow
2288 * @color: color of the shadow
2289 * @dx: horizontal offset of shadow
2290 * @dy: vertical offset of shadow
2291 * @spread: how far the shadow spreads towards the inside
2292 * @blur_radius: how much blur to apply to the shadow
2293 *
2294 * Creates a `GskRenderNode` that will render an outset shadow
2295 * around the box given by @outline.
2296 *
2297 * Returns: (transfer full) (type GskOutsetShadowNode): A new `GskRenderNode`
2298 */
2299GskRenderNode *
2300gsk_outset_shadow_node_new (const GskRoundedRect *outline,
2301 const GdkRGBA *color,
2302 float dx,
2303 float dy,
2304 float spread,
2305 float blur_radius)
2306{
2307 GskOutsetShadowNode *self;
2308 GskRenderNode *node;
2309 float top, right, bottom, left;
2310
2311 g_return_val_if_fail (outline != NULL, NULL);
2312 g_return_val_if_fail (color != NULL, NULL);
2313
2314 self = gsk_render_node_alloc (node_type: GSK_OUTSET_SHADOW_NODE);
2315 node = (GskRenderNode *) self;
2316
2317 gsk_rounded_rect_init_copy (self: &self->outline, src: outline);
2318 self->color = *color;
2319 self->dx = dx;
2320 self->dy = dy;
2321 self->spread = spread;
2322 self->blur_radius = blur_radius;
2323
2324 gsk_outset_shadow_get_extents (self, top: &top, right: &right, bottom: &bottom, left: &left);
2325
2326 graphene_rect_init_from_rect (r: &node->bounds, src: &self->outline.bounds);
2327 node->bounds.origin.x -= left;
2328 node->bounds.origin.y -= top;
2329 node->bounds.size.width += left + right;
2330 node->bounds.size.height += top + bottom;
2331
2332 return node;
2333}
2334
2335/**
2336 * gsk_outset_shadow_node_get_outline:
2337 * @node: (type GskOutsetShadowNode): a `GskRenderNode` for an outset shadow
2338 *
2339 * Retrieves the outline rectangle of the outset shadow.
2340 *
2341 * Returns: (transfer none): a rounded rectangle
2342 */
2343const GskRoundedRect *
2344gsk_outset_shadow_node_get_outline (const GskRenderNode *node)
2345{
2346 const GskOutsetShadowNode *self = (const GskOutsetShadowNode *) node;
2347
2348 return &self->outline;
2349}
2350
2351/**
2352 * gsk_outset_shadow_node_get_color:
2353 * @node: (type GskOutsetShadowNode): a `GskRenderNode` for an outset shadow
2354 *
2355 * Retrieves the color of the outset shadow.
2356 *
2357 * Returns: (transfer none): a color
2358 */
2359const GdkRGBA *
2360gsk_outset_shadow_node_get_color (const GskRenderNode *node)
2361{
2362 const GskOutsetShadowNode *self = (const GskOutsetShadowNode *) node;
2363
2364 return &self->color;
2365}
2366
2367/**
2368 * gsk_outset_shadow_node_get_dx:
2369 * @node: (type GskOutsetShadowNode): a `GskRenderNode` for an outset shadow
2370 *
2371 * Retrieves the horizontal offset of the outset shadow.
2372 *
2373 * Returns: an offset, in pixels
2374 */
2375float
2376gsk_outset_shadow_node_get_dx (const GskRenderNode *node)
2377{
2378 const GskOutsetShadowNode *self = (const GskOutsetShadowNode *) node;
2379
2380 return self->dx;
2381}
2382
2383/**
2384 * gsk_outset_shadow_node_get_dy:
2385 * @node: (type GskOutsetShadowNode): a `GskRenderNode` for an outset shadow
2386 *
2387 * Retrieves the vertical offset of the outset shadow.
2388 *
2389 * Returns: an offset, in pixels
2390 */
2391float
2392gsk_outset_shadow_node_get_dy (const GskRenderNode *node)
2393{
2394 const GskOutsetShadowNode *self = (const GskOutsetShadowNode *) node;
2395
2396 return self->dy;
2397}
2398
2399/**
2400 * gsk_outset_shadow_node_get_spread:
2401 * @node: (type GskOutsetShadowNode): a `GskRenderNode` for an outset shadow
2402 *
2403 * Retrieves how much the shadow spreads outwards.
2404 *
2405 * Returns: the size of the shadow, in pixels
2406 */
2407float
2408gsk_outset_shadow_node_get_spread (const GskRenderNode *node)
2409{
2410 const GskOutsetShadowNode *self = (const GskOutsetShadowNode *) node;
2411
2412 return self->spread;
2413}
2414
2415/**
2416 * gsk_outset_shadow_node_get_blur_radius:
2417 * @node: (type GskOutsetShadowNode): a `GskRenderNode` for an outset shadow
2418 *
2419 * Retrieves the blur radius of the shadow.
2420 *
2421 * Returns: the blur radius, in pixels
2422 */
2423float
2424gsk_outset_shadow_node_get_blur_radius (const GskRenderNode *node)
2425{
2426 const GskOutsetShadowNode *self = (const GskOutsetShadowNode *) node;
2427
2428 return self->blur_radius;
2429}
2430
2431/*** GSK_CAIRO_NODE ***/
2432
2433/**
2434 * GskCairoNode:
2435 *
2436 * A render node for a Cairo surface.
2437 */
2438struct _GskCairoNode
2439{
2440 GskRenderNode render_node;
2441
2442 cairo_surface_t *surface;
2443};
2444
2445static void
2446gsk_cairo_node_finalize (GskRenderNode *node)
2447{
2448 GskCairoNode *self = (GskCairoNode *) node;
2449 GskRenderNodeClass *parent_class = g_type_class_peek (type: g_type_parent (GSK_TYPE_CAIRO_NODE));
2450
2451 if (self->surface)
2452 cairo_surface_destroy (surface: self->surface);
2453
2454 parent_class->finalize (node);
2455}
2456
2457static void
2458gsk_cairo_node_draw (GskRenderNode *node,
2459 cairo_t *cr)
2460{
2461 GskCairoNode *self = (GskCairoNode *) node;
2462
2463 if (self->surface == NULL)
2464 return;
2465
2466 cairo_set_source_surface (cr, surface: self->surface, x: 0, y: 0);
2467 cairo_paint (cr);
2468}
2469
2470/**
2471 * gsk_cairo_node_get_surface:
2472 * @node: (type GskCairoNode): a `GskRenderNode` for a Cairo surface
2473 *
2474 * Retrieves the Cairo surface used by the render node.
2475 *
2476 * Returns: (transfer none): a Cairo surface
2477 */
2478cairo_surface_t *
2479gsk_cairo_node_get_surface (GskRenderNode *node)
2480{
2481 GskCairoNode *self = (GskCairoNode *) node;
2482
2483 g_return_val_if_fail (GSK_IS_RENDER_NODE_TYPE (node, GSK_CAIRO_NODE), NULL);
2484
2485 return self->surface;
2486}
2487
2488/**
2489 * gsk_cairo_node_new:
2490 * @bounds: the rectangle to render to
2491 *
2492 * Creates a `GskRenderNode` that will render a cairo surface
2493 * into the area given by @bounds.
2494 *
2495 * You can draw to the cairo surface using [method@Gsk.CairoNode.get_draw_context].
2496 *
2497 * Returns: (transfer full) (type GskCairoNode): A new `GskRenderNode`
2498 */
2499GskRenderNode *
2500gsk_cairo_node_new (const graphene_rect_t *bounds)
2501{
2502 GskCairoNode *self;
2503 GskRenderNode *node;
2504
2505 g_return_val_if_fail (bounds != NULL, NULL);
2506
2507 self = gsk_render_node_alloc (node_type: GSK_CAIRO_NODE);
2508 node = (GskRenderNode *) self;
2509
2510 graphene_rect_init_from_rect (r: &node->bounds, src: bounds);
2511
2512 return node;
2513}
2514
2515/**
2516 * gsk_cairo_node_get_draw_context:
2517 * @node: (type GskCairoNode): a `GskRenderNode` for a Cairo surface
2518 *
2519 * Creates a Cairo context for drawing using the surface associated
2520 * to the render node.
2521 *
2522 * If no surface exists yet, a surface will be created optimized for
2523 * rendering to @renderer.
2524 *
2525 * Returns: (transfer full): a Cairo context used for drawing; use
2526 * cairo_destroy() when done drawing
2527 */
2528cairo_t *
2529gsk_cairo_node_get_draw_context (GskRenderNode *node)
2530{
2531 GskCairoNode *self = (GskCairoNode *) node;
2532 int width, height;
2533 cairo_t *res;
2534
2535 g_return_val_if_fail (GSK_IS_RENDER_NODE_TYPE (node, GSK_CAIRO_NODE), NULL);
2536
2537 width = ceilf (x: node->bounds.size.width);
2538 height = ceilf (x: node->bounds.size.height);
2539
2540 if (width <= 0 || height <= 0)
2541 {
2542 cairo_surface_t *surface = cairo_image_surface_create (format: CAIRO_FORMAT_ARGB32, width: 0, height: 0);
2543 res = cairo_create (target: surface);
2544 cairo_surface_destroy (surface);
2545 }
2546 else if (self->surface == NULL)
2547 {
2548 self->surface = cairo_recording_surface_create (content: CAIRO_CONTENT_COLOR_ALPHA,
2549 extents: &(cairo_rectangle_t) {
2550 node->bounds.origin.x,
2551 node->bounds.origin.y,
2552 node->bounds.size.width,
2553 node->bounds.size.height
2554 });
2555 res = cairo_create (target: self->surface);
2556 }
2557 else
2558 {
2559 res = cairo_create (target: self->surface);
2560 }
2561
2562 gsk_cairo_rectangle (cr: res, rect: &node->bounds);
2563 cairo_clip (cr: res);
2564
2565 return res;
2566}
2567
2568/**** GSK_CONTAINER_NODE ***/
2569
2570/**
2571 * GskContainerNode:
2572 *
2573 * A render node that can contain other render nodes.
2574 */
2575struct _GskContainerNode
2576{
2577 GskRenderNode render_node;
2578
2579 guint n_children;
2580 GskRenderNode **children;
2581};
2582
2583static void
2584gsk_container_node_finalize (GskRenderNode *node)
2585{
2586 GskContainerNode *container = (GskContainerNode *) node;
2587 GskRenderNodeClass *parent_class = g_type_class_peek (type: g_type_parent (GSK_TYPE_CONTAINER_NODE));
2588
2589 for (guint i = 0; i < container->n_children; i++)
2590 gsk_render_node_unref (node: container->children[i]);
2591
2592 g_free (mem: container->children);
2593
2594 parent_class->finalize (node);
2595}
2596
2597static void
2598gsk_container_node_draw (GskRenderNode *node,
2599 cairo_t *cr)
2600{
2601 GskContainerNode *container = (GskContainerNode *) node;
2602 guint i;
2603
2604 for (i = 0; i < container->n_children; i++)
2605 {
2606 gsk_render_node_draw (node: container->children[i], cr);
2607 }
2608}
2609
2610static int
2611gsk_container_node_compare_func (gconstpointer elem1, gconstpointer elem2, gpointer data)
2612{
2613 return gsk_render_node_can_diff (node1: (const GskRenderNode *) elem1, node2: (const GskRenderNode *) elem2) ? 0 : 1;
2614}
2615
2616static GskDiffResult
2617gsk_container_node_keep_func (gconstpointer elem1, gconstpointer elem2, gpointer data)
2618{
2619 gsk_render_node_diff (node1: (GskRenderNode *) elem1, node2: (GskRenderNode *) elem2, region: data);
2620 if (cairo_region_num_rectangles (region: data) > MAX_RECTS_IN_DIFF)
2621 return GSK_DIFF_ABORTED;
2622
2623 return GSK_DIFF_OK;
2624}
2625
2626static GskDiffResult
2627gsk_container_node_change_func (gconstpointer elem, gsize idx, gpointer data)
2628{
2629 const GskRenderNode *node = elem;
2630 cairo_region_t *region = data;
2631 cairo_rectangle_int_t rect;
2632
2633 rectangle_init_from_graphene (cairo: &rect, graphene: &node->bounds);
2634 cairo_region_union_rectangle (dst: region, rectangle: &rect);
2635 if (cairo_region_num_rectangles (region) > MAX_RECTS_IN_DIFF)
2636 return GSK_DIFF_ABORTED;
2637
2638 return GSK_DIFF_OK;
2639}
2640
2641static GskDiffSettings *
2642gsk_container_node_get_diff_settings (void)
2643{
2644 static GskDiffSettings *settings = NULL;
2645
2646 if (G_LIKELY (settings))
2647 return settings;
2648
2649 settings = gsk_diff_settings_new (compare_func: gsk_container_node_compare_func,
2650 keep_func: gsk_container_node_keep_func,
2651 delete_func: gsk_container_node_change_func,
2652 insert_func: gsk_container_node_change_func);
2653 gsk_diff_settings_set_allow_abort (settings, TRUE);
2654
2655 return settings;
2656}
2657
2658static gboolean
2659gsk_render_node_diff_multiple (GskRenderNode **nodes1,
2660 gsize n_nodes1,
2661 GskRenderNode **nodes2,
2662 gsize n_nodes2,
2663 cairo_region_t *region)
2664{
2665 return gsk_diff (elem1: (gconstpointer *) nodes1, n1: n_nodes1,
2666 elem2: (gconstpointer *) nodes2, n2: n_nodes2,
2667 settings: gsk_container_node_get_diff_settings (),
2668 data: region) == GSK_DIFF_OK;
2669}
2670
2671void
2672gsk_container_node_diff_with (GskRenderNode *container,
2673 GskRenderNode *other,
2674 cairo_region_t *region)
2675{
2676 GskContainerNode *self = (GskContainerNode *) container;
2677
2678 if (gsk_render_node_diff_multiple (nodes1: self->children,
2679 n_nodes1: self->n_children,
2680 nodes2: &other,
2681 n_nodes2: 1,
2682 region))
2683 return;
2684
2685 gsk_render_node_diff_impossible (node1: container, node2: other, region);
2686}
2687
2688static void
2689gsk_container_node_diff (GskRenderNode *node1,
2690 GskRenderNode *node2,
2691 cairo_region_t *region)
2692{
2693 GskContainerNode *self1 = (GskContainerNode *) node1;
2694 GskContainerNode *self2 = (GskContainerNode *) node2;
2695
2696 if (gsk_render_node_diff_multiple (nodes1: self1->children,
2697 n_nodes1: self1->n_children,
2698 nodes2: self2->children,
2699 n_nodes2: self2->n_children,
2700 region))
2701 return;
2702
2703 gsk_render_node_diff_impossible (node1, node2, region);
2704}
2705
2706/**
2707 * gsk_container_node_new:
2708 * @children: (array length=n_children) (transfer none): The children of the node
2709 * @n_children: Number of children in the @children array
2710 *
2711 * Creates a new `GskRenderNode` instance for holding the given @children.
2712 *
2713 * The new node will acquire a reference to each of the children.
2714 *
2715 * Returns: (transfer full) (type GskContainerNode): the new `GskRenderNode`
2716 */
2717GskRenderNode *
2718gsk_container_node_new (GskRenderNode **children,
2719 guint n_children)
2720{
2721 GskContainerNode *self;
2722 GskRenderNode *node;
2723
2724 self = gsk_render_node_alloc (node_type: GSK_CONTAINER_NODE);
2725 node = (GskRenderNode *) self;
2726
2727 self->n_children = n_children;
2728
2729 if (n_children == 0)
2730 {
2731 graphene_rect_init_from_rect (r: &node->bounds, src: graphene_rect_zero ());
2732 }
2733 else
2734 {
2735 graphene_rect_t bounds;
2736
2737 self->children = g_malloc_n (n_blocks: n_children, n_block_bytes: sizeof (GskRenderNode *));
2738
2739 self->children[0] = gsk_render_node_ref (node: children[0]);
2740 graphene_rect_init_from_rect (r: &bounds, src: &(children[0]->bounds));
2741 node->prefers_high_depth = gsk_render_node_prefers_high_depth (node: children[0]);
2742
2743 for (guint i = 1; i < n_children; i++)
2744 {
2745 self->children[i] = gsk_render_node_ref (node: children[i]);
2746 graphene_rect_union (a: &bounds, b: &(children[i]->bounds), res: &bounds);
2747 node->prefers_high_depth |= gsk_render_node_prefers_high_depth (node: children[i]);
2748 }
2749
2750 graphene_rect_init_from_rect (r: &node->bounds, src: &bounds);
2751 }
2752
2753 return node;
2754}
2755
2756/**
2757 * gsk_container_node_get_n_children:
2758 * @node: (type GskContainerNode): a container `GskRenderNode`
2759 *
2760 * Retrieves the number of direct children of @node.
2761 *
2762 * Returns: the number of children of the `GskRenderNode`
2763 */
2764guint
2765gsk_container_node_get_n_children (const GskRenderNode *node)
2766{
2767 const GskContainerNode *self = (const GskContainerNode *) node;
2768
2769 return self->n_children;
2770}
2771
2772/**
2773 * gsk_container_node_get_child:
2774 * @node: (type GskContainerNode): a container `GskRenderNode`
2775 * @idx: the position of the child to get
2776 *
2777 * Gets one of the children of @container.
2778 *
2779 * Returns: (transfer none): the @idx'th child of @container
2780 */
2781GskRenderNode *
2782gsk_container_node_get_child (const GskRenderNode *node,
2783 guint idx)
2784{
2785 const GskContainerNode *self = (const GskContainerNode *) node;
2786
2787 g_return_val_if_fail (GSK_IS_RENDER_NODE_TYPE (node, GSK_CONTAINER_NODE), NULL);
2788 g_return_val_if_fail (idx < self->n_children, NULL);
2789
2790 return self->children[idx];
2791}
2792
2793GskRenderNode **
2794gsk_container_node_get_children (const GskRenderNode *node,
2795 guint *n_children)
2796{
2797 const GskContainerNode *self = (const GskContainerNode *) node;
2798
2799 *n_children = self->n_children;
2800
2801 return self->children;
2802}
2803
2804/*** GSK_TRANSFORM_NODE ***/
2805
2806/**
2807 * GskTransformNode:
2808 *
2809 * A render node applying a `GskTransform` to its single child node.
2810 */
2811struct _GskTransformNode
2812{
2813 GskRenderNode render_node;
2814
2815 GskRenderNode *child;
2816 GskTransform *transform;
2817 float dx, dy;
2818};
2819
2820static void
2821gsk_transform_node_finalize (GskRenderNode *node)
2822{
2823 GskTransformNode *self = (GskTransformNode *) node;
2824 GskRenderNodeClass *parent_class = g_type_class_peek (type: g_type_parent (GSK_TYPE_TRANSFORM_NODE));
2825
2826 gsk_render_node_unref (node: self->child);
2827 gsk_transform_unref (self: self->transform);
2828
2829 parent_class->finalize (node);
2830}
2831
2832static void
2833gsk_transform_node_draw (GskRenderNode *node,
2834 cairo_t *cr)
2835{
2836 GskTransformNode *self = (GskTransformNode *) node;
2837 float xx, yx, xy, yy, dx, dy;
2838 cairo_matrix_t ctm;
2839
2840 if (gsk_transform_get_category (self->transform) < GSK_TRANSFORM_CATEGORY_2D)
2841 {
2842 cairo_set_source_rgb (cr, red: 255 / 255., green: 105 / 255., blue: 180 / 255.);
2843 gsk_cairo_rectangle (cr, rect: &node->bounds);
2844 cairo_fill (cr);
2845 return;
2846 }
2847
2848 gsk_transform_to_2d (self: self->transform, out_xx: &xx, out_yx: &yx, out_xy: &xy, out_yy: &yy, out_dx: &dx, out_dy: &dy);
2849 cairo_matrix_init (matrix: &ctm, xx, yx, xy, yy, x0: dx, y0: dy);
2850 GSK_NOTE (CAIRO, g_message ("CTM = { .xx = %g, .yx = %g, .xy = %g, .yy = %g, .x0 = %g, .y0 = %g }",
2851 ctm.xx, ctm.yx,
2852 ctm.xy, ctm.yy,
2853 ctm.x0, ctm.y0));
2854 if (xx * yy == xy * yx)
2855 {
2856 /* broken matrix here. This can happen during transitions
2857 * (like when flipping an axis at the point where scale == 0)
2858 * and just means that nothing should be drawn.
2859 * But Cairo thows lots of ugly errors instead of silently
2860 * going on. So We silently go on.
2861 */
2862 return;
2863 }
2864 cairo_transform (cr, matrix: &ctm);
2865
2866 gsk_render_node_draw (node: self->child, cr);
2867}
2868
2869static gboolean
2870gsk_transform_node_can_diff (const GskRenderNode *node1,
2871 const GskRenderNode *node2)
2872{
2873 GskTransformNode *self1 = (GskTransformNode *) node1;
2874 GskTransformNode *self2 = (GskTransformNode *) node2;
2875
2876 if (!gsk_transform_equal (first: self1->transform, second: self2->transform))
2877 return FALSE;
2878
2879 return gsk_render_node_can_diff (node1: self1->child, node2: self2->child);
2880}
2881
2882static void
2883gsk_transform_node_diff (GskRenderNode *node1,
2884 GskRenderNode *node2,
2885 cairo_region_t *region)
2886{
2887 GskTransformNode *self1 = (GskTransformNode *) node1;
2888 GskTransformNode *self2 = (GskTransformNode *) node2;
2889
2890 if (!gsk_transform_equal (first: self1->transform, second: self2->transform))
2891 {
2892 gsk_render_node_diff_impossible (node1, node2, region);
2893 return;
2894 }
2895
2896 if (self1->child == self2->child)
2897 return;
2898
2899 switch (gsk_transform_get_category (self1->transform))
2900 {
2901 case GSK_TRANSFORM_CATEGORY_IDENTITY:
2902 gsk_render_node_diff (node1: self1->child, node2: self2->child, region);
2903 break;
2904
2905 case GSK_TRANSFORM_CATEGORY_2D_TRANSLATE:
2906 {
2907 cairo_region_t *sub;
2908 float dx, dy;
2909 gsk_transform_to_translate (self: self1->transform, out_dx: &dx, out_dy: &dy);
2910 sub = cairo_region_create ();
2911 gsk_render_node_diff (node1: self1->child, node2: self2->child, region: sub);
2912 cairo_region_translate (region: sub, dx: floorf (x: dx), dy: floorf (x: dy));
2913 if (floorf (x: dx) != dx)
2914 {
2915 cairo_region_t *tmp = cairo_region_copy (original: sub);
2916 cairo_region_translate (region: tmp, dx: 1, dy: 0);
2917 cairo_region_union (dst: sub, other: tmp);
2918 cairo_region_destroy (region: tmp);
2919 }
2920 if (floorf (x: dy) != dy)
2921 {
2922 cairo_region_t *tmp = cairo_region_copy (original: sub);
2923 cairo_region_translate (region: tmp, dx: 0, dy: 1);
2924 cairo_region_union (dst: sub, other: tmp);
2925 cairo_region_destroy (region: tmp);
2926 }
2927 cairo_region_union (dst: region, other: sub);
2928 cairo_region_destroy (region: sub);
2929 }
2930 break;
2931
2932 case GSK_TRANSFORM_CATEGORY_UNKNOWN:
2933 case GSK_TRANSFORM_CATEGORY_ANY:
2934 case GSK_TRANSFORM_CATEGORY_3D:
2935 case GSK_TRANSFORM_CATEGORY_2D:
2936 case GSK_TRANSFORM_CATEGORY_2D_AFFINE:
2937 default:
2938 gsk_render_node_diff_impossible (node1, node2, region);
2939 break;
2940 }
2941}
2942
2943/**
2944 * gsk_transform_node_new:
2945 * @child: The node to transform
2946 * @transform: (transfer none): The transform to apply
2947 *
2948 * Creates a `GskRenderNode` that will transform the given @child
2949 * with the given @transform.
2950 *
2951 * Returns: (transfer full) (type GskTransformNode): A new `GskRenderNode`
2952 */
2953GskRenderNode *
2954gsk_transform_node_new (GskRenderNode *child,
2955 GskTransform *transform)
2956{
2957 GskTransformNode *self;
2958 GskRenderNode *node;
2959
2960 g_return_val_if_fail (GSK_IS_RENDER_NODE (child), NULL);
2961 g_return_val_if_fail (transform != NULL, NULL);
2962
2963 self = gsk_render_node_alloc (node_type: GSK_TRANSFORM_NODE);
2964 node = (GskRenderNode *) self;
2965
2966 self->child = gsk_render_node_ref (node: child);
2967 self->transform = gsk_transform_ref (self: transform);
2968
2969 if (gsk_transform_get_category (transform) >= GSK_TRANSFORM_CATEGORY_2D_TRANSLATE)
2970 gsk_transform_to_translate (self: transform, out_dx: &self->dx, out_dy: &self->dy);
2971 else
2972 self->dx = self->dy = 0;
2973
2974 gsk_transform_transform_bounds (self: self->transform,
2975 rect: &child->bounds,
2976 out_rect: &node->bounds);
2977
2978 node->prefers_high_depth = gsk_render_node_prefers_high_depth (node: child);
2979
2980 return node;
2981}
2982
2983/**
2984 * gsk_transform_node_get_child:
2985 * @node: (type GskTransformNode): a `GskRenderNode` for a transform
2986 *
2987 * Gets the child node that is getting transformed by the given @node.
2988 *
2989 * Returns: (transfer none): The child that is getting transformed
2990 */
2991GskRenderNode *
2992gsk_transform_node_get_child (const GskRenderNode *node)
2993{
2994 const GskTransformNode *self = (const GskTransformNode *) node;
2995
2996 return self->child;
2997}
2998
2999/**
3000 * gsk_transform_node_get_transform:
3001 * @node: (type GskTransformNode): a `GskRenderNode` for a transform
3002 *
3003 * Retrieves the `GskTransform` used by the @node.
3004 *
3005 * Returns: (transfer none): a `GskTransform`
3006 */
3007GskTransform *
3008gsk_transform_node_get_transform (const GskRenderNode *node)
3009{
3010 const GskTransformNode *self = (const GskTransformNode *) node;
3011
3012 return self->transform;
3013}
3014
3015void
3016gsk_transform_node_get_translate (const GskRenderNode *node,
3017 float *dx,
3018 float *dy)
3019{
3020 const GskTransformNode *self = (const GskTransformNode *) node;
3021
3022 *dx = self->dx;
3023 *dy = self->dy;
3024}
3025
3026/*** GSK_OPACITY_NODE ***/
3027
3028/**
3029 * GskOpacityNode:
3030 *
3031 * A render node controlling the opacity of its single child node.
3032 */
3033struct _GskOpacityNode
3034{
3035 GskRenderNode render_node;
3036
3037 GskRenderNode *child;
3038 float opacity;
3039};
3040
3041static void
3042gsk_opacity_node_finalize (GskRenderNode *node)
3043{
3044 GskOpacityNode *self = (GskOpacityNode *) node;
3045 GskRenderNodeClass *parent_class = g_type_class_peek (type: g_type_parent (GSK_TYPE_OPACITY_NODE));
3046
3047 gsk_render_node_unref (node: self->child);
3048
3049 parent_class->finalize (node);
3050}
3051
3052static void
3053gsk_opacity_node_draw (GskRenderNode *node,
3054 cairo_t *cr)
3055{
3056 GskOpacityNode *self = (GskOpacityNode *) node;
3057
3058 cairo_save (cr);
3059
3060 /* clip so the push_group() creates a smaller surface */
3061 gsk_cairo_rectangle (cr, rect: &node->bounds);
3062 cairo_clip (cr);
3063
3064 cairo_push_group (cr);
3065
3066 gsk_render_node_draw (node: self->child, cr);
3067
3068 cairo_pop_group_to_source (cr);
3069 cairo_paint_with_alpha (cr, alpha: self->opacity);
3070
3071 cairo_restore (cr);
3072}
3073
3074static void
3075gsk_opacity_node_diff (GskRenderNode *node1,
3076 GskRenderNode *node2,
3077 cairo_region_t *region)
3078{
3079 GskOpacityNode *self1 = (GskOpacityNode *) node1;
3080 GskOpacityNode *self2 = (GskOpacityNode *) node2;
3081
3082 if (self1->opacity == self2->opacity)
3083 gsk_render_node_diff (node1: self1->child, node2: self2->child, region);
3084 else
3085 gsk_render_node_diff_impossible (node1, node2, region);
3086}
3087
3088/**
3089 * gsk_opacity_node_new:
3090 * @child: The node to draw
3091 * @opacity: The opacity to apply
3092 *
3093 * Creates a `GskRenderNode` that will drawn the @child with reduced
3094 * @opacity.
3095 *
3096 * Returns: (transfer full) (type GskOpacityNode): A new `GskRenderNode`
3097 */
3098GskRenderNode *
3099gsk_opacity_node_new (GskRenderNode *child,
3100 float opacity)
3101{
3102 GskOpacityNode *self;
3103 GskRenderNode *node;
3104
3105 g_return_val_if_fail (GSK_IS_RENDER_NODE (child), NULL);
3106
3107 self = gsk_render_node_alloc (node_type: GSK_OPACITY_NODE);
3108 node = (GskRenderNode *) self;
3109
3110 self->child = gsk_render_node_ref (node: child);
3111 self->opacity = CLAMP (opacity, 0.0, 1.0);
3112
3113 graphene_rect_init_from_rect (r: &node->bounds, src: &child->bounds);
3114
3115 node->prefers_high_depth = gsk_render_node_prefers_high_depth (node: child);
3116
3117 return node;
3118}
3119
3120/**
3121 * gsk_opacity_node_get_child:
3122 * @node: (type GskOpacityNode): a `GskRenderNode` for an opacity
3123 *
3124 * Gets the child node that is getting opacityed by the given @node.
3125 *
3126 * Returns: (transfer none): The child that is getting opacityed
3127 */
3128GskRenderNode *
3129gsk_opacity_node_get_child (const GskRenderNode *node)
3130{
3131 const GskOpacityNode *self = (const GskOpacityNode *) node;
3132
3133 return self->child;
3134}
3135
3136/**
3137 * gsk_opacity_node_get_opacity:
3138 * @node: (type GskOpacityNode): a `GskRenderNode` for an opacity
3139 *
3140 * Gets the transparency factor for an opacity node.
3141 *
3142 * Returns: the opacity factor
3143 */
3144float
3145gsk_opacity_node_get_opacity (const GskRenderNode *node)
3146{
3147 const GskOpacityNode *self = (const GskOpacityNode *) node;
3148
3149 return self->opacity;
3150}
3151
3152/*** GSK_COLOR_MATRIX_NODE ***/
3153
3154/**
3155 * GskColorMatrixNode:
3156 *
3157 * A render node controlling the color matrix of its single child node.
3158 */
3159struct _GskColorMatrixNode
3160{
3161 GskRenderNode render_node;
3162
3163 GskRenderNode *child;
3164 graphene_matrix_t color_matrix;
3165 graphene_vec4_t color_offset;
3166};
3167
3168static void
3169gsk_color_matrix_node_finalize (GskRenderNode *node)
3170{
3171 GskColorMatrixNode *self = (GskColorMatrixNode *) node;
3172 GskRenderNodeClass *parent_class = g_type_class_peek (type: g_type_parent (GSK_TYPE_COLOR_MATRIX_NODE));
3173
3174 gsk_render_node_unref (node: self->child);
3175
3176 parent_class->finalize (node);
3177}
3178
3179static void
3180gsk_color_matrix_node_draw (GskRenderNode *node,
3181 cairo_t *cr)
3182{
3183 GskColorMatrixNode *self = (GskColorMatrixNode *) node;
3184 cairo_pattern_t *pattern;
3185 cairo_surface_t *surface, *image_surface;
3186 graphene_vec4_t pixel;
3187 guint32* pixel_data;
3188 guchar *data;
3189 gsize x, y, width, height, stride;
3190 float alpha;
3191
3192 cairo_save (cr);
3193
3194 /* clip so the push_group() creates a smaller surface */
3195 gsk_cairo_rectangle (cr, rect: &node->bounds);
3196 cairo_clip (cr);
3197
3198 cairo_push_group (cr);
3199
3200 gsk_render_node_draw (node: self->child, cr);
3201
3202 pattern = cairo_pop_group (cr);
3203 cairo_pattern_get_surface (pattern, surface: &surface);
3204 image_surface = cairo_surface_map_to_image (surface, NULL);
3205
3206 data = cairo_image_surface_get_data (surface: image_surface);
3207 width = cairo_image_surface_get_width (surface: image_surface);
3208 height = cairo_image_surface_get_height (surface: image_surface);
3209 stride = cairo_image_surface_get_stride (surface: image_surface);
3210
3211 for (y = 0; y < height; y++)
3212 {
3213 pixel_data = (guint32 *) data;
3214 for (x = 0; x < width; x++)
3215 {
3216 alpha = ((pixel_data[x] >> 24) & 0xFF) / 255.0;
3217
3218 if (alpha == 0)
3219 {
3220 graphene_vec4_init (v: &pixel, x: 0.0, y: 0.0, z: 0.0, w: 0.0);
3221 }
3222 else
3223 {
3224 graphene_vec4_init (v: &pixel,
3225 x: ((pixel_data[x] >> 16) & 0xFF) / (255.0 * alpha),
3226 y: ((pixel_data[x] >> 8) & 0xFF) / (255.0 * alpha),
3227 z: ( pixel_data[x] & 0xFF) / (255.0 * alpha),
3228 w: alpha);
3229 graphene_matrix_transform_vec4 (m: &self->color_matrix, v: &pixel, res: &pixel);
3230 }
3231
3232 graphene_vec4_add (a: &pixel, b: &self->color_offset, res: &pixel);
3233
3234 alpha = graphene_vec4_get_w (v: &pixel);
3235 if (alpha > 0.0)
3236 {
3237 alpha = MIN (alpha, 1.0);
3238 pixel_data[x] = (((guint32) roundf (x: alpha * 255)) << 24) |
3239 (((guint32) roundf (CLAMP (graphene_vec4_get_x (&pixel), 0, 1) * alpha * 255)) << 16) |
3240 (((guint32) roundf (CLAMP (graphene_vec4_get_y (&pixel), 0, 1) * alpha * 255)) << 8) |
3241 ((guint32) roundf (CLAMP (graphene_vec4_get_z (&pixel), 0, 1) * alpha * 255));
3242 }
3243 else
3244 {
3245 pixel_data[x] = 0;
3246 }
3247 }
3248 data += stride;
3249 }
3250
3251 cairo_surface_mark_dirty (surface: image_surface);
3252 cairo_surface_unmap_image (surface, image: image_surface);
3253
3254 cairo_set_source (cr, source: pattern);
3255 cairo_paint (cr);
3256
3257 cairo_restore (cr);
3258 cairo_pattern_destroy (pattern);
3259}
3260
3261static void
3262gsk_color_matrix_node_diff (GskRenderNode *node1,
3263 GskRenderNode *node2,
3264 cairo_region_t *region)
3265{
3266 GskColorMatrixNode *self1 = (GskColorMatrixNode *) node1;
3267 GskColorMatrixNode *self2 = (GskColorMatrixNode *) node2;
3268
3269 if (!graphene_vec4_equal (v1: &self1->color_offset, v2: &self2->color_offset))
3270 goto nope;
3271
3272 if (!graphene_matrix_equal_fast (a: &self1->color_matrix, b: &self2->color_matrix))
3273 goto nope;
3274
3275 gsk_render_node_diff (node1: self1->child, node2: self2->child, region);
3276 return;
3277
3278nope:
3279 gsk_render_node_diff_impossible (node1, node2, region);
3280 return;
3281}
3282
3283/**
3284 * gsk_color_matrix_node_new:
3285 * @child: The node to draw
3286 * @color_matrix: The matrix to apply
3287 * @color_offset: Values to add to the color
3288 *
3289 * Creates a `GskRenderNode` that will drawn the @child with
3290 * @color_matrix.
3291 *
3292 * In particular, the node will transform the operation
3293 *
3294 * pixel = color_matrix * pixel + color_offset
3295 *
3296 * for every pixel.
3297 *
3298 * Returns: (transfer full) (type GskColorMatrixNode): A new `GskRenderNode`
3299 */
3300GskRenderNode *
3301gsk_color_matrix_node_new (GskRenderNode *child,
3302 const graphene_matrix_t *color_matrix,
3303 const graphene_vec4_t *color_offset)
3304{
3305 GskColorMatrixNode *self;
3306 GskRenderNode *node;
3307
3308 g_return_val_if_fail (GSK_IS_RENDER_NODE (child), NULL);
3309
3310 self = gsk_render_node_alloc (node_type: GSK_COLOR_MATRIX_NODE);
3311 node = (GskRenderNode *) self;
3312
3313 self->child = gsk_render_node_ref (node: child);
3314 graphene_matrix_init_from_matrix (m: &self->color_matrix, src: color_matrix);
3315 graphene_vec4_init_from_vec4 (v: &self->color_offset, src: color_offset);
3316
3317 graphene_rect_init_from_rect (r: &node->bounds, src: &child->bounds);
3318
3319 node->prefers_high_depth = gsk_render_node_prefers_high_depth (node: child);
3320
3321 return node;
3322}
3323
3324/**
3325 * gsk_color_matrix_node_get_child:
3326 * @node: (type GskColorMatrixNode): a color matrix `GskRenderNode`
3327 *
3328 * Gets the child node that is getting its colors modified by the given @node.
3329 *
3330 * Returns: (transfer none): The child that is getting its colors modified
3331 **/
3332GskRenderNode *
3333gsk_color_matrix_node_get_child (const GskRenderNode *node)
3334{
3335 const GskColorMatrixNode *self = (const GskColorMatrixNode *) node;
3336
3337 return self->child;
3338}
3339
3340/**
3341 * gsk_color_matrix_node_get_color_matrix:
3342 * @node: (type GskColorMatrixNode): a color matrix `GskRenderNode`
3343 *
3344 * Retrieves the color matrix used by the @node.
3345 *
3346 * Returns: a 4x4 color matrix
3347 */
3348const graphene_matrix_t *
3349gsk_color_matrix_node_get_color_matrix (const GskRenderNode *node)
3350{
3351 const GskColorMatrixNode *self = (const GskColorMatrixNode *) node;
3352
3353 return &self->color_matrix;
3354}
3355
3356/**
3357 * gsk_color_matrix_node_get_color_offset:
3358 * @node: (type GskColorMatrixNode): a color matrix `GskRenderNode`
3359 *
3360 * Retrieves the color offset used by the @node.
3361 *
3362 * Returns: a color vector
3363 */
3364const graphene_vec4_t *
3365gsk_color_matrix_node_get_color_offset (const GskRenderNode *node)
3366{
3367 const GskColorMatrixNode *self = (const GskColorMatrixNode *) node;
3368
3369 return &self->color_offset;
3370}
3371
3372/*** GSK_REPEAT_NODE ***/
3373
3374/**
3375 * GskRepeatNode:
3376 *
3377 * A render node repeating its single child node.
3378 */
3379struct _GskRepeatNode
3380{
3381 GskRenderNode render_node;
3382
3383 GskRenderNode *child;
3384 graphene_rect_t child_bounds;
3385};
3386
3387static void
3388gsk_repeat_node_finalize (GskRenderNode *node)
3389{
3390 GskRepeatNode *self = (GskRepeatNode *) node;
3391 GskRenderNodeClass *parent_class = g_type_class_peek (type: g_type_parent (GSK_TYPE_REPEAT_NODE));
3392
3393 gsk_render_node_unref (node: self->child);
3394
3395 parent_class->finalize (node);
3396}
3397
3398static void
3399gsk_repeat_node_draw (GskRenderNode *node,
3400 cairo_t *cr)
3401{
3402 GskRepeatNode *self = (GskRepeatNode *) node;
3403 cairo_pattern_t *pattern;
3404 cairo_surface_t *surface;
3405 cairo_t *surface_cr;
3406
3407 surface = cairo_surface_create_similar (other: cairo_get_target (cr),
3408 content: CAIRO_CONTENT_COLOR_ALPHA,
3409 width: ceilf (x: self->child_bounds.size.width),
3410 height: ceilf (x: self->child_bounds.size.height));
3411 surface_cr = cairo_create (target: surface);
3412 cairo_translate (cr: surface_cr,
3413 tx: - self->child_bounds.origin.x,
3414 ty: - self->child_bounds.origin.y);
3415 gsk_render_node_draw (node: self->child, cr: surface_cr);
3416 cairo_destroy (cr: surface_cr);
3417
3418 pattern = cairo_pattern_create_for_surface (surface);
3419 cairo_pattern_set_extend (pattern, extend: CAIRO_EXTEND_REPEAT);
3420 cairo_pattern_set_matrix (pattern,
3421 matrix: &(cairo_matrix_t) {
3422 .xx = 1.0,
3423 .yy = 1.0,
3424 .x0 = - self->child_bounds.origin.x,
3425 .y0 = - self->child_bounds.origin.y
3426 });
3427 cairo_set_source (cr, source: pattern);
3428 cairo_pattern_destroy (pattern);
3429 cairo_surface_destroy (surface);
3430
3431 gsk_cairo_rectangle (cr, rect: &node->bounds);
3432 cairo_fill (cr);
3433}
3434
3435/**
3436 * gsk_repeat_node_new:
3437 * @bounds: The bounds of the area to be painted
3438 * @child: The child to repeat
3439 * @child_bounds: (nullable): The area of the child to repeat or %NULL to
3440 * use the child's bounds
3441 *
3442 * Creates a `GskRenderNode` that will repeat the drawing of @child across
3443 * the given @bounds.
3444 *
3445 * Returns: (transfer full) (type GskRepeatNode): A new `GskRenderNode`
3446 */
3447GskRenderNode *
3448gsk_repeat_node_new (const graphene_rect_t *bounds,
3449 GskRenderNode *child,
3450 const graphene_rect_t *child_bounds)
3451{
3452 GskRepeatNode *self;
3453 GskRenderNode *node;
3454
3455 g_return_val_if_fail (bounds != NULL, NULL);
3456 g_return_val_if_fail (GSK_IS_RENDER_NODE (child), NULL);
3457
3458 self = gsk_render_node_alloc (node_type: GSK_REPEAT_NODE);
3459 node = (GskRenderNode *) self;
3460
3461 graphene_rect_init_from_rect (r: &node->bounds, src: bounds);
3462
3463 self->child = gsk_render_node_ref (node: child);
3464
3465 if (child_bounds)
3466 graphene_rect_init_from_rect (r: &self->child_bounds, src: child_bounds);
3467 else
3468 graphene_rect_init_from_rect (r: &self->child_bounds, src: &child->bounds);
3469
3470 node->prefers_high_depth = gsk_render_node_prefers_high_depth (node: child);
3471
3472 return node;
3473}
3474
3475/**
3476 * gsk_repeat_node_get_child:
3477 * @node: (type GskRepeatNode): a repeat `GskRenderNode`
3478 *
3479 * Retrieves the child of @node.
3480 *
3481 * Returns: (transfer none): a `GskRenderNode`
3482 */
3483GskRenderNode *
3484gsk_repeat_node_get_child (const GskRenderNode *node)
3485{
3486 const GskRepeatNode *self = (const GskRepeatNode *) node;
3487
3488 return self->child;
3489}
3490
3491/**
3492 * gsk_repeat_node_get_child_bounds:
3493 * @node: (type GskRepeatNode): a repeat `GskRenderNode`
3494 *
3495 * Retrieves the bounding rectangle of the child of @node.
3496 *
3497 * Returns: (transfer none): a bounding rectangle
3498 */
3499const graphene_rect_t *
3500gsk_repeat_node_get_child_bounds (const GskRenderNode *node)
3501{
3502 const GskRepeatNode *self = (const GskRepeatNode *) node;
3503
3504 return &self->child_bounds;
3505}
3506
3507/*** GSK_CLIP_NODE ***/
3508
3509/**
3510 * GskClipNode:
3511 *
3512 * A render node applying a rectangular clip to its single child node.
3513 */
3514struct _GskClipNode
3515{
3516 GskRenderNode render_node;
3517
3518 GskRenderNode *child;
3519 graphene_rect_t clip;
3520};
3521
3522static void
3523gsk_clip_node_finalize (GskRenderNode *node)
3524{
3525 GskClipNode *self = (GskClipNode *) node;
3526 GskRenderNodeClass *parent_class = g_type_class_peek (type: g_type_parent (GSK_TYPE_CLIP_NODE));
3527
3528 gsk_render_node_unref (node: self->child);
3529
3530 parent_class->finalize (node);
3531}
3532
3533static void
3534gsk_clip_node_draw (GskRenderNode *node,
3535 cairo_t *cr)
3536{
3537 GskClipNode *self = (GskClipNode *) node;
3538
3539 cairo_save (cr);
3540
3541 gsk_cairo_rectangle (cr, rect: &self->clip);
3542 cairo_clip (cr);
3543
3544 gsk_render_node_draw (node: self->child, cr);
3545
3546 cairo_restore (cr);
3547}
3548
3549static void
3550gsk_clip_node_diff (GskRenderNode *node1,
3551 GskRenderNode *node2,
3552 cairo_region_t *region)
3553{
3554 GskClipNode *self1 = (GskClipNode *) node1;
3555 GskClipNode *self2 = (GskClipNode *) node2;
3556
3557 if (graphene_rect_equal (a: &self1->clip, b: &self2->clip))
3558 {
3559 cairo_region_t *sub;
3560 cairo_rectangle_int_t clip_rect;
3561
3562 sub = cairo_region_create();
3563 gsk_render_node_diff (node1: self1->child, node2: self2->child, region: sub);
3564 rectangle_init_from_graphene (cairo: &clip_rect, graphene: &self1->clip);
3565 cairo_region_intersect_rectangle (dst: sub, rectangle: &clip_rect);
3566 cairo_region_union (dst: region, other: sub);
3567 cairo_region_destroy (region: sub);
3568 }
3569 else
3570 {
3571 gsk_render_node_diff_impossible (node1, node2, region);
3572 }
3573}
3574
3575/**
3576 * gsk_clip_node_new:
3577 * @child: The node to draw
3578 * @clip: The clip to apply
3579 *
3580 * Creates a `GskRenderNode` that will clip the @child to the area
3581 * given by @clip.
3582 *
3583 * Returns: (transfer full) (type GskClipNode): A new `GskRenderNode`
3584 */
3585GskRenderNode *
3586gsk_clip_node_new (GskRenderNode *child,
3587 const graphene_rect_t *clip)
3588{
3589 GskClipNode *self;
3590 GskRenderNode *node;
3591
3592 g_return_val_if_fail (GSK_IS_RENDER_NODE (child), NULL);
3593 g_return_val_if_fail (clip != NULL, NULL);
3594
3595 self = gsk_render_node_alloc (node_type: GSK_CLIP_NODE);
3596 node = (GskRenderNode *) self;
3597
3598 self->child = gsk_render_node_ref (node: child);
3599 graphene_rect_normalize_r (r: clip, res: &self->clip);
3600
3601 graphene_rect_intersection (a: &self->clip, b: &child->bounds, res: &node->bounds);
3602
3603 node->prefers_high_depth = gsk_render_node_prefers_high_depth (node: child);
3604
3605 return node;
3606}
3607
3608/**
3609 * gsk_clip_node_get_child:
3610 * @node: (type GskClipNode): a clip @GskRenderNode
3611 *
3612 * Gets the child node that is getting clipped by the given @node.
3613 *
3614 * Returns: (transfer none): The child that is getting clipped
3615 **/
3616GskRenderNode *
3617gsk_clip_node_get_child (const GskRenderNode *node)
3618{
3619 const GskClipNode *self = (const GskClipNode *) node;
3620
3621 return self->child;
3622}
3623
3624/**
3625 * gsk_clip_node_get_clip:
3626 * @node: (type GskClipNode): a `GskClipNode`
3627 *
3628 * Retrieves the clip rectangle for @node.
3629 *
3630 * Returns: a clip rectangle
3631 */
3632const graphene_rect_t *
3633gsk_clip_node_get_clip (const GskRenderNode *node)
3634{
3635 const GskClipNode *self = (const GskClipNode *) node;
3636
3637 return &self->clip;
3638}
3639
3640/*** GSK_ROUNDED_CLIP_NODE ***/
3641
3642/**
3643 * GskRoundedClipNode:
3644 *
3645 * A render node applying a rounded rectangle clip to its single child.
3646 */
3647struct _GskRoundedClipNode
3648{
3649 GskRenderNode render_node;
3650
3651 GskRenderNode *child;
3652 GskRoundedRect clip;
3653};
3654
3655static void
3656gsk_rounded_clip_node_finalize (GskRenderNode *node)
3657{
3658 GskRoundedClipNode *self = (GskRoundedClipNode *) node;
3659 GskRenderNodeClass *parent_class = g_type_class_peek (type: g_type_parent (GSK_TYPE_ROUNDED_CLIP_NODE));
3660
3661 gsk_render_node_unref (node: self->child);
3662
3663 parent_class->finalize (node);
3664}
3665
3666static void
3667gsk_rounded_clip_node_draw (GskRenderNode *node,
3668 cairo_t *cr)
3669{
3670 GskRoundedClipNode *self = (GskRoundedClipNode *) node;
3671
3672 cairo_save (cr);
3673
3674 gsk_rounded_rect_path (self: &self->clip, cr);
3675 cairo_clip (cr);
3676
3677 gsk_render_node_draw (node: self->child, cr);
3678
3679 cairo_restore (cr);
3680}
3681
3682static void
3683gsk_rounded_clip_node_diff (GskRenderNode *node1,
3684 GskRenderNode *node2,
3685 cairo_region_t *region)
3686{
3687 GskRoundedClipNode *self1 = (GskRoundedClipNode *) node1;
3688 GskRoundedClipNode *self2 = (GskRoundedClipNode *) node2;
3689
3690 if (gsk_rounded_rect_equal (rect1: &self1->clip, rect2: &self2->clip))
3691 {
3692 cairo_region_t *sub;
3693 cairo_rectangle_int_t clip_rect;
3694
3695 sub = cairo_region_create();
3696 gsk_render_node_diff (node1: self1->child, node2: self2->child, region: sub);
3697 rectangle_init_from_graphene (cairo: &clip_rect, graphene: &self1->clip.bounds);
3698 cairo_region_intersect_rectangle (dst: sub, rectangle: &clip_rect);
3699 cairo_region_union (dst: region, other: sub);
3700 cairo_region_destroy (region: sub);
3701 }
3702 else
3703 {
3704 gsk_render_node_diff_impossible (node1, node2, region);
3705 }
3706}
3707
3708/**
3709 * gsk_rounded_clip_node_new:
3710 * @child: The node to draw
3711 * @clip: The clip to apply
3712 *
3713 * Creates a `GskRenderNode` that will clip the @child to the area
3714 * given by @clip.
3715 *
3716 * Returns: (transfer none) (type GskRoundedClipNode): A new `GskRenderNode`
3717 */
3718GskRenderNode *
3719gsk_rounded_clip_node_new (GskRenderNode *child,
3720 const GskRoundedRect *clip)
3721{
3722 GskRoundedClipNode *self;
3723 GskRenderNode *node;
3724
3725 g_return_val_if_fail (GSK_IS_RENDER_NODE (child), NULL);
3726 g_return_val_if_fail (clip != NULL, NULL);
3727
3728 self = gsk_render_node_alloc (node_type: GSK_ROUNDED_CLIP_NODE);
3729 node = (GskRenderNode *) self;
3730
3731 self->child = gsk_render_node_ref (node: child);
3732 gsk_rounded_rect_init_copy (self: &self->clip, src: clip);
3733
3734 graphene_rect_intersection (a: &self->clip.bounds, b: &child->bounds, res: &node->bounds);
3735
3736 node->prefers_high_depth = gsk_render_node_prefers_high_depth (node: child);
3737
3738 return node;
3739}
3740
3741/**
3742 * gsk_rounded_clip_node_get_child:
3743 * @node: (type GskRoundedClipNode): a rounded clip `GskRenderNode`
3744 *
3745 * Gets the child node that is getting clipped by the given @node.
3746 *
3747 * Returns: (transfer none): The child that is getting clipped
3748 **/
3749GskRenderNode *
3750gsk_rounded_clip_node_get_child (const GskRenderNode *node)
3751{
3752 const GskRoundedClipNode *self = (const GskRoundedClipNode *) node;
3753
3754 return self->child;
3755}
3756
3757/**
3758 * gsk_rounded_clip_node_get_clip:
3759 * @node: (type GskRoundedClipNode): a rounded clip `GskRenderNode`
3760 *
3761 * Retrieves the rounded rectangle used to clip the contents of the @node.
3762 *
3763 * Returns: (transfer none): a rounded rectangle
3764 */
3765const GskRoundedRect *
3766gsk_rounded_clip_node_get_clip (const GskRenderNode *node)
3767{
3768 const GskRoundedClipNode *self = (const GskRoundedClipNode *) node;
3769
3770 return &self->clip;
3771}
3772
3773/*** GSK_SHADOW_NODE ***/
3774
3775/**
3776 * GskShadowNode:
3777 *
3778 * A render node drawing one or more shadows behind its single child node.
3779 */
3780struct _GskShadowNode
3781{
3782 GskRenderNode render_node;
3783
3784 GskRenderNode *child;
3785
3786 gsize n_shadows;
3787 GskShadow *shadows;
3788};
3789
3790static void
3791gsk_shadow_node_finalize (GskRenderNode *node)
3792{
3793 GskShadowNode *self = (GskShadowNode *) node;
3794 GskRenderNodeClass *parent_class = g_type_class_peek (type: g_type_parent (GSK_TYPE_SHADOW_NODE));
3795
3796 gsk_render_node_unref (node: self->child);
3797 g_free (mem: self->shadows);
3798
3799 parent_class->finalize (node);
3800}
3801
3802static void
3803gsk_shadow_node_draw (GskRenderNode *node,
3804 cairo_t *cr)
3805{
3806 GskShadowNode *self = (GskShadowNode *) node;
3807 cairo_pattern_t *pattern;
3808 gsize i;
3809
3810 cairo_save (cr);
3811 /* clip so the push_group() creates a small surface */
3812 gsk_cairo_rectangle (cr, rect: &self->child->bounds);
3813 cairo_clip (cr);
3814 cairo_push_group (cr);
3815 gsk_render_node_draw (node: self->child, cr);
3816 pattern = cairo_pop_group (cr);
3817 cairo_restore (cr);
3818
3819 for (i = 0; i < self->n_shadows; i++)
3820 {
3821 GskShadow *shadow = &self->shadows[i];
3822
3823 /* We don't need to draw invisible shadows */
3824 if (gdk_rgba_is_clear (rgba: &shadow->color))
3825 continue;
3826
3827 cairo_save (cr);
3828 gdk_cairo_set_source_rgba (cr, rgba: &shadow->color);
3829 cr = gsk_cairo_blur_start_drawing (cr, radius: shadow->radius, blur_flags: GSK_BLUR_X | GSK_BLUR_Y);
3830
3831 cairo_translate (cr, tx: shadow->dx, ty: shadow->dy);
3832 cairo_mask (cr, pattern);
3833
3834 cr = gsk_cairo_blur_finish_drawing (cr, radius: shadow->radius, color: &shadow->color, blur_flags: GSK_BLUR_X | GSK_BLUR_Y);
3835 cairo_restore (cr);
3836 }
3837
3838 cairo_set_source (cr, source: pattern);
3839 cairo_paint (cr);
3840
3841 cairo_pattern_destroy (pattern);
3842}
3843
3844static void
3845gsk_shadow_node_diff (GskRenderNode *node1,
3846 GskRenderNode *node2,
3847 cairo_region_t *region)
3848{
3849 GskShadowNode *self1 = (GskShadowNode *) node1;
3850 GskShadowNode *self2 = (GskShadowNode *) node2;
3851 int top = 0, right = 0, bottom = 0, left = 0;
3852 cairo_region_t *sub;
3853 cairo_rectangle_int_t rect;
3854 gsize i, n;
3855
3856 if (self1->n_shadows != self2->n_shadows)
3857 {
3858 gsk_render_node_diff_impossible (node1, node2, region);
3859 return;
3860 }
3861
3862 for (i = 0; i < self1->n_shadows; i++)
3863 {
3864 GskShadow *shadow1 = &self1->shadows[i];
3865 GskShadow *shadow2 = &self2->shadows[i];
3866 float clip_radius;
3867
3868 if (!gdk_rgba_equal (p1: &shadow1->color, p2: &shadow2->color) ||
3869 shadow1->dx != shadow2->dx ||
3870 shadow1->dy != shadow2->dy ||
3871 shadow1->radius != shadow2->radius)
3872 {
3873 gsk_render_node_diff_impossible (node1, node2, region);
3874 return;
3875 }
3876
3877 clip_radius = gsk_cairo_blur_compute_pixels (radius: shadow1->radius / 2.0);
3878 top = MAX (top, ceil (clip_radius - shadow1->dy));
3879 right = MAX (right, ceil (clip_radius + shadow1->dx));
3880 bottom = MAX (bottom, ceil (clip_radius + shadow1->dy));
3881 left = MAX (left, ceil (clip_radius - shadow1->dx));
3882 }
3883
3884 sub = cairo_region_create ();
3885 gsk_render_node_diff (node1: self1->child, node2: self2->child, region: sub);
3886
3887 n = cairo_region_num_rectangles (region: sub);
3888 for (i = 0; i < n; i++)
3889 {
3890 cairo_region_get_rectangle (region: sub, nth: i, rectangle: &rect);
3891 rect.x -= left;
3892 rect.y -= top;
3893 rect.width += left + right;
3894 rect.height += top + bottom;
3895 cairo_region_union_rectangle (dst: region, rectangle: &rect);
3896 }
3897 cairo_region_destroy (region: sub);
3898}
3899
3900static void
3901gsk_shadow_node_get_bounds (GskShadowNode *self,
3902 graphene_rect_t *bounds)
3903{
3904 float top = 0, right = 0, bottom = 0, left = 0;
3905 gsize i;
3906
3907 graphene_rect_init_from_rect (r: bounds, src: &self->child->bounds);
3908
3909 for (i = 0; i < self->n_shadows; i++)
3910 {
3911 float clip_radius = gsk_cairo_blur_compute_pixels (radius: self->shadows[i].radius / 2.0);
3912 top = MAX (top, clip_radius - self->shadows[i].dy);
3913 right = MAX (right, clip_radius + self->shadows[i].dx);
3914 bottom = MAX (bottom, clip_radius + self->shadows[i].dy);
3915 left = MAX (left, clip_radius - self->shadows[i].dx);
3916 }
3917
3918 bounds->origin.x -= left;
3919 bounds->origin.y -= top;
3920 bounds->size.width += left + right;
3921 bounds->size.height += top + bottom;
3922}
3923
3924/**
3925 * gsk_shadow_node_new:
3926 * @child: The node to draw
3927 * @shadows: (array length=n_shadows): The shadows to apply
3928 * @n_shadows: number of entries in the @shadows array
3929 *
3930 * Creates a `GskRenderNode` that will draw a @child with the given
3931 * @shadows below it.
3932 *
3933 * Returns: (transfer full) (type GskShadowNode): A new `GskRenderNode`
3934 */
3935GskRenderNode *
3936gsk_shadow_node_new (GskRenderNode *child,
3937 const GskShadow *shadows,
3938 gsize n_shadows)
3939{
3940 GskShadowNode *self;
3941 GskRenderNode *node;
3942
3943 g_return_val_if_fail (GSK_IS_RENDER_NODE (child), NULL);
3944 g_return_val_if_fail (shadows != NULL, NULL);
3945 g_return_val_if_fail (n_shadows > 0, NULL);
3946
3947 self = gsk_render_node_alloc (node_type: GSK_SHADOW_NODE);
3948 node = (GskRenderNode *) self;
3949
3950 self->child = gsk_render_node_ref (node: child);
3951 self->n_shadows = n_shadows;
3952 self->shadows = g_malloc_n (n_blocks: n_shadows, n_block_bytes: sizeof (GskShadow));
3953 memcpy (dest: self->shadows, src: shadows, n: n_shadows * sizeof (GskShadow));
3954
3955 gsk_shadow_node_get_bounds (self, bounds: &node->bounds);
3956
3957 node->prefers_high_depth = gsk_render_node_prefers_high_depth (node: child);
3958
3959 return node;
3960}
3961
3962/**
3963 * gsk_shadow_node_get_child:
3964 * @node: (type GskShadowNode): a shadow `GskRenderNode`
3965 *
3966 * Retrieves the child `GskRenderNode` of the shadow @node.
3967 *
3968 * Returns: (transfer none): the child render node
3969 */
3970GskRenderNode *
3971gsk_shadow_node_get_child (const GskRenderNode *node)
3972{
3973 const GskShadowNode *self = (const GskShadowNode *) node;
3974
3975 return self->child;
3976}
3977
3978/**
3979 * gsk_shadow_node_get_shadow:
3980 * @node: (type GskShadowNode): a shadow `GskRenderNode`
3981 * @i: the given index
3982 *
3983 * Retrieves the shadow data at the given index @i.
3984 *
3985 * Returns: (transfer none): the shadow data
3986 */
3987const GskShadow *
3988gsk_shadow_node_get_shadow (const GskRenderNode *node,
3989 gsize i)
3990{
3991 const GskShadowNode *self = (const GskShadowNode *) node;
3992
3993 return &self->shadows[i];
3994}
3995
3996/**
3997 * gsk_shadow_node_get_n_shadows:
3998 * @node: (type GskShadowNode): a shadow `GskRenderNode`
3999 *
4000 * Retrieves the number of shadows in the @node.
4001 *
4002 * Returns: the number of shadows.
4003 */
4004gsize
4005gsk_shadow_node_get_n_shadows (const GskRenderNode *node)
4006{
4007 const GskShadowNode *self = (const GskShadowNode *) node;
4008
4009 return self->n_shadows;
4010}
4011
4012/*** GSK_BLEND_NODE ***/
4013
4014/**
4015 * GskBlendNode:
4016 *
4017 * A render node applying a blending function between its two child nodes.
4018 */
4019struct _GskBlendNode
4020{
4021 GskRenderNode render_node;
4022
4023 GskRenderNode *bottom;
4024 GskRenderNode *top;
4025 GskBlendMode blend_mode;
4026};
4027
4028static cairo_operator_t
4029gsk_blend_mode_to_cairo_operator (GskBlendMode blend_mode)
4030{
4031 switch (blend_mode)
4032 {
4033 default:
4034 g_assert_not_reached ();
4035 case GSK_BLEND_MODE_DEFAULT:
4036 return CAIRO_OPERATOR_OVER;
4037 case GSK_BLEND_MODE_MULTIPLY:
4038 return CAIRO_OPERATOR_MULTIPLY;
4039 case GSK_BLEND_MODE_SCREEN:
4040 return CAIRO_OPERATOR_SCREEN;
4041 case GSK_BLEND_MODE_OVERLAY:
4042 return CAIRO_OPERATOR_OVERLAY;
4043 case GSK_BLEND_MODE_DARKEN:
4044 return CAIRO_OPERATOR_DARKEN;
4045 case GSK_BLEND_MODE_LIGHTEN:
4046 return CAIRO_OPERATOR_LIGHTEN;
4047 case GSK_BLEND_MODE_COLOR_DODGE:
4048 return CAIRO_OPERATOR_COLOR_DODGE;
4049 case GSK_BLEND_MODE_COLOR_BURN:
4050 return CAIRO_OPERATOR_COLOR_BURN;
4051 case GSK_BLEND_MODE_HARD_LIGHT:
4052 return CAIRO_OPERATOR_HARD_LIGHT;
4053 case GSK_BLEND_MODE_SOFT_LIGHT:
4054 return CAIRO_OPERATOR_SOFT_LIGHT;
4055 case GSK_BLEND_MODE_DIFFERENCE:
4056 return CAIRO_OPERATOR_DIFFERENCE;
4057 case GSK_BLEND_MODE_EXCLUSION:
4058 return CAIRO_OPERATOR_EXCLUSION;
4059 case GSK_BLEND_MODE_COLOR:
4060 return CAIRO_OPERATOR_HSL_COLOR;
4061 case GSK_BLEND_MODE_HUE:
4062 return CAIRO_OPERATOR_HSL_HUE;
4063 case GSK_BLEND_MODE_SATURATION:
4064 return CAIRO_OPERATOR_HSL_SATURATION;
4065 case GSK_BLEND_MODE_LUMINOSITY:
4066 return CAIRO_OPERATOR_HSL_LUMINOSITY;
4067 }
4068}
4069
4070static void
4071gsk_blend_node_finalize (GskRenderNode *node)
4072{
4073 GskBlendNode *self = (GskBlendNode *) node;
4074 GskRenderNodeClass *parent_class = g_type_class_peek (type: g_type_parent (GSK_TYPE_BLEND_NODE));
4075
4076 gsk_render_node_unref (node: self->bottom);
4077 gsk_render_node_unref (node: self->top);
4078
4079 parent_class->finalize (node);
4080}
4081
4082static void
4083gsk_blend_node_draw (GskRenderNode *node,
4084 cairo_t *cr)
4085{
4086 GskBlendNode *self = (GskBlendNode *) node;
4087
4088 cairo_push_group (cr);
4089 gsk_render_node_draw (node: self->bottom, cr);
4090
4091 cairo_push_group (cr);
4092 gsk_render_node_draw (node: self->top, cr);
4093
4094 cairo_pop_group_to_source (cr);
4095 cairo_set_operator (cr, op: gsk_blend_mode_to_cairo_operator (blend_mode: self->blend_mode));
4096 cairo_paint (cr);
4097
4098 cairo_pop_group_to_source (cr); /* resets operator */
4099 cairo_paint (cr);
4100}
4101
4102static void
4103gsk_blend_node_diff (GskRenderNode *node1,
4104 GskRenderNode *node2,
4105 cairo_region_t *region)
4106{
4107 GskBlendNode *self1 = (GskBlendNode *) node1;
4108 GskBlendNode *self2 = (GskBlendNode *) node2;
4109
4110 if (self1->blend_mode == self2->blend_mode)
4111 {
4112 gsk_render_node_diff (node1: self1->top, node2: self2->top, region);
4113 gsk_render_node_diff (node1: self1->bottom, node2: self2->bottom, region);
4114 }
4115 else
4116 {
4117 gsk_render_node_diff_impossible (node1, node2, region);
4118 }
4119}
4120
4121/**
4122 * gsk_blend_node_new:
4123 * @bottom: The bottom node to be drawn
4124 * @top: The node to be blended onto the @bottom node
4125 * @blend_mode: The blend mode to use
4126 *
4127 * Creates a `GskRenderNode` that will use @blend_mode to blend the @top
4128 * node onto the @bottom node.
4129 *
4130 * Returns: (transfer full) (type GskBlendNode): A new `GskRenderNode`
4131 */
4132GskRenderNode *
4133gsk_blend_node_new (GskRenderNode *bottom,
4134 GskRenderNode *top,
4135 GskBlendMode blend_mode)
4136{
4137 GskBlendNode *self;
4138 GskRenderNode *node;
4139
4140 g_return_val_if_fail (GSK_IS_RENDER_NODE (bottom), NULL);
4141 g_return_val_if_fail (GSK_IS_RENDER_NODE (top), NULL);
4142
4143 self = gsk_render_node_alloc (node_type: GSK_BLEND_NODE);
4144 node = (GskRenderNode *) self;
4145
4146 self->bottom = gsk_render_node_ref (node: bottom);
4147 self->top = gsk_render_node_ref (node: top);
4148 self->blend_mode = blend_mode;
4149
4150 graphene_rect_union (a: &bottom->bounds, b: &top->bounds, res: &node->bounds);
4151
4152 node->prefers_high_depth = gsk_render_node_prefers_high_depth (node: bottom) || gsk_render_node_prefers_high_depth (node: top);
4153
4154 return node;
4155}
4156
4157/**
4158 * gsk_blend_node_get_bottom_child:
4159 * @node: (type GskBlendNode): a blending `GskRenderNode`
4160 *
4161 * Retrieves the bottom `GskRenderNode` child of the @node.
4162 *
4163 * Returns: (transfer none): the bottom child node
4164 */
4165GskRenderNode *
4166gsk_blend_node_get_bottom_child (const GskRenderNode *node)
4167{
4168 const GskBlendNode *self = (const GskBlendNode *) node;
4169
4170 return self->bottom;
4171}
4172
4173/**
4174 * gsk_blend_node_get_top_child:
4175 * @node: (type GskBlendNode): a blending `GskRenderNode`
4176 *
4177 * Retrieves the top `GskRenderNode` child of the @node.
4178 *
4179 * Returns: (transfer none): the top child node
4180 */
4181GskRenderNode *
4182gsk_blend_node_get_top_child (const GskRenderNode *node)
4183{
4184 const GskBlendNode *self = (const GskBlendNode *) node;
4185
4186 return self->top;
4187}
4188
4189/**
4190 * gsk_blend_node_get_blend_mode:
4191 * @node: (type GskBlendNode): a blending `GskRenderNode`
4192 *
4193 * Retrieves the blend mode used by @node.
4194 *
4195 * Returns: the blend mode
4196 */
4197GskBlendMode
4198gsk_blend_node_get_blend_mode (const GskRenderNode *node)
4199{
4200 const GskBlendNode *self = (const GskBlendNode *) node;
4201
4202 return self->blend_mode;
4203}
4204
4205/*** GSK_CROSS_FADE_NODE ***/
4206
4207/**
4208 * GskCrossFadeNode:
4209 *
4210 * A render node cross fading between two child nodes.
4211 */
4212struct _GskCrossFadeNode
4213{
4214 GskRenderNode render_node;
4215
4216 GskRenderNode *start;
4217 GskRenderNode *end;
4218 float progress;
4219};
4220
4221static void
4222gsk_cross_fade_node_finalize (GskRenderNode *node)
4223{
4224 GskCrossFadeNode *self = (GskCrossFadeNode *) node;
4225 GskRenderNodeClass *parent_class = g_type_class_peek (type: g_type_parent (GSK_TYPE_CROSS_FADE_NODE));
4226
4227 gsk_render_node_unref (node: self->start);
4228 gsk_render_node_unref (node: self->end);
4229
4230 parent_class->finalize (node);
4231}
4232
4233static void
4234gsk_cross_fade_node_draw (GskRenderNode *node,
4235 cairo_t *cr)
4236{
4237 GskCrossFadeNode *self = (GskCrossFadeNode *) node;
4238
4239 cairo_push_group_with_content (cr, content: CAIRO_CONTENT_COLOR_ALPHA);
4240 gsk_render_node_draw (node: self->start, cr);
4241
4242 cairo_push_group_with_content (cr, content: CAIRO_CONTENT_COLOR_ALPHA);
4243 gsk_render_node_draw (node: self->end, cr);
4244
4245 cairo_pop_group_to_source (cr);
4246 cairo_set_operator (cr, op: CAIRO_OPERATOR_SOURCE);
4247 cairo_paint_with_alpha (cr, alpha: self->progress);
4248
4249 cairo_pop_group_to_source (cr); /* resets operator */
4250 cairo_paint (cr);
4251}
4252
4253static void
4254gsk_cross_fade_node_diff (GskRenderNode *node1,
4255 GskRenderNode *node2,
4256 cairo_region_t *region)
4257{
4258 GskCrossFadeNode *self1 = (GskCrossFadeNode *) node1;
4259 GskCrossFadeNode *self2 = (GskCrossFadeNode *) node2;
4260
4261 if (self1->progress == self2->progress)
4262 {
4263 gsk_render_node_diff (node1: self1->start, node2: self2->start, region);
4264 gsk_render_node_diff (node1: self1->end, node2: self2->end, region);
4265 return;
4266 }
4267
4268 gsk_render_node_diff_impossible (node1, node2, region);
4269}
4270
4271/**
4272 * gsk_cross_fade_node_new:
4273 * @start: The start node to be drawn
4274 * @end: The node to be cross_fadeed onto the @start node
4275 * @progress: How far the fade has progressed from start to end. The value will
4276 * be clamped to the range [0 ... 1]
4277 *
4278 * Creates a `GskRenderNode` that will do a cross-fade between @start and @end.
4279 *
4280 * Returns: (transfer full) (type GskCrossFadeNode): A new `GskRenderNode`
4281 */
4282GskRenderNode *
4283gsk_cross_fade_node_new (GskRenderNode *start,
4284 GskRenderNode *end,
4285 float progress)
4286{
4287 GskCrossFadeNode *self;
4288 GskRenderNode *node;
4289
4290 g_return_val_if_fail (GSK_IS_RENDER_NODE (start), NULL);
4291 g_return_val_if_fail (GSK_IS_RENDER_NODE (end), NULL);
4292
4293 self = gsk_render_node_alloc (node_type: GSK_CROSS_FADE_NODE);
4294 node = (GskRenderNode *) self;
4295
4296 self->start = gsk_render_node_ref (node: start);
4297 self->end = gsk_render_node_ref (node: end);
4298 self->progress = CLAMP (progress, 0.0, 1.0);
4299
4300 graphene_rect_union (a: &start->bounds, b: &end->bounds, res: &node->bounds);
4301
4302 node->prefers_high_depth = gsk_render_node_prefers_high_depth (node: start) || gsk_render_node_prefers_high_depth (node: end);
4303
4304 return node;
4305}
4306
4307/**
4308 * gsk_cross_fade_node_get_start_child:
4309 * @node: (type GskCrossFadeNode): a cross-fading `GskRenderNode`
4310 *
4311 * Retrieves the child `GskRenderNode` at the beginning of the cross-fade.
4312 *
4313 * Returns: (transfer none): a `GskRenderNode`
4314 */
4315GskRenderNode *
4316gsk_cross_fade_node_get_start_child (const GskRenderNode *node)
4317{
4318 const GskCrossFadeNode *self = (const GskCrossFadeNode *) node;
4319
4320 return self->start;
4321}
4322
4323/**
4324 * gsk_cross_fade_node_get_end_child:
4325 * @node: (type GskCrossFadeNode): a cross-fading `GskRenderNode`
4326 *
4327 * Retrieves the child `GskRenderNode` at the end of the cross-fade.
4328 *
4329 * Returns: (transfer none): a `GskRenderNode`
4330 */
4331GskRenderNode *
4332gsk_cross_fade_node_get_end_child (const GskRenderNode *node)
4333{
4334 const GskCrossFadeNode *self = (const GskCrossFadeNode *) node;
4335
4336 return self->end;
4337}
4338
4339/**
4340 * gsk_cross_fade_node_get_progress:
4341 * @node: (type GskCrossFadeNode): a cross-fading `GskRenderNode`
4342 *
4343 * Retrieves the progress value of the cross fade.
4344 *
4345 * Returns: the progress value, between 0 and 1
4346 */
4347float
4348gsk_cross_fade_node_get_progress (const GskRenderNode *node)
4349{
4350 const GskCrossFadeNode *self = (const GskCrossFadeNode *) node;
4351
4352 return self->progress;
4353}
4354
4355/*** GSK_TEXT_NODE ***/
4356
4357/**
4358 * GskTextNode:
4359 *
4360 * A render node drawing a set of glyphs.
4361 */
4362struct _GskTextNode
4363{
4364 GskRenderNode render_node;
4365
4366 PangoFont *font;
4367 gboolean has_color_glyphs;
4368
4369 GdkRGBA color;
4370 graphene_point_t offset;
4371
4372 guint num_glyphs;
4373 PangoGlyphInfo *glyphs;
4374};
4375
4376static void
4377gsk_text_node_finalize (GskRenderNode *node)
4378{
4379 GskTextNode *self = (GskTextNode *) node;
4380 GskRenderNodeClass *parent_class = g_type_class_peek (type: g_type_parent (GSK_TYPE_TEXT_NODE));
4381
4382 g_object_unref (object: self->font);
4383 g_free (mem: self->glyphs);
4384
4385 parent_class->finalize (node);
4386}
4387
4388static void
4389gsk_text_node_draw (GskRenderNode *node,
4390 cairo_t *cr)
4391{
4392 GskTextNode *self = (GskTextNode *) node;
4393 PangoGlyphString glyphs;
4394
4395 glyphs.num_glyphs = self->num_glyphs;
4396 glyphs.glyphs = self->glyphs;
4397 glyphs.log_clusters = NULL;
4398
4399 cairo_save (cr);
4400
4401 gdk_cairo_set_source_rgba (cr, rgba: &self->color);
4402 cairo_translate (cr, tx: self->offset.x, ty: self->offset.y);
4403 pango_cairo_show_glyph_string (cr, font: self->font, glyphs: &glyphs);
4404
4405 cairo_restore (cr);
4406}
4407
4408static void
4409gsk_text_node_diff (GskRenderNode *node1,
4410 GskRenderNode *node2,
4411 cairo_region_t *region)
4412{
4413 GskTextNode *self1 = (GskTextNode *) node1;
4414 GskTextNode *self2 = (GskTextNode *) node2;
4415
4416 if (self1->font == self2->font &&
4417 gdk_rgba_equal (p1: &self1->color, p2: &self2->color) &&
4418 graphene_point_equal (a: &self1->offset, b: &self2->offset) &&
4419 self1->num_glyphs == self2->num_glyphs)
4420 {
4421 guint i;
4422
4423 for (i = 0; i < self1->num_glyphs; i++)
4424 {
4425 PangoGlyphInfo *info1 = &self1->glyphs[i];
4426 PangoGlyphInfo *info2 = &self2->glyphs[i];
4427
4428 if (info1->glyph == info2->glyph &&
4429 info1->geometry.width == info2->geometry.width &&
4430 info1->geometry.x_offset == info2->geometry.x_offset &&
4431 info1->geometry.y_offset == info2->geometry.y_offset &&
4432 info1->attr.is_cluster_start == info2->attr.is_cluster_start &&
4433 info1->attr.is_color == info2->attr.is_color)
4434 continue;
4435
4436 gsk_render_node_diff_impossible (node1, node2, region);
4437 return;
4438 }
4439
4440 return;
4441 }
4442
4443 gsk_render_node_diff_impossible (node1, node2, region);
4444}
4445
4446/**
4447 * gsk_text_node_new:
4448 * @font: the `PangoFont` containing the glyphs
4449 * @glyphs: the `PangoGlyphString` to render
4450 * @color: the foreground color to render with
4451 * @offset: offset of the baseline
4452 *
4453 * Creates a render node that renders the given glyphs.
4454 *
4455 * Note that @color may not be used if the font contains
4456 * color glyphs.
4457 *
4458 * Returns: (nullable) (transfer full) (type GskTextNode): a new `GskRenderNode`
4459 */
4460GskRenderNode *
4461gsk_text_node_new (PangoFont *font,
4462 PangoGlyphString *glyphs,
4463 const GdkRGBA *color,
4464 const graphene_point_t *offset)
4465{
4466 GskTextNode *self;
4467 GskRenderNode *node;
4468 PangoRectangle ink_rect;
4469 PangoGlyphInfo *glyph_infos;
4470 int n;
4471
4472 pango_glyph_string_extents (glyphs, font, ink_rect: &ink_rect, NULL);
4473 pango_extents_to_pixels (inclusive: &ink_rect, NULL);
4474
4475 /* Don't create nodes with empty bounds */
4476 if (ink_rect.width == 0 || ink_rect.height == 0)
4477 return NULL;
4478
4479 self = gsk_render_node_alloc (node_type: GSK_TEXT_NODE);
4480 node = (GskRenderNode *) self;
4481
4482 self->font = g_object_ref (font);
4483 self->color = *color;
4484 self->offset = *offset;
4485 self->has_color_glyphs = FALSE;
4486
4487 glyph_infos = g_malloc_n (n_blocks: glyphs->num_glyphs, n_block_bytes: sizeof (PangoGlyphInfo));
4488
4489 n = 0;
4490 for (int i = 0; i < glyphs->num_glyphs; i++)
4491 {
4492 /* skip empty glyphs */
4493 if (glyphs->glyphs[i].glyph == PANGO_GLYPH_EMPTY)
4494 continue;
4495
4496 glyph_infos[n] = glyphs->glyphs[i];
4497
4498 if (glyphs->glyphs[i].attr.is_color)
4499 self->has_color_glyphs = TRUE;
4500
4501 n++;
4502 }
4503
4504 self->glyphs = glyph_infos;
4505 self->num_glyphs = n;
4506
4507 graphene_rect_init (r: &node->bounds,
4508 x: offset->x + ink_rect.x - 1,
4509 y: offset->y + ink_rect.y - 1,
4510 width: ink_rect.width + 2,
4511 height: ink_rect.height + 2);
4512
4513 return node;
4514}
4515
4516/**
4517 * gsk_text_node_get_color:
4518 * @node: (type GskTextNode): a text `GskRenderNode`
4519 *
4520 * Retrieves the color used by the text @node.
4521 *
4522 * Returns: (transfer none): the text color
4523 */
4524const GdkRGBA *
4525gsk_text_node_get_color (const GskRenderNode *node)
4526{
4527 const GskTextNode *self = (const GskTextNode *) node;
4528
4529 return &self->color;
4530}
4531
4532/**
4533 * gsk_text_node_get_font:
4534 * @node: (type GskTextNode): The `GskRenderNode`
4535 *
4536 * Returns the font used by the text @node.
4537 *
4538 * Returns: (transfer none): the font
4539 */
4540PangoFont *
4541gsk_text_node_get_font (const GskRenderNode *node)
4542{
4543 const GskTextNode *self = (const GskTextNode *) node;
4544
4545 return self->font;
4546}
4547
4548/**
4549 * gsk_text_node_has_color_glyphs:
4550 * @node: (type GskTextNode): a text `GskRenderNode`
4551 *
4552 * Checks whether the text @node has color glyphs.
4553 *
4554 * Returns: %TRUE if the text node has color glyphs
4555 *
4556 * Since: 4.2
4557 */
4558gboolean
4559gsk_text_node_has_color_glyphs (const GskRenderNode *node)
4560{
4561 const GskTextNode *self = (const GskTextNode *) node;
4562
4563 return self->has_color_glyphs;
4564}
4565
4566/**
4567 * gsk_text_node_get_num_glyphs:
4568 * @node: (type GskTextNode): a text `GskRenderNode`
4569 *
4570 * Retrieves the number of glyphs in the text node.
4571 *
4572 * Returns: the number of glyphs
4573 */
4574guint
4575gsk_text_node_get_num_glyphs (const GskRenderNode *node)
4576{
4577 const GskTextNode *self = (const GskTextNode *) node;
4578
4579 return self->num_glyphs;
4580}
4581
4582/**
4583 * gsk_text_node_get_glyphs:
4584 * @node: (type GskTextNode): a text `GskRenderNode`
4585 * @n_glyphs: (out) (optional): the number of glyphs returned
4586 *
4587 * Retrieves the glyph information in the @node.
4588 *
4589 * Returns: (transfer none) (array length=n_glyphs): the glyph information
4590 */
4591const PangoGlyphInfo *
4592gsk_text_node_get_glyphs (const GskRenderNode *node,
4593 guint *n_glyphs)
4594{
4595 const GskTextNode *self = (const GskTextNode *) node;
4596
4597 if (n_glyphs != NULL)
4598 *n_glyphs = self->num_glyphs;
4599
4600 return self->glyphs;
4601}
4602
4603/**
4604 * gsk_text_node_get_offset:
4605 * @node: (type GskTextNode): a text `GskRenderNode`
4606 *
4607 * Retrieves the offset applied to the text.
4608 *
4609 * Returns: (transfer none): a point with the horizontal and vertical offsets
4610 */
4611const graphene_point_t *
4612gsk_text_node_get_offset (const GskRenderNode *node)
4613{
4614 const GskTextNode *self = (const GskTextNode *) node;
4615
4616 return &self->offset;
4617}
4618
4619/*** GSK_BLUR_NODE ***/
4620
4621/**
4622 * GskBlurNode:
4623 *
4624 * A render node applying a blur effect to its single child.
4625 */
4626struct _GskBlurNode
4627{
4628 GskRenderNode render_node;
4629
4630 GskRenderNode *child;
4631 float radius;
4632};
4633
4634static void
4635gsk_blur_node_finalize (GskRenderNode *node)
4636{
4637 GskBlurNode *self = (GskBlurNode *) node;
4638 GskRenderNodeClass *parent_class = g_type_class_peek (type: g_type_parent (GSK_TYPE_BLUR_NODE));
4639
4640 gsk_render_node_unref (node: self->child);
4641
4642 parent_class->finalize (node);
4643}
4644
4645static void
4646blur_once (cairo_surface_t *src,
4647 cairo_surface_t *dest,
4648 int radius,
4649 guchar *div_kernel_size)
4650{
4651 int width, height, src_rowstride, dest_rowstride, n_channels;
4652 guchar *p_src, *p_dest, *c1, *c2;
4653 int x, y, i, i1, i2, width_minus_1, height_minus_1, radius_plus_1;
4654 int r, g, b, a;
4655 guchar *p_dest_row, *p_dest_col;
4656
4657 width = cairo_image_surface_get_width (surface: src);
4658 height = cairo_image_surface_get_height (surface: src);
4659 n_channels = 4;
4660 radius_plus_1 = radius + 1;
4661
4662 /* horizontal blur */
4663 p_src = cairo_image_surface_get_data (surface: src);
4664 p_dest = cairo_image_surface_get_data (surface: dest);
4665 src_rowstride = cairo_image_surface_get_stride (surface: src);
4666 dest_rowstride = cairo_image_surface_get_stride (surface: dest);
4667
4668 width_minus_1 = width - 1;
4669 for (y = 0; y < height; y++)
4670 {
4671 /* calc the initial sums of the kernel */
4672 r = g = b = a = 0;
4673 for (i = -radius; i <= radius; i++)
4674 {
4675 c1 = p_src + (CLAMP (i, 0, width_minus_1) * n_channels);
4676 r += c1[0];
4677 g += c1[1];
4678 b += c1[2];
4679 a += c1[3];
4680 }
4681 p_dest_row = p_dest;
4682 for (x = 0; x < width; x++)
4683 {
4684 /* set as the mean of the kernel */
4685 p_dest_row[0] = div_kernel_size[r];
4686 p_dest_row[1] = div_kernel_size[g];
4687 p_dest_row[2] = div_kernel_size[b];
4688 p_dest_row[3] = div_kernel_size[a];
4689 p_dest_row += n_channels;
4690
4691 /* the pixel to add to the kernel */
4692 i1 = x + radius_plus_1;
4693 if (i1 > width_minus_1)
4694 i1 = width_minus_1;
4695 c1 = p_src + (i1 * n_channels);
4696
4697 /* the pixel to remove from the kernel */
4698 i2 = x - radius;
4699 if (i2 < 0)
4700 i2 = 0;
4701 c2 = p_src + (i2 * n_channels);
4702
4703 /* calc the new sums of the kernel */
4704 r += c1[0] - c2[0];
4705 g += c1[1] - c2[1];
4706 b += c1[2] - c2[2];
4707 a += c1[3] - c2[3];
4708 }
4709
4710 p_src += src_rowstride;
4711 p_dest += dest_rowstride;
4712 }
4713
4714 /* vertical blur */
4715 p_src = cairo_image_surface_get_data (surface: dest);
4716 p_dest = cairo_image_surface_get_data (surface: src);
4717 src_rowstride = cairo_image_surface_get_stride (surface: dest);
4718 dest_rowstride = cairo_image_surface_get_stride (surface: src);
4719
4720 height_minus_1 = height - 1;
4721 for (x = 0; x < width; x++)
4722 {
4723 /* calc the initial sums of the kernel */
4724 r = g = b = a = 0;
4725 for (i = -radius; i <= radius; i++)
4726 {
4727 c1 = p_src + (CLAMP (i, 0, height_minus_1) * src_rowstride);
4728 r += c1[0];
4729 g += c1[1];
4730 b += c1[2];
4731 a += c1[3];
4732 }
4733
4734 p_dest_col = p_dest;
4735 for (y = 0; y < height; y++)
4736 {
4737 /* set as the mean of the kernel */
4738
4739 p_dest_col[0] = div_kernel_size[r];
4740 p_dest_col[1] = div_kernel_size[g];
4741 p_dest_col[2] = div_kernel_size[b];
4742 p_dest_col[3] = div_kernel_size[a];
4743 p_dest_col += dest_rowstride;
4744
4745 /* the pixel to add to the kernel */
4746 i1 = y + radius_plus_1;
4747 if (i1 > height_minus_1)
4748 i1 = height_minus_1;
4749 c1 = p_src + (i1 * src_rowstride);
4750
4751 /* the pixel to remove from the kernel */
4752 i2 = y - radius;
4753 if (i2 < 0)
4754 i2 = 0;
4755 c2 = p_src + (i2 * src_rowstride);
4756 /* calc the new sums of the kernel */
4757 r += c1[0] - c2[0];
4758 g += c1[1] - c2[1];
4759 b += c1[2] - c2[2];
4760 a += c1[3] - c2[3];
4761 }
4762
4763 p_src += n_channels;
4764 p_dest += n_channels;
4765 }
4766}
4767
4768static void
4769blur_image_surface (cairo_surface_t *surface, int radius, int iterations)
4770{
4771 int kernel_size;
4772 int i;
4773 guchar *div_kernel_size;
4774 cairo_surface_t *tmp;
4775 int width, height;
4776
4777 width = cairo_image_surface_get_width (surface);
4778 height = cairo_image_surface_get_height (surface);
4779 tmp = cairo_image_surface_create (format: CAIRO_FORMAT_ARGB32, width, height);
4780
4781 kernel_size = 2 * radius + 1;
4782 div_kernel_size = g_new (guchar, 256 * kernel_size);
4783 for (i = 0; i < 256 * kernel_size; i++)
4784 div_kernel_size[i] = (guchar) (i / kernel_size);
4785
4786 while (iterations-- > 0)
4787 blur_once (src: surface, dest: tmp, radius, div_kernel_size);
4788
4789 g_free (mem: div_kernel_size);
4790 cairo_surface_destroy (surface: tmp);
4791}
4792
4793static void
4794gsk_blur_node_draw (GskRenderNode *node,
4795 cairo_t *cr)
4796{
4797 GskBlurNode *self = (GskBlurNode *) node;
4798 cairo_pattern_t *pattern;
4799 cairo_surface_t *surface;
4800 cairo_surface_t *image_surface;
4801
4802 cairo_save (cr);
4803
4804 /* clip so the push_group() creates a smaller surface */
4805 gsk_cairo_rectangle (cr, rect: &node->bounds);
4806 cairo_clip (cr);
4807
4808 cairo_push_group (cr);
4809
4810 gsk_render_node_draw (node: self->child, cr);
4811
4812 pattern = cairo_pop_group (cr);
4813 cairo_pattern_get_surface (pattern, surface: &surface);
4814 image_surface = cairo_surface_map_to_image (surface, NULL);
4815 blur_image_surface (surface: image_surface, radius: (int)self->radius, iterations: 3);
4816 cairo_surface_mark_dirty (surface);
4817 cairo_surface_unmap_image (surface, image: image_surface);
4818
4819 cairo_set_source (cr, source: pattern);
4820 cairo_rectangle (cr,
4821 x: node->bounds.origin.x, y: node->bounds.origin.y,
4822 width: node->bounds.size.width, height: node->bounds.size.height);
4823 cairo_fill (cr);
4824
4825 cairo_restore (cr);
4826 cairo_pattern_destroy (pattern);
4827}
4828
4829static void
4830gsk_blur_node_diff (GskRenderNode *node1,
4831 GskRenderNode *node2,
4832 cairo_region_t *region)
4833{
4834 GskBlurNode *self1 = (GskBlurNode *) node1;
4835 GskBlurNode *self2 = (GskBlurNode *) node2;
4836
4837 if (self1->radius == self2->radius)
4838 {
4839 cairo_rectangle_int_t rect;
4840 cairo_region_t *sub;
4841 int i, n, clip_radius;
4842
4843 clip_radius = ceil (x: gsk_cairo_blur_compute_pixels (radius: self1->radius / 2.0));
4844 sub = cairo_region_create ();
4845 gsk_render_node_diff (node1: self1->child, node2: self2->child, region: sub);
4846
4847 n = cairo_region_num_rectangles (region: sub);
4848 for (i = 0; i < n; i++)
4849 {
4850 cairo_region_get_rectangle (region: sub, nth: i, rectangle: &rect);
4851 rect.x -= clip_radius;
4852 rect.y -= clip_radius;
4853 rect.width += 2 * clip_radius;
4854 rect.height += 2 * clip_radius;
4855 cairo_region_union_rectangle (dst: region, rectangle: &rect);
4856 }
4857 cairo_region_destroy (region: sub);
4858 }
4859 else
4860 {
4861 gsk_render_node_diff_impossible (node1, node2, region);
4862 }
4863}
4864
4865/**
4866 * gsk_blur_node_new:
4867 * @child: the child node to blur
4868 * @radius: the blur radius. Must be positive
4869 *
4870 * Creates a render node that blurs the child.
4871 *
4872 * Returns: (transfer full) (type GskBlurNode): a new `GskRenderNode`
4873 */
4874GskRenderNode *
4875gsk_blur_node_new (GskRenderNode *child,
4876 float radius)
4877{
4878 GskBlurNode *self;
4879 GskRenderNode *node;
4880 float clip_radius;
4881
4882 g_return_val_if_fail (GSK_IS_RENDER_NODE (child), NULL);
4883 g_return_val_if_fail (radius >= 0, NULL);
4884
4885 self = gsk_render_node_alloc (node_type: GSK_BLUR_NODE);
4886 node = (GskRenderNode *) self;
4887
4888 self->child = gsk_render_node_ref (node: child);
4889 self->radius = radius;
4890
4891 clip_radius = gsk_cairo_blur_compute_pixels (radius: radius / 2.0);
4892
4893 graphene_rect_init_from_rect (r: &node->bounds, src: &child->bounds);
4894 graphene_rect_inset (r: &self->render_node.bounds,
4895 d_x: - clip_radius,
4896 d_y: - clip_radius);
4897
4898 node->prefers_high_depth = gsk_render_node_prefers_high_depth (node: child);
4899
4900 return node;
4901}
4902
4903/**
4904 * gsk_blur_node_get_child:
4905 * @node: (type GskBlurNode): a blur `GskRenderNode`
4906 *
4907 * Retrieves the child `GskRenderNode` of the blur @node.
4908 *
4909 * Returns: (transfer none): the blurred child node
4910 */
4911GskRenderNode *
4912gsk_blur_node_get_child (const GskRenderNode *node)
4913{
4914 const GskBlurNode *self = (const GskBlurNode *) node;
4915
4916 return self->child;
4917}
4918
4919/**
4920 * gsk_blur_node_get_radius:
4921 * @node: (type GskBlurNode): a blur `GskRenderNode`
4922 *
4923 * Retrieves the blur radius of the @node.
4924 *
4925 * Returns: the blur radius
4926 */
4927float
4928gsk_blur_node_get_radius (const GskRenderNode *node)
4929{
4930 const GskBlurNode *self = (const GskBlurNode *) node;
4931
4932 return self->radius;
4933}
4934
4935/*** GSK_DEBUG_NODE ***/
4936
4937/**
4938 * GskDebugNode:
4939 *
4940 * A render node that emits a debugging message when drawing its
4941 * child node.
4942 */
4943struct _GskDebugNode
4944{
4945 GskRenderNode render_node;
4946
4947 GskRenderNode *child;
4948 char *message;
4949};
4950
4951static void
4952gsk_debug_node_finalize (GskRenderNode *node)
4953{
4954 GskDebugNode *self = (GskDebugNode *) node;
4955 GskRenderNodeClass *parent_class = g_type_class_peek (type: g_type_parent (GSK_TYPE_DEBUG_NODE));
4956
4957 gsk_render_node_unref (node: self->child);
4958 g_free (mem: self->message);
4959
4960 parent_class->finalize (node);
4961}
4962
4963static void
4964gsk_debug_node_draw (GskRenderNode *node,
4965 cairo_t *cr)
4966{
4967 GskDebugNode *self = (GskDebugNode *) node;
4968
4969 gsk_render_node_draw (node: self->child, cr);
4970}
4971
4972static gboolean
4973gsk_debug_node_can_diff (const GskRenderNode *node1,
4974 const GskRenderNode *node2)
4975{
4976 GskDebugNode *self1 = (GskDebugNode *) node1;
4977 GskDebugNode *self2 = (GskDebugNode *) node2;
4978
4979 return gsk_render_node_can_diff (node1: self1->child, node2: self2->child);
4980}
4981
4982static void
4983gsk_debug_node_diff (GskRenderNode *node1,
4984 GskRenderNode *node2,
4985 cairo_region_t *region)
4986{
4987 GskDebugNode *self1 = (GskDebugNode *) node1;
4988 GskDebugNode *self2 = (GskDebugNode *) node2;
4989
4990 gsk_render_node_diff (node1: self1->child, node2: self2->child, region);
4991}
4992
4993/**
4994 * gsk_debug_node_new:
4995 * @child: The child to add debug info for
4996 * @message: (transfer full): The debug message
4997 *
4998 * Creates a `GskRenderNode` that will add debug information about
4999 * the given @child.
5000 *
5001 * Adding this node has no visual effect.
5002 *
5003 * Returns: (transfer full) (type GskDebugNode): A new `GskRenderNode`
5004 */
5005GskRenderNode *
5006gsk_debug_node_new (GskRenderNode *child,
5007 char *message)
5008{
5009 GskDebugNode *self;
5010 GskRenderNode *node;
5011
5012 g_return_val_if_fail (GSK_IS_RENDER_NODE (child), NULL);
5013
5014 self = gsk_render_node_alloc (node_type: GSK_DEBUG_NODE);
5015 node = (GskRenderNode *) self;
5016
5017 self->child = gsk_render_node_ref (node: child);
5018 self->message = message;
5019
5020 graphene_rect_init_from_rect (r: &node->bounds, src: &child->bounds);
5021
5022 node->prefers_high_depth = gsk_render_node_prefers_high_depth (node: child);
5023
5024 return node;
5025}
5026
5027/**
5028 * gsk_debug_node_get_child:
5029 * @node: (type GskDebugNode): a debug `GskRenderNode`
5030 *
5031 * Gets the child node that is getting drawn by the given @node.
5032 *
5033 * Returns: (transfer none): the child `GskRenderNode`
5034 **/
5035GskRenderNode *
5036gsk_debug_node_get_child (const GskRenderNode *node)
5037{
5038 const GskDebugNode *self = (const GskDebugNode *) node;
5039
5040 return self->child;
5041}
5042
5043/**
5044 * gsk_debug_node_get_message:
5045 * @node: (type GskDebugNode): a debug `GskRenderNode`
5046 *
5047 * Gets the debug message that was set on this node
5048 *
5049 * Returns: (transfer none): The debug message
5050 **/
5051const char *
5052gsk_debug_node_get_message (const GskRenderNode *node)
5053{
5054 const GskDebugNode *self = (const GskDebugNode *) node;
5055
5056 return self->message;
5057}
5058
5059/*** GSK_GL_SHADER_NODE ***/
5060
5061/**
5062 * GskGLShaderNode:
5063 *
5064 * A render node using a GL shader when drawing its children nodes.
5065 */
5066struct _GskGLShaderNode
5067{
5068 GskRenderNode render_node;
5069
5070 GskGLShader *shader;
5071 GBytes *args;
5072 GskRenderNode **children;
5073 guint n_children;
5074};
5075
5076static void
5077gsk_gl_shader_node_finalize (GskRenderNode *node)
5078{
5079 GskGLShaderNode *self = (GskGLShaderNode *) node;
5080 GskRenderNodeClass *parent_class = g_type_class_peek (type: g_type_parent (GSK_TYPE_GL_SHADER_NODE));
5081
5082 for (guint i = 0; i < self->n_children; i++)
5083 gsk_render_node_unref (node: self->children[i]);
5084 g_free (mem: self->children);
5085
5086 g_bytes_unref (bytes: self->args);
5087
5088 g_object_unref (object: self->shader);
5089
5090 parent_class->finalize (node);
5091}
5092
5093static void
5094gsk_gl_shader_node_draw (GskRenderNode *node,
5095 cairo_t *cr)
5096{
5097 cairo_set_source_rgb (cr, red: 255 / 255., green: 105 / 255., blue: 180 / 255.);
5098 gsk_cairo_rectangle (cr, rect: &node->bounds);
5099 cairo_fill (cr);
5100}
5101
5102static void
5103gsk_gl_shader_node_diff (GskRenderNode *node1,
5104 GskRenderNode *node2,
5105 cairo_region_t *region)
5106{
5107 GskGLShaderNode *self1 = (GskGLShaderNode *) node1;
5108 GskGLShaderNode *self2 = (GskGLShaderNode *) node2;
5109
5110 if (graphene_rect_equal (a: &node1->bounds, b: &node2->bounds) &&
5111 self1->shader == self2->shader &&
5112 g_bytes_compare (bytes1: self1->args, bytes2: self2->args) == 0 &&
5113 self1->n_children == self2->n_children)
5114 {
5115 cairo_region_t *child_region = cairo_region_create();
5116 for (guint i = 0; i < self1->n_children; i++)
5117 gsk_render_node_diff (node1: self1->children[i], node2: self2->children[i], region: child_region);
5118 if (!cairo_region_is_empty (region: child_region))
5119 gsk_render_node_diff_impossible (node1, node2, region);
5120 cairo_region_destroy (region: child_region);
5121 }
5122 else
5123 {
5124 gsk_render_node_diff_impossible (node1, node2, region);
5125 }
5126}
5127
5128/**
5129 * gsk_gl_shader_node_new:
5130 * @shader: the `GskGLShader`
5131 * @bounds: the rectangle to render the shader into
5132 * @args: Arguments for the uniforms
5133 * @children: (nullable) (array length=n_children): array of child nodes,
5134 * these will be rendered to textures and used as input.
5135 * @n_children: Length of @children (currenly the GL backend supports
5136 * up to 4 children)
5137 *
5138 * Creates a `GskRenderNode` that will render the given @shader into the
5139 * area given by @bounds.
5140 *
5141 * The @args is a block of data to use for uniform input, as per types and
5142 * offsets defined by the @shader. Normally this is generated by
5143 * [method@Gsk.GLShader.format_args] or [struct@Gsk.ShaderArgsBuilder].
5144 *
5145 * See [class@Gsk.GLShader] for details about how the shader should be written.
5146 *
5147 * All the children will be rendered into textures (if they aren't already
5148 * `GskTextureNodes`, which will be used directly). These textures will be
5149 * sent as input to the shader.
5150 *
5151 * If the renderer doesn't support GL shaders, or if there is any problem
5152 * when compiling the shader, then the node will draw pink. You should use
5153 * [method@Gsk.GLShader.compile] to ensure the @shader will work for the
5154 * renderer before using it.
5155 *
5156 * Returns: (transfer full) (type GskGLShaderNode): A new `GskRenderNode`
5157 */
5158GskRenderNode *
5159gsk_gl_shader_node_new (GskGLShader *shader,
5160 const graphene_rect_t *bounds,
5161 GBytes *args,
5162 GskRenderNode **children,
5163 guint n_children)
5164{
5165 GskGLShaderNode *self;
5166 GskRenderNode *node;
5167
5168 g_return_val_if_fail (GSK_IS_GL_SHADER (shader), NULL);
5169 g_return_val_if_fail (bounds != NULL, NULL);
5170 g_return_val_if_fail (args != NULL, NULL);
5171 g_return_val_if_fail (g_bytes_get_size (args) == gsk_gl_shader_get_args_size (shader), NULL);
5172 g_return_val_if_fail ((children == NULL && n_children == 0) ||
5173 (n_children == gsk_gl_shader_get_n_textures (shader)), NULL);
5174
5175 self = gsk_render_node_alloc (node_type: GSK_GL_SHADER_NODE);
5176 node = (GskRenderNode *) self;
5177
5178 graphene_rect_init_from_rect (r: &node->bounds, src: bounds);
5179 self->shader = g_object_ref (shader);
5180
5181 self->args = g_bytes_ref (bytes: args);
5182
5183 self->n_children = n_children;
5184 if (n_children > 0)
5185 {
5186 self->children = g_malloc_n (n_blocks: n_children, n_block_bytes: sizeof (GskRenderNode *));
5187 for (guint i = 0; i < n_children; i++)
5188 {
5189 self->children[i] = gsk_render_node_ref (node: children[i]);
5190 node->prefers_high_depth |= gsk_render_node_prefers_high_depth (node: children[i]);
5191 }
5192 }
5193
5194 return node;
5195}
5196
5197/**
5198 * gsk_gl_shader_node_get_n_children:
5199 * @node: (type GskGLShaderNode): a `GskRenderNode` for a gl shader
5200 *
5201 * Returns the number of children
5202 *
5203 * Returns: The number of children
5204 */
5205guint
5206gsk_gl_shader_node_get_n_children (const GskRenderNode *node)
5207{
5208 const GskGLShaderNode *self = (const GskGLShaderNode *) node;
5209
5210 return self->n_children;
5211}
5212
5213/**
5214 * gsk_gl_shader_node_get_child:
5215 * @node: (type GskGLShaderNode): a `GskRenderNode` for a gl shader
5216 * @idx: the position of the child to get
5217 *
5218 * Gets one of the children.
5219 *
5220 * Returns: (transfer none): the @idx'th child of @node
5221 */
5222GskRenderNode *
5223gsk_gl_shader_node_get_child (const GskRenderNode *node,
5224 guint idx)
5225{
5226 const GskGLShaderNode *self = (const GskGLShaderNode *) node;
5227
5228 return self->children[idx];
5229}
5230
5231/**
5232 * gsk_gl_shader_node_get_shader:
5233 * @node: (type GskGLShaderNode): a `GskRenderNode` for a gl shader
5234 *
5235 * Gets shader code for the node.
5236 *
5237 * Returns: (transfer none): the `GskGLShader` shader
5238 */
5239GskGLShader *
5240gsk_gl_shader_node_get_shader (const GskRenderNode *node)
5241{
5242 const GskGLShaderNode *self = (const GskGLShaderNode *) node;
5243
5244 return self->shader;
5245}
5246
5247/**
5248 * gsk_gl_shader_node_get_args:
5249 * @node: (type GskGLShaderNode): a `GskRenderNode` for a gl shader
5250 *
5251 * Gets args for the node.
5252 *
5253 * Returns: (transfer none): A `GBytes` with the uniform arguments
5254 */
5255GBytes *
5256gsk_gl_shader_node_get_args (const GskRenderNode *node)
5257{
5258 const GskGLShaderNode *self = (const GskGLShaderNode *) node;
5259
5260 return self->args;
5261}
5262
5263GType gsk_render_node_types[GSK_RENDER_NODE_TYPE_N_TYPES];
5264
5265#ifndef I_
5266# define I_(str) g_intern_static_string ((str))
5267#endif
5268
5269#define GSK_DEFINE_RENDER_NODE_TYPE(type_name, TYPE_ENUM_VALUE) \
5270GType \
5271type_name ## _get_type (void) { \
5272 gsk_render_node_init_types (); \
5273 g_assert (gsk_render_node_types[TYPE_ENUM_VALUE] != G_TYPE_INVALID); \
5274 return gsk_render_node_types[TYPE_ENUM_VALUE]; \
5275}
5276
5277GSK_DEFINE_RENDER_NODE_TYPE (gsk_container_node, GSK_CONTAINER_NODE)
5278GSK_DEFINE_RENDER_NODE_TYPE (gsk_cairo_node, GSK_CAIRO_NODE)
5279GSK_DEFINE_RENDER_NODE_TYPE (gsk_color_node, GSK_COLOR_NODE)
5280GSK_DEFINE_RENDER_NODE_TYPE (gsk_linear_gradient_node, GSK_LINEAR_GRADIENT_NODE)
5281GSK_DEFINE_RENDER_NODE_TYPE (gsk_repeating_linear_gradient_node, GSK_REPEATING_LINEAR_GRADIENT_NODE)
5282GSK_DEFINE_RENDER_NODE_TYPE (gsk_radial_gradient_node, GSK_RADIAL_GRADIENT_NODE)
5283GSK_DEFINE_RENDER_NODE_TYPE (gsk_repeating_radial_gradient_node, GSK_REPEATING_RADIAL_GRADIENT_NODE)
5284GSK_DEFINE_RENDER_NODE_TYPE (gsk_conic_gradient_node, GSK_CONIC_GRADIENT_NODE)
5285GSK_DEFINE_RENDER_NODE_TYPE (gsk_border_node, GSK_BORDER_NODE)
5286GSK_DEFINE_RENDER_NODE_TYPE (gsk_texture_node, GSK_TEXTURE_NODE)
5287GSK_DEFINE_RENDER_NODE_TYPE (gsk_inset_shadow_node, GSK_INSET_SHADOW_NODE)
5288GSK_DEFINE_RENDER_NODE_TYPE (gsk_outset_shadow_node, GSK_OUTSET_SHADOW_NODE)
5289GSK_DEFINE_RENDER_NODE_TYPE (gsk_transform_node, GSK_TRANSFORM_NODE)
5290GSK_DEFINE_RENDER_NODE_TYPE (gsk_opacity_node, GSK_OPACITY_NODE)
5291GSK_DEFINE_RENDER_NODE_TYPE (gsk_color_matrix_node, GSK_COLOR_MATRIX_NODE)
5292GSK_DEFINE_RENDER_NODE_TYPE (gsk_repeat_node, GSK_REPEAT_NODE)
5293GSK_DEFINE_RENDER_NODE_TYPE (gsk_clip_node, GSK_CLIP_NODE)
5294GSK_DEFINE_RENDER_NODE_TYPE (gsk_rounded_clip_node, GSK_ROUNDED_CLIP_NODE)
5295GSK_DEFINE_RENDER_NODE_TYPE (gsk_shadow_node, GSK_SHADOW_NODE)
5296GSK_DEFINE_RENDER_NODE_TYPE (gsk_blend_node, GSK_BLEND_NODE)
5297GSK_DEFINE_RENDER_NODE_TYPE (gsk_cross_fade_node, GSK_CROSS_FADE_NODE)
5298GSK_DEFINE_RENDER_NODE_TYPE (gsk_text_node, GSK_TEXT_NODE)
5299GSK_DEFINE_RENDER_NODE_TYPE (gsk_blur_node, GSK_BLUR_NODE)
5300GSK_DEFINE_RENDER_NODE_TYPE (gsk_gl_shader_node, GSK_GL_SHADER_NODE)
5301GSK_DEFINE_RENDER_NODE_TYPE (gsk_debug_node, GSK_DEBUG_NODE)
5302
5303static void
5304gsk_render_node_init_types_once (void)
5305{
5306 {
5307 const GskRenderNodeTypeInfo node_info =
5308 {
5309 GSK_CONTAINER_NODE,
5310 sizeof (GskContainerNode),
5311 NULL,
5312 gsk_container_node_finalize,
5313 gsk_container_node_draw,
5314 NULL,
5315 gsk_container_node_diff,
5316 };
5317
5318 GType node_type = gsk_render_node_type_register_static (I_("GskContainerNode"), node_info: &node_info);
5319 gsk_render_node_types[GSK_CONTAINER_NODE] = node_type;
5320 }
5321
5322 {
5323 const GskRenderNodeTypeInfo node_info =
5324 {
5325 GSK_CAIRO_NODE,
5326 sizeof (GskCairoNode),
5327 NULL,
5328 gsk_cairo_node_finalize,
5329 gsk_cairo_node_draw,
5330 NULL,
5331 NULL,
5332 };
5333
5334 GType node_type = gsk_render_node_type_register_static (I_("GskCairoNode"), node_info: &node_info);
5335 gsk_render_node_types[GSK_CAIRO_NODE] = node_type;
5336 }
5337
5338 {
5339 const GskRenderNodeTypeInfo node_info =
5340 {
5341 GSK_COLOR_NODE,
5342 sizeof (GskColorNode),
5343 NULL,
5344 NULL,
5345 gsk_color_node_draw,
5346 NULL,
5347 gsk_color_node_diff,
5348 };
5349
5350 GType node_type = gsk_render_node_type_register_static (I_("GskColorNode"), node_info: &node_info);
5351 gsk_render_node_types[GSK_COLOR_NODE] = node_type;
5352 }
5353
5354 {
5355 const GskRenderNodeTypeInfo node_info =
5356 {
5357 GSK_LINEAR_GRADIENT_NODE,
5358 sizeof (GskLinearGradientNode),
5359 NULL,
5360 gsk_linear_gradient_node_finalize,
5361 gsk_linear_gradient_node_draw,
5362 NULL,
5363 gsk_linear_gradient_node_diff,
5364 };
5365
5366 GType node_type = gsk_render_node_type_register_static (I_("GskLinearGradientNode"), node_info: &node_info);
5367 gsk_render_node_types[GSK_LINEAR_GRADIENT_NODE] = node_type;
5368 }
5369
5370 {
5371 const GskRenderNodeTypeInfo node_info =
5372 {
5373 GSK_REPEATING_LINEAR_GRADIENT_NODE,
5374 sizeof (GskLinearGradientNode),
5375 NULL,
5376 gsk_linear_gradient_node_finalize,
5377 gsk_linear_gradient_node_draw,
5378 NULL,
5379 gsk_linear_gradient_node_diff,
5380 };
5381
5382 GType node_type = gsk_render_node_type_register_static (I_("GskRepeatingLinearGradientNode"), node_info: &node_info);
5383 gsk_render_node_types[GSK_REPEATING_LINEAR_GRADIENT_NODE] = node_type;
5384 }
5385
5386 {
5387 const GskRenderNodeTypeInfo node_info =
5388 {
5389 GSK_RADIAL_GRADIENT_NODE,
5390 sizeof (GskRadialGradientNode),
5391 NULL,
5392 gsk_radial_gradient_node_finalize,
5393 gsk_radial_gradient_node_draw,
5394 NULL,
5395 gsk_radial_gradient_node_diff,
5396 };
5397
5398 GType node_type = gsk_render_node_type_register_static (I_("GskRadialGradientNode"), node_info: &node_info);
5399 gsk_render_node_types[GSK_RADIAL_GRADIENT_NODE] = node_type;
5400 }
5401
5402 {
5403 const GskRenderNodeTypeInfo node_info =
5404 {
5405 GSK_REPEATING_RADIAL_GRADIENT_NODE,
5406 sizeof (GskRadialGradientNode),
5407 NULL,
5408 gsk_radial_gradient_node_finalize,
5409 gsk_radial_gradient_node_draw,
5410 NULL,
5411 gsk_radial_gradient_node_diff,
5412 };
5413
5414 GType node_type = gsk_render_node_type_register_static (I_("GskRepeatingRadialGradientNode"), node_info: &node_info);
5415 gsk_render_node_types[GSK_REPEATING_RADIAL_GRADIENT_NODE] = node_type;
5416 }
5417
5418 {
5419 const GskRenderNodeTypeInfo node_info =
5420 {
5421 GSK_CONIC_GRADIENT_NODE,
5422 sizeof (GskConicGradientNode),
5423 NULL,
5424 gsk_conic_gradient_node_finalize,
5425 gsk_conic_gradient_node_draw,
5426 NULL,
5427 gsk_conic_gradient_node_diff,
5428 };
5429
5430 GType node_type = gsk_render_node_type_register_static (I_("GskConicGradientNode"), node_info: &node_info);
5431 gsk_render_node_types[GSK_CONIC_GRADIENT_NODE] = node_type;
5432 }
5433
5434 {
5435 const GskRenderNodeTypeInfo node_info =
5436 {
5437 GSK_BORDER_NODE,
5438 sizeof (GskBorderNode),
5439 NULL,
5440 NULL,
5441 gsk_border_node_draw,
5442 NULL,
5443 gsk_border_node_diff,
5444 };
5445
5446 GType node_type = gsk_render_node_type_register_static (I_("GskBorderNode"), node_info: &node_info);
5447 gsk_render_node_types[GSK_BORDER_NODE] = node_type;
5448 }
5449
5450 {
5451 const GskRenderNodeTypeInfo node_info =
5452 {
5453 GSK_TEXTURE_NODE,
5454 sizeof (GskTextureNode),
5455 NULL,
5456 gsk_texture_node_finalize,
5457 gsk_texture_node_draw,
5458 NULL,
5459 gsk_texture_node_diff,
5460 };
5461
5462 GType node_type = gsk_render_node_type_register_static (I_("GskTextureNode"), node_info: &node_info);
5463 gsk_render_node_types[GSK_TEXTURE_NODE] = node_type;
5464 }
5465
5466 {
5467 const GskRenderNodeTypeInfo node_info =
5468 {
5469 GSK_INSET_SHADOW_NODE,
5470 sizeof (GskInsetShadowNode),
5471 NULL,
5472 NULL,
5473 gsk_inset_shadow_node_draw,
5474 NULL,
5475 gsk_inset_shadow_node_diff,
5476 };
5477
5478 GType node_type = gsk_render_node_type_register_static (I_("GskInsetShadowNode"), node_info: &node_info);
5479 gsk_render_node_types[GSK_INSET_SHADOW_NODE] = node_type;
5480 }
5481
5482 {
5483 const GskRenderNodeTypeInfo node_info =
5484 {
5485 GSK_OUTSET_SHADOW_NODE,
5486 sizeof (GskOutsetShadowNode),
5487 NULL,
5488 NULL,
5489 gsk_outset_shadow_node_draw,
5490 NULL,
5491 gsk_outset_shadow_node_diff,
5492 };
5493
5494 GType node_type = gsk_render_node_type_register_static (I_("GskOutsetShadowNode"), node_info: &node_info);
5495 gsk_render_node_types[GSK_OUTSET_SHADOW_NODE] = node_type;
5496 }
5497
5498 {
5499 const GskRenderNodeTypeInfo node_info =
5500 {
5501 GSK_TRANSFORM_NODE,
5502 sizeof (GskTransformNode),
5503 NULL,
5504 gsk_transform_node_finalize,
5505 gsk_transform_node_draw,
5506 gsk_transform_node_can_diff,
5507 gsk_transform_node_diff,
5508 };
5509
5510 GType node_type = gsk_render_node_type_register_static (I_("GskTransformNode"), node_info: &node_info);
5511 gsk_render_node_types[GSK_TRANSFORM_NODE] = node_type;
5512 }
5513
5514 {
5515 const GskRenderNodeTypeInfo node_info =
5516 {
5517 GSK_OPACITY_NODE,
5518 sizeof (GskOpacityNode),
5519 NULL,
5520 gsk_opacity_node_finalize,
5521 gsk_opacity_node_draw,
5522 NULL,
5523 gsk_opacity_node_diff,
5524 };
5525
5526 GType node_type = gsk_render_node_type_register_static (I_("GskOpacityNode"), node_info: &node_info);
5527 gsk_render_node_types[GSK_OPACITY_NODE] = node_type;
5528 }
5529
5530 {
5531 const GskRenderNodeTypeInfo node_info =
5532 {
5533 GSK_COLOR_MATRIX_NODE,
5534 sizeof (GskColorMatrixNode),
5535 NULL,
5536 gsk_color_matrix_node_finalize,
5537 gsk_color_matrix_node_draw,
5538 NULL,
5539 gsk_color_matrix_node_diff,
5540 };
5541
5542 GType node_type = gsk_render_node_type_register_static (I_("GskColorMatrixNode"), node_info: &node_info);
5543 gsk_render_node_types[GSK_COLOR_MATRIX_NODE] = node_type;
5544 }
5545
5546 {
5547 const GskRenderNodeTypeInfo node_info =
5548 {
5549 GSK_REPEAT_NODE,
5550 sizeof (GskRepeatNode),
5551 NULL,
5552 gsk_repeat_node_finalize,
5553 gsk_repeat_node_draw,
5554 NULL,
5555 NULL,
5556 };
5557
5558 GType node_type = gsk_render_node_type_register_static (I_("GskRepeatNode"), node_info: &node_info);
5559 gsk_render_node_types[GSK_REPEAT_NODE] = node_type;
5560 }
5561
5562 {
5563 const GskRenderNodeTypeInfo node_info =
5564 {
5565 GSK_CLIP_NODE,
5566 sizeof (GskClipNode),
5567 NULL,
5568 gsk_clip_node_finalize,
5569 gsk_clip_node_draw,
5570 NULL,
5571 gsk_clip_node_diff,
5572 };
5573
5574 GType node_type = gsk_render_node_type_register_static (I_("GskClipNode"), node_info: &node_info);
5575 gsk_render_node_types[GSK_CLIP_NODE] = node_type;
5576 }
5577
5578 {
5579 const GskRenderNodeTypeInfo node_info =
5580 {
5581 GSK_ROUNDED_CLIP_NODE,
5582 sizeof (GskRoundedClipNode),
5583 NULL,
5584 gsk_rounded_clip_node_finalize,
5585 gsk_rounded_clip_node_draw,
5586 NULL,
5587 gsk_rounded_clip_node_diff,
5588 };
5589
5590 GType node_type = gsk_render_node_type_register_static (I_("GskRoundedClipNode"), node_info: &node_info);
5591 gsk_render_node_types[GSK_ROUNDED_CLIP_NODE] = node_type;
5592 }
5593
5594 {
5595 const GskRenderNodeTypeInfo node_info =
5596 {
5597 GSK_SHADOW_NODE,
5598 sizeof (GskShadowNode),
5599 NULL,
5600 gsk_shadow_node_finalize,
5601 gsk_shadow_node_draw,
5602 NULL,
5603 gsk_shadow_node_diff,
5604 };
5605
5606 GType node_type = gsk_render_node_type_register_static (I_("GskShadowNode"), node_info: &node_info);
5607 gsk_render_node_types[GSK_SHADOW_NODE] = node_type;
5608 }
5609
5610 {
5611 const GskRenderNodeTypeInfo node_info =
5612 {
5613 GSK_BLEND_NODE,
5614 sizeof (GskBlendNode),
5615 NULL,
5616 gsk_blend_node_finalize,
5617 gsk_blend_node_draw,
5618 NULL,
5619 gsk_blend_node_diff,
5620 };
5621
5622 GType node_type = gsk_render_node_type_register_static (I_("GskBlendNode"), node_info: &node_info);
5623 gsk_render_node_types[GSK_BLEND_NODE] = node_type;
5624 }
5625
5626 {
5627 const GskRenderNodeTypeInfo node_info =
5628 {
5629 GSK_CROSS_FADE_NODE,
5630 sizeof (GskCrossFadeNode),
5631 NULL,
5632 gsk_cross_fade_node_finalize,
5633 gsk_cross_fade_node_draw,
5634 NULL,
5635 gsk_cross_fade_node_diff,
5636 };
5637
5638 GType node_type = gsk_render_node_type_register_static (I_("GskCrossFadeNode"), node_info: &node_info);
5639 gsk_render_node_types[GSK_CROSS_FADE_NODE] = node_type;
5640 }
5641
5642 {
5643 const GskRenderNodeTypeInfo node_info =
5644 {
5645 GSK_TEXT_NODE,
5646 sizeof (GskTextNode),
5647 NULL,
5648 gsk_text_node_finalize,
5649 gsk_text_node_draw,
5650 NULL,
5651 gsk_text_node_diff,
5652 };
5653
5654 GType node_type = gsk_render_node_type_register_static (I_("GskTextNode"), node_info: &node_info);
5655 gsk_render_node_types[GSK_TEXT_NODE] = node_type;
5656 }
5657
5658 {
5659 const GskRenderNodeTypeInfo node_info =
5660 {
5661 GSK_BLUR_NODE,
5662 sizeof (GskBlurNode),
5663 NULL,
5664 gsk_blur_node_finalize,
5665 gsk_blur_node_draw,
5666 NULL,
5667 gsk_blur_node_diff,
5668 };
5669
5670 GType node_type = gsk_render_node_type_register_static (I_("GskBlurNode"), node_info: &node_info);
5671 gsk_render_node_types[GSK_BLUR_NODE] = node_type;
5672 }
5673
5674 {
5675 const GskRenderNodeTypeInfo node_info =
5676 {
5677 GSK_GL_SHADER_NODE,
5678 sizeof (GskGLShaderNode),
5679 NULL,
5680 gsk_gl_shader_node_finalize,
5681 gsk_gl_shader_node_draw,
5682 NULL,
5683 gsk_gl_shader_node_diff,
5684 };
5685
5686 GType node_type = gsk_render_node_type_register_static (I_("GskGLShaderNode"), node_info: &node_info);
5687 gsk_render_node_types[GSK_GL_SHADER_NODE] = node_type;
5688 }
5689
5690 {
5691 const GskRenderNodeTypeInfo node_info =
5692 {
5693 GSK_DEBUG_NODE,
5694 sizeof (GskDebugNode),
5695 NULL,
5696 gsk_debug_node_finalize,
5697 gsk_debug_node_draw,
5698 gsk_debug_node_can_diff,
5699 gsk_debug_node_diff,
5700 };
5701
5702 GType node_type = gsk_render_node_type_register_static (I_("GskDebugNode"), node_info: &node_info);
5703 gsk_render_node_types[GSK_DEBUG_NODE] = node_type;
5704 }
5705}
5706
5707static void
5708gsk_render_node_content_serializer_finish (GObject *source,
5709 GAsyncResult *result,
5710 gpointer serializer)
5711{
5712 GOutputStream *stream = G_OUTPUT_STREAM (source);
5713 GError *error = NULL;
5714
5715 if (g_output_stream_splice_finish (stream, result, error: &error) < 0)
5716 gdk_content_serializer_return_error (serializer, error);
5717 else
5718 gdk_content_serializer_return_success (serializer);
5719}
5720
5721static void
5722gsk_render_node_content_serializer (GdkContentSerializer *serializer)
5723{
5724 GInputStream *input;
5725 const GValue *value;
5726 GskRenderNode *node;
5727 GBytes *bytes;
5728
5729 value = gdk_content_serializer_get_value (serializer);
5730 node = gsk_value_get_render_node (value);
5731 bytes = gsk_render_node_serialize (node);
5732 input = g_memory_input_stream_new_from_bytes (bytes);
5733
5734 g_output_stream_splice_async (stream: gdk_content_serializer_get_output_stream (serializer),
5735 source: input,
5736 flags: G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE,
5737 io_priority: gdk_content_serializer_get_priority (serializer),
5738 cancellable: gdk_content_serializer_get_cancellable (serializer),
5739 callback: gsk_render_node_content_serializer_finish,
5740 user_data: serializer);
5741 g_object_unref (object: input);
5742 g_bytes_unref (bytes);
5743}
5744
5745static void
5746gsk_render_node_content_deserializer_finish (GObject *source,
5747 GAsyncResult *result,
5748 gpointer deserializer)
5749{
5750 GOutputStream *stream = G_OUTPUT_STREAM (source);
5751 GError *error = NULL;
5752 gssize written;
5753 GValue *value;
5754 GskRenderNode *node;
5755 GBytes *bytes;
5756
5757 written = g_output_stream_splice_finish (stream, result, error: &error);
5758 if (written < 0)
5759 {
5760 gdk_content_deserializer_return_error (deserializer, error);
5761 return;
5762 }
5763
5764 bytes = g_memory_output_stream_steal_as_bytes (G_MEMORY_OUTPUT_STREAM (stream));
5765
5766 /* For now, we ignore any parsing errors. We might want to revisit that if it turns
5767 * out copy/paste leads to too many errors */
5768 node = gsk_render_node_deserialize (bytes, NULL, NULL);
5769
5770 value = gdk_content_deserializer_get_value (deserializer);
5771 gsk_value_take_render_node (value, node);
5772
5773 gdk_content_deserializer_return_success (deserializer);
5774}
5775
5776static void
5777gsk_render_node_content_deserializer (GdkContentDeserializer *deserializer)
5778{
5779 GOutputStream *output;
5780
5781 output = g_memory_output_stream_new_resizable ();
5782
5783 g_output_stream_splice_async (stream: output,
5784 source: gdk_content_deserializer_get_input_stream (deserializer),
5785 flags: G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE | G_OUTPUT_STREAM_SPLICE_CLOSE_TARGET,
5786 io_priority: gdk_content_deserializer_get_priority (deserializer),
5787 cancellable: gdk_content_deserializer_get_cancellable (deserializer),
5788 callback: gsk_render_node_content_deserializer_finish,
5789 user_data: deserializer);
5790 g_object_unref (object: output);
5791}
5792
5793static void
5794gsk_render_node_init_content_serializers (void)
5795{
5796 gdk_content_register_serializer (GSK_TYPE_RENDER_NODE,
5797 mime_type: "application/x-gtk-render-node",
5798 serialize: gsk_render_node_content_serializer,
5799 NULL,
5800 NULL);
5801 gdk_content_register_serializer (GSK_TYPE_RENDER_NODE,
5802 mime_type: "text/plain;charset=utf-8",
5803 serialize: gsk_render_node_content_serializer,
5804 NULL,
5805 NULL);
5806 /* The serialization format only outputs ASCII, so we can do this */
5807 gdk_content_register_serializer (GSK_TYPE_RENDER_NODE,
5808 mime_type: "text/plain",
5809 serialize: gsk_render_node_content_serializer,
5810 NULL,
5811 NULL);
5812
5813 gdk_content_register_deserializer (mime_type: "application/x-gtk-render-node",
5814 GSK_TYPE_RENDER_NODE,
5815 deserialize: gsk_render_node_content_deserializer,
5816 NULL,
5817 NULL);
5818}
5819
5820/*< private >
5821 * gsk_render_node_init_types:
5822 *
5823 * Initialize all the `GskRenderNode` types provided by GSK.
5824 */
5825void
5826gsk_render_node_init_types (void)
5827{
5828 static gsize register_types__volatile;
5829
5830 if (g_once_init_enter (&register_types__volatile))
5831 {
5832 gboolean initialized = TRUE;
5833 gsk_render_node_init_types_once ();
5834 gsk_render_node_init_content_serializers ();
5835 g_once_init_leave (&register_types__volatile, initialized);
5836 }
5837}
5838

source code of gtk/gsk/gskrendernodeimpl.c