From 1c5cc5093937b9ea68bba8200f2d71eb613e2979 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C5=99emysl=20Eric=20Janouch?= Date: Tue, 9 Nov 2021 19:42:43 +0100 Subject: [PATCH] Add very basic SVG support We need to refactor, so that SVGs are pre-rendered on each change of scaling by librsvg directly, because some elements may be rasterized. It would be best to also support building against resvg. --- README.adoc | 4 +- fastiv-io.c | 98 ++++++++++++++++++++++++++++++++++++++++++++++- fastiv-io.h | 2 + fastiv-view.c | 61 ++++++++++++++++++++--------- fastiv.desktop | 2 +- meson.build | 3 ++ meson_options.txt | 2 + 7 files changed, 150 insertions(+), 22 deletions(-) diff --git a/README.adoc b/README.adoc index bd0584f..0b1b04f 100644 --- a/README.adoc +++ b/README.adoc @@ -2,7 +2,7 @@ fastiv ====== 'fastiv' is a fast image viewer, supporting BMP, PNG, GIF, JPEG, and optionally -RAW pictures. Currently, it's not particularly usable. +RAW and SVG pictures. Currently, it's not particularly usable. Non-goals --------- @@ -19,7 +19,7 @@ Building and Running -------------------- Build dependencies: Meson, pkg-config + Runtime dependencies: gtk+-3.0, pixman-1, shared-mime-info, libpng>=1.5.4, -libturbojpeg, LibRaw (optional) +libturbojpeg, LibRaw (optional), librsvg-2.0 (optional) $ git clone --recursive https://git.janouch.name/p/fastiv.git $ meson builddir diff --git a/fastiv-io.c b/fastiv-io.c index a450b73..14f4e3b 100644 --- a/fastiv-io.c +++ b/fastiv-io.c @@ -23,6 +23,9 @@ #ifdef HAVE_LIBRAW #include #endif // HAVE_LIBRAW +#ifdef HAVE_LIBRSVG +#include +#endif // HAVE_LIBRSVG #define WUFFS_IMPLEMENTATION #define WUFFS_CONFIG__MODULES @@ -38,6 +41,7 @@ #include "wuffs-mirror-release-c/release/c/wuffs-v0.3.c" #include "xdg.h" +#include "fastiv-io.h" // A subset of shared-mime-info that produces an appropriate list of // file extensions. Chiefly motivated by the suckiness of RAW images: @@ -50,6 +54,9 @@ const char *fastiv_io_supported_media_types[] = { #ifdef HAVE_LIBRAW "image/x-dcraw", #endif // HAVE_LIBRAW +#ifdef HAVE_LIBRSVG + "image/svg+xml", +#endif // HAVE_LIBRSVG NULL }; @@ -384,6 +391,81 @@ open_libraw(const gchar *data, gsize len, GError **error) } #endif // HAVE_LIBRAW --------------------------------------------------------- +#ifdef HAVE_LIBRSVG // -------------------------------------------------------- + +#ifdef FASTIV_RSVG_DEBUG +#include +#include +#endif + +// FIXME: librsvg rasterizes filters, so this method isn't fully appropriate. +static cairo_surface_t * +open_librsvg(const gchar *data, gsize len, GError **error) +{ + RsvgHandle *handle = + rsvg_handle_new_from_data((const guint8 *) data, len, error); + if (!handle) + return NULL; + + // TODO(p): Acquire this from somewhere else. + rsvg_handle_set_dpi(handle, 96); + + double w = 0, h = 0; + if (!rsvg_handle_get_intrinsic_size_in_pixels(handle, &w, &h)) { + set_error(error, "cannot compute pixel dimensions"); + g_object_unref(handle); + return NULL; + } + + cairo_rectangle_t extents = { + .x = 0, .y = 0, .width = ceil(w), .height = ceil(h)}; + cairo_surface_t *surface = + cairo_recording_surface_create(CAIRO_CONTENT_COLOR_ALPHA, &extents); + +#ifdef FASTIV_RSVG_DEBUG + cairo_device_t *script = cairo_script_create("cairo.script"); + cairo_surface_t *tee = + cairo_script_surface_create_for_target(script, surface); + cairo_t *cr = cairo_create(tee); + cairo_device_destroy(script); + cairo_surface_destroy(tee); +#else + cairo_t *cr = cairo_create(surface); +#endif + + RsvgRectangle viewport = {.x = 0, .y = 0, .width = w, .height = h}; + if (!rsvg_handle_render_document(handle, cr, &viewport, error)) { + cairo_surface_destroy(surface); + cairo_destroy(cr); + g_object_unref(handle); + return NULL; + } + + cairo_destroy(cr); + g_object_unref(handle); + +#ifdef FASTIV_RSVG_DEBUG + cairo_surface_t *svg = cairo_svg_surface_create("cairo.svg", w, h); + cr = cairo_create(svg); + cairo_set_source_surface(cr, surface, 0, 0); + cairo_paint(cr); + cairo_destroy(cr); + cairo_surface_destroy(svg); + + cairo_surface_t *png = + cairo_image_surface_create(CAIRO_FORMAT_ARGB32, w * 10, h * 10); + cr = cairo_create(png); + cairo_scale(cr, 10, 10); + cairo_set_source_surface(cr, surface, 0, 0); + cairo_paint(cr); + cairo_destroy(cr); + cairo_surface_write_to_png(png, "cairo.png"); + cairo_surface_destroy(png); +#endif + return surface; +} + +#endif // HAVE_LIBRSVG -------------------------------------------------------- cairo_surface_t * fastiv_io_open(const gchar *path, GError **error) @@ -399,6 +481,14 @@ fastiv_io_open(const gchar *path, GError **error) if (!g_file_get_contents(path, &data, &len, error)) return NULL; + cairo_surface_t *surface = fastiv_io_open_from_data(data, len, error); + free(data); + return surface; +} + +cairo_surface_t * +fastiv_io_open_from_data(const char *data, size_t len, GError **error) +{ wuffs_base__slice_u8 prefix = wuffs_base__make_slice_u8((uint8_t *) data, len); @@ -433,11 +523,17 @@ fastiv_io_open(const gchar *path, GError **error) // notably only continue with LIBRAW_FILE_UNSUPPORTED. g_clear_error(error); #endif // HAVE_LIBRAW --------------------------------------------------------- +#ifdef HAVE_LIBRSVG // -------------------------------------------------------- + if ((surface = open_librsvg(data, len, error))) + break; + + // XXX: It doesn't look like librsvg can return sensible errors. + g_clear_error(error); +#endif // HAVE_LIBRSVG -------------------------------------------------------- // TODO(p): Integrate gdk-pixbuf as a fallback (optional dependency). set_error(error, "unsupported file type"); } - free(data); return surface; } diff --git a/fastiv-io.h b/fastiv-io.h index 96b4929..616bfc8 100644 --- a/fastiv-io.h +++ b/fastiv-io.h @@ -23,4 +23,6 @@ extern const char *fastiv_io_supported_media_types[]; cairo_surface_t *fastiv_io_open(const gchar *path, GError **error); +cairo_surface_t *fastiv_io_open_from_data( + const char *data, size_t len, GError **error); cairo_surface_t *fastiv_io_lookup_thumbnail(const gchar *target); diff --git a/fastiv-view.c b/fastiv-view.c index da4aa12..948db9e 100644 --- a/fastiv-view.c +++ b/fastiv-view.c @@ -29,22 +29,28 @@ struct _FastivView { G_DEFINE_TYPE(FastivView, fastiv_view, GTK_TYPE_WIDGET) -static int -get_display_width(FastivView *self) +static void +get_display_dimensions(FastivView *self, int *width, int *height) { + *width = *height = 0; if (!self->surface) - return 0; + return; - return ceil(cairo_image_surface_get_width(self->surface) * self->scale); -} + cairo_rectangle_t extents = {}; + switch (cairo_surface_get_type(self->surface)) { + case CAIRO_SURFACE_TYPE_IMAGE: + extents.width = cairo_image_surface_get_width(self->surface); + extents.height = cairo_image_surface_get_height(self->surface); + break; + case CAIRO_SURFACE_TYPE_RECORDING: + (void) cairo_recording_surface_get_extents(self->surface, &extents); + break; + default: + g_assert_not_reached(); + } -static int -get_display_height(FastivView *self) -{ - if (!self->surface) - return 0; - - return ceil(cairo_image_surface_get_height(self->surface) * self->scale); + *width = ceil(extents.width * self->scale); + *height = ceil(extents.height * self->scale); } static void @@ -60,15 +66,17 @@ static void fastiv_view_get_preferred_height( GtkWidget *widget, gint *minimum, gint *natural) { - FastivView *self = FASTIV_VIEW(widget); - *minimum = *natural = get_display_height(self); + int width, height; + get_display_dimensions(FASTIV_VIEW(widget), &width, &height); + *minimum = *natural = height; } static void fastiv_view_get_preferred_width(GtkWidget *widget, gint *minimum, gint *natural) { - FastivView *self = FASTIV_VIEW(widget); - *minimum = *natural = get_display_width(self); + int width, height; + get_display_dimensions(FASTIV_VIEW(widget), &width, &height); + *minimum = *natural = width; } static void @@ -115,8 +123,8 @@ fastiv_view_draw(GtkWidget *widget, cairo_t *cr) gtk_render_background(gtk_widget_get_style_context(widget), cr, 0, 0, allocation.width, allocation.height); - int w = get_display_width(self); - int h = get_display_height(self); + int w, h; + get_display_dimensions(self, &w, &h); double x = 0; double y = 0; @@ -125,6 +133,23 @@ fastiv_view_draw(GtkWidget *widget, cairo_t *cr) if (h < allocation.height) y = round((allocation.height - h) / 2.); + // FIXME: Recording surfaces do not work well with CAIRO_SURFACE_TYPE_XLIB, + // we always get a shitty pixmap, where transparency contains junk. + if (cairo_surface_get_type(self->surface) == CAIRO_SURFACE_TYPE_RECORDING) { + cairo_surface_t *image = + cairo_image_surface_create(CAIRO_FORMAT_ARGB32, w, h); + cairo_t *tcr = cairo_create(image); + cairo_scale(tcr, self->scale, self->scale); + cairo_set_source_surface(tcr, self->surface, 0, 0); + cairo_paint(tcr); + cairo_destroy(tcr); + + cairo_set_source_surface(cr, image, x, y); + cairo_paint(cr); + cairo_surface_destroy(image); + return TRUE; + } + // XXX: The rounding together with padding may result in up to // a pixel's worth of made-up picture data. cairo_rectangle(cr, x, y, w, h); diff --git a/fastiv.desktop b/fastiv.desktop index f048f98..9ef47d0 100644 --- a/fastiv.desktop +++ b/fastiv.desktop @@ -8,4 +8,4 @@ Terminal=false StartupNotify=true Categories=Graphics;2DGraphics;Viewer; # TODO(p): Generate this list from source files. -MimeType=image/png;image/bmp;image/gif;image/jpeg;image/x-dcraw; +MimeType=image/png;image/bmp;image/gif;image/jpeg;image/x-dcraw;image/svg+xml; diff --git a/meson.build b/meson.build index 4e1d299..d5acea1 100644 --- a/meson.build +++ b/meson.build @@ -2,12 +2,14 @@ project('fastiv', 'c', default_options : ['c_std=gnu99'], version : '0.1.0') # TODO(p): Use libraw_r later, when we start parallelizing/preloading. libraw = dependency('libraw', required : get_option('libraw')) +librsvg = dependency('librsvg-2.0', required : get_option('librsvg')) dependencies = [ dependency('gtk+-3.0'), dependency('libturbojpeg'), dependency('libpng', version : '>=1.5.4'), dependency('pixman-1'), libraw, + librsvg, meson.get_compiler('c').find_library('m', required : false), ] @@ -15,6 +17,7 @@ conf = configuration_data() conf.set_quoted('PROJECT_NAME', meson.project_name()) conf.set_quoted('PROJECT_VERSION', meson.project_version()) conf.set('HAVE_LIBRAW', libraw.found()) +conf.set('HAVE_LIBRSVG', librsvg.found()) configure_file( output : 'config.h', configuration : conf, diff --git a/meson_options.txt b/meson_options.txt index 0d56b56..3d89ed8 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -1,2 +1,4 @@ option('libraw', type : 'feature', value : 'auto', description : 'Build with RAW support, requires LibRaw') +option('librsvg', type : 'feature', value : 'auto', + description : 'Build with SVG support, requires librsvg')