From 3aa381612547a27910d28c06efed236f1db64612 Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Wed, 11 Nov 2020 07:36:04 +0100 Subject: [PATCH] gsk: Add GskStrokeNode Take a rendernode as source and a GskPath and GskStroke, and fill the area that is covered when stroking the path with the given stroke parameters, like cairo_stroke() would. --- gsk/broadway/gskbroadwayrenderer.c | 2 + gsk/gl/gskglrenderjob.c | 6 + gsk/gskenums.h | 2 + gsk/gskrendernode.h | 15 +++ gsk/gskrendernodeimpl.c | 204 +++++++++++++++++++++++++++++ gsk/gskrendernodeparser.c | 26 +++- gsk/vulkan/gskvulkanrenderpass.c | 1 + gtk/inspector/recorder.c | 47 ++++++- tools/gtk-rendernode-tool-info.c | 4 + 9 files changed, 303 insertions(+), 4 deletions(-) diff --git a/gsk/broadway/gskbroadwayrenderer.c b/gsk/broadway/gskbroadwayrenderer.c index 201c0cc034..e8a3ad0df4 100644 --- a/gsk/broadway/gskbroadwayrenderer.c +++ b/gsk/broadway/gskbroadwayrenderer.c @@ -272,6 +272,7 @@ collect_reused_child_nodes (GskRenderer *renderer, case GSK_BLUR_NODE: case GSK_MASK_NODE: case GSK_FILL_NODE: + case GSK_STROKE_NODE: default: @@ -861,6 +862,7 @@ gsk_broadway_renderer_add_node (GskRenderer *renderer, case GSK_BLUR_NODE: case GSK_GL_SHADER_NODE: case GSK_FILL_NODE: + case GSK_STROKE_NODE: default: break; /* Fallback */ } diff --git a/gsk/gl/gskglrenderjob.c b/gsk/gl/gskglrenderjob.c index 84d4f08ad2..aabc433275 100644 --- a/gsk/gl/gskglrenderjob.c +++ b/gsk/gl/gskglrenderjob.c @@ -256,6 +256,7 @@ node_supports_2d_transform (const GskRenderNode *node) case GSK_BLUR_NODE: case GSK_MASK_NODE: case GSK_FILL_NODE: + case GSK_STROKE_NODE: return TRUE; case GSK_SHADOW_NODE: @@ -311,6 +312,7 @@ node_supports_transform (const GskRenderNode *node) case GSK_BLUR_NODE: case GSK_MASK_NODE: case GSK_FILL_NODE: + case GSK_STROKE_NODE: return TRUE; case GSK_SHADOW_NODE: @@ -4095,6 +4097,10 @@ gsk_gl_render_job_visit_node (GskGLRenderJob *job, gsk_gl_render_job_visit_as_fallback (job, node); break; + case GSK_STROKE_NODE: + gsk_gl_render_job_visit_as_fallback (job, node); + break; + case GSK_NOT_A_RENDER_NODE: default: g_assert_not_reached (); diff --git a/gsk/gskenums.h b/gsk/gskenums.h index 411826e393..e7cf119d28 100644 --- a/gsk/gskenums.h +++ b/gsk/gskenums.h @@ -43,6 +43,7 @@ * @GSK_CLIP_NODE: A node that clips its child to a rectangular area * @GSK_ROUNDED_CLIP_NODE: A node that clips its child to a rounded rectangle * @GSK_FILL_NODE: A node that fills a path + * @GSK_STROKE_NODE: A node that strokes a path * @GSK_SHADOW_NODE: A node that draws a shadow below its child * @GSK_BLEND_NODE: A node that blends two children together * @GSK_CROSS_FADE_NODE: A node that cross-fades between two children @@ -76,6 +77,7 @@ typedef enum { GSK_CLIP_NODE, GSK_ROUNDED_CLIP_NODE, GSK_FILL_NODE, + GSK_STROKE_NODE, GSK_SHADOW_NODE, GSK_BLEND_NODE, GSK_CROSS_FADE_NODE, diff --git a/gsk/gskrendernode.h b/gsk/gskrendernode.h index c2c48c4905..7024b47121 100644 --- a/gsk/gskrendernode.h +++ b/gsk/gskrendernode.h @@ -159,6 +159,7 @@ GskRenderNode * gsk_render_node_deserialize (GBytes #define GSK_TYPE_CLIP_NODE (gsk_clip_node_get_type()) #define GSK_TYPE_ROUNDED_CLIP_NODE (gsk_rounded_clip_node_get_type()) #define GSK_TYPE_FILL_NODE (gsk_fill_node_get_type()) +#define GSK_TYPE_STROKE_NODE (gsk_stroke_node_get_type()) #define GSK_TYPE_SHADOW_NODE (gsk_shadow_node_get_type()) #define GSK_TYPE_BLEND_NODE (gsk_blend_node_get_type()) #define GSK_TYPE_CROSS_FADE_NODE (gsk_cross_fade_node_get_type()) @@ -188,6 +189,7 @@ typedef struct _GskRepeatNode GskRepeatNode; typedef struct _GskClipNode GskClipNode; typedef struct _GskRoundedClipNode GskRoundedClipNode; typedef struct _GskFillNode GskFillNode; +typedef struct _GskStrokeNode GskStrokeNode; typedef struct _GskShadowNode GskShadowNode; typedef struct _GskBlendNode GskBlendNode; typedef struct _GskCrossFadeNode GskCrossFadeNode; @@ -474,6 +476,19 @@ GskPath * gsk_fill_node_get_path (const GskRender GDK_AVAILABLE_IN_4_14 GskFillRule gsk_fill_node_get_fill_rule (const GskRenderNode *node); +GDK_AVAILABLE_IN_4_14 +GType gsk_stroke_node_get_type (void) G_GNUC_CONST; +GDK_AVAILABLE_IN_4_14 +GskRenderNode * gsk_stroke_node_new (GskRenderNode *child, + GskPath *path, + const GskStroke *stroke); +GDK_AVAILABLE_IN_4_14 +GskRenderNode * gsk_stroke_node_get_child (const GskRenderNode *node); +GDK_AVAILABLE_IN_4_14 +GskPath * gsk_stroke_node_get_path (const GskRenderNode *node); +GDK_AVAILABLE_IN_4_14 +const GskStroke * gsk_stroke_node_get_stroke (const GskRenderNode *node); + GDK_AVAILABLE_IN_ALL GType gsk_shadow_node_get_type (void) G_GNUC_CONST; GDK_AVAILABLE_IN_ALL diff --git a/gsk/gskrendernodeimpl.c b/gsk/gskrendernodeimpl.c index d6f3429440..c17f30f7a3 100644 --- a/gsk/gskrendernodeimpl.c +++ b/gsk/gskrendernodeimpl.c @@ -29,6 +29,7 @@ #include "gskrectprivate.h" #include "gskrendererprivate.h" #include "gskroundedrectprivate.h" +#include "gskstrokeprivate.h" #include "gsktransformprivate.h" #include "gdk/gdktextureprivate.h" @@ -4568,6 +4569,203 @@ gsk_fill_node_get_fill_rule (const GskRenderNode *node) return self->fill_rule; } +/* }}} */ +/* {{{ GSK_STROKE_NODE */ + +struct _GskStrokeNode +{ + GskRenderNode render_node; + + GskRenderNode *child; + GskPath *path; + GskStroke stroke; +}; + +static void +gsk_stroke_node_finalize (GskRenderNode *node) +{ + GskStrokeNode *self = (GskStrokeNode *) node; + GskRenderNodeClass *parent_class = g_type_class_peek (g_type_parent (GSK_TYPE_STROKE_NODE)); + + gsk_render_node_unref (self->child); + gsk_path_unref (self->path); + gsk_stroke_clear (&self->stroke); + + parent_class->finalize (node); +} + +static void +gsk_stroke_node_draw (GskRenderNode *node, + cairo_t *cr) +{ + GskStrokeNode *self = (GskStrokeNode *) node; + + cairo_save (cr); + + if (gsk_render_node_get_node_type (self->child) == GSK_COLOR_NODE && + gsk_rect_contains_rect (&self->child->bounds, &node->bounds)) + { + gdk_cairo_set_source_rgba (cr, gsk_color_node_get_color (self->child)); + } + else + { + gsk_cairo_rectangle (cr, &self->child->bounds); + cairo_clip (cr); + cairo_push_group (cr); + gsk_render_node_draw (self->child, cr); + cairo_pop_group_to_source (cr); + } + + gsk_stroke_to_cairo (&self->stroke, cr); + + gsk_path_to_cairo (self->path, cr); + cairo_stroke (cr); + + cairo_restore (cr); +} + +static void +gsk_stroke_node_diff (GskRenderNode *node1, + GskRenderNode *node2, + cairo_region_t *region) +{ + GskStrokeNode *self1 = (GskStrokeNode *) node1; + GskStrokeNode *self2 = (GskStrokeNode *) node2; + + if (self1->path == self2->path && + gsk_stroke_equal (&self1->stroke, &self2->stroke)) + { + cairo_region_t *sub; + cairo_rectangle_int_t clip_rect; + + sub = cairo_region_create(); + gsk_render_node_diff (self1->child, self2->child, sub); + rectangle_init_from_graphene (&clip_rect, &node1->bounds); + cairo_region_intersect_rectangle (sub, &clip_rect); + cairo_region_union (region, sub); + cairo_region_destroy (sub); + } + else + { + gsk_render_node_diff_impossible (node1, node2, region); + } +} + +static void +gsk_stroke_node_class_init (gpointer g_class, + gpointer class_data) +{ + GskRenderNodeClass *node_class = g_class; + + node_class->node_type = GSK_STROKE_NODE; + + node_class->finalize = gsk_stroke_node_finalize; + node_class->draw = gsk_stroke_node_draw; + node_class->diff = gsk_stroke_node_diff; +} + +/** + * gsk_stroke_node_new: + * @child: The node to stroke the area with + * @path: (transfer none): The path describing the area to stroke + * @stroke: (transfer none): The stroke attributes to use + * + * Creates a #GskRenderNode that will stroke the @child along the given + * @path using the attributes defined in @stroke. + * + * Returns: (transfer none) (type GskStrokeNode): A new #GskRenderNode + * + * Since: 4.14 + */ +GskRenderNode * +gsk_stroke_node_new (GskRenderNode *child, + GskPath *path, + const GskStroke *stroke) +{ + GskStrokeNode *self; + GskRenderNode *node; + graphene_rect_t stroke_bounds; + + g_return_val_if_fail (GSK_IS_RENDER_NODE (child), NULL); + g_return_val_if_fail (path != NULL, NULL); + g_return_val_if_fail (stroke != NULL, NULL); + + self = gsk_render_node_alloc (GSK_STROKE_NODE); + node = (GskRenderNode *) self; + + self->child = gsk_render_node_ref (child); + self->path = gsk_path_ref (path); + gsk_stroke_init_copy (&self->stroke, stroke); + + if (gsk_path_get_stroke_bounds (self->path, &self->stroke, &stroke_bounds)) + graphene_rect_intersection (&stroke_bounds, &child->bounds, &node->bounds); + else + graphene_rect_init_from_rect (&node->bounds, graphene_rect_zero ()); + + return node; +} + +/** + * gsk_stroke_node_get_child: + * @node: (type GskStrokeNode): a stroke #GskRenderNode + * + * Gets the child node that is getting drawn by the given @node. + * + * Returns: (transfer none): The child that is getting drawn + * + * Since: 4.14 + */ +GskRenderNode * +gsk_stroke_node_get_child (const GskRenderNode *node) +{ + const GskStrokeNode *self = (const GskStrokeNode *) node; + + g_return_val_if_fail (GSK_IS_RENDER_NODE_TYPE (node, GSK_STROKE_NODE), NULL); + + return self->child; +} + +/** + * gsk_stroke_node_get_path: + * @node: (type GskStrokeNode): a stroke #GskRenderNode + * + * Retrieves the path that will be stroked with the contents of + * the @node. + * + * Returns: (transfer none): a #GskPath + * + * Since: 4.14 + */ +GskPath * +gsk_stroke_node_get_path (const GskRenderNode *node) +{ + const GskStrokeNode *self = (const GskStrokeNode *) node; + + g_return_val_if_fail (GSK_IS_RENDER_NODE_TYPE (node, GSK_STROKE_NODE), NULL); + + return self->path; +} + +/** + * gsk_stroke_node_get_stroke: + * @node: (type GskStrokeNode): a stroke #GskRenderNode + * + * Retrieves the stroke attributes used in this @node. + * + * Returns: a #GskStroke + * + * Since: 4.14 + */ +const GskStroke * +gsk_stroke_node_get_stroke (const GskRenderNode *node) +{ + const GskStrokeNode *self = (const GskStrokeNode *) node; + + g_return_val_if_fail (GSK_IS_RENDER_NODE_TYPE (node, GSK_STROKE_NODE), NULL); + + return &self->stroke; +} + /* }}} */ /* {{{ GSK_SHADOW_NODE */ @@ -6462,6 +6660,7 @@ GSK_DEFINE_RENDER_NODE_TYPE (gsk_repeat_node, GSK_REPEAT_NODE) GSK_DEFINE_RENDER_NODE_TYPE (gsk_clip_node, GSK_CLIP_NODE) GSK_DEFINE_RENDER_NODE_TYPE (gsk_rounded_clip_node, GSK_ROUNDED_CLIP_NODE) GSK_DEFINE_RENDER_NODE_TYPE (gsk_fill_node, GSK_FILL_NODE) +GSK_DEFINE_RENDER_NODE_TYPE (gsk_stroke_node, GSK_STROKE_NODE) GSK_DEFINE_RENDER_NODE_TYPE (gsk_shadow_node, GSK_SHADOW_NODE) GSK_DEFINE_RENDER_NODE_TYPE (gsk_blend_node, GSK_BLEND_NODE) GSK_DEFINE_RENDER_NODE_TYPE (gsk_cross_fade_node, GSK_CROSS_FADE_NODE) @@ -6615,6 +6814,11 @@ gsk_render_node_init_types_once (void) sizeof (GskFillNode), gsk_fill_node_class_init); gsk_render_node_types[GSK_FILL_NODE] = node_type; + + node_type = gsk_render_node_type_register_static (I_("GskStrokeNode"), + sizeof (GskStrokeNode), + gsk_stroke_node_class_init); + gsk_render_node_types[GSK_STROKE_NODE] = node_type; } static void diff --git a/gsk/gskrendernodeparser.c b/gsk/gskrendernodeparser.c index 799b5c11b2..8dabd05c09 100644 --- a/gsk/gskrendernodeparser.c +++ b/gsk/gskrendernodeparser.c @@ -26,6 +26,7 @@ #include "gskpath.h" #include "gskroundedrectprivate.h" #include "gskrendernodeprivate.h" +#include "gskstroke.h" #include "gsktransformprivate.h" #include "gdk/gdkrgbaprivate.h" @@ -2438,6 +2439,10 @@ printer_init_duplicates_for_node (Printer *printer, printer_init_duplicates_for_node (printer, gsk_fill_node_get_child (node)); break; + case GSK_STROKE_NODE: + printer_init_duplicates_for_node (printer, gsk_stroke_node_get_child (node)); + break; + case GSK_BLEND_NODE: printer_init_duplicates_for_node (printer, gsk_blend_node_get_bottom_child (node)); printer_init_duplicates_for_node (printer, gsk_blend_node_get_top_child (node)); @@ -2663,7 +2668,7 @@ append_float_param (Printer *p, float value, float default_value) { - /* Don't approximate-compare here, better be topo verbose */ + /* Don't approximate-compare here, better be too verbose */ if (value == default_value) return; @@ -3228,6 +3233,25 @@ render_node_print (Printer *p, } break; + case GSK_STROKE_NODE: + { + const GskStroke *stroke; + char *path_str; + + start_node (p, "stroke", node_name); + + append_node_param (p, "child", gsk_stroke_node_get_child (node)); + path_str = gsk_path_to_string (gsk_stroke_node_get_path (node)); + append_string_param (p, "path", path_str); + g_free (path_str); + + stroke = gsk_stroke_node_get_stroke (node); + append_float_param (p, "line-width", gsk_stroke_get_line_width (stroke), 0.0); + + end_node (p); + } + break; + case GSK_TRANSFORM_NODE: { GskTransform *transform = gsk_transform_node_get_transform (node); diff --git a/gsk/vulkan/gskvulkanrenderpass.c b/gsk/vulkan/gskvulkanrenderpass.c index ed543525be..e9bbbf2c04 100644 --- a/gsk/vulkan/gskvulkanrenderpass.c +++ b/gsk/vulkan/gskvulkanrenderpass.c @@ -1261,6 +1261,7 @@ static const GskVulkanRenderPassNodeFunc nodes_vtable[] = { [GSK_TEXTURE_SCALE_NODE] = gsk_vulkan_render_pass_add_texture_scale_node, [GSK_MASK_NODE] = gsk_vulkan_render_pass_add_mask_node, [GSK_FILL_NODE] = NULL, + [GSK_STROKE_NODE] = NULL, }; static void diff --git a/gtk/inspector/recorder.c b/gtk/inspector/recorder.c index 87e09362e1..ef0c2e0d9b 100644 --- a/gtk/inspector/recorder.c +++ b/gtk/inspector/recorder.c @@ -301,6 +301,9 @@ create_list_model_for_render_node (GskRenderNode *node) case GSK_FILL_NODE: return create_render_node_list_model ((GskRenderNode *[1]) { gsk_fill_node_get_child (node) }, 1); + case GSK_STROKE_NODE: + return create_render_node_list_model ((GskRenderNode *[1]) { gsk_stroke_node_get_child (node) }, 1); + case GSK_SHADOW_NODE: return create_render_node_list_model ((GskRenderNode *[1]) { gsk_shadow_node_get_child (node) }, 1); @@ -430,6 +433,8 @@ node_type_name (GskRenderNodeType type) return "Rounded Clip"; case GSK_FILL_NODE: return "Fill"; + case GSK_STROKE_NODE: + return "Stroke"; case GSK_SHADOW_NODE: return "Shadow"; case GSK_BLEND_NODE: @@ -472,6 +477,7 @@ node_name (GskRenderNode *node) case GSK_CLIP_NODE: case GSK_ROUNDED_CLIP_NODE: case GSK_FILL_NODE: + case GSK_STROKE_NODE: case GSK_SHADOW_NODE: case GSK_BLEND_NODE: case GSK_MASK_NODE: @@ -900,6 +906,20 @@ add_float_row (GListStore *store, g_free (text); } +static const char * +enum_to_nick (GType type, + int value) +{ + GEnumClass *class; + GEnumValue *v; + + class = g_type_class_ref (type); + v = g_enum_get_value (class, value); + g_type_class_unref (class); + + return v->value_nick; +} + static void populate_render_node_properties (GListStore *store, GskRenderNode *node) @@ -1139,9 +1159,7 @@ populate_render_node_properties (GListStore *store, case GSK_BLEND_NODE: { GskBlendMode mode = gsk_blend_node_get_blend_mode (node); - tmp = g_enum_to_string (GSK_TYPE_BLEND_MODE, mode); - add_text_row (store, "Blendmode", tmp); - g_free (tmp); + add_text_row (store, "Blendmode", enum_to_nick (GSK_TYPE_BLEND_MODE, mode)); } break; @@ -1386,10 +1404,33 @@ populate_render_node_properties (GListStore *store, case GSK_FILL_NODE: { GskPath *path = gsk_fill_node_get_path (node); + GskFillRule fill_rule = gsk_fill_node_get_fill_rule (node); tmp = gsk_path_to_string (path); add_text_row (store, "Path", tmp); g_free (tmp); + + add_text_row (store, "Fill rule", enum_to_nick (GSK_TYPE_FILL_RULE, fill_rule)); + } + break; + + case GSK_STROKE_NODE: + { + GskPath *path = gsk_stroke_node_get_path (node); + const GskStroke *stroke = gsk_stroke_node_get_stroke (node); + GskLineCap line_cap = gsk_stroke_get_line_cap (stroke); + GskLineJoin line_join = gsk_stroke_get_line_join (stroke); + + tmp = gsk_path_to_string (path); + add_text_row (store, "Path", tmp); + g_free (tmp); + + tmp = g_strdup_printf ("%.2f", gsk_stroke_get_line_width (stroke)); + add_text_row (store, "Line width", tmp); + g_free (tmp); + + add_text_row (store, "Line cap", enum_to_nick (GSK_TYPE_LINE_CAP, line_cap)); + add_text_row (store, "Line join", enum_to_nick (GSK_TYPE_LINE_JOIN, line_join)); } break; diff --git a/tools/gtk-rendernode-tool-info.c b/tools/gtk-rendernode-tool-info.c index a62c3d1984..fbc8048771 100644 --- a/tools/gtk-rendernode-tool-info.c +++ b/tools/gtk-rendernode-tool-info.c @@ -134,6 +134,10 @@ count_nodes (GskRenderNode *node, count_nodes (gsk_fill_node_get_child (node), counts, &d); break; + case GSK_STROKE_NODE: + count_nodes (gsk_stroke_node_get_child (node), counts, &d); + break; + case GSK_NOT_A_RENDER_NODE: default: g_assert_not_reached ();