diff options
Diffstat (limited to 'fiv-io.c')
-rw-r--r-- | fiv-io.c | 196 |
1 files changed, 169 insertions, 27 deletions
@@ -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); |