aboutsummaryrefslogtreecommitdiff
path: root/fiv-io.c
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 /fiv-io.c
parent2f993502fc6584a5877b300a1353cabf58c4e0e9 (diff)
downloadfiv-336053f24d40bc5c350cad285a8d5e146c09d321.tar.gz
fiv-336053f24d40bc5c350cad285a8d5e146c09d321.tar.xz
fiv-336053f24d40bc5c350cad285a8d5e146c09d321.zip
Implement trivial wide thumbnail production
Also make libwebp a required dependency.
Diffstat (limited to 'fiv-io.c')
-rw-r--r--fiv-io.c196
1 files changed, 169 insertions, 27 deletions
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);