aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPřemysl Eric Janouch <p@janouch.name>2021-12-26 19:41:42 +0100
committerPřemysl Eric Janouch <p@janouch.name>2021-12-27 21:51:01 +0100
commit336053f24d40bc5c350cad285a8d5e146c09d321 (patch)
treec6128561d31f351ffe311ff5dbbe987efd5e0e13
parent2f993502fc6584a5877b300a1353cabf58c4e0e9 (diff)
downloadfiv-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.adoc12
-rw-r--r--fastiv.c33
-rw-r--r--fiv-browser.c123
-rw-r--r--fiv-io.c196
-rw-r--r--fiv-io.h8
-rw-r--r--fiv-view.c40
-rw-r--r--meson.build16
-rw-r--r--meson_options.txt2
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
diff --git a/fastiv.c b/fastiv.c
index 9c0b11f..b253df1 100644
--- a/fastiv.c
+++ b/fastiv.c
@@ -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);
}
diff --git a/fiv-io.c b/fiv-io.c
index 468f223..97d10ed 100644
--- a/fiv-io.c
+++ b/fiv-io.c
@@ -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);
diff --git a/fiv-io.h b/fiv-io.h
index 69e3c90..ae39601 100644
--- a/fiv-io.h
+++ b/fiv-io.h
@@ -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);
diff --git a/fiv-view.c b/fiv-view.c
index d03e9ee..467dd0f 100644
--- a/fiv-view.c
+++ b/fiv-view.c
@@ -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',