diff options
| author | Přemysl Eric Janouch <p@janouch.name> | 2021-12-22 10:59:03 +0100 | 
|---|---|---|
| committer | Přemysl Eric Janouch <p@janouch.name> | 2021-12-22 14:20:39 +0100 | 
| commit | 2d4cab52b37c58c881e8bb7786adbe868f004a63 (patch) | |
| tree | 7386bd3c94da4bc099c498f4b68feab8d7aaecd8 | |
| parent | 46edd4406c360ca85e83b7bec30ce115e186346f (diff) | |
| download | fiv-2d4cab52b37c58c881e8bb7786adbe868f004a63.tar.gz fiv-2d4cab52b37c58c881e8bb7786adbe868f004a63.tar.xz fiv-2d4cab52b37c58c881e8bb7786adbe868f004a63.zip  | |
Integrate jpeg-quantsmooth
Also, don't pointlessly store JPEGs in an ARGB Cairo surface.
| -rw-r--r-- | .gitmodules | 3 | ||||
| -rw-r--r-- | fastiv.c | 9 | ||||
| -rw-r--r-- | fiv-io.c | 122 | ||||
| -rw-r--r-- | fiv-io.h | 7 | ||||
| -rw-r--r-- | fiv-view.c | 46 | ||||
| -rw-r--r-- | fiv-view.h | 1 | ||||
| m--------- | jpeg-quantsmooth | 0 | ||||
| -rw-r--r-- | meson.build | 3 | ||||
| -rw-r--r-- | meson_options.txt | 2 | ||||
| -rw-r--r-- | resources/heal-symbolic.svg | 107 | ||||
| -rw-r--r-- | resources/resources.gresource.xml | 1 | 
11 files changed, 289 insertions, 12 deletions
diff --git a/.gitmodules b/.gitmodules index c6b083b..e8c4d71 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@  [submodule "wuffs-mirror-release-c"]  	path = wuffs-mirror-release-c  	url = https://github.com/google/wuffs-mirror-release-c +[submodule "jpeg-quantsmooth"] +	path = jpeg-quantsmooth +	url = https://github.com/ilyakurdyukov/jpeg-quantsmooth.git @@ -231,6 +231,7 @@ make_key_window(void)  	/* Or perhaps "blur-symbolic", also in the extended set. */ \  	XX(SMOOTH,        T("blend-tool-symbolic", "Smooth scaling")) \  	XX(CHECKERBOARD,  T("checkerboard-symbolic", "Highlight transparency")) \ +	XX(ENHANCE,       T("heal-symbolic", "Enhance low-quality JPEG")) \  	/* XX(COLOR,      B("preferences-color-symbolic", "Color management")) */ \  	XX(SAVE,          B("document-save-as-symbolic", "Save as...")) \  	XX(PRINT,         B("document-print-symbolic", "Print...")) \ @@ -1091,6 +1092,7 @@ make_view_toolbar(void)  	toolbar_toggler(TOOLBAR_FIT,           "scale-to-fit");  	toolbar_toggler(TOOLBAR_SMOOTH,        "filter");  	toolbar_toggler(TOOLBAR_CHECKERBOARD,  "checkerboard"); +	toolbar_toggler(TOOLBAR_ENHANCE,       "enhance");  	toolbar_command(TOOLBAR_PRINT,         FIV_VIEW_COMMAND_PRINT);  	toolbar_command(TOOLBAR_SAVE,          FIV_VIEW_COMMAND_SAVE_PAGE);  	toolbar_command(TOOLBAR_INFO,          FIV_VIEW_COMMAND_INFO); @@ -1109,12 +1111,19 @@ make_view_toolbar(void)  		G_CALLBACK(on_notify_view_boolean), g.toolbar[TOOLBAR_SMOOTH]);  	g_signal_connect(g.view, "notify::checkerboard",  		G_CALLBACK(on_notify_view_boolean), g.toolbar[TOOLBAR_CHECKERBOARD]); +	g_signal_connect(g.view, "notify::enhance", +		G_CALLBACK(on_notify_view_boolean), g.toolbar[TOOLBAR_ENHANCE]);  	g_object_notify(G_OBJECT(g.view), "scale");  	g_object_notify(G_OBJECT(g.view), "playing");  	g_object_notify(G_OBJECT(g.view), "scale-to-fit");  	g_object_notify(G_OBJECT(g.view), "filter");  	g_object_notify(G_OBJECT(g.view), "checkerboard"); +	g_object_notify(G_OBJECT(g.view), "enhance"); + +#ifndef HAVE_JPEG_QS +	gtk_widget_set_no_show_all(g.toolbar[TOOLBAR_ENHANCE], TRUE); +#endif  	GCallback callback = G_CALLBACK(on_view_actions_changed);  	g_signal_connect(g.view, "notify::has-image", callback, NULL); @@ -24,6 +24,16 @@  #include <spng.h>  #include <turbojpeg.h> + +#ifdef HAVE_JPEG_QS +#include <jpeglib.h> +#include <setjmp.h> +// This library is tricky to build, simply make it work at all. +#define NO_SIMD +#include <jpeg-quantsmooth/quantsmooth.h> +#undef NO_SIMD +#endif  // HAVE_JPEG_QS +  #ifdef HAVE_LIBRAW  #include <libraw.h>  #endif  // HAVE_LIBRAW @@ -159,7 +169,7 @@ try_append_page(cairo_surface_t *surface, cairo_surface_t **result,  	return true;  } -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +// --- Wuffs -------------------------------------------------------------------  // From libwebp, verified to exactly match [x * a / 255].  #define PREMULTIPLY8(a, x) (((uint32_t) (x) * (uint32_t) (a) * 32897U) >> 23) @@ -614,6 +624,8 @@ open_wuffs_using(wuffs_base__image_decoder *(*allocate)(),  	return surface;  } +// --- JPEG -------------------------------------------------------------------- +  static void  trivial_cmyk_to_host_byte_order_argb(unsigned char *p, int len)  { @@ -747,10 +759,10 @@ open_libjpeg_turbo(const gchar *data, gsize len, GError **error)  	int pixel_format = (colorspace == TJCS_CMYK || colorspace == TJCS_YCCK)  		? TJPF_CMYK -		: (G_BYTE_ORDER == G_LITTLE_ENDIAN ? TJPF_BGRA : TJPF_ARGB); +		: (G_BYTE_ORDER == G_LITTLE_ENDIAN ? TJPF_BGRX : TJPF_XRGB);  	cairo_surface_t *surface = -		cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height); +		cairo_image_surface_create(CAIRO_FORMAT_RGB24, width, height);  	cairo_status_t surface_status = cairo_surface_status(surface);  	if (surface_status != CAIRO_STATUS_SUCCESS) {  		set_error(error, cairo_status_to_string(surface_status)); @@ -791,6 +803,97 @@ open_libjpeg_turbo(const gchar *data, gsize len, GError **error)  	return surface;  } +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +#ifdef HAVE_JPEG_QS + +struct libjpeg_error_mgr { +	struct jpeg_error_mgr pub; +	jmp_buf buf; +	GError **error; +}; + +static void +libjpeg_error_exit(j_common_ptr cinfo) +{ +	struct libjpeg_error_mgr *err = (struct libjpeg_error_mgr *) cinfo->err; +	char buf[JMSG_LENGTH_MAX] = ""; +	(*cinfo->err->format_message)(cinfo, buf); +	set_error(err->error, buf); +	longjmp(err->buf, 1); +} + +static cairo_surface_t * +open_libjpeg_enhanced(const gchar *data, gsize len, GError **error) +{ +	cairo_surface_t *volatile surface = NULL; + +	struct libjpeg_error_mgr jerr = {.error = error}; +	struct jpeg_decompress_struct cinfo = {.err = jpeg_std_error(&jerr.pub)}; +	jerr.pub.error_exit = libjpeg_error_exit; +	if (setjmp(jerr.buf)) { +		g_clear_pointer(&surface, cairo_surface_destroy); +		jpeg_destroy_decompress(&cinfo); +		return NULL; +	} + +	jpeg_create_decompress(&cinfo); +	jpeg_mem_src(&cinfo, (const unsigned char *) data, len); +	(void) jpeg_read_header(&cinfo, true); +	if (cinfo.jpeg_color_space == JCS_CMYK || +		cinfo.jpeg_color_space == JCS_YCCK) +		cinfo.out_color_space = JCS_CMYK; +	else if (G_BYTE_ORDER == G_BIG_ENDIAN) +		cinfo.out_color_space = JCS_EXT_XRGB; +	else +		cinfo.out_color_space = JCS_EXT_BGRX; + +	jpeg_calc_output_dimensions(&cinfo); +	int width = cinfo.output_width; +	int height = cinfo.output_height; + +	surface = cairo_image_surface_create(CAIRO_FORMAT_RGB24, width, height); +	cairo_status_t surface_status = cairo_surface_status(surface); +	if (surface_status != CAIRO_STATUS_SUCCESS) { +		set_error(error, cairo_status_to_string(surface_status)); +		longjmp(jerr.buf, 1); +	} + +	unsigned char *surface_data = cairo_image_surface_get_data(surface); +	int surface_stride = cairo_image_surface_get_stride(surface); +	JSAMPARRAY lines = (*cinfo.mem->alloc_small)( +		(j_common_ptr) &cinfo, JPOOL_IMAGE, sizeof *lines * height); +	for (int i = 0; i < height; i++) +		lines[i] = surface_data + i * surface_stride; + +	// Go for the maximum quality setting. +	jpegqs_control_t opts = { +		.flags = JPEGQS_DIAGONALS | JPEGQS_JOINT_YUV | JPEGQS_UPSAMPLE_UV, +		.threads = g_get_num_processors(), +		.niter = 3, +	}; + +	(void) jpegqs_start_decompress(&cinfo, &opts); +	while (cinfo.output_scanline < cinfo.output_height) +		(void) jpeg_read_scanlines(&cinfo, lines + cinfo.output_scanline, +			cinfo.output_height - cinfo.output_scanline); +	if (cinfo.out_color_space == JCS_CMYK) +		trivial_cmyk_to_host_byte_order_argb( +			surface_data, cinfo.output_width * cinfo.output_height); +	cairo_surface_mark_dirty(surface); +	(void) jpegqs_finish_decompress(&cinfo); + +	jpeg_destroy_decompress(&cinfo); +	parse_jpeg_metadata(surface, data, len); +	return surface; +} + +#else +#define open_libjpeg_enhanced open_libjpeg_turbo +#endif + +// --- Optional dependencies --------------------------------------------------- +  #ifdef HAVE_LIBRAW  // ---------------------------------------------------------  static cairo_surface_t * @@ -1831,7 +1934,7 @@ cairo_user_data_key_t fiv_io_key_page_next;  cairo_user_data_key_t fiv_io_key_page_previous;  cairo_surface_t * -fiv_io_open(const gchar *path, GError **error) +fiv_io_open(const gchar *path, gboolean enhance, GError **error)  {  	// TODO(p): Don't always load everything into memory, test type first,  	// so that we can reject non-pictures early.  Wuffs only needs the first @@ -1852,14 +1955,15 @@ fiv_io_open(const gchar *path, GError **error)  	if (!g_file_get_contents(path, &data, &len, error))  		return NULL; -	cairo_surface_t *surface = fiv_io_open_from_data(data, len, path, error); +	cairo_surface_t *surface = +		fiv_io_open_from_data(data, len, path, enhance, error);  	free(data);  	return surface;  }  cairo_surface_t * -fiv_io_open_from_data( -	const char *data, size_t len, const gchar *path, GError **error) +fiv_io_open_from_data(const char *data, size_t len, const gchar *path, +	gboolean enhance, GError **error)  {  	wuffs_base__slice_u8 prefix =  		wuffs_base__make_slice_u8((uint8_t *) data, len); @@ -1884,7 +1988,9 @@ fiv_io_open_from_data(  			error);  		break;  	case WUFFS_BASE__FOURCC__JPEG: -		surface = open_libjpeg_turbo(data, len, error); +		surface = enhance +			? open_libjpeg_enhanced(data, len, error) +			: open_libjpeg_turbo(data, len, error);  		break;  	default:  #ifdef HAVE_LIBRAW  // --------------------------------------------------------- @@ -55,9 +55,10 @@ extern cairo_user_data_key_t fiv_io_key_page_next;  /// There is no wrap-around. This is a weak pointer.  extern cairo_user_data_key_t fiv_io_key_page_previous; -cairo_surface_t *fiv_io_open(const gchar *path, GError **error); -cairo_surface_t *fiv_io_open_from_data( -	const char *data, size_t len, const gchar *path, GError **error); +cairo_surface_t *fiv_io_open( +	const gchar *path, gboolean enhance, GError **error); +cairo_surface_t *fiv_io_open_from_data(const char *data, size_t len, +	const gchar *path, gboolean enhance, GError **error);  int fiv_io_filecmp(GFile *f1, GFile *f2); @@ -40,9 +40,12 @@ struct _FivView {  	FivIoOrientation orientation;       ///< Current page orientation  	bool filter;                        ///< Smooth scaling toggle  	bool checkerboard;                  ///< Show checkerboard background +	bool enhance;                       ///< Try to enhance picture data  	bool scale_to_fit;                  ///< Image no larger than the allocation  	double scale;                       ///< Scaling factor +	cairo_surface_t *enhance_swap;      ///< Quick swap in/out +  	int remaining_loops;                ///< Greater than zero if limited  	gint64 frame_time;                  ///< Current frame's start, µs precision  	gulong frame_update_connection;     ///< GdkFrameClock::update @@ -95,6 +98,7 @@ enum {  	PROP_SCALE_TO_FIT,  	PROP_FILTER,  	PROP_CHECKERBOARD, +	PROP_ENHANCE,  	PROP_PLAYING,  	PROP_HAS_IMAGE,  	PROP_CAN_ANIMATE, @@ -110,6 +114,7 @@ fiv_view_finalize(GObject *gobject)  {  	FivView *self = FIV_VIEW(gobject);  	cairo_surface_destroy(self->image); +	g_clear_pointer(&self->enhance_swap, cairo_surface_destroy);  	g_free(self->path);  	G_OBJECT_CLASS(fiv_view_parent_class)->finalize(gobject); @@ -133,6 +138,9 @@ fiv_view_get_property(  	case PROP_CHECKERBOARD:  		g_value_set_boolean(value, self->checkerboard);  		break; +	case PROP_ENHANCE: +		g_value_set_boolean(value, self->enhance); +		break;  	case PROP_PLAYING:  		g_value_set_boolean(value, !!self->frame_update_connection);  		break; @@ -173,6 +181,10 @@ fiv_view_set_property(  		if (self->checkerboard != g_value_get_boolean(value))  			fiv_view_command(self, FIV_VIEW_COMMAND_TOGGLE_CHECKERBOARD);  		break; +	case PROP_ENHANCE: +		if (self->enhance != g_value_get_boolean(value)) +			fiv_view_command(self, FIV_VIEW_COMMAND_TOGGLE_ENHANCE); +		break;  	default:  		G_OBJECT_WARN_INVALID_PROPERTY_ID(object, property_id, pspec);  	} @@ -1044,6 +1056,9 @@ fiv_view_class_init(FivViewClass *klass)  	view_properties[PROP_CHECKERBOARD] = g_param_spec_boolean(  		"checkerboard", "Show checkerboard", "Highlight transparent background",  		TRUE, G_PARAM_READWRITE); +	view_properties[PROP_ENHANCE] = g_param_spec_boolean( +		"enhance", "Enhance JPEG", "Enhance low-quality JPEG", +		TRUE, G_PARAM_READWRITE);  	view_properties[PROP_PLAYING] = g_param_spec_boolean(  		"playing", "Playing animation", "An animation is running",  		FALSE, G_PARAM_READABLE); @@ -1095,12 +1110,20 @@ fiv_view_init(FivView *self)  gboolean  fiv_view_open(FivView *self, const gchar *path, GError **error)  { -	cairo_surface_t *surface = fiv_io_open(path, error); +	cairo_surface_t *surface = fiv_io_open(path, self->enhance, error);  	if (!surface)  		return FALSE;  	if (self->image)  		cairo_surface_destroy(self->image); +	// This is extremely expensive, and only works sometimes. +	g_clear_pointer(&self->enhance_swap, cairo_surface_destroy); +	if (self->enhance) { +		self->enhance = FALSE; +		g_object_notify_by_pspec( +			G_OBJECT(self), view_properties[PROP_ENHANCE]); +	} +  	self->frame = self->page = NULL;  	self->image = surface;  	switch_page(self, self->image); @@ -1134,6 +1157,21 @@ frame_step(FivView *self, int step)  	gtk_widget_queue_draw(GTK_WIDGET(self));  } +static void +swap_enhanced_image(FivView *self) +{ +	GError *error = NULL; +	cairo_surface_t *surface = self->enhance_swap; +	if (!surface) +		surface = fiv_io_open(self->path, self->enhance, &error); +	if (!surface) { +		show_error_dialog(get_toplevel(GTK_WIDGET(self)), error); +	} else { +		self->enhance_swap = self->image; +		switch_page(self, (self->image = surface)); +	} +} +  void  fiv_view_command(FivView *self, FivViewCommand command)  { @@ -1187,6 +1225,12 @@ fiv_view_command(FivView *self, FivViewCommand command)  		g_object_notify_by_pspec(  			G_OBJECT(self), view_properties[PROP_CHECKERBOARD]);  		gtk_widget_queue_draw(widget); +	break; case FIV_VIEW_COMMAND_TOGGLE_ENHANCE: +		self->enhance = !self->enhance; +		g_object_notify_by_pspec( +			G_OBJECT(self), view_properties[PROP_ENHANCE]); +		swap_enhanced_image(self); +  	break; case FIV_VIEW_COMMAND_PRINT:  		print(self);  	break; case FIV_VIEW_COMMAND_SAVE_PAGE: @@ -43,6 +43,7 @@ typedef enum _FivViewCommand {  	FIV_VIEW_COMMAND_TOGGLE_FILTER,  	FIV_VIEW_COMMAND_TOGGLE_CHECKERBOARD, +	FIV_VIEW_COMMAND_TOGGLE_ENHANCE,  	FIV_VIEW_COMMAND_PRINT,  	FIV_VIEW_COMMAND_SAVE_PAGE,  	FIV_VIEW_COMMAND_INFO, diff --git a/jpeg-quantsmooth b/jpeg-quantsmooth new file mode 160000 +Subproject c86c6418ea6c827513d206694847033f9ca5015 diff --git a/meson.build b/meson.build index 0cabcc4..de58bd5 100644 --- a/meson.build +++ b/meson.build @@ -28,6 +28,7 @@ gdkpixbuf = dependency('gdk-pixbuf-2.0', required : get_option('gdk-pixbuf'))  dependencies = [  	dependency('gtk+-3.0'),  	dependency('libturbojpeg'), +	dependency('libjpeg', required : get_option('jpeg-qs')),  	dependency('spng', version : '>=0.7.0',  		default_options: 'default_library=static'),  	dependency('pixman-1'), @@ -47,6 +48,8 @@ dependencies = [  conf = configuration_data()  conf.set_quoted('PROJECT_NAME', meson.project_name())  conf.set_quoted('PROJECT_VERSION', meson.project_version()) +# TODO(p): Wrap it in a Meson subproject, try to enable SIMD. +conf.set('HAVE_JPEG_QS', get_option('jpeg-qs').enabled())  conf.set('HAVE_LIBRAW', libraw.found())  conf.set('HAVE_LIBRSVG', librsvg.found())  conf.set('HAVE_XCURSOR', xcursor.found()) diff --git a/meson_options.txt b/meson_options.txt index 97e393b..5318848 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -1,3 +1,5 @@ +option('jpeg-qs', type : 'feature', value : 'enabled', +	description : 'Build with JPEG Quant Smooth integration')  option('libraw', type : 'feature', value : 'auto',  	description : 'Build with raw photo support, requires LibRaw')  option('librsvg', type : 'feature', value : 'auto', diff --git a/resources/heal-symbolic.svg b/resources/heal-symbolic.svg new file mode 100644 index 0000000..4d488c2 --- /dev/null +++ b/resources/heal-symbolic.svg @@ -0,0 +1,107 @@ +<?xml version="1.0" encoding="UTF-8"?> +<svg height="16px" viewBox="0 0 16 16" width="16px" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"> +    <filter id="a" height="100%" width="100%" x="0%" y="0%"> +        <feColorMatrix in="SourceGraphic" type="matrix" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0"/> +    </filter> +    <mask id="b"> +        <g filter="url(#a)"> +            <path d="m 0 0 h 16 v 16 h -16 z" fill-opacity="0.3"/> +        </g> +    </mask> +    <clipPath id="c"> +        <path d="m 0 0 h 1600 v 1200 h -1600 z"/> +    </clipPath> +    <mask id="d"> +        <g filter="url(#a)"> +            <path d="m 0 0 h 16 v 16 h -16 z" fill-opacity="0.05"/> +        </g> +    </mask> +    <clipPath id="e"> +        <path d="m 0 0 h 1600 v 1200 h -1600 z"/> +    </clipPath> +    <mask id="f"> +        <g filter="url(#a)"> +            <path d="m 0 0 h 16 v 16 h -16 z" fill-opacity="0.05"/> +        </g> +    </mask> +    <clipPath id="g"> +        <path d="m 0 0 h 1600 v 1200 h -1600 z"/> +    </clipPath> +    <mask id="h"> +        <g filter="url(#a)"> +            <path d="m 0 0 h 16 v 16 h -16 z" fill-opacity="0.05"/> +        </g> +    </mask> +    <clipPath id="i"> +        <path d="m 0 0 h 1600 v 1200 h -1600 z"/> +    </clipPath> +    <mask id="j"> +        <g filter="url(#a)"> +            <path d="m 0 0 h 16 v 16 h -16 z" fill-opacity="0.05"/> +        </g> +    </mask> +    <clipPath id="k"> +        <path d="m 0 0 h 1600 v 1200 h -1600 z"/> +    </clipPath> +    <mask id="l"> +        <g filter="url(#a)"> +            <path d="m 0 0 h 16 v 16 h -16 z" fill-opacity="0.05"/> +        </g> +    </mask> +    <clipPath id="m"> +        <path d="m 0 0 h 1600 v 1200 h -1600 z"/> +    </clipPath> +    <mask id="n"> +        <g filter="url(#a)"> +            <path d="m 0 0 h 16 v 16 h -16 z" fill-opacity="0.05"/> +        </g> +    </mask> +    <clipPath id="o"> +        <path d="m 0 0 h 1600 v 1200 h -1600 z"/> +    </clipPath> +    <mask id="p"> +        <g filter="url(#a)"> +            <path d="m 0 0 h 16 v 16 h -16 z" fill-opacity="0.3"/> +        </g> +    </mask> +    <clipPath id="q"> +        <path d="m 0 0 h 1600 v 1200 h -1600 z"/> +    </clipPath> +    <mask id="r"> +        <g filter="url(#a)"> +            <path d="m 0 0 h 16 v 16 h -16 z" fill-opacity="0.5"/> +        </g> +    </mask> +    <clipPath id="s"> +        <path d="m 0 0 h 1600 v 1200 h -1600 z"/> +    </clipPath> +    <g clip-path="url(#c)" mask="url(#b)" transform="matrix(1 0 0 1 -96 -756)"> +        <path d="m 562.460938 212.058594 h 10.449218 c -1.183594 0.492187 -1.296875 2.460937 0 3 h -10.449218 z m 0 0" fill="#2e3436"/> +    </g> +    <g clip-path="url(#e)" mask="url(#d)" transform="matrix(1 0 0 1 -96 -756)"> +        <path d="m 16 748 h 1 v 1 h -1 z m 0 0" fill="#2e3436" fill-rule="evenodd"/> +    </g> +    <g clip-path="url(#g)" mask="url(#f)" transform="matrix(1 0 0 1 -96 -756)"> +        <path d="m 17 747 h 1 v 1 h -1 z m 0 0" fill="#2e3436" fill-rule="evenodd"/> +    </g> +    <g clip-path="url(#i)" mask="url(#h)" transform="matrix(1 0 0 1 -96 -756)"> +        <path d="m 18 750 h 1 v 1 h -1 z m 0 0" fill="#2e3436" fill-rule="evenodd"/> +    </g> +    <g clip-path="url(#k)" mask="url(#j)" transform="matrix(1 0 0 1 -96 -756)"> +        <path d="m 16 750 h 1 v 1 h -1 z m 0 0" fill="#2e3436" fill-rule="evenodd"/> +    </g> +    <g clip-path="url(#m)" mask="url(#l)" transform="matrix(1 0 0 1 -96 -756)"> +        <path d="m 17 751 h 1 v 1 h -1 z m 0 0" fill="#2e3436" fill-rule="evenodd"/> +    </g> +    <g clip-path="url(#o)" mask="url(#n)" transform="matrix(1 0 0 1 -96 -756)"> +        <path d="m 19 751 h 1 v 1 h -1 z m 0 0" fill="#2e3436" fill-rule="evenodd"/> +    </g> +    <path d="m 8.4375 1.480469 c -0.21875 0.179687 -0.410156 0.410156 -0.546875 0.675781 l -4.65625 9.125 c -0.542969 1.070312 -0.109375 2.386719 0.96875 2.960938 l 0.945313 0.496093 c 1.074218 0.570313 2.371093 0.175781 2.914062 -0.894531 l 4.660156 -9.121094 c 0.542969 -1.070312 0.109375 -2.390625 -0.96875 -2.960937 l -0.945312 -0.496094 c -0.804688 -0.429687 -1.726563 -0.320313 -2.371094 0.214844 z m -1.472656 4.464843 c 0.332031 -0.246093 0.8125 -0.179687 1.0625 0.140626 c 0.253906 0.320312 0.1875 0.789062 -0.140625 1.035156 c -0.332031 0.246094 -0.8125 0.183594 -1.0625 -0.140625 c -0.253907 -0.320313 -0.1875 -0.789063 0.140625 -1.035157 z m -0.992188 1.980469 c 0.328125 -0.246093 0.808594 -0.183593 1.0625 0.136719 c 0.25 0.324219 0.1875 0.792969 -0.144531 1.039062 c -0.328125 0.246094 -0.808594 0.179688 -1.0625 -0.140624 c -0.25 -0.320313 -0.1875 -0.789063 0.144531 -1.035157 z m 3.023438 -1.011719 c 0.328125 -0.246093 0.808594 -0.183593 1.0625 0.140626 c 0.25 0.320312 0.1875 0.789062 -0.144532 1.035156 c -0.328124 0.246094 -0.808593 0.183594 -1.0625 -0.140625 c -0.25 -0.320313 -0.1875 -0.789063 0.144532 -1.035157 z m -0.992188 1.980469 c 0.328125 -0.246093 0.808594 -0.183593 1.0625 0.136719 c 0.25 0.324219 0.183594 0.792969 -0.144531 1.039062 c -0.328125 0.242188 -0.8125 0.179688 -1.0625 -0.140624 c -0.25 -0.320313 -0.1875 -0.789063 0.144531 -1.035157 z m 0 0" fill="#2e3436"/> +    <path d="m 3.59375 3 c -0.839844 -0.035156 -1.609375 0.4375 -1.96875 1.28125 l -0.4375 1 c -0.480469 1.128906 0 2.46875 1.09375 3 l 0.875 0.40625 l 2.4375 -4.90625 l -1.15625 -0.5625 c -0.273438 -0.132812 -0.5625 -0.207031 -0.84375 -0.21875 z m 9.28125 4.28125 l -2.46875 4.90625 l 1.21875 0.59375 c 1.09375 0.53125 2.332031 0.066406 2.8125 -1.0625 l 0.4375 -1 c 0.480469 -1.128906 0 -2.46875 -1.09375 -3 z m 0 0" fill="#2e3436" fill-opacity="0.501961"/> +    <g clip-path="url(#q)" mask="url(#p)" transform="matrix(1 0 0 1 -96 -756)"> +        <path d="m 136 776 v 7 h 7 v -7 z m 0 0" fill="#2e3436"/> +    </g> +    <g clip-path="url(#s)" mask="url(#r)" transform="matrix(1 0 0 1 -96 -756)"> +        <path d="m 219 758 h 3 v 12 h -3 z m 0 0" fill="#2e3436"/> +    </g> +</svg> diff --git a/resources/resources.gresource.xml b/resources/resources.gresource.xml index 1a99827..45439b6 100644 --- a/resources/resources.gresource.xml +++ b/resources/resources.gresource.xml @@ -5,5 +5,6 @@  		<file preprocess="xml-stripblanks">funnel-symbolic.svg</file>  		<file preprocess="xml-stripblanks">blend-tool-symbolic.svg</file>  		<file preprocess="xml-stripblanks">checkerboard-symbolic.svg</file> +		<file preprocess="xml-stripblanks">heal-symbolic.svg</file>  	</gresource>  </gresources>  | 
