Move thumbnails into their own source file
And clean up identifiers.
This commit is contained in:
		
							
								
								
									
										23
									
								
								fastiv.c
									
									
									
									
									
								
							
							
						
						
									
										23
									
								
								fastiv.c
									
									
									
									
									
								
							@@ -31,6 +31,7 @@
 | 
				
			|||||||
#include "fiv-browser.h"
 | 
					#include "fiv-browser.h"
 | 
				
			||||||
#include "fiv-io.h"
 | 
					#include "fiv-io.h"
 | 
				
			||||||
#include "fiv-sidebar.h"
 | 
					#include "fiv-sidebar.h"
 | 
				
			||||||
 | 
					#include "fiv-thumbnail.h"
 | 
				
			||||||
#include "fiv-view.h"
 | 
					#include "fiv-view.h"
 | 
				
			||||||
#include "xdg.h"
 | 
					#include "xdg.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -644,12 +645,12 @@ on_open_location(G_GNUC_UNUSED GtkPlacesSidebar *sidebar, GFile *location,
 | 
				
			|||||||
static void
 | 
					static void
 | 
				
			||||||
on_toolbar_zoom(G_GNUC_UNUSED GtkButton *button, gpointer user_data)
 | 
					on_toolbar_zoom(G_GNUC_UNUSED GtkButton *button, gpointer user_data)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	FivIoThumbnailSize size = FIV_IO_THUMBNAIL_SIZE_COUNT;
 | 
						FivThumbnailSize size = FIV_THUMBNAIL_SIZE_COUNT;
 | 
				
			||||||
	g_object_get(g.browser, "thumbnail-size", &size, NULL);
 | 
						g_object_get(g.browser, "thumbnail-size", &size, NULL);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	size += (gintptr) user_data;
 | 
						size += (gintptr) user_data;
 | 
				
			||||||
	g_return_if_fail(size >= FIV_IO_THUMBNAIL_SIZE_MIN &&
 | 
						g_return_if_fail(size >= FIV_THUMBNAIL_SIZE_MIN &&
 | 
				
			||||||
		size <= FIV_IO_THUMBNAIL_SIZE_MAX);
 | 
							size <= FIV_THUMBNAIL_SIZE_MAX);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	g_object_set(g.browser, "thumbnail-size", size, NULL);
 | 
						g_object_set(g.browser, "thumbnail-size", size, NULL);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -658,10 +659,10 @@ static void
 | 
				
			|||||||
on_notify_thumbnail_size(
 | 
					on_notify_thumbnail_size(
 | 
				
			||||||
	GObject *object, GParamSpec *param_spec, G_GNUC_UNUSED gpointer user_data)
 | 
						GObject *object, GParamSpec *param_spec, G_GNUC_UNUSED gpointer user_data)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	FivIoThumbnailSize size = 0;
 | 
						FivThumbnailSize size = 0;
 | 
				
			||||||
	g_object_get(object, g_param_spec_get_name(param_spec), &size, NULL);
 | 
						g_object_get(object, g_param_spec_get_name(param_spec), &size, NULL);
 | 
				
			||||||
	gtk_widget_set_sensitive(g.plus, size < FIV_IO_THUMBNAIL_SIZE_MAX);
 | 
						gtk_widget_set_sensitive(g.plus, size < FIV_THUMBNAIL_SIZE_MAX);
 | 
				
			||||||
	gtk_widget_set_sensitive(g.minus, size > FIV_IO_THUMBNAIL_SIZE_MIN);
 | 
						gtk_widget_set_sensitive(g.minus, size > FIV_THUMBNAIL_SIZE_MIN);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void
 | 
					static void
 | 
				
			||||||
@@ -1236,16 +1237,16 @@ main(int argc, char *argv[])
 | 
				
			|||||||
		if (!path_arg)
 | 
							if (!path_arg)
 | 
				
			||||||
			exit_fatal("no path given");
 | 
								exit_fatal("no path given");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		FivIoThumbnailSize size = 0;
 | 
							FivThumbnailSize size = 0;
 | 
				
			||||||
		for (; size < FIV_IO_THUMBNAIL_SIZE_COUNT; size++)
 | 
							for (; size < FIV_THUMBNAIL_SIZE_COUNT; size++)
 | 
				
			||||||
			if (!strcmp(fiv_io_thumbnail_sizes[size].thumbnail_spec_name,
 | 
								if (!strcmp(fiv_thumbnail_sizes[size].thumbnail_spec_name,
 | 
				
			||||||
					thumbnail_size))
 | 
										thumbnail_size))
 | 
				
			||||||
				break;
 | 
									break;
 | 
				
			||||||
		if (size >= FIV_IO_THUMBNAIL_SIZE_COUNT)
 | 
							if (size >= FIV_THUMBNAIL_SIZE_COUNT)
 | 
				
			||||||
			exit_fatal("unknown thumbnail size: %s", thumbnail_size);
 | 
								exit_fatal("unknown thumbnail size: %s", thumbnail_size);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		GFile *target = g_file_new_for_path(path_arg);
 | 
							GFile *target = g_file_new_for_path(path_arg);
 | 
				
			||||||
		if (!fiv_io_produce_thumbnail(target, size, &error))
 | 
							if (!fiv_thumbnail_produce(target, size, &error))
 | 
				
			||||||
			exit_fatal("%s", error->message);
 | 
								exit_fatal("%s", error->message);
 | 
				
			||||||
		g_object_unref(target);
 | 
							g_object_unref(target);
 | 
				
			||||||
		return 0;
 | 
							return 0;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -22,6 +22,7 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
#include "fiv-browser.h"
 | 
					#include "fiv-browser.h"
 | 
				
			||||||
#include "fiv-io.h"
 | 
					#include "fiv-io.h"
 | 
				
			||||||
 | 
					#include "fiv-thumbnail.h"
 | 
				
			||||||
#include "fiv-view.h"
 | 
					#include "fiv-view.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// --- Widget ------------------------------------------------------------------
 | 
					// --- Widget ------------------------------------------------------------------
 | 
				
			||||||
@@ -43,7 +44,7 @@
 | 
				
			|||||||
struct _FivBrowser {
 | 
					struct _FivBrowser {
 | 
				
			||||||
	GtkWidget parent_instance;
 | 
						GtkWidget parent_instance;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	FivIoThumbnailSize item_size;       ///< Thumbnail size
 | 
						FivThumbnailSize item_size;         ///< Thumbnail size
 | 
				
			||||||
	int item_height;                    ///< Thumbnail height in pixels
 | 
						int item_height;                    ///< Thumbnail height in pixels
 | 
				
			||||||
	int item_spacing;                   ///< Space between items in pixels
 | 
						int item_spacing;                   ///< Space between items in pixels
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -303,8 +304,8 @@ rescale_thumbnail(cairo_surface_t *thumbnail, double row_height)
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	double scale_x = 1;
 | 
						double scale_x = 1;
 | 
				
			||||||
	double scale_y = 1;
 | 
						double scale_y = 1;
 | 
				
			||||||
	if (width > FIV_IO_WIDE_THUMBNAIL_COEFFICIENT * height) {
 | 
						if (width > FIV_THUMBNAIL_WIDE_COEFFICIENT * height) {
 | 
				
			||||||
		scale_x = FIV_IO_WIDE_THUMBNAIL_COEFFICIENT * row_height / width;
 | 
							scale_x = FIV_THUMBNAIL_WIDE_COEFFICIENT * row_height / width;
 | 
				
			||||||
		scale_y = round(scale_x * height) / height;
 | 
							scale_y = round(scale_x * height) / height;
 | 
				
			||||||
	} else {
 | 
						} else {
 | 
				
			||||||
		scale_y = row_height / height;
 | 
							scale_y = row_height / height;
 | 
				
			||||||
@@ -350,7 +351,7 @@ rescale_thumbnail(cairo_surface_t *thumbnail, double row_height)
 | 
				
			|||||||
	pixman_image_unref(dest);
 | 
						pixman_image_unref(dest);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	cairo_surface_set_user_data(
 | 
						cairo_surface_set_user_data(
 | 
				
			||||||
		scaled, &fiv_io_key_thumbnail_lq, (void *) (intptr_t) 1, NULL);
 | 
							scaled, &fiv_thumbnail_key_lq, (void *) (intptr_t) 1, NULL);
 | 
				
			||||||
	cairo_surface_destroy(thumbnail);
 | 
						cairo_surface_destroy(thumbnail);
 | 
				
			||||||
	cairo_surface_mark_dirty(scaled);
 | 
						cairo_surface_mark_dirty(scaled);
 | 
				
			||||||
	return scaled;
 | 
						return scaled;
 | 
				
			||||||
@@ -366,8 +367,7 @@ entry_add_thumbnail(gpointer data, gpointer user_data)
 | 
				
			|||||||
	FivBrowser *browser = FIV_BROWSER(user_data);
 | 
						FivBrowser *browser = FIV_BROWSER(user_data);
 | 
				
			||||||
	GFile *file = g_file_new_for_uri(self->uri);
 | 
						GFile *file = g_file_new_for_uri(self->uri);
 | 
				
			||||||
	self->thumbnail = rescale_thumbnail(
 | 
						self->thumbnail = rescale_thumbnail(
 | 
				
			||||||
		fiv_io_lookup_thumbnail(file, browser->item_size),
 | 
							fiv_thumbnail_lookup(file, browser->item_size), browser->item_height);
 | 
				
			||||||
		browser->item_height);
 | 
					 | 
				
			||||||
	if (self->thumbnail)
 | 
						if (self->thumbnail)
 | 
				
			||||||
		goto out;
 | 
							goto out;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -508,7 +508,7 @@ thumbnailer_next(FivBrowser *self)
 | 
				
			|||||||
	GError *error = NULL;
 | 
						GError *error = NULL;
 | 
				
			||||||
	self->thumbnailer = g_subprocess_new(G_SUBPROCESS_FLAGS_NONE, &error,
 | 
						self->thumbnailer = g_subprocess_new(G_SUBPROCESS_FLAGS_NONE, &error,
 | 
				
			||||||
		PROJECT_NAME, "--thumbnail",
 | 
							PROJECT_NAME, "--thumbnail",
 | 
				
			||||||
		fiv_io_thumbnail_sizes[self->item_size].thumbnail_spec_name, "--", path,
 | 
							fiv_thumbnail_sizes[self->item_size].thumbnail_spec_name, "--", path,
 | 
				
			||||||
		NULL);
 | 
							NULL);
 | 
				
			||||||
	g_free(path);
 | 
						g_free(path);
 | 
				
			||||||
	if (error) {
 | 
						if (error) {
 | 
				
			||||||
@@ -542,7 +542,7 @@ thumbnailer_start(FivBrowser *self)
 | 
				
			|||||||
	thumbnailer_abort(self);
 | 
						thumbnailer_abort(self);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// TODO(p): Leave out all paths containing .cache/thumbnails altogether.
 | 
						// TODO(p): Leave out all paths containing .cache/thumbnails altogether.
 | 
				
			||||||
	gchar *thumbnails_dir = fiv_io_get_thumbnail_root();
 | 
						gchar *thumbnails_dir = fiv_thumbnail_get_root();
 | 
				
			||||||
	GFile *thumbnails = g_file_new_for_path(thumbnails_dir);
 | 
						GFile *thumbnails = g_file_new_for_path(thumbnails_dir);
 | 
				
			||||||
	g_free(thumbnails_dir);
 | 
						g_free(thumbnails_dir);
 | 
				
			||||||
	GFile *current = g_file_new_for_path(self->path);
 | 
						GFile *current = g_file_new_for_path(self->path);
 | 
				
			||||||
@@ -558,7 +558,7 @@ thumbnailer_start(FivBrowser *self)
 | 
				
			|||||||
		if (entry->icon)
 | 
							if (entry->icon)
 | 
				
			||||||
			missing = g_list_prepend(missing, entry);
 | 
								missing = g_list_prepend(missing, entry);
 | 
				
			||||||
		else if (cairo_surface_get_user_data(
 | 
							else if (cairo_surface_get_user_data(
 | 
				
			||||||
			entry->thumbnail, &fiv_io_key_thumbnail_lq))
 | 
								entry->thumbnail, &fiv_thumbnail_key_lq))
 | 
				
			||||||
			lq = g_list_prepend(lq, entry);
 | 
								lq = g_list_prepend(lq, entry);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -778,14 +778,14 @@ fiv_browser_get_property(
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
static void
 | 
					static void
 | 
				
			||||||
set_item_size(FivBrowser *self, FivIoThumbnailSize size)
 | 
					set_item_size(FivBrowser *self, FivThumbnailSize size)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
	if (size < FIV_IO_THUMBNAIL_SIZE_MIN || size > FIV_IO_THUMBNAIL_SIZE_MAX)
 | 
						if (size < FIV_THUMBNAIL_SIZE_MIN || size > FIV_THUMBNAIL_SIZE_MAX)
 | 
				
			||||||
		return;
 | 
							return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (size != self->item_size) {
 | 
						if (size != self->item_size) {
 | 
				
			||||||
		self->item_size = size;
 | 
							self->item_size = size;
 | 
				
			||||||
		self->item_height = fiv_io_thumbnail_sizes[self->item_size].size;
 | 
							self->item_height = fiv_thumbnail_sizes[self->item_size].size;
 | 
				
			||||||
		reload_thumbnails(self);
 | 
							reload_thumbnails(self);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		g_object_notify_by_pspec(
 | 
							g_object_notify_by_pspec(
 | 
				
			||||||
@@ -822,7 +822,7 @@ fiv_browser_get_preferred_width(GtkWidget *widget, gint *minimum, gint *natural)
 | 
				
			|||||||
	GtkBorder padding = {};
 | 
						GtkBorder padding = {};
 | 
				
			||||||
	gtk_style_context_get_padding(style, GTK_STATE_FLAG_NORMAL, &padding);
 | 
						gtk_style_context_get_padding(style, GTK_STATE_FLAG_NORMAL, &padding);
 | 
				
			||||||
	*minimum = *natural =
 | 
						*minimum = *natural =
 | 
				
			||||||
		FIV_IO_WIDE_THUMBNAIL_COEFFICIENT * self->item_height + padding.left +
 | 
							FIV_THUMBNAIL_WIDE_COEFFICIENT * self->item_height + padding.left +
 | 
				
			||||||
		2 * self->item_border_x + padding.right;
 | 
							2 * self->item_border_x + padding.right;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -1100,7 +1100,7 @@ fiv_browser_class_init(FivBrowserClass *klass)
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	browser_properties[PROP_THUMBNAIL_SIZE] = g_param_spec_enum(
 | 
						browser_properties[PROP_THUMBNAIL_SIZE] = g_param_spec_enum(
 | 
				
			||||||
		"thumbnail-size", "Thumbnail size", "The thumbnail height to use",
 | 
							"thumbnail-size", "Thumbnail size", "The thumbnail height to use",
 | 
				
			||||||
		FIV_TYPE_IO_THUMBNAIL_SIZE, FIV_IO_THUMBNAIL_SIZE_NORMAL,
 | 
							FIV_TYPE_THUMBNAIL_SIZE, FIV_THUMBNAIL_SIZE_NORMAL,
 | 
				
			||||||
		G_PARAM_READWRITE);
 | 
							G_PARAM_READWRITE);
 | 
				
			||||||
	g_object_class_install_properties(
 | 
						g_object_class_install_properties(
 | 
				
			||||||
		object_class, N_PROPERTIES, browser_properties);
 | 
							object_class, N_PROPERTIES, browser_properties);
 | 
				
			||||||
@@ -1145,7 +1145,7 @@ fiv_browser_init(FivBrowser *self)
 | 
				
			|||||||
	self->layouted_rows = g_array_new(FALSE, TRUE, sizeof(Row));
 | 
						self->layouted_rows = g_array_new(FALSE, TRUE, sizeof(Row));
 | 
				
			||||||
	g_array_set_clear_func(self->layouted_rows, (GDestroyNotify) row_free);
 | 
						g_array_set_clear_func(self->layouted_rows, (GDestroyNotify) row_free);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	set_item_size(self, FIV_IO_THUMBNAIL_SIZE_NORMAL);
 | 
						set_item_size(self, FIV_THUMBNAIL_SIZE_NORMAL);
 | 
				
			||||||
	self->selected = -1;
 | 
						self->selected = -1;
 | 
				
			||||||
	self->glow = cairo_image_surface_create(CAIRO_FORMAT_A1, 0, 0);
 | 
						self->glow = cairo_image_surface_create(CAIRO_FORMAT_A1, 0, 0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										486
									
								
								fiv-io.c
									
									
									
									
									
								
							
							
						
						
									
										486
									
								
								fiv-io.c
									
									
									
									
									
								
							@@ -20,9 +20,7 @@
 | 
				
			|||||||
#include <cairo.h>
 | 
					#include <cairo.h>
 | 
				
			||||||
#include <errno.h>
 | 
					#include <errno.h>
 | 
				
			||||||
#include <glib.h>
 | 
					#include <glib.h>
 | 
				
			||||||
#include <glib/gstdio.h>
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include <spng.h>
 | 
					 | 
				
			||||||
#include <turbojpeg.h>
 | 
					#include <turbojpeg.h>
 | 
				
			||||||
#include <webp/decode.h>
 | 
					#include <webp/decode.h>
 | 
				
			||||||
#include <webp/demux.h>
 | 
					#include <webp/demux.h>
 | 
				
			||||||
@@ -78,7 +76,6 @@
 | 
				
			|||||||
#include "wuffs-mirror-release-c/release/c/wuffs-v0.3.c"
 | 
					#include "wuffs-mirror-release-c/release/c/wuffs-v0.3.c"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#include "fiv-io.h"
 | 
					#include "fiv-io.h"
 | 
				
			||||||
#include "xdg.h"
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
#if CAIRO_VERSION >= 11702 && X11_ACTUALLY_SUPPORTS_RGBA128F_OR_WE_USE_OPENGL
 | 
					#if CAIRO_VERSION >= 11702 && X11_ACTUALLY_SUPPORTS_RGBA128F_OR_WE_USE_OPENGL
 | 
				
			||||||
#define FIV_CAIRO_RGBA128F
 | 
					#define FIV_CAIRO_RGBA128F
 | 
				
			||||||
@@ -135,6 +132,22 @@ fiv_io_all_supported_media_types(void)
 | 
				
			|||||||
	return (char **) g_ptr_array_free(types, FALSE);
 | 
						return (char **) g_ptr_array_free(types, FALSE);
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					int
 | 
				
			||||||
 | 
					fiv_io_filecmp(GFile *location1, GFile *location2)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						if (g_file_has_prefix(location1, location2))
 | 
				
			||||||
 | 
							return +1;
 | 
				
			||||||
 | 
						if (g_file_has_prefix(location2, location1))
 | 
				
			||||||
 | 
							return -1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						gchar *name1 = g_file_get_parse_name(location1);
 | 
				
			||||||
 | 
						gchar *name2 = g_file_get_parse_name(location2);
 | 
				
			||||||
 | 
						int result = g_utf8_collate(name1, name2);
 | 
				
			||||||
 | 
						g_free(name1);
 | 
				
			||||||
 | 
						g_free(name2);
 | 
				
			||||||
 | 
						return result;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | 
					// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | 
				
			||||||
 | 
					
 | 
				
			||||||
#define FIV_IO_ERROR fiv_io_error_quark()
 | 
					#define FIV_IO_ERROR fiv_io_error_quark()
 | 
				
			||||||
@@ -421,9 +434,6 @@ fiv_io_profile_finalize(cairo_surface_t *image, FivIoProfile target)
 | 
				
			|||||||
	return image;
 | 
						return image;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// From libwebp, verified to exactly match [x * a / 255].
 | 
					 | 
				
			||||||
#define PREMULTIPLY8(a, x) (((uint32_t) (x) * (uint32_t) (a) * 32897U) >> 23)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static void
 | 
					static void
 | 
				
			||||||
fiv_io_premultiply_argb32(cairo_surface_t *surface)
 | 
					fiv_io_premultiply_argb32(cairo_surface_t *surface)
 | 
				
			||||||
{
 | 
					{
 | 
				
			||||||
@@ -2297,8 +2307,6 @@ cairo_user_data_key_t fiv_io_key_loops;
 | 
				
			|||||||
cairo_user_data_key_t fiv_io_key_page_next;
 | 
					cairo_user_data_key_t fiv_io_key_page_next;
 | 
				
			||||||
cairo_user_data_key_t fiv_io_key_page_previous;
 | 
					cairo_user_data_key_t fiv_io_key_page_previous;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
cairo_user_data_key_t fiv_io_key_thumbnail_lq;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
cairo_surface_t *
 | 
					cairo_surface_t *
 | 
				
			||||||
fiv_io_open(
 | 
					fiv_io_open(
 | 
				
			||||||
	const gchar *path, FivIoProfile profile, gboolean enhance, GError **error)
 | 
						const gchar *path, FivIoProfile profile, gboolean enhance, GError **error)
 | 
				
			||||||
@@ -2754,465 +2762,3 @@ fiv_io_save_metadata(cairo_surface_t *page, const gchar *path, GError **error)
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
	return TRUE;
 | 
						return TRUE;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					 | 
				
			||||||
// --- Thumbnails --------------------------------------------------------------
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#ifndef __linux__
 | 
					 | 
				
			||||||
#define st_mtim st_mtimespec
 | 
					 | 
				
			||||||
#endif  // ! __linux__
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
GType
 | 
					 | 
				
			||||||
fiv_io_thumbnail_size_get_type(void)
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	static gsize guard;
 | 
					 | 
				
			||||||
	if (g_once_init_enter(&guard)) {
 | 
					 | 
				
			||||||
#define XX(name, value, dir) {FIV_IO_THUMBNAIL_SIZE_ ## name, \
 | 
					 | 
				
			||||||
	"FIV_IO_THUMBNAIL_SIZE_" #name, #name},
 | 
					 | 
				
			||||||
		static const GEnumValue values[] = {FIV_IO_THUMBNAIL_SIZES(XX) {}};
 | 
					 | 
				
			||||||
#undef XX
 | 
					 | 
				
			||||||
		GType type = g_enum_register_static(
 | 
					 | 
				
			||||||
			g_intern_static_string("FivIoThumbnailSize"), values);
 | 
					 | 
				
			||||||
		g_once_init_leave(&guard, type);
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return guard;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
#define XX(name, value, dir) {value, dir},
 | 
					 | 
				
			||||||
FivIoThumbnailSizeInfo
 | 
					 | 
				
			||||||
	fiv_io_thumbnail_sizes[FIV_IO_THUMBNAIL_SIZE_COUNT] = {
 | 
					 | 
				
			||||||
		FIV_IO_THUMBNAIL_SIZES(XX)};
 | 
					 | 
				
			||||||
#undef XX
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static void
 | 
					 | 
				
			||||||
mark_thumbnail_lq(cairo_surface_t *surface)
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	cairo_surface_set_user_data(
 | 
					 | 
				
			||||||
		surface, &fiv_io_key_thumbnail_lq, (void *) (intptr_t) 1, NULL);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
gchar *
 | 
					 | 
				
			||||||
fiv_io_get_thumbnail_root(void)
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	gchar *cache_dir = get_xdg_home_dir("XDG_CACHE_HOME", ".cache");
 | 
					 | 
				
			||||||
	gchar *thumbnails_dir = g_build_filename(cache_dir, "thumbnails", NULL);
 | 
					 | 
				
			||||||
	g_free(cache_dir);
 | 
					 | 
				
			||||||
	return thumbnails_dir;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// In principle similar to rescale_thumbnail() from fiv-browser.c.
 | 
					 | 
				
			||||||
static cairo_surface_t *
 | 
					 | 
				
			||||||
rescale_thumbnail(cairo_surface_t *thumbnail, double row_height)
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	cairo_format_t format = cairo_image_surface_get_format(thumbnail);
 | 
					 | 
				
			||||||
	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 > FIV_IO_WIDE_THUMBNAIL_COEFFICIENT * height) {
 | 
					 | 
				
			||||||
		scale_x = FIV_IO_WIDE_THUMBNAIL_COEFFICIENT * 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);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	int projected_width = round(scale_x * width);
 | 
					 | 
				
			||||||
	int projected_height = round(scale_y * height);
 | 
					 | 
				
			||||||
	cairo_surface_t *scaled = cairo_image_surface_create(
 | 
					 | 
				
			||||||
		(format == CAIRO_FORMAT_RGB24 || format == CAIRO_FORMAT_RGB30)
 | 
					 | 
				
			||||||
			? CAIRO_FORMAT_RGB24
 | 
					 | 
				
			||||||
			: CAIRO_FORMAT_ARGB32,
 | 
					 | 
				
			||||||
		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);
 | 
					 | 
				
			||||||
	mark_thumbnail_lq(scaled);
 | 
					 | 
				
			||||||
	return scaled;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static WebPData
 | 
					 | 
				
			||||||
encode_thumbnail(cairo_surface_t *surface)
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	WebPData bitstream = {};
 | 
					 | 
				
			||||||
	WebPConfig config = {};
 | 
					 | 
				
			||||||
	if (!WebPConfigInit(&config) || !WebPConfigLosslessPreset(&config, 6))
 | 
					 | 
				
			||||||
		return bitstream;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	config.near_lossless = 95;
 | 
					 | 
				
			||||||
	config.thread_level = true;
 | 
					 | 
				
			||||||
	if (!WebPValidateConfig(&config))
 | 
					 | 
				
			||||||
		return bitstream;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	bitstream.bytes = fiv_io_encode_webp(surface, &config, &bitstream.size);
 | 
					 | 
				
			||||||
	return bitstream;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static void
 | 
					 | 
				
			||||||
save_thumbnail(cairo_surface_t *thumbnail, const char *path, GString *thum)
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	WebPMux *mux = WebPMuxNew();
 | 
					 | 
				
			||||||
	WebPData bitstream = encode_thumbnail(thumbnail);
 | 
					 | 
				
			||||||
	gboolean ok = WebPMuxSetImage(mux, &bitstream, true) == WEBP_MUX_OK;
 | 
					 | 
				
			||||||
	WebPDataClear(&bitstream);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	WebPData data = {.bytes = (const uint8_t *) thum->str, .size = thum->len};
 | 
					 | 
				
			||||||
	ok = ok && WebPMuxSetChunk(mux, "THUM", &data, false) == WEBP_MUX_OK;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	WebPData assembled = {};
 | 
					 | 
				
			||||||
	WebPDataInit(&assembled);
 | 
					 | 
				
			||||||
	ok = ok && WebPMuxAssemble(mux, &assembled) == WEBP_MUX_OK;
 | 
					 | 
				
			||||||
	WebPMuxDelete(mux);
 | 
					 | 
				
			||||||
	if (!ok) {
 | 
					 | 
				
			||||||
		g_warning("thumbnail encoding failed");
 | 
					 | 
				
			||||||
		return;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	GError *e = NULL;
 | 
					 | 
				
			||||||
	while (!g_file_set_contents(
 | 
					 | 
				
			||||||
		path, (const gchar *) assembled.bytes, assembled.size, &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.
 | 
					 | 
				
			||||||
	WebPDataClear(&assembled);
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
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();
 | 
					 | 
				
			||||||
	gsize filesize = g_mapped_file_get_length(mf);
 | 
					 | 
				
			||||||
	cairo_surface_t *surface = fiv_io_open_from_data(
 | 
					 | 
				
			||||||
		g_mapped_file_get_contents(mf), filesize, 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 *thumbnails_dir = fiv_io_get_thumbnail_root();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	GString *thum = g_string_new("");
 | 
					 | 
				
			||||||
	g_string_append_printf(
 | 
					 | 
				
			||||||
		thum, "%s%c%s%c", "Thumb::URI", 0, uri, 0);
 | 
					 | 
				
			||||||
	g_string_append_printf(
 | 
					 | 
				
			||||||
		thum, "%s%c%ld%c", "Thumb::Mtime", 0, (long) st.st_mtim.tv_sec, 0);
 | 
					 | 
				
			||||||
	g_string_append_printf(
 | 
					 | 
				
			||||||
		thum, "%s%c%ld%c", "Thumb::Size", 0, (long) filesize, 0);
 | 
					 | 
				
			||||||
	g_string_append_printf(thum, "%s%c%d%c", "Thumb::Image::Width", 0,
 | 
					 | 
				
			||||||
		cairo_image_surface_get_width(surface), 0);
 | 
					 | 
				
			||||||
	g_string_append_printf(thum, "%s%c%d%c", "Thumb::Image::Height", 0,
 | 
					 | 
				
			||||||
		cairo_image_surface_get_height(surface), 0);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Without a CMM, no conversion is attempted.
 | 
					 | 
				
			||||||
	if (sRGB) {
 | 
					 | 
				
			||||||
		g_string_append_printf(
 | 
					 | 
				
			||||||
			thum, "%s%c%s%c", "Thumb::ColorSpace", 0, "sRGB", 0);
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	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/wide-%s/%s.webp", thumbnails_dir,
 | 
					 | 
				
			||||||
			fiv_io_thumbnail_sizes[use].thumbnail_spec_name, sum);
 | 
					 | 
				
			||||||
		save_thumbnail(scaled, path, thum);
 | 
					 | 
				
			||||||
		cairo_surface_destroy(scaled);
 | 
					 | 
				
			||||||
		g_free(path);
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	g_string_free(thum, TRUE);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	g_free(thumbnails_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 fiv_io_key_thum.
 | 
					 | 
				
			||||||
	(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,
 | 
					 | 
				
			||||||
	const gchar *target, time_t mtime)
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	// May contain Thumb::Image::Width Thumb::Image::Height,
 | 
					 | 
				
			||||||
	// but those aren't interesting currently (would be for fast previews).
 | 
					 | 
				
			||||||
	bool need_uri = true, need_mtime = true;
 | 
					 | 
				
			||||||
	for (uint32_t i = 0; i < texts_len; i++) {
 | 
					 | 
				
			||||||
		struct spng_text *text = texts + i;
 | 
					 | 
				
			||||||
		if (!strcmp(text->keyword, "Thumb::URI")) {
 | 
					 | 
				
			||||||
			need_uri = false;
 | 
					 | 
				
			||||||
			if (strcmp(target, text->text))
 | 
					 | 
				
			||||||
				return false;
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		if (!strcmp(text->keyword, "Thumb::MTime")) {
 | 
					 | 
				
			||||||
			need_mtime = false;
 | 
					 | 
				
			||||||
			if (atol(text->text) != mtime)
 | 
					 | 
				
			||||||
				return false;
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return need_uri || need_mtime ? -1 : true;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static int  // tri-state
 | 
					 | 
				
			||||||
check_spng_thumbnail(spng_ctx *ctx, const gchar *target, time_t mtime, int *err)
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	uint32_t texts_len = 0;
 | 
					 | 
				
			||||||
	if ((*err = spng_get_text(ctx, NULL, &texts_len)))
 | 
					 | 
				
			||||||
		return false;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	int result = false;
 | 
					 | 
				
			||||||
	struct spng_text *texts = g_malloc0_n(texts_len, sizeof *texts);
 | 
					 | 
				
			||||||
	if (!(*err = spng_get_text(ctx, texts, &texts_len)))
 | 
					 | 
				
			||||||
		result = check_spng_thumbnail_texts(texts, texts_len, target, mtime);
 | 
					 | 
				
			||||||
	g_free(texts);
 | 
					 | 
				
			||||||
	return result;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
static cairo_surface_t *
 | 
					 | 
				
			||||||
read_spng_thumbnail(
 | 
					 | 
				
			||||||
	const gchar *path, const gchar *uri, time_t mtime, GError **error)
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	FILE *fp;
 | 
					 | 
				
			||||||
	cairo_surface_t *result = NULL;
 | 
					 | 
				
			||||||
	if (!(fp = fopen(path, "rb"))) {
 | 
					 | 
				
			||||||
		set_error(error, g_strerror(errno));
 | 
					 | 
				
			||||||
		return NULL;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	errno = 0;
 | 
					 | 
				
			||||||
	spng_ctx *ctx = spng_ctx_new(0);
 | 
					 | 
				
			||||||
	if (!ctx) {
 | 
					 | 
				
			||||||
		set_error(error, g_strerror(errno));
 | 
					 | 
				
			||||||
		goto fail_init;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	int err;
 | 
					 | 
				
			||||||
	size_t size = 0;
 | 
					 | 
				
			||||||
	if ((err = spng_set_png_file(ctx, fp)) ||
 | 
					 | 
				
			||||||
		(err = spng_set_image_limits(ctx, INT16_MAX, INT16_MAX)) ||
 | 
					 | 
				
			||||||
		(err = spng_decoded_image_size(ctx, SPNG_FMT_RGBA8, &size))) {
 | 
					 | 
				
			||||||
		set_error(error, spng_strerror(err));
 | 
					 | 
				
			||||||
		goto fail;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	if (check_spng_thumbnail(ctx, uri, mtime, &err) == false) {
 | 
					 | 
				
			||||||
		set_error(error, err ? spng_strerror(err) : "mismatch");
 | 
					 | 
				
			||||||
		goto fail;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	struct spng_ihdr ihdr = {};
 | 
					 | 
				
			||||||
	struct spng_trns trns = {};
 | 
					 | 
				
			||||||
	spng_get_ihdr(ctx, &ihdr);
 | 
					 | 
				
			||||||
	bool may_be_translucent = !spng_get_trns(ctx, &trns) ||
 | 
					 | 
				
			||||||
		ihdr.color_type == SPNG_COLOR_TYPE_GRAYSCALE_ALPHA ||
 | 
					 | 
				
			||||||
		ihdr.color_type == SPNG_COLOR_TYPE_TRUECOLOR_ALPHA;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	cairo_surface_t *surface = cairo_image_surface_create(
 | 
					 | 
				
			||||||
		may_be_translucent ? CAIRO_FORMAT_ARGB32 : CAIRO_FORMAT_RGB24,
 | 
					 | 
				
			||||||
		ihdr.width, ihdr.height);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	cairo_status_t surface_status = cairo_surface_status(surface);
 | 
					 | 
				
			||||||
	if (surface_status != CAIRO_STATUS_SUCCESS) {
 | 
					 | 
				
			||||||
		set_error(error, cairo_status_to_string(surface_status));
 | 
					 | 
				
			||||||
		goto fail_data;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	uint32_t *data = (uint32_t *) cairo_image_surface_get_data(surface);
 | 
					 | 
				
			||||||
	g_assert((size_t) cairo_image_surface_get_stride(surface) *
 | 
					 | 
				
			||||||
		cairo_image_surface_get_height(surface) == size);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	cairo_surface_flush(surface);
 | 
					 | 
				
			||||||
	if ((err = spng_decode_image(ctx, data, size, SPNG_FMT_RGBA8,
 | 
					 | 
				
			||||||
		SPNG_DECODE_TRNS | SPNG_DECODE_GAMMA))) {
 | 
					 | 
				
			||||||
		set_error(error, spng_strerror(err));
 | 
					 | 
				
			||||||
		goto fail_data;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// The specification does not say where the required metadata should be,
 | 
					 | 
				
			||||||
	// it could very well be broken up into two parts.
 | 
					 | 
				
			||||||
	if (check_spng_thumbnail(ctx, uri, mtime, &err) != true) {
 | 
					 | 
				
			||||||
		set_error(
 | 
					 | 
				
			||||||
			error, err ? spng_strerror(err) : "mismatch or not a thumbnail");
 | 
					 | 
				
			||||||
		goto fail_data;
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// pixman can be mildly abused to do this operation, but it won't be faster.
 | 
					 | 
				
			||||||
	if (may_be_translucent) {
 | 
					 | 
				
			||||||
		for (size_t i = size / sizeof *data; i--; ) {
 | 
					 | 
				
			||||||
			const uint8_t *unit = (const uint8_t *) &data[i];
 | 
					 | 
				
			||||||
			uint32_t a = unit[3],
 | 
					 | 
				
			||||||
				b = PREMULTIPLY8(a, unit[2]),
 | 
					 | 
				
			||||||
				g = PREMULTIPLY8(a, unit[1]),
 | 
					 | 
				
			||||||
				r = PREMULTIPLY8(a, unit[0]);
 | 
					 | 
				
			||||||
			data[i] = a << 24 | r << 16 | g << 8 | b;
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	} else {
 | 
					 | 
				
			||||||
		for (size_t i = size / sizeof *data; i--; ) {
 | 
					 | 
				
			||||||
			uint32_t rgba = g_ntohl(data[i]);
 | 
					 | 
				
			||||||
			data[i] = rgba << 24 | rgba >> 8;
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	cairo_surface_mark_dirty((result = surface));
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
fail_data:
 | 
					 | 
				
			||||||
	if (!result)
 | 
					 | 
				
			||||||
		cairo_surface_destroy(surface);
 | 
					 | 
				
			||||||
fail:
 | 
					 | 
				
			||||||
	spng_ctx_free(ctx);
 | 
					 | 
				
			||||||
fail_init:
 | 
					 | 
				
			||||||
	fclose(fp);
 | 
					 | 
				
			||||||
	return result;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
cairo_surface_t *
 | 
					 | 
				
			||||||
fiv_io_lookup_thumbnail(GFile *target, FivIoThumbnailSize size)
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	g_return_val_if_fail(size >= FIV_IO_THUMBNAIL_SIZE_MIN &&
 | 
					 | 
				
			||||||
		size <= FIV_IO_THUMBNAIL_SIZE_MAX, NULL);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// Local files only, at least for now.
 | 
					 | 
				
			||||||
	gchar *path = g_file_get_path(target);
 | 
					 | 
				
			||||||
	if (!path)
 | 
					 | 
				
			||||||
		return NULL;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	GStatBuf st = {};
 | 
					 | 
				
			||||||
	int err = g_stat(path, &st);
 | 
					 | 
				
			||||||
	g_free(path);
 | 
					 | 
				
			||||||
	if (err)
 | 
					 | 
				
			||||||
		return NULL;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	gchar *uri = g_file_get_uri(target);
 | 
					 | 
				
			||||||
	gchar *sum = g_compute_checksum_for_string(G_CHECKSUM_MD5, uri, -1);
 | 
					 | 
				
			||||||
	gchar *thumbnails_dir = fiv_io_get_thumbnail_root();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	// The lookup sequence is: nominal..max, then mirroring back to ..min.
 | 
					 | 
				
			||||||
	cairo_surface_t *result = NULL;
 | 
					 | 
				
			||||||
	GError *error = NULL;
 | 
					 | 
				
			||||||
	for (int i = 0; i < FIV_IO_THUMBNAIL_SIZE_COUNT; i++) {
 | 
					 | 
				
			||||||
		FivIoThumbnailSize use = size + i;
 | 
					 | 
				
			||||||
		if (use > FIV_IO_THUMBNAIL_SIZE_MAX)
 | 
					 | 
				
			||||||
			use = FIV_IO_THUMBNAIL_SIZE_MAX - i;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		const char *name = fiv_io_thumbnail_sizes[use].thumbnail_spec_name;
 | 
					 | 
				
			||||||
		gchar *wide =
 | 
					 | 
				
			||||||
			g_strdup_printf("%s/wide-%s/%s.webp", thumbnails_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) {
 | 
					 | 
				
			||||||
			// Higher up we can't distinguish images smaller than the thumbnail.
 | 
					 | 
				
			||||||
			// Also, try not to rescale the already rescaled.
 | 
					 | 
				
			||||||
			if (use != size)
 | 
					 | 
				
			||||||
				mark_thumbnail_lq(result);
 | 
					 | 
				
			||||||
			break;
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
		gchar *path =
 | 
					 | 
				
			||||||
			g_strdup_printf("%s/%s/%s.png", thumbnails_dir, name, sum);
 | 
					 | 
				
			||||||
		result = read_spng_thumbnail(path, uri, st.st_mtim.tv_sec, &error);
 | 
					 | 
				
			||||||
		if (error) {
 | 
					 | 
				
			||||||
			g_debug("%s: %s", path, error->message);
 | 
					 | 
				
			||||||
			g_clear_error(&error);
 | 
					 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
		g_free(path);
 | 
					 | 
				
			||||||
		if (result) {
 | 
					 | 
				
			||||||
			// Whatever produced it, we may be able to outclass it.
 | 
					 | 
				
			||||||
			mark_thumbnail_lq(result);
 | 
					 | 
				
			||||||
			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(thumbnails_dir);
 | 
					 | 
				
			||||||
	g_free(sum);
 | 
					 | 
				
			||||||
	g_free(uri);
 | 
					 | 
				
			||||||
	return result;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
int
 | 
					 | 
				
			||||||
fiv_io_filecmp(GFile *location1, GFile *location2)
 | 
					 | 
				
			||||||
{
 | 
					 | 
				
			||||||
	if (g_file_has_prefix(location1, location2))
 | 
					 | 
				
			||||||
		return +1;
 | 
					 | 
				
			||||||
	if (g_file_has_prefix(location2, location1))
 | 
					 | 
				
			||||||
		return -1;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	gchar *name1 = g_file_get_parse_name(location1);
 | 
					 | 
				
			||||||
	gchar *name2 = g_file_get_parse_name(location2);
 | 
					 | 
				
			||||||
	int result = g_utf8_collate(name1, name2);
 | 
					 | 
				
			||||||
	g_free(name1);
 | 
					 | 
				
			||||||
	g_free(name2);
 | 
					 | 
				
			||||||
	return result;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										51
									
								
								fiv-io.h
									
									
									
									
									
								
							
							
						
						
									
										51
									
								
								fiv-io.h
									
									
									
									
									
								
							@@ -31,6 +31,9 @@ FivIoProfile fiv_io_profile_new(const void *data, size_t len);
 | 
				
			|||||||
FivIoProfile fiv_io_profile_new_sRGB(void);
 | 
					FivIoProfile fiv_io_profile_new_sRGB(void);
 | 
				
			||||||
void fiv_io_profile_free(FivIoProfile self);
 | 
					void fiv_io_profile_free(FivIoProfile self);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// From libwebp, verified to exactly match [x * a / 255].
 | 
				
			||||||
 | 
					#define PREMULTIPLY8(a, x) (((uint32_t) (x) * (uint32_t) (a) * 32897U) >> 23)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// --- Loading -----------------------------------------------------------------
 | 
					// --- Loading -----------------------------------------------------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
extern const char *fiv_io_supported_media_types[];
 | 
					extern const char *fiv_io_supported_media_types[];
 | 
				
			||||||
@@ -69,9 +72,6 @@ extern cairo_user_data_key_t fiv_io_key_page_next;
 | 
				
			|||||||
/// There is no wrap-around. This is a weak pointer.
 | 
					/// There is no wrap-around. This is a weak pointer.
 | 
				
			||||||
extern cairo_user_data_key_t fiv_io_key_page_previous;
 | 
					extern cairo_user_data_key_t fiv_io_key_page_previous;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/// If non-NULL, indicates a thumbnail of insufficient quality.
 | 
					 | 
				
			||||||
extern cairo_user_data_key_t fiv_io_key_thumbnail_lq;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
cairo_surface_t *fiv_io_open(
 | 
					cairo_surface_t *fiv_io_open(
 | 
				
			||||||
	const gchar *path, FivIoProfile profile, gboolean enhance, GError **error);
 | 
						const gchar *path, FivIoProfile profile, gboolean enhance, GError **error);
 | 
				
			||||||
cairo_surface_t *fiv_io_open_from_data(const char *data, size_t len,
 | 
					cairo_surface_t *fiv_io_open_from_data(const char *data, size_t len,
 | 
				
			||||||
@@ -113,48 +113,3 @@ FivIoOrientation fiv_io_exif_orientation(const guint8 *exif, gsize len);
 | 
				
			|||||||
/// Save metadata attached by this module in Exiv2 format.
 | 
					/// Save metadata attached by this module in Exiv2 format.
 | 
				
			||||||
gboolean fiv_io_save_metadata(
 | 
					gboolean fiv_io_save_metadata(
 | 
				
			||||||
	cairo_surface_t *page, const gchar *path, GError **error);
 | 
						cairo_surface_t *page, const gchar *path, GError **error);
 | 
				
			||||||
 | 
					 | 
				
			||||||
// --- Thumbnails --------------------------------------------------------------
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// And this is how you avoid glib-mkenums.
 | 
					 | 
				
			||||||
typedef enum _FivIoThumbnailSize {
 | 
					 | 
				
			||||||
#define FIV_IO_THUMBNAIL_SIZES(XX) \
 | 
					 | 
				
			||||||
	XX(SMALL,  128, "normal") \
 | 
					 | 
				
			||||||
	XX(NORMAL, 256, "large") \
 | 
					 | 
				
			||||||
	XX(LARGE,  512, "x-large") \
 | 
					 | 
				
			||||||
	XX(HUGE,  1024, "xx-large")
 | 
					 | 
				
			||||||
#define XX(name, value, dir) FIV_IO_THUMBNAIL_SIZE_ ## name,
 | 
					 | 
				
			||||||
	FIV_IO_THUMBNAIL_SIZES(XX)
 | 
					 | 
				
			||||||
#undef XX
 | 
					 | 
				
			||||||
	FIV_IO_THUMBNAIL_SIZE_COUNT,
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
	FIV_IO_THUMBNAIL_SIZE_MIN = 0,
 | 
					 | 
				
			||||||
	FIV_IO_THUMBNAIL_SIZE_MAX = FIV_IO_THUMBNAIL_SIZE_COUNT - 1
 | 
					 | 
				
			||||||
} FivIoThumbnailSize;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
GType fiv_io_thumbnail_size_get_type(void) G_GNUC_CONST;
 | 
					 | 
				
			||||||
#define FIV_TYPE_IO_THUMBNAIL_SIZE (fiv_io_thumbnail_size_get_type())
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
typedef struct _FivIoThumbnailSizeInfo {
 | 
					 | 
				
			||||||
	int size;                           ///< Nominal size in pixels
 | 
					 | 
				
			||||||
	const char *thumbnail_spec_name;    ///< thumbnail-spec directory name
 | 
					 | 
				
			||||||
} FivIoThumbnailSizeInfo;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
extern FivIoThumbnailSizeInfo
 | 
					 | 
				
			||||||
	fiv_io_thumbnail_sizes[FIV_IO_THUMBNAIL_SIZE_COUNT];
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
enum {
 | 
					 | 
				
			||||||
	FIV_IO_WIDE_THUMBNAIL_COEFFICIENT = 2
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/// Returns this user's root thumbnail directory.
 | 
					 | 
				
			||||||
gchar *fiv_io_get_thumbnail_root(void);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
/// 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);
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										494
									
								
								fiv-thumbnail.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										494
									
								
								fiv-thumbnail.c
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,494 @@
 | 
				
			|||||||
 | 
					//
 | 
				
			||||||
 | 
					// fiv-thumbnail.c: thumbnail management
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// Copyright (c) 2021, Přemysl Eric Janouch <p@janouch.name>
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// Permission to use, copy, modify, and/or distribute this software for any
 | 
				
			||||||
 | 
					// purpose with or without fee is hereby granted.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 | 
				
			||||||
 | 
					// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 | 
				
			||||||
 | 
					// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
 | 
				
			||||||
 | 
					// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 | 
				
			||||||
 | 
					// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
 | 
				
			||||||
 | 
					// OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
 | 
				
			||||||
 | 
					// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <spng.h>
 | 
				
			||||||
 | 
					#include <webp/encode.h>
 | 
				
			||||||
 | 
					#include <webp/mux.h>
 | 
				
			||||||
 | 
					#include <glib/gstdio.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <math.h>
 | 
				
			||||||
 | 
					#include <stdbool.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include "fiv-io.h"
 | 
				
			||||||
 | 
					#include "fiv-thumbnail.h"
 | 
				
			||||||
 | 
					#include "xdg.h"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#ifndef __linux__
 | 
				
			||||||
 | 
					#define st_mtim st_mtimespec
 | 
				
			||||||
 | 
					#endif  // ! __linux__
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// TODO(p): Consider merging back with fiv-io.
 | 
				
			||||||
 | 
					#define FIV_THUMBNAIL_ERROR fiv_thumbnail_error_quark()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					G_DEFINE_QUARK(fiv-thumbnail-error-quark, fiv_thumbnail_error)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					enum FivThumbnailError {
 | 
				
			||||||
 | 
						FIV_THUMBNAIL_ERROR_IO
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void
 | 
				
			||||||
 | 
					set_error(GError **error, const char *message)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						g_set_error_literal(
 | 
				
			||||||
 | 
							error, FIV_THUMBNAIL_ERROR, FIV_THUMBNAIL_ERROR_IO, message);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					GType
 | 
				
			||||||
 | 
					fiv_thumbnail_size_get_type(void)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						static gsize guard;
 | 
				
			||||||
 | 
						if (g_once_init_enter(&guard)) {
 | 
				
			||||||
 | 
					#define XX(name, value, dir) {FIV_THUMBNAIL_SIZE_ ## name, \
 | 
				
			||||||
 | 
						"FIV_THUMBNAIL_SIZE_" #name, #name},
 | 
				
			||||||
 | 
							static const GEnumValue values[] = {FIV_THUMBNAIL_SIZES(XX) {}};
 | 
				
			||||||
 | 
					#undef XX
 | 
				
			||||||
 | 
							GType type = g_enum_register_static(
 | 
				
			||||||
 | 
								g_intern_static_string("FivThumbnailSize"), values);
 | 
				
			||||||
 | 
							g_once_init_leave(&guard, type);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return guard;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#define XX(name, value, dir) {value, dir},
 | 
				
			||||||
 | 
					FivThumbnailSizeInfo
 | 
				
			||||||
 | 
						fiv_thumbnail_sizes[FIV_THUMBNAIL_SIZE_COUNT] = {
 | 
				
			||||||
 | 
							FIV_THUMBNAIL_SIZES(XX)};
 | 
				
			||||||
 | 
					#undef XX
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					cairo_user_data_key_t fiv_thumbnail_key_lq;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void
 | 
				
			||||||
 | 
					mark_thumbnail_lq(cairo_surface_t *surface)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						cairo_surface_set_user_data(
 | 
				
			||||||
 | 
							surface, &fiv_thumbnail_key_lq, (void *) (intptr_t) 1, NULL);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					gchar *
 | 
				
			||||||
 | 
					fiv_thumbnail_get_root(void)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						gchar *cache_dir = get_xdg_home_dir("XDG_CACHE_HOME", ".cache");
 | 
				
			||||||
 | 
						gchar *thumbnails_dir = g_build_filename(cache_dir, "thumbnails", NULL);
 | 
				
			||||||
 | 
						g_free(cache_dir);
 | 
				
			||||||
 | 
						return thumbnails_dir;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// In principle similar to rescale_thumbnail() from fiv-browser.c.
 | 
				
			||||||
 | 
					static cairo_surface_t *
 | 
				
			||||||
 | 
					rescale_thumbnail(cairo_surface_t *thumbnail, double row_height)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						cairo_format_t format = cairo_image_surface_get_format(thumbnail);
 | 
				
			||||||
 | 
						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 > FIV_THUMBNAIL_WIDE_COEFFICIENT * height) {
 | 
				
			||||||
 | 
							scale_x = FIV_THUMBNAIL_WIDE_COEFFICIENT * 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);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						int projected_width = round(scale_x * width);
 | 
				
			||||||
 | 
						int projected_height = round(scale_y * height);
 | 
				
			||||||
 | 
						cairo_surface_t *scaled = cairo_image_surface_create(
 | 
				
			||||||
 | 
							(format == CAIRO_FORMAT_RGB24 || format == CAIRO_FORMAT_RGB30)
 | 
				
			||||||
 | 
								? CAIRO_FORMAT_RGB24
 | 
				
			||||||
 | 
								: CAIRO_FORMAT_ARGB32,
 | 
				
			||||||
 | 
							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);
 | 
				
			||||||
 | 
						mark_thumbnail_lq(scaled);
 | 
				
			||||||
 | 
						return scaled;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static WebPData
 | 
				
			||||||
 | 
					encode_thumbnail(cairo_surface_t *surface)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						WebPData bitstream = {};
 | 
				
			||||||
 | 
						WebPConfig config = {};
 | 
				
			||||||
 | 
						if (!WebPConfigInit(&config) || !WebPConfigLosslessPreset(&config, 6))
 | 
				
			||||||
 | 
							return bitstream;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						config.near_lossless = 95;
 | 
				
			||||||
 | 
						config.thread_level = true;
 | 
				
			||||||
 | 
						if (!WebPValidateConfig(&config))
 | 
				
			||||||
 | 
							return bitstream;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						bitstream.bytes = fiv_io_encode_webp(surface, &config, &bitstream.size);
 | 
				
			||||||
 | 
						return bitstream;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static void
 | 
				
			||||||
 | 
					save_thumbnail(cairo_surface_t *thumbnail, const char *path, GString *thum)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						WebPMux *mux = WebPMuxNew();
 | 
				
			||||||
 | 
						WebPData bitstream = encode_thumbnail(thumbnail);
 | 
				
			||||||
 | 
						gboolean ok = WebPMuxSetImage(mux, &bitstream, true) == WEBP_MUX_OK;
 | 
				
			||||||
 | 
						WebPDataClear(&bitstream);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						WebPData data = {.bytes = (const uint8_t *) thum->str, .size = thum->len};
 | 
				
			||||||
 | 
						ok = ok && WebPMuxSetChunk(mux, "THUM", &data, false) == WEBP_MUX_OK;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						WebPData assembled = {};
 | 
				
			||||||
 | 
						WebPDataInit(&assembled);
 | 
				
			||||||
 | 
						ok = ok && WebPMuxAssemble(mux, &assembled) == WEBP_MUX_OK;
 | 
				
			||||||
 | 
						WebPMuxDelete(mux);
 | 
				
			||||||
 | 
						if (!ok) {
 | 
				
			||||||
 | 
							g_warning("thumbnail encoding failed");
 | 
				
			||||||
 | 
							return;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						GError *e = NULL;
 | 
				
			||||||
 | 
						while (!g_file_set_contents(
 | 
				
			||||||
 | 
							path, (const gchar *) assembled.bytes, assembled.size, &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.
 | 
				
			||||||
 | 
						WebPDataClear(&assembled);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					gboolean
 | 
				
			||||||
 | 
					fiv_thumbnail_produce(GFile *target, FivThumbnailSize max_size, GError **error)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						g_return_val_if_fail(max_size >= FIV_THUMBNAIL_SIZE_MIN &&
 | 
				
			||||||
 | 
							max_size <= FIV_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();
 | 
				
			||||||
 | 
						gsize filesize = g_mapped_file_get_length(mf);
 | 
				
			||||||
 | 
						cairo_surface_t *surface = fiv_io_open_from_data(
 | 
				
			||||||
 | 
							g_mapped_file_get_contents(mf), filesize, 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_thumbnail_lookup().
 | 
				
			||||||
 | 
						gchar *uri = g_file_get_uri(target);
 | 
				
			||||||
 | 
						gchar *sum = g_compute_checksum_for_string(G_CHECKSUM_MD5, uri, -1);
 | 
				
			||||||
 | 
						gchar *thumbnails_dir = fiv_thumbnail_get_root();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						GString *thum = g_string_new("");
 | 
				
			||||||
 | 
						g_string_append_printf(
 | 
				
			||||||
 | 
							thum, "%s%c%s%c", "Thumb::URI", 0, uri, 0);
 | 
				
			||||||
 | 
						g_string_append_printf(
 | 
				
			||||||
 | 
							thum, "%s%c%ld%c", "Thumb::Mtime", 0, (long) st.st_mtim.tv_sec, 0);
 | 
				
			||||||
 | 
						g_string_append_printf(
 | 
				
			||||||
 | 
							thum, "%s%c%ld%c", "Thumb::Size", 0, (long) filesize, 0);
 | 
				
			||||||
 | 
						g_string_append_printf(thum, "%s%c%d%c", "Thumb::Image::Width", 0,
 | 
				
			||||||
 | 
							cairo_image_surface_get_width(surface), 0);
 | 
				
			||||||
 | 
						g_string_append_printf(thum, "%s%c%d%c", "Thumb::Image::Height", 0,
 | 
				
			||||||
 | 
							cairo_image_surface_get_height(surface), 0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Without a CMM, no conversion is attempted.
 | 
				
			||||||
 | 
						if (sRGB) {
 | 
				
			||||||
 | 
							g_string_append_printf(
 | 
				
			||||||
 | 
								thum, "%s%c%s%c", "Thumb::ColorSpace", 0, "sRGB", 0);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						for (int use = max_size; use >= FIV_THUMBNAIL_SIZE_MIN; use--) {
 | 
				
			||||||
 | 
							cairo_surface_t *scaled =
 | 
				
			||||||
 | 
								rescale_thumbnail(surface, fiv_thumbnail_sizes[use].size);
 | 
				
			||||||
 | 
							gchar *path = g_strdup_printf("%s/wide-%s/%s.webp", thumbnails_dir,
 | 
				
			||||||
 | 
								fiv_thumbnail_sizes[use].thumbnail_spec_name, sum);
 | 
				
			||||||
 | 
							save_thumbnail(scaled, path, thum);
 | 
				
			||||||
 | 
							cairo_surface_destroy(scaled);
 | 
				
			||||||
 | 
							g_free(path);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						g_string_free(thum, TRUE);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						g_free(thumbnails_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 fiv_io_key_thum.
 | 
				
			||||||
 | 
						(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,
 | 
				
			||||||
 | 
						const gchar *target, time_t mtime)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						// May contain Thumb::Image::Width Thumb::Image::Height,
 | 
				
			||||||
 | 
						// but those aren't interesting currently (would be for fast previews).
 | 
				
			||||||
 | 
						bool need_uri = true, need_mtime = true;
 | 
				
			||||||
 | 
						for (uint32_t i = 0; i < texts_len; i++) {
 | 
				
			||||||
 | 
							struct spng_text *text = texts + i;
 | 
				
			||||||
 | 
							if (!strcmp(text->keyword, "Thumb::URI")) {
 | 
				
			||||||
 | 
								need_uri = false;
 | 
				
			||||||
 | 
								if (strcmp(target, text->text))
 | 
				
			||||||
 | 
									return false;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if (!strcmp(text->keyword, "Thumb::MTime")) {
 | 
				
			||||||
 | 
								need_mtime = false;
 | 
				
			||||||
 | 
								if (atol(text->text) != mtime)
 | 
				
			||||||
 | 
									return false;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return need_uri || need_mtime ? -1 : true;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static int  // tri-state
 | 
				
			||||||
 | 
					check_spng_thumbnail(spng_ctx *ctx, const gchar *target, time_t mtime, int *err)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						uint32_t texts_len = 0;
 | 
				
			||||||
 | 
						if ((*err = spng_get_text(ctx, NULL, &texts_len)))
 | 
				
			||||||
 | 
							return false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						int result = false;
 | 
				
			||||||
 | 
						struct spng_text *texts = g_malloc0_n(texts_len, sizeof *texts);
 | 
				
			||||||
 | 
						if (!(*err = spng_get_text(ctx, texts, &texts_len)))
 | 
				
			||||||
 | 
							result = check_spng_thumbnail_texts(texts, texts_len, target, mtime);
 | 
				
			||||||
 | 
						g_free(texts);
 | 
				
			||||||
 | 
						return result;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					static cairo_surface_t *
 | 
				
			||||||
 | 
					read_spng_thumbnail(
 | 
				
			||||||
 | 
						const gchar *path, const gchar *uri, time_t mtime, GError **error)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						FILE *fp;
 | 
				
			||||||
 | 
						cairo_surface_t *result = NULL;
 | 
				
			||||||
 | 
						if (!(fp = fopen(path, "rb"))) {
 | 
				
			||||||
 | 
							set_error(error, g_strerror(errno));
 | 
				
			||||||
 | 
							return NULL;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						errno = 0;
 | 
				
			||||||
 | 
						spng_ctx *ctx = spng_ctx_new(0);
 | 
				
			||||||
 | 
						if (!ctx) {
 | 
				
			||||||
 | 
							set_error(error, g_strerror(errno));
 | 
				
			||||||
 | 
							goto fail_init;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						int err;
 | 
				
			||||||
 | 
						size_t size = 0;
 | 
				
			||||||
 | 
						if ((err = spng_set_png_file(ctx, fp)) ||
 | 
				
			||||||
 | 
							(err = spng_set_image_limits(ctx, INT16_MAX, INT16_MAX)) ||
 | 
				
			||||||
 | 
							(err = spng_decoded_image_size(ctx, SPNG_FMT_RGBA8, &size))) {
 | 
				
			||||||
 | 
							set_error(error, spng_strerror(err));
 | 
				
			||||||
 | 
							goto fail;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if (check_spng_thumbnail(ctx, uri, mtime, &err) == false) {
 | 
				
			||||||
 | 
							set_error(error, err ? spng_strerror(err) : "mismatch");
 | 
				
			||||||
 | 
							goto fail;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						struct spng_ihdr ihdr = {};
 | 
				
			||||||
 | 
						struct spng_trns trns = {};
 | 
				
			||||||
 | 
						spng_get_ihdr(ctx, &ihdr);
 | 
				
			||||||
 | 
						bool may_be_translucent = !spng_get_trns(ctx, &trns) ||
 | 
				
			||||||
 | 
							ihdr.color_type == SPNG_COLOR_TYPE_GRAYSCALE_ALPHA ||
 | 
				
			||||||
 | 
							ihdr.color_type == SPNG_COLOR_TYPE_TRUECOLOR_ALPHA;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						cairo_surface_t *surface = cairo_image_surface_create(
 | 
				
			||||||
 | 
							may_be_translucent ? CAIRO_FORMAT_ARGB32 : CAIRO_FORMAT_RGB24,
 | 
				
			||||||
 | 
							ihdr.width, ihdr.height);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						cairo_status_t surface_status = cairo_surface_status(surface);
 | 
				
			||||||
 | 
						if (surface_status != CAIRO_STATUS_SUCCESS) {
 | 
				
			||||||
 | 
							set_error(error, cairo_status_to_string(surface_status));
 | 
				
			||||||
 | 
							goto fail_data;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						uint32_t *data = (uint32_t *) cairo_image_surface_get_data(surface);
 | 
				
			||||||
 | 
						g_assert((size_t) cairo_image_surface_get_stride(surface) *
 | 
				
			||||||
 | 
							cairo_image_surface_get_height(surface) == size);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						cairo_surface_flush(surface);
 | 
				
			||||||
 | 
						if ((err = spng_decode_image(ctx, data, size, SPNG_FMT_RGBA8,
 | 
				
			||||||
 | 
							SPNG_DECODE_TRNS | SPNG_DECODE_GAMMA))) {
 | 
				
			||||||
 | 
							set_error(error, spng_strerror(err));
 | 
				
			||||||
 | 
							goto fail_data;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// The specification does not say where the required metadata should be,
 | 
				
			||||||
 | 
						// it could very well be broken up into two parts.
 | 
				
			||||||
 | 
						if (check_spng_thumbnail(ctx, uri, mtime, &err) != true) {
 | 
				
			||||||
 | 
							set_error(
 | 
				
			||||||
 | 
								error, err ? spng_strerror(err) : "mismatch or not a thumbnail");
 | 
				
			||||||
 | 
							goto fail_data;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// pixman can be mildly abused to do this operation, but it won't be faster.
 | 
				
			||||||
 | 
						if (may_be_translucent) {
 | 
				
			||||||
 | 
							for (size_t i = size / sizeof *data; i--; ) {
 | 
				
			||||||
 | 
								const uint8_t *unit = (const uint8_t *) &data[i];
 | 
				
			||||||
 | 
								uint32_t a = unit[3],
 | 
				
			||||||
 | 
									b = PREMULTIPLY8(a, unit[2]),
 | 
				
			||||||
 | 
									g = PREMULTIPLY8(a, unit[1]),
 | 
				
			||||||
 | 
									r = PREMULTIPLY8(a, unit[0]);
 | 
				
			||||||
 | 
								data[i] = a << 24 | r << 16 | g << 8 | b;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							for (size_t i = size / sizeof *data; i--; ) {
 | 
				
			||||||
 | 
								uint32_t rgba = g_ntohl(data[i]);
 | 
				
			||||||
 | 
								data[i] = rgba << 24 | rgba >> 8;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						cairo_surface_mark_dirty((result = surface));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					fail_data:
 | 
				
			||||||
 | 
						if (!result)
 | 
				
			||||||
 | 
							cairo_surface_destroy(surface);
 | 
				
			||||||
 | 
					fail:
 | 
				
			||||||
 | 
						spng_ctx_free(ctx);
 | 
				
			||||||
 | 
					fail_init:
 | 
				
			||||||
 | 
						fclose(fp);
 | 
				
			||||||
 | 
						return result;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					cairo_surface_t *
 | 
				
			||||||
 | 
					fiv_thumbnail_lookup(GFile *target, FivThumbnailSize size)
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
						g_return_val_if_fail(size >= FIV_THUMBNAIL_SIZE_MIN &&
 | 
				
			||||||
 | 
							size <= FIV_THUMBNAIL_SIZE_MAX, NULL);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Local files only, at least for now.
 | 
				
			||||||
 | 
						gchar *path = g_file_get_path(target);
 | 
				
			||||||
 | 
						if (!path)
 | 
				
			||||||
 | 
							return NULL;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						GStatBuf st = {};
 | 
				
			||||||
 | 
						int err = g_stat(path, &st);
 | 
				
			||||||
 | 
						g_free(path);
 | 
				
			||||||
 | 
						if (err)
 | 
				
			||||||
 | 
							return NULL;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						gchar *uri = g_file_get_uri(target);
 | 
				
			||||||
 | 
						gchar *sum = g_compute_checksum_for_string(G_CHECKSUM_MD5, uri, -1);
 | 
				
			||||||
 | 
						gchar *thumbnails_dir = fiv_thumbnail_get_root();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// The lookup sequence is: nominal..max, then mirroring back to ..min.
 | 
				
			||||||
 | 
						cairo_surface_t *result = NULL;
 | 
				
			||||||
 | 
						GError *error = NULL;
 | 
				
			||||||
 | 
						for (int i = 0; i < FIV_THUMBNAIL_SIZE_COUNT; i++) {
 | 
				
			||||||
 | 
							FivThumbnailSize use = size + i;
 | 
				
			||||||
 | 
							if (use > FIV_THUMBNAIL_SIZE_MAX)
 | 
				
			||||||
 | 
								use = FIV_THUMBNAIL_SIZE_MAX - i;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							const char *name = fiv_thumbnail_sizes[use].thumbnail_spec_name;
 | 
				
			||||||
 | 
							gchar *wide =
 | 
				
			||||||
 | 
								g_strdup_printf("%s/wide-%s/%s.webp", thumbnails_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) {
 | 
				
			||||||
 | 
								// Higher up we can't distinguish images smaller than the thumbnail.
 | 
				
			||||||
 | 
								// Also, try not to rescale the already rescaled.
 | 
				
			||||||
 | 
								if (use != size)
 | 
				
			||||||
 | 
									mark_thumbnail_lq(result);
 | 
				
			||||||
 | 
								break;
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							gchar *path =
 | 
				
			||||||
 | 
								g_strdup_printf("%s/%s/%s.png", thumbnails_dir, name, sum);
 | 
				
			||||||
 | 
							result = read_spng_thumbnail(path, uri, st.st_mtim.tv_sec, &error);
 | 
				
			||||||
 | 
							if (error) {
 | 
				
			||||||
 | 
								g_debug("%s: %s", path, error->message);
 | 
				
			||||||
 | 
								g_clear_error(&error);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							g_free(path);
 | 
				
			||||||
 | 
							if (result) {
 | 
				
			||||||
 | 
								// Whatever produced it, we may be able to outclass it.
 | 
				
			||||||
 | 
								mark_thumbnail_lq(result);
 | 
				
			||||||
 | 
								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(thumbnails_dir);
 | 
				
			||||||
 | 
						g_free(sum);
 | 
				
			||||||
 | 
						g_free(uri);
 | 
				
			||||||
 | 
						return result;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										64
									
								
								fiv-thumbnail.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								fiv-thumbnail.h
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,64 @@
 | 
				
			|||||||
 | 
					//
 | 
				
			||||||
 | 
					// fiv-thumbnail.h: thumbnail management
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// Copyright (c) 2021, Přemysl Eric Janouch <p@janouch.name>
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// Permission to use, copy, modify, and/or distribute this software for any
 | 
				
			||||||
 | 
					// purpose with or without fee is hereby granted.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 | 
				
			||||||
 | 
					// WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 | 
				
			||||||
 | 
					// MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
 | 
				
			||||||
 | 
					// SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 | 
				
			||||||
 | 
					// WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
 | 
				
			||||||
 | 
					// OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
 | 
				
			||||||
 | 
					// CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#pragma once
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#include <cairo.h>
 | 
				
			||||||
 | 
					#include <gio/gio.h>
 | 
				
			||||||
 | 
					#include <glib.h>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// And this is how you avoid glib-mkenums.
 | 
				
			||||||
 | 
					typedef enum _FivThumbnailSize {
 | 
				
			||||||
 | 
					#define FIV_THUMBNAIL_SIZES(XX) \
 | 
				
			||||||
 | 
						XX(SMALL,  128, "normal") \
 | 
				
			||||||
 | 
						XX(NORMAL, 256, "large") \
 | 
				
			||||||
 | 
						XX(LARGE,  512, "x-large") \
 | 
				
			||||||
 | 
						XX(HUGE,  1024, "xx-large")
 | 
				
			||||||
 | 
					#define XX(name, value, dir) FIV_THUMBNAIL_SIZE_ ## name,
 | 
				
			||||||
 | 
						FIV_THUMBNAIL_SIZES(XX)
 | 
				
			||||||
 | 
					#undef XX
 | 
				
			||||||
 | 
						FIV_THUMBNAIL_SIZE_COUNT,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						FIV_THUMBNAIL_SIZE_MIN = 0,
 | 
				
			||||||
 | 
						FIV_THUMBNAIL_SIZE_MAX = FIV_THUMBNAIL_SIZE_COUNT - 1
 | 
				
			||||||
 | 
					} FivThumbnailSize;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					GType fiv_thumbnail_size_get_type(void) G_GNUC_CONST;
 | 
				
			||||||
 | 
					#define FIV_TYPE_THUMBNAIL_SIZE (fiv_thumbnail_size_get_type())
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					typedef struct _FivThumbnailSizeInfo {
 | 
				
			||||||
 | 
						int size;                           ///< Nominal size in pixels
 | 
				
			||||||
 | 
						const char *thumbnail_spec_name;    ///< thumbnail-spec directory name
 | 
				
			||||||
 | 
					} FivThumbnailSizeInfo;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					extern FivThumbnailSizeInfo fiv_thumbnail_sizes[FIV_THUMBNAIL_SIZE_COUNT];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					enum { FIV_THUMBNAIL_WIDE_COEFFICIENT = 2 };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// If non-NULL, indicates a thumbnail of insufficient quality.
 | 
				
			||||||
 | 
					extern cairo_user_data_key_t fiv_thumbnail_key_lq;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Returns this user's root thumbnail directory.
 | 
				
			||||||
 | 
					gchar *fiv_thumbnail_get_root(void);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Generates wide thumbnails of up to the specified size, saves them in cache.
 | 
				
			||||||
 | 
					gboolean fiv_thumbnail_produce(
 | 
				
			||||||
 | 
						GFile *target, FivThumbnailSize max_size, GError **error);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/// Retrieves a thumbnail of the most appropriate quality and resolution
 | 
				
			||||||
 | 
					/// for the target file.
 | 
				
			||||||
 | 
					cairo_surface_t *fiv_thumbnail_lookup(GFile *target, FivThumbnailSize size);
 | 
				
			||||||
@@ -71,7 +71,7 @@ resources = gnome.compile_resources('resources',
 | 
				
			|||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
exe = executable('fastiv', 'fastiv.c', 'fiv-view.c', 'fiv-io.c',
 | 
					exe = executable('fastiv', 'fastiv.c', 'fiv-view.c', 'fiv-io.c',
 | 
				
			||||||
	'fiv-browser.c', 'fiv-sidebar.c', 'xdg.c', resources,
 | 
						'fiv-browser.c', 'fiv-sidebar.c', 'fiv-thumbnail.c', 'xdg.c', resources,
 | 
				
			||||||
	install : true,
 | 
						install : true,
 | 
				
			||||||
	dependencies : [dependencies])
 | 
						dependencies : [dependencies])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user