Implement trivial wide thumbnail production

Also make libwebp a required dependency.
This commit is contained in:
Přemysl Eric Janouch 2021-12-26 19:41:42 +01:00
parent 2f993502fc
commit 336053f24d
Signed by: p
GPG Key ID: A0420B94F92B9493
8 changed files with 354 additions and 76 deletions

View File

@ -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

View File

@ -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);

View File

@ -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);
}

196
fiv-io.c
View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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())

View File

@ -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',