diff options
author | Přemysl Eric Janouch <p@janouch.name> | 2021-12-26 19:41:42 +0100 |
---|---|---|
committer | Přemysl Eric Janouch <p@janouch.name> | 2021-12-27 21:51:01 +0100 |
commit | 336053f24d40bc5c350cad285a8d5e146c09d321 (patch) | |
tree | c6128561d31f351ffe311ff5dbbe987efd5e0e13 | |
parent | 2f993502fc6584a5877b300a1353cabf58c4e0e9 (diff) | |
download | fiv-336053f24d40bc5c350cad285a8d5e146c09d321.tar.gz fiv-336053f24d40bc5c350cad285a8d5e146c09d321.tar.xz fiv-336053f24d40bc5c350cad285a8d5e146c09d321.zip |
Implement trivial wide thumbnail production
Also make libwebp a required dependency.
-rw-r--r-- | README.adoc | 12 | ||||
-rw-r--r-- | fastiv.c | 33 | ||||
-rw-r--r-- | fiv-browser.c | 123 | ||||
-rw-r--r-- | fiv-io.c | 196 | ||||
-rw-r--r-- | fiv-io.h | 8 | ||||
-rw-r--r-- | fiv-view.c | 40 | ||||
-rw-r--r-- | meson.build | 16 | ||||
-rw-r--r-- | meson_options.txt | 2 |
8 files changed, 354 insertions, 76 deletions
diff --git a/README.adoc b/README.adoc index f133222..e1689f5 100644 --- a/README.adoc +++ b/README.adoc @@ -1,9 +1,9 @@ fastiv ====== -'fastiv' is a fast image viewer, supporting BMP, PNG, GIF, JPEG, and optionally -raw photos, HEIC, AVIF, WebP, SVG, X11 cursors and TIFF, or whatever gdk-pixbuf -loads. +'fastiv' is a fast image viewer, directly supporting BMP, PNG, GIF, JPEG, WebP, +and optionally raw photos, HEIC, AVIF, SVG, X11 cursors and TIFF, +or whatever gdk-pixbuf loads. Its development status can be summarized as '`beta`'. E.g., certain GIFs animate wrong. @@ -25,9 +25,9 @@ Building and Running -------------------- Build dependencies: Meson, pkg-config + Runtime dependencies: gtk+-3.0, glib>=2.64, pixman-1, shared-mime-info, -spng>=0.7.0, libturbojpeg + -Optional dependencies: lcms2, LibRaw, librsvg-2.0, xcursor, libwebp, libheif, -libtiff, gdk-pixbuf-2.0, ExifTool +libturbojpeg, libwebp, spng>=0.7.0 + +Optional dependencies: lcms2, LibRaw, librsvg-2.0, xcursor, libheif, libtiff, +gdk-pixbuf-2.0, ExifTool $ git clone --recursive https://git.janouch.name/p/fastiv.git $ meson builddir @@ -1191,7 +1191,7 @@ main(int argc, char *argv[]) { gboolean show_version = FALSE, show_supported_media_types = FALSE, browse = FALSE; - gchar **path_args = NULL; + gchar **path_args = NULL, *thumbnail_size = NULL; const GOptionEntry options[] = { {G_OPTION_REMAINING, 0, 0, G_OPTION_ARG_FILENAME_ARRAY, &path_args, NULL, "[FILE | DIRECTORY]"}, @@ -1201,6 +1201,9 @@ main(int argc, char *argv[]) {"browse", 0, G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_NONE, &browse, "Start in filesystem browsing mode", NULL}, + {"thumbnail", 0, G_OPTION_FLAG_IN_MAIN, + G_OPTION_ARG_STRING, &thumbnail_size, + "Generate thumbnails for an image, up to the given size", "SIZE"}, {"version", 'V', G_OPTION_FLAG_IN_MAIN, G_OPTION_ARG_NONE, &show_version, "Output version information and exit", NULL}, {}, @@ -1224,9 +1227,29 @@ main(int argc, char *argv[]) // NOTE: Firefox and Eye of GNOME both interpret multiple arguments // in a special way. This is problematic, because one-element lists // are unrepresentable. + // TODO(p): Require a command line switch, load a virtual folder. + // We may want or need to create a custom GVfs. // TODO(p): Complain to the user if there's more than one argument. // Best show the help message, if we can figure that out. const gchar *path_arg = path_args ? path_args[0] : NULL; + if (thumbnail_size) { + if (!path_arg) + exit_fatal("no path given"); + + FivIoThumbnailSize size = 0; + for (; size < FIV_IO_THUMBNAIL_SIZE_COUNT; size++) + if (!strcmp(fiv_io_thumbnail_sizes[size].thumbnail_spec_name, + thumbnail_size)) + break; + if (size >= FIV_IO_THUMBNAIL_SIZE_COUNT) + exit_fatal("unknown thumbnail size: %s", thumbnail_size); + + GFile *target = g_file_new_for_path(path_arg); + if (!fiv_io_produce_thumbnail(target, size, &error)) + exit_fatal("%s", error->message); + g_object_unref(target); + return 0; + } gtk_window_set_default_icon_name(PROJECT_NAME); gtk_icon_theme_add_resource_path( @@ -1254,7 +1277,6 @@ main(int argc, char *argv[]) gtk_box_pack_start(GTK_BOX(g.view_box), gtk_separator_new(GTK_ORIENTATION_VERTICAL), FALSE, FALSE, 0); gtk_box_pack_start(GTK_BOX(g.view_box), view_scroller, TRUE, TRUE, 0); - gtk_widget_show_all(g.view_box); g.browser_scroller = gtk_scrolled_window_new(NULL, NULL); g.browser = g_object_new(FIV_TYPE_BROWSER, NULL); @@ -1320,15 +1342,16 @@ main(int argc, char *argv[]) g_signal_connect(g.browser_paned, "button-press-event", G_CALLBACK(on_button_press_browser_paned), NULL); - // TODO(p): Can we not do it here separately? - gtk_widget_show_all(g.browser_paned); - g.stack = gtk_stack_new(); gtk_stack_set_transition_type( GTK_STACK(g.stack), GTK_STACK_TRANSITION_TYPE_NONE); gtk_container_add(GTK_CONTAINER(g.stack), g.view_box); gtk_container_add(GTK_CONTAINER(g.stack), g.browser_paned); + // TODO(p): Can we not do it here separately? + gtk_widget_show_all(g.view_box); + gtk_widget_show_all(g.browser_paned); + g.window = gtk_window_new(GTK_WINDOW_TOPLEVEL); g_signal_connect(g.window, "destroy", G_CALLBACK(gtk_main_quit), NULL); diff --git a/fiv-browser.c b/fiv-browser.c index 2dfd70e..788197c 100644 --- a/fiv-browser.c +++ b/fiv-browser.c @@ -18,6 +18,8 @@ #include <math.h> #include <pixman.h> +#include "config.h" + #include "fiv-browser.h" #include "fiv-io.h" #include "fiv-view.h" @@ -50,6 +52,10 @@ struct _FivBrowser { GArray *layouted_rows; ///< [Row] int selected; + GList *thumbnail_queue; ///< URIs to thumbnail + GSubprocess *thumbnailer; ///< A slave for the current queue head + GCancellable *thumbnail_cancel; ///< Cancellable handle + GdkCursor *pointer; ///< Cached pointer cursor cairo_surface_t *glow; ///< CAIRO_FORMAT_A8 mask int item_border_x; ///< L/R .item margin + border @@ -440,6 +446,115 @@ reload_thumbnails(FivBrowser *self) gtk_widget_queue_resize(GTK_WIDGET(self)); } +// --- Slave management -------------------------------------------------------- + +static void thumbnailer_step(FivBrowser *self); + +static void +thumbnailer_process(FivBrowser *self, const gchar *uri) +{ + // TODO(p): Consider using Entry pointers directly. + Entry *entry = NULL; + for (guint i = 0; i < self->entries->len; i++) { + Entry *e = &g_array_index(self->entries, Entry, i); + if (!g_strcmp0(e->uri, uri)) { + entry = e; + break; + } + } + if (!entry) { + g_warning("finished thumbnailing an unknown URI"); + return; + } + + entry_add_thumbnail(entry, self); + materialize_icon(self, entry); + gtk_widget_queue_resize(GTK_WIDGET(self)); +} + +static void +on_thumbnailer_ready(GObject *object, GAsyncResult *res, gpointer user_data) +{ + GSubprocess *subprocess = G_SUBPROCESS(object); + FivBrowser *self = FIV_BROWSER(user_data); + GError *error = NULL; + if (!g_subprocess_wait_check_finish(subprocess, res, &error)) { + g_warning("%s", error->message); + g_error_free(error); + } + + gboolean succeeded = g_subprocess_get_if_exited(self->thumbnailer) && + g_subprocess_get_exit_status(self->thumbnailer) == EXIT_SUCCESS; + g_clear_object(&self->thumbnailer); + if (!self->thumbnail_queue) { + g_warning("finished thumbnailing an unknown image"); + return; + } + + gchar *uri = self->thumbnail_queue->data; + self->thumbnail_queue = + g_list_delete_link(self->thumbnail_queue, self->thumbnail_queue); + if (succeeded) + thumbnailer_process(self, uri); + g_free(uri); + + // TODO(p): Eliminate high recursion depth with non-paths. + thumbnailer_step(self); +} + +static void +thumbnailer_step(FivBrowser *self) +{ + if (!self->thumbnail_queue) + return; + + GFile *file = g_file_new_for_uri(self->thumbnail_queue->data); + gchar *path = g_file_get_path(file); + g_object_unref(file); + + GError *error = NULL; + self->thumbnailer = g_subprocess_new(G_SUBPROCESS_FLAGS_NONE, &error, + PROJECT_NAME, "--thumbnail", + fiv_io_thumbnail_sizes[self->item_size].thumbnail_spec_name, "--", path, + NULL); + g_free(path); + + if (error) { + g_warning("%s", error->message); + g_error_free(error); + return; + } + + self->thumbnail_cancel = g_cancellable_new(); + g_subprocess_wait_check_async( + self->thumbnailer, self->thumbnail_cancel, on_thumbnailer_ready, self); +} + +static void +thumbnailer_launch(FivBrowser *self) +{ + if (self->thumbnailer) { + g_cancellable_cancel(self->thumbnail_cancel); + g_clear_object(&self->thumbnail_cancel); + + // Just let it exit on its own. + g_clear_object(&self->thumbnailer); + g_list_free_full(self->thumbnail_queue, g_free); + self->thumbnail_queue = NULL; + } + + // TODO(p): Also collect rescaled images. + GList *missing = NULL, *rescaled = NULL; + for (guint i = self->entries->len; i--; ) { + Entry *e = &g_array_index(self->entries, Entry, i); + if (e->icon) + missing = g_list_prepend(missing, g_strdup(e->uri)); + } + + self->thumbnail_queue = g_list_concat(missing, rescaled); + thumbnailer_step(self); +} + // --- Context menu------------------------------------------------------------- typedef struct _OpenContext { @@ -633,6 +748,13 @@ fiv_browser_finalize(GObject *gobject) cairo_surface_destroy(self->glow); g_clear_object(&self->pointer); + g_list_free_full(self->thumbnail_queue, g_free); + g_clear_object(&self->thumbnailer); + if (self->thumbnail_cancel) { + g_cancellable_cancel(self->thumbnail_cancel); + g_clear_object(&self->thumbnail_cancel); + } + G_OBJECT_CLASS(fiv_browser_parent_class)->finalize(gobject); } @@ -1076,4 +1198,5 @@ fiv_browser_load( g_array_sort(self->entries, entry_compare); reload_thumbnails(self); + thumbnailer_launch(self); } @@ -24,6 +24,10 @@ #include <spng.h> #include <turbojpeg.h> +#include <webp/decode.h> +#include <webp/demux.h> +#include <webp/encode.h> +#include <webp/mux.h> // Colour management must be handled before RGB conversions. #ifdef HAVE_LCMS2 @@ -48,12 +52,6 @@ #ifdef HAVE_XCURSOR #include <X11/Xcursor/Xcursor.h> #endif // HAVE_XCURSOR -#ifdef HAVE_LIBWEBP -#include <webp/decode.h> -#include <webp/demux.h> -#include <webp/encode.h> -#include <webp/mux.h> -#endif // HAVE_LIBWEBP #ifdef HAVE_LIBHEIF #include <libheif/heif.h> #endif // HAVE_LIBHEIF @@ -94,6 +92,7 @@ const char *fiv_io_supported_media_types[] = { "image/gif", "image/png", "image/jpeg", + "image/webp", #ifdef HAVE_LIBRAW "image/x-dcraw", #endif // HAVE_LIBRAW @@ -103,9 +102,6 @@ const char *fiv_io_supported_media_types[] = { #ifdef HAVE_XCURSOR "image/x-xcursor", #endif // HAVE_XCURSOR -#ifdef HAVE_LIBWEBP - "image/webp", -#endif // HAVE_LIBWEBP #ifdef HAVE_LIBHEIF "image/heic", "image/heif", @@ -1553,7 +1549,6 @@ open_xcursor(const gchar *data, gsize len, GError **error) } #endif // HAVE_XCURSOR -------------------------------------------------------- -#ifdef HAVE_LIBWEBP //--------------------------------------------------------- static cairo_surface_t * load_libwebp_nonanimated(WebPDecoderConfig *config, const WebPData *wd, @@ -1763,7 +1758,6 @@ fail: return result; } -#endif // HAVE_LIBWEBP -------------------------------------------------------- #ifdef HAVE_LIBHEIF //--------------------------------------------------------- static cairo_surface_t * @@ -2355,6 +2349,14 @@ fiv_io_open_from_data(const char *data, size_t len, const gchar *path, : open_libjpeg_turbo(data, len, profile, error); break; default: + // TODO(p): https://github.com/google/wuffs/commit/4c04ac1 + if ((surface = open_libwebp(data, len, path, profile, error))) + break; + if (error) { + g_debug("%s", (*error)->message); + g_clear_error(error); + } + #ifdef HAVE_LIBRAW // --------------------------------------------------------- if ((surface = open_libraw(data, len, error))) break; @@ -2384,15 +2386,6 @@ fiv_io_open_from_data(const char *data, size_t len, const gchar *path, g_clear_error(error); } #endif // HAVE_XCURSOR -------------------------------------------------------- -#ifdef HAVE_LIBWEBP //--------------------------------------------------------- - // TODO(p): https://github.com/google/wuffs/commit/4c04ac1 - if ((surface = open_libwebp(data, len, path, profile, error))) - break; - if (error) { - g_debug("%s", (*error)->message); - g_clear_error(error); - } -#endif // HAVE_LIBWEBP -------------------------------------------------------- #ifdef HAVE_LIBHEIF //--------------------------------------------------------- if ((surface = open_libheif(data, len, path, profile, error))) break; @@ -2443,7 +2436,6 @@ fiv_io_open_from_data(const char *data, size_t len, const gchar *path, } // --- Export ------------------------------------------------------------------ -#ifdef HAVE_LIBWEBP static WebPData encode_lossless_webp(cairo_surface_t *surface) @@ -2603,7 +2595,6 @@ fiv_io_save(cairo_surface_t *page, cairo_surface_t *frame, FivIoProfile target, return ok; } -#endif // HAVE_LIBWEBP // --- Metadata ---------------------------------------------------------------- FivIoOrientation @@ -2744,6 +2735,10 @@ fiv_io_save_metadata(cairo_surface_t *page, const gchar *path, GError **error) // --- Thumbnails -------------------------------------------------------------- +#ifndef __linux__ +#define st_mtim st_mtimespec +#endif // ! __linux__ + GType fiv_io_thumbnail_size_get_type(void) { @@ -2766,11 +2761,143 @@ FivIoThumbnailSizeInfo FIV_IO_THUMBNAIL_SIZES(XX)}; #undef XX +// TODO(p): Put the constant in a header file, share with fiv-browser.c. +static const double g_wide_thumbnail_factor = 2; + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -#ifndef __linux__ -#define st_mtim st_mtimespec -#endif // ! __linux__ +// In principle similar to rescale_thumbnail() from fiv-browser.c. +static cairo_surface_t * +rescale_thumbnail(cairo_surface_t *thumbnail, double row_height) +{ + int width = cairo_image_surface_get_width(thumbnail); + int height = cairo_image_surface_get_height(thumbnail); + + double scale_x = 1; + double scale_y = 1; + if (width > g_wide_thumbnail_factor * height) { + scale_x = g_wide_thumbnail_factor * row_height / width; + scale_y = round(scale_x * height) / height; + } else { + scale_y = row_height / height; + scale_x = round(scale_y * width) / width; + } + if (scale_x == 1 && scale_y == 1) + return cairo_surface_reference(thumbnail); + + // TODO(p): Don't always include an alpha channel. + cairo_format_t cairo_format = CAIRO_FORMAT_ARGB32; + + int projected_width = round(scale_x * width); + int projected_height = round(scale_y * height); + cairo_surface_t *scaled = cairo_image_surface_create( + cairo_format, projected_width, projected_height); + + cairo_t *cr = cairo_create(scaled); + cairo_scale(cr, scale_x, scale_y); + + cairo_set_source_surface(cr, thumbnail, 0, 0); + cairo_pattern_t *pattern = cairo_get_source(cr); + cairo_pattern_set_filter(pattern, CAIRO_FILTER_BEST); + cairo_pattern_set_extend(pattern, CAIRO_EXTEND_PAD); + + cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE); + cairo_paint(cr); + cairo_destroy(cr); + return scaled; +} + +gboolean +fiv_io_produce_thumbnail(GFile *target, FivIoThumbnailSize size, GError **error) +{ + g_return_val_if_fail(size >= FIV_IO_THUMBNAIL_SIZE_MIN && + size <= FIV_IO_THUMBNAIL_SIZE_MAX, FALSE); + + // Local files only, at least for now. + gchar *path = g_file_get_path(target); + if (!path) + return FALSE; + + GMappedFile *mf = g_mapped_file_new(path, FALSE, error); + if (!mf) { + g_free(path); + return FALSE; + } + + GStatBuf st = {}; + if (g_stat(path, &st)) { + set_error(error, g_strerror(errno)); + g_free(path); + return FALSE; + } + + // TODO(p): Add a flag to avoid loading all pages and frames. + FivIoProfile sRGB = fiv_io_profile_new_sRGB(); + cairo_surface_t *surface = + fiv_io_open_from_data(g_mapped_file_get_contents(mf), + g_mapped_file_get_length(mf), path, sRGB, FALSE, error); + + g_free(path); + g_mapped_file_unref(mf); + if (sRGB) + fiv_io_profile_free(sRGB); + if (!surface) + return FALSE; + + // Boilerplate copied from fiv_io_lookup_thumbnail(). + gchar *uri = g_file_get_uri(target); + gchar *sum = g_compute_checksum_for_string(G_CHECKSUM_MD5, uri, -1); + gchar *cache_dir = get_xdg_home_dir("XDG_CACHE_HOME", ".cache"); + + for (int use = size; use >= FIV_IO_THUMBNAIL_SIZE_MIN; use--) { + cairo_surface_t *scaled = + rescale_thumbnail(surface, fiv_io_thumbnail_sizes[use].size); + gchar *path = g_strdup_printf("%s/thumbnails/wide-%s/%s.webp", + cache_dir, fiv_io_thumbnail_sizes[use].thumbnail_spec_name, sum); + + GError *e = NULL; + while (!fiv_io_save(scaled, scaled, NULL, path, &e)) { + bool missing_parents = + e->domain == G_FILE_ERROR && e->code == G_FILE_ERROR_NOENT; + g_debug("%s: %s", path, e->message); + g_clear_error(&e); + if (!missing_parents) + break; + + gchar *dirname = g_path_get_dirname(path); + int err = g_mkdir_with_parents(dirname, 0755); + if (err) + g_debug("%s: %s", dirname, g_strerror(errno)); + + g_free(dirname); + if (err) + break; + } + + // It would be possible to create square thumbnails as well, + // but it seems like wasted effort. + cairo_surface_destroy(scaled); + g_free(path); + } + + g_free(cache_dir); + g_free(sum); + g_free(uri); + cairo_surface_destroy(surface); + return TRUE; +} + +static cairo_surface_t * +read_wide_thumbnail( + const gchar *path, const gchar *uri, time_t mtime, GError **error) +{ + // TODO(p): Validate. + (void) uri; + (void) mtime; + return fiv_io_open(path, NULL, FALSE, error); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - static int // tri-state check_spng_thumbnail_texts(struct spng_text *texts, uint32_t texts_len, @@ -2935,8 +3062,20 @@ fiv_io_lookup_thumbnail(GFile *target, FivIoThumbnailSize size) if (use > FIV_IO_THUMBNAIL_SIZE_MAX) use = FIV_IO_THUMBNAIL_SIZE_MAX - i; - gchar *path = g_strdup_printf("%s/thumbnails/%s/%s.png", cache_dir, - fiv_io_thumbnail_sizes[use].thumbnail_spec_name, sum); + const char *name = fiv_io_thumbnail_sizes[use].thumbnail_spec_name; + gchar *wide = g_strdup_printf( + "%s/thumbnails/wide-%s/%s.webp", cache_dir, name, sum); + result = read_wide_thumbnail(wide, uri, st.st_mtim.tv_sec, &error); + if (error) { + g_debug("%s: %s", wide, error->message); + g_clear_error(&error); + } + g_free(wide); + if (result) + break; + + gchar *path = + g_strdup_printf("%s/thumbnails/%s/%s.png", cache_dir, name, sum); result = read_spng_thumbnail(path, uri, st.st_mtim.tv_sec, &error); if (error) { g_debug("%s: %s", path, error->message); @@ -2947,6 +3086,9 @@ fiv_io_lookup_thumbnail(GFile *target, FivIoThumbnailSize size) break; } + // TODO(p): We can definitely extract embedded thumbnails, but it should be + // done as a separate stage--the file may be stored on a slow device. + g_free(cache_dir); g_free(sum); g_free(uri); @@ -76,7 +76,7 @@ int fiv_io_filecmp(GFile *f1, GFile *f2); // --- Export ------------------------------------------------------------------ -/// Requires libwebp. +/// Saves the page as a lossless WebP still picture or animation. /// If no exact frame is specified, this potentially creates an animation. gboolean fiv_io_save(cairo_surface_t *page, cairo_surface_t *frame, FivIoProfile target, const gchar *path, GError **error); @@ -131,5 +131,11 @@ typedef struct _FivIoThumbnailSizeInfo { extern FivIoThumbnailSizeInfo fiv_io_thumbnail_sizes[FIV_IO_THUMBNAIL_SIZE_COUNT]; +/// Generates wide thumbnails of up to the specified size, saves them in cache. +gboolean fiv_io_produce_thumbnail( + GFile *target, FivIoThumbnailSize size, GError **error); + +/// Retrieves a thumbnail of the most appropriate quality and resolution +/// for the target file. cairo_surface_t *fiv_io_lookup_thumbnail( GFile *target, FivIoThumbnailSize size); @@ -797,18 +797,7 @@ save_as(FivView *self, cairo_surface_t *frame) "_Cancel", GTK_RESPONSE_CANCEL, "_Save", GTK_RESPONSE_ACCEPT, NULL); GtkFileChooser *chooser = GTK_FILE_CHOOSER(dialog); - - // TODO(p): Consider a hard dependency on libwebp, or clean this up. -#ifdef HAVE_LIBWEBP - // This is the best general format: supports lossless encoding, animations, - // alpha channel, and Exif and ICC profile metadata. - // PNG is another viable option, but sPNG can't do APNG, Wuffs can't save, - // and libpng is a pain in the arse. - GtkFileFilter *webp_filter = gtk_file_filter_new(); - gtk_file_filter_add_mime_type(webp_filter, "image/webp"); - gtk_file_filter_add_pattern(webp_filter, "*.webp"); - gtk_file_filter_set_name(webp_filter, "Lossless WebP (*.webp)"); - gtk_file_chooser_add_filter(chooser, webp_filter); + gtk_file_chooser_set_do_overwrite_confirmation(chooser, TRUE); // Note that GTK+'s save dialog is too stupid to automatically change // the extension when user changes the filter. Presumably, @@ -819,14 +808,21 @@ save_as(FivView *self, cairo_surface_t *frame) g_free(basename); gtk_file_chooser_set_current_name(chooser, name); g_free(name); -#endif // HAVE_LIBWEBP - - gtk_file_chooser_set_do_overwrite_confirmation(chooser, TRUE); gchar *dirname = g_path_get_dirname(self->path); gtk_file_chooser_set_current_folder(chooser, dirname); g_free(dirname); + // This is the best general format: supports lossless encoding, animations, + // alpha channel, and Exif and ICC profile metadata. + // PNG is another viable option, but sPNG can't do APNG, Wuffs can't save, + // and libpng is a pain in the arse. + GtkFileFilter *webp_filter = gtk_file_filter_new(); + gtk_file_filter_add_mime_type(webp_filter, "image/webp"); + gtk_file_filter_add_pattern(webp_filter, "*.webp"); + gtk_file_filter_set_name(webp_filter, "Lossless WebP (*.webp)"); + gtk_file_chooser_add_filter(chooser, webp_filter); + // The format is supported by Exiv2 and ExifTool. // This is mostly a developer tool. GtkFileFilter *exv_filter = gtk_file_filter_new(); @@ -835,22 +831,16 @@ save_as(FivView *self, cairo_surface_t *frame) gtk_file_filter_set_name(exv_filter, "Exiv2 metadata (*.exv)"); gtk_file_chooser_add_filter(chooser, exv_filter); + GError *error = NULL; switch (gtk_dialog_run(GTK_DIALOG(dialog))) { gchar *path; case GTK_RESPONSE_ACCEPT: path = gtk_file_chooser_get_filename(chooser); - - GError *error = NULL; -#ifdef HAVE_LIBWEBP - if (gtk_file_chooser_get_filter(chooser) == webp_filter) - fiv_io_save(self->page, frame, target, path, &error); - else -#endif // HAVE_LIBWEBP - fiv_io_save_metadata(self->page, path, &error); - if (error) + if (!(gtk_file_chooser_get_filter(chooser) == webp_filter + ? fiv_io_save(self->page, frame, target, path, &error) + : fiv_io_save_metadata(self->page, path, &error))) show_error_dialog(window, error); g_free(path); - // Fall-through. default: gtk_widget_destroy(dialog); diff --git a/meson.build b/meson.build index f3fae8c..ecad76f 100644 --- a/meson.build +++ b/meson.build @@ -14,15 +14,11 @@ if get_option('buildtype').startswith('debug') add_project_link_arguments(flags, language : ['c']) endif -# TODO(p): Use libraw_r later, when we start parallelizing/preloading. lcms2 = dependency('lcms2', required : get_option('lcms2')) +# Note that only libraw_r is thread-safe, but we'll just run it out-of process. libraw = dependency('libraw', required : get_option('libraw')) librsvg = dependency('librsvg-2.0', required : get_option('librsvg')) xcursor = dependency('xcursor', required : get_option('xcursor')) -libwebp = dependency('libwebp', required : get_option('libwebp')) -libwebpdemux = dependency('libwebpdemux', required : get_option('libwebp')) -libwebpdecoder = dependency('libwebpdecoder', required : get_option('libwebp')) -libwebpmux = dependency('libwebpmux', required : get_option('libwebp')) libheif = dependency('libheif', required : get_option('libheif')) libtiff = dependency('libtiff-4', required : get_option('libtiff')) gdkpixbuf = dependency('gdk-pixbuf-2.0', required : get_option('gdk-pixbuf')) @@ -32,6 +28,11 @@ dependencies = [ dependency('libturbojpeg'), dependency('libjpeg', required : get_option('jpeg-qs')), + dependency('libwebp'), + dependency('libwebpdemux'), + dependency('libwebpdecoder'), + dependency('libwebpmux'), + # https://github.com/google/wuffs/issues/58 dependency('spng', version : '>=0.7.0', default_options: 'default_library=static'), @@ -39,10 +40,6 @@ dependencies = [ libraw, librsvg, xcursor, - libwebp, - libwebpdemux, - libwebpdecoder, - libwebpmux, libheif, libtiff, gdkpixbuf, @@ -58,7 +55,6 @@ conf.set('HAVE_LCMS2', lcms2.found()) conf.set('HAVE_LIBRAW', libraw.found()) conf.set('HAVE_LIBRSVG', librsvg.found()) conf.set('HAVE_XCURSOR', xcursor.found()) -conf.set('HAVE_LIBWEBP', libwebp.found()) conf.set('HAVE_LIBHEIF', libheif.found()) conf.set('HAVE_LIBTIFF', libtiff.found()) conf.set('HAVE_GDKPIXBUF', gdkpixbuf.found()) diff --git a/meson_options.txt b/meson_options.txt index e8c6f7d..1ecc87b 100644 --- a/meson_options.txt +++ b/meson_options.txt @@ -8,8 +8,6 @@ option('librsvg', type : 'feature', value : 'auto', description : 'Build with SVG support, requires librsvg') option('xcursor', type : 'feature', value : 'auto', description : 'Build with Xcursor support, requires libXcursor') -option('libwebp', type : 'feature', value : 'auto', - description : 'Build with WEBP support, requires libwebp') option('libheif', type : 'feature', value : 'auto', description : 'Build with HEIF/AVIF support, requires libheif') option('libtiff', type : 'feature', value : 'auto', |