From 0db9677b3abd5851574ac98f59c56ea2460a1f4c Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Fri, 10 Mar 2023 15:30:26 +0100 Subject: rendernode: Register SVG serializer This allows dropping or copy/pasting rendernodes into apps that accept SVGs. Not sure how useful this is because we advertise text/plain from rendernodes already and we prefer that. --- gsk/gskrendernodeimpl.c | 97 ++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 84 insertions(+), 13 deletions(-) diff --git a/gsk/gskrendernodeimpl.c b/gsk/gskrendernodeimpl.c index 5d1f19da5b..9c091120c8 100644 --- a/gsk/gskrendernodeimpl.c +++ b/gsk/gskrendernodeimpl.c @@ -31,6 +31,10 @@ #include "gdk/gdkmemoryformatprivate.h" #include "gdk/gdkprivate.h" +#include +#ifdef CAIRO_HAS_SVG_SURFACE +#include +#endif #include /* maximal number of rectangles we keep in a diff region before we throw @@ -6227,9 +6231,9 @@ gsk_render_node_init_types_once (void) } static void -gsk_render_node_content_serializer_finish (GObject *source, - GAsyncResult *result, - gpointer serializer) +gsk_render_node_serialize_bytes_finish (GObject *source, + GAsyncResult *result, + gpointer serializer) { GOutputStream *stream = G_OUTPUT_STREAM (source); GError *error = NULL; @@ -6241,16 +6245,11 @@ gsk_render_node_content_serializer_finish (GObject *source, } static void -gsk_render_node_content_serializer (GdkContentSerializer *serializer) +gsk_render_node_serialize_bytes (GdkContentSerializer *serializer, + GBytes *bytes) { GInputStream *input; - const GValue *value; - GskRenderNode *node; - GBytes *bytes; - value = gdk_content_serializer_get_value (serializer); - node = gsk_value_get_render_node (value); - bytes = gsk_render_node_serialize (node); input = g_memory_input_stream_new_from_bytes (bytes); g_output_stream_splice_async (gdk_content_serializer_get_output_stream (serializer), @@ -6258,16 +6257,81 @@ gsk_render_node_content_serializer (GdkContentSerializer *serializer) G_OUTPUT_STREAM_SPLICE_CLOSE_SOURCE, gdk_content_serializer_get_priority (serializer), gdk_content_serializer_get_cancellable (serializer), - gsk_render_node_content_serializer_finish, + gsk_render_node_serialize_bytes_finish, serializer); g_object_unref (input); g_bytes_unref (bytes); } +#ifdef CAIRO_HAS_SVG_SURFACE +static cairo_status_t +gsk_render_node_cairo_serializer_write (gpointer user_data, + const unsigned char *data, + unsigned int length) +{ + g_byte_array_append (user_data, data, length); + + return CAIRO_STATUS_SUCCESS; +} + +static void +gsk_render_node_svg_serializer (GdkContentSerializer *serializer) +{ + GskRenderNode *node; + cairo_surface_t *surface; + cairo_t *cr; + graphene_rect_t bounds; + GByteArray *array; + + node = gsk_value_get_render_node (gdk_content_serializer_get_value (serializer)); + gsk_render_node_get_bounds (node, &bounds); + array = g_byte_array_new (); + + surface = cairo_svg_surface_create_for_stream (gsk_render_node_cairo_serializer_write, + array, + bounds.size.width, + bounds.size.height); + cairo_svg_surface_set_document_unit (surface, CAIRO_SVG_UNIT_PX); + cairo_surface_set_device_offset (surface, -bounds.origin.x, -bounds.origin.y); + + cr = cairo_create (surface); + gsk_render_node_draw (node, cr); + cairo_destroy (cr); + + cairo_surface_finish (surface); + if (cairo_surface_status (surface) == CAIRO_STATUS_SUCCESS) + { + gsk_render_node_serialize_bytes (serializer, g_byte_array_free_to_bytes (array)); + } + else + { + GError *error = g_error_new_literal (G_IO_ERROR, G_IO_ERROR_FAILED, + cairo_status_to_string (cairo_surface_status (surface))); + gdk_content_serializer_return_error (serializer, error); + g_byte_array_unref (array); + } + cairo_surface_destroy (surface); +} +#endif + +static void +gsk_render_node_content_serializer (GdkContentSerializer *serializer) +{ + const GValue *value; + GskRenderNode *node; + GBytes *bytes; + + value = gdk_content_serializer_get_value (serializer); + node = gsk_value_get_render_node (value); + bytes = gsk_render_node_serialize (node); + + gsk_render_node_serialize_bytes (serializer, bytes); +} + static void gsk_render_node_content_deserializer_finish (GObject *source, - GAsyncResult *result, - gpointer deserializer) + GAsyncResult *result, + gpointer deserializer) { GOutputStream *stream = G_OUTPUT_STREAM (source); GError *error = NULL; @@ -6331,6 +6395,13 @@ gsk_render_node_init_content_serializers (void) gsk_render_node_content_serializer, NULL, NULL); +#ifdef CAIRO_HAS_SVG_SURFACE + gdk_content_register_serializer (GSK_TYPE_RENDER_NODE, + "image/svg+xml", + gsk_render_node_svg_serializer, + NULL, + NULL); +#endif gdk_content_register_deserializer ("application/x-gtk-render-node", GSK_TYPE_RENDER_NODE, -- cgit v1.2.1 From fc74eed4253b35fddbc792f7398c5bbeb9c54933 Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Sat, 11 Mar 2023 00:08:20 +0100 Subject: rendernode: Register PNG serializer This allows dropping or copy/pasting rendernodes into apps that accept images. --- gsk/gskrendernodeimpl.c | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/gsk/gskrendernodeimpl.c b/gsk/gskrendernodeimpl.c index 9c091120c8..8e0b0a8415 100644 --- a/gsk/gskrendernodeimpl.c +++ b/gsk/gskrendernodeimpl.c @@ -21,8 +21,10 @@ #include "gskrendernodeprivate.h" #include "gskcairoblurprivate.h" +#include "gskcairorenderer.h" #include "gskdebugprivate.h" #include "gskdiffprivate.h" +#include "gl/gskglrenderer.h" #include "gskrendererprivate.h" #include "gskroundedrectprivate.h" #include "gsktransformprivate.h" @@ -6314,6 +6316,36 @@ gsk_render_node_svg_serializer (GdkContentSerializer *serializer) } #endif +static void +gsk_render_node_png_serializer (GdkContentSerializer *serializer) +{ + GskRenderNode *node; + GdkTexture *texture; + GskRenderer *renderer; + GBytes *bytes; + + node = gsk_value_get_render_node (gdk_content_serializer_get_value (serializer)); + + renderer = gsk_gl_renderer_new (); + if (!gsk_renderer_realize (renderer, NULL, NULL)) + { + g_object_unref (renderer); + renderer = gsk_cairo_renderer_new (); + if (!gsk_renderer_realize (renderer, NULL, NULL)) + { + g_assert_not_reached (); + } + } + texture = gsk_renderer_render_texture (renderer, node, NULL); + gsk_renderer_unrealize (renderer); + g_object_unref (renderer); + + bytes = gdk_texture_save_to_png_bytes (texture); + g_object_unref (texture); + + gsk_render_node_serialize_bytes (serializer, bytes); +} + static void gsk_render_node_content_serializer (GdkContentSerializer *serializer) { @@ -6402,6 +6434,11 @@ gsk_render_node_init_content_serializers (void) NULL, NULL); #endif + gdk_content_register_serializer (GSK_TYPE_RENDER_NODE, + "image/png", + gsk_render_node_png_serializer, + NULL, + NULL); gdk_content_register_deserializer ("application/x-gtk-render-node", GSK_TYPE_RENDER_NODE, -- cgit v1.2.1 From 7c960034716db8846d0c06defa574260b98f7ac3 Mon Sep 17 00:00:00 2001 From: Benjamin Otte Date: Fri, 10 Mar 2023 22:58:19 +0100 Subject: node-editor: Add more export options Auto-detect tiff and svg and if those are chosen, save to that format. --- demos/node-editor/node-editor-window.c | 190 ++++++++++++++++++++++++++++++--- 1 file changed, 174 insertions(+), 16 deletions(-) diff --git a/demos/node-editor/node-editor-window.c b/demos/node-editor/node-editor-window.c index c3b876ac5e..3fe3d818c7 100644 --- a/demos/node-editor/node-editor-window.c +++ b/demos/node-editor/node-editor-window.c @@ -32,6 +32,11 @@ #include "gsk/vulkan/gskvulkanrenderer.h" #endif +#include +#ifdef CAIRO_HAS_SVG_SURFACE +#include +#endif + typedef struct { gsize start_chars; @@ -643,23 +648,34 @@ save_cb (GtkWidget *button, g_object_unref (dialog); } -static GdkTexture * -create_texture (NodeEditorWindow *self) +static GskRenderNode * +create_node (NodeEditorWindow *self) { GdkPaintable *paintable; GtkSnapshot *snapshot; - GskRenderer *renderer; GskRenderNode *node; - GdkTexture *texture; paintable = gtk_picture_get_paintable (GTK_PICTURE (self->picture)); if (paintable == NULL || gdk_paintable_get_intrinsic_width (paintable) <= 0 || gdk_paintable_get_intrinsic_height (paintable) <= 0) return NULL; + snapshot = gtk_snapshot_new (); gdk_paintable_snapshot (paintable, snapshot, gdk_paintable_get_intrinsic_width (paintable), gdk_paintable_get_intrinsic_height (paintable)); node = gtk_snapshot_free_to_node (snapshot); + + return node; +} + +static GdkTexture * +create_texture (NodeEditorWindow *self) +{ + GskRenderer *renderer; + GskRenderNode *node; + GdkTexture *texture; + + node = create_node (self); if (node == NULL) return NULL; @@ -670,6 +686,58 @@ create_texture (NodeEditorWindow *self) return texture; } +#ifdef CAIRO_HAS_SVG_SURFACE +static cairo_status_t +cairo_serializer_write (gpointer user_data, + const unsigned char *data, + unsigned int length) +{ + g_byte_array_append (user_data, data, length); + + return CAIRO_STATUS_SUCCESS; +} + +static GBytes * +create_svg (GskRenderNode *node, + GError **error) +{ + cairo_surface_t *surface; + cairo_t *cr; + graphene_rect_t bounds; + GByteArray *array; + + gsk_render_node_get_bounds (node, &bounds); + array = g_byte_array_new (); + + surface = cairo_svg_surface_create_for_stream (cairo_serializer_write, + array, + bounds.size.width, + bounds.size.height); + cairo_svg_surface_set_document_unit (surface, CAIRO_SVG_UNIT_PX); + cairo_surface_set_device_offset (surface, -bounds.origin.x, -bounds.origin.y); + + cr = cairo_create (surface); + gsk_render_node_draw (node, cr); + cairo_destroy (cr); + + cairo_surface_finish (surface); + if (cairo_surface_status (surface) == CAIRO_STATUS_SUCCESS) + { + cairo_surface_destroy (surface); + return g_byte_array_free_to_bytes (array); + } + else + { + g_set_error (error, + G_IO_ERROR, G_IO_ERROR_FAILED, + "%s", cairo_status_to_string (cairo_surface_status (surface))); + cairo_surface_destroy (surface); + g_byte_array_unref (array); + return NULL; + } +} +#endif + static GdkTexture * create_cairo_texture (NodeEditorWindow *self) { @@ -702,50 +770,140 @@ create_cairo_texture (NodeEditorWindow *self) } static void -export_image_response_cb (GObject *source, +export_image_saved_cb (GObject *source, + GAsyncResult *result, + void *user_data) +{ + GError *error = NULL; + + if (!g_file_replace_contents_finish (G_FILE (source), result, NULL, &error)) + { + GtkAlertDialog *alert; + + alert = gtk_alert_dialog_new ("Exporting to image failed"); + gtk_alert_dialog_set_detail (alert, error->message); + gtk_alert_dialog_show (alert, NULL); + g_object_unref (alert); + g_clear_error (&error); + } +} + +static void +export_image_response_cb (GObject *source, GAsyncResult *result, - void *user_data) + void *user_data) { GtkFileDialog *dialog = GTK_FILE_DIALOG (source); - GdkTexture *texture = user_data; + GskRenderNode *node = user_data; GFile *file; + char *uri; + GBytes *bytes; file = gtk_file_dialog_save_finish (dialog, result, NULL); - if (file) + if (file == NULL) + { + gsk_render_node_unref (node); + return; + } + + uri = g_file_get_uri (file); +#ifdef CAIRO_HAS_SVG_SURFACE + if (g_str_has_suffix (uri, "svg")) { - if (!gdk_texture_save_to_png (texture, g_file_peek_path (file))) + GError *error = NULL; + + bytes = create_svg (node, &error); + if (bytes == NULL) { GtkAlertDialog *alert; alert = gtk_alert_dialog_new ("Exporting to image failed"); - gtk_alert_dialog_show (alert, GTK_WINDOW (gtk_window_get_transient_for (GTK_WINDOW (dialog)))); + gtk_alert_dialog_set_detail (alert, error->message); + gtk_alert_dialog_show (alert, NULL); g_object_unref (alert); + g_clear_error (&error); } + } + else +#endif + { + GdkTexture *texture; + GskRenderer *renderer; - g_object_unref (file); + renderer = gsk_gl_renderer_new (); + if (!gsk_renderer_realize (renderer, NULL, NULL)) + { + g_object_unref (renderer); + renderer = gsk_cairo_renderer_new (); + if (!gsk_renderer_realize (renderer, NULL, NULL)) + { + g_assert_not_reached (); + } + } + texture = gsk_renderer_render_texture (renderer, node, NULL); + gsk_renderer_unrealize (renderer); + g_object_unref (renderer); + + if (g_str_has_suffix (uri, "tiff")) + bytes = gdk_texture_save_to_tiff_bytes (texture); + else + bytes = gdk_texture_save_to_png_bytes (texture); + g_object_unref (texture); } + g_free (uri); - g_object_unref (texture); + if (bytes) + { + g_file_replace_contents_bytes_async (file, + bytes, + NULL, + FALSE, + 0, + NULL, + export_image_saved_cb, + NULL); + g_bytes_unref (bytes); + } + gsk_render_node_unref (node); + g_object_unref (file); } static void export_image_cb (GtkWidget *button, NodeEditorWindow *self) { - GdkTexture *texture; + GskRenderNode *node; GtkFileDialog *dialog; + GtkFileFilter *filter; + GListStore *filters; - texture = create_texture (self); - if (texture == NULL) + node = create_node (self); + if (node == NULL) return; + filters = g_list_store_new (GTK_TYPE_FILE_FILTER); + filter = gtk_file_filter_new (); + gtk_file_filter_add_mime_type (filter, "image/png"); + g_list_store_append (filters, filter); + g_object_unref (filter); + filter = gtk_file_filter_new (); + gtk_file_filter_add_mime_type (filter, "image/svg+xml"); + g_list_store_append (filters, filter); + g_object_unref (filter); + filter = gtk_file_filter_new (); + gtk_file_filter_add_mime_type (filter, "image/tiff"); + g_list_store_append (filters, filter); + g_object_unref (filter); + dialog = gtk_file_dialog_new (); gtk_file_dialog_set_title (dialog, ""); gtk_file_dialog_set_initial_name (dialog, "example.png"); + gtk_file_dialog_set_filters (dialog, G_LIST_MODEL (filters)); gtk_file_dialog_save (dialog, GTK_WINDOW (gtk_widget_get_root (GTK_WIDGET (button))), NULL, - export_image_response_cb, texture); + export_image_response_cb, node); + g_object_unref (filters); g_object_unref (dialog); } -- cgit v1.2.1