Implement trivial wide thumbnail production

Also make libwebp a required dependency.
This commit is contained in:
2021-12-26 19:41:42 +01:00
parent 2f993502fc
commit 336053f24d
8 changed files with 354 additions and 76 deletions

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