Compare commits

...

6 Commits

Author SHA1 Message Date
aaa7cb93c3
Fix transparent gdk-pixbuf loading 2021-12-28 02:07:42 +01:00
d4b51f07b5
Avoid unused alpha channels when rescaling 2021-12-28 02:07:42 +01:00
720464327c
Clean up 2021-12-28 02:07:42 +01:00
ad1ff06aff
Avoid producing thumbnails of thumbnails 2021-12-28 02:07:42 +01:00
af2eb411d9
Try to regenerate low quality thumbnails 2021-12-28 02:07:42 +01:00
004919cbc5
Clean up
This makes the thumbnailer able to load at most one directory,
which we don't particularly mind.
2021-12-28 00:37:55 +01:00
3 changed files with 120 additions and 75 deletions

View File

@ -48,11 +48,11 @@ struct _FivBrowser {
int item_spacing; ///< Space between items in pixels
char *path; ///< Current path
GArray *entries; ///< [Entry]
GArray *layouted_rows; ///< [Row]
GArray *entries; ///< []Entry
GArray *layouted_rows; ///< []Row
int selected;
GList *thumbnail_queue; ///< URIs to thumbnail
GList *thumbnail_queue; ///< Entry pointers
GSubprocess *thumbnailer; ///< A slave for the current queue head
GCancellable *thumbnail_cancel; ///< Cancellable handle
@ -66,8 +66,6 @@ typedef struct entry Entry;
typedef struct item Item;
typedef struct row Row;
static const double g_permitted_width_multiplier = 2;
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
struct entry {
@ -305,8 +303,8 @@ rescale_thumbnail(cairo_surface_t *thumbnail, double row_height)
double scale_x = 1;
double scale_y = 1;
if (width > g_permitted_width_multiplier * height) {
scale_x = g_permitted_width_multiplier * row_height / width;
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;
@ -351,6 +349,8 @@ rescale_thumbnail(cairo_surface_t *thumbnail, double row_height)
pixman_image_unref(src);
pixman_image_unref(dest);
cairo_surface_set_user_data(
scaled, &fiv_io_key_thumbnail_lq, (void *) (intptr_t) 1, NULL);
cairo_surface_destroy(thumbnail);
cairo_surface_mark_dirty(scaled);
return scaled;
@ -448,25 +448,11 @@ reload_thumbnails(FivBrowser *self)
// --- Slave management --------------------------------------------------------
static void thumbnailer_step(FivBrowser *self);
static void thumbnailer_next(FivBrowser *self);
static void
thumbnailer_process(FivBrowser *self, const gchar *uri)
thumbnailer_reprocess_entry(FivBrowser *self, Entry *entry)
{
// TODO(p): Consider using Entry pointers directly.
Entry *entry = NULL;
for (guint i = 0; i < self->entries->len; i++) {
Entry *e = &g_array_index(self->entries, Entry, i);
if (!g_strcmp0(e->uri, uri)) {
entry = e;
break;
}
}
if (!entry) {
g_warning("finished thumbnailing an unknown URI");
return;
}
entry_add_thumbnail(entry, self);
materialize_icon(self, entry);
gtk_widget_queue_resize(GTK_WIDGET(self));
@ -491,26 +477,33 @@ on_thumbnailer_ready(GObject *object, GAsyncResult *res, gpointer user_data)
return;
}
gchar *uri = self->thumbnail_queue->data;
Entry *entry = self->thumbnail_queue->data;
self->thumbnail_queue =
g_list_delete_link(self->thumbnail_queue, self->thumbnail_queue);
if (succeeded)
thumbnailer_process(self, uri);
g_free(uri);
thumbnailer_reprocess_entry(self, entry);
// TODO(p): Eliminate high recursion depth with non-paths.
thumbnailer_step(self);
thumbnailer_next(self);
}
static void
thumbnailer_step(FivBrowser *self)
thumbnailer_next(FivBrowser *self)
{
if (!self->thumbnail_queue)
// TODO(p): At least launch multiple thumbnailers in parallel.
GList *link = self->thumbnail_queue;
if (!link)
return;
GFile *file = g_file_new_for_uri(self->thumbnail_queue->data);
const Entry *entry = link->data;
GFile *file = g_file_new_for_uri(entry->uri);
gchar *path = g_file_get_path(file);
g_object_unref(file);
if (!path) {
// TODO(p): Support thumbnailing non-local URIs in some manner.
self->thumbnail_queue = g_list_delete_link(self->thumbnail_queue, link);
return;
}
GError *error = NULL;
self->thumbnailer = g_subprocess_new(G_SUBPROCESS_FLAGS_NONE, &error,
@ -518,7 +511,6 @@ thumbnailer_step(FivBrowser *self)
fiv_io_thumbnail_sizes[self->item_size].thumbnail_spec_name, "--", path,
NULL);
g_free(path);
if (error) {
g_warning("%s", error->message);
g_error_free(error);
@ -531,28 +523,47 @@ thumbnailer_step(FivBrowser *self)
}
static void
thumbnailer_launch(FivBrowser *self)
thumbnailer_abort(FivBrowser *self)
{
if (self->thumbnailer) {
if (self->thumbnail_cancel) {
g_cancellable_cancel(self->thumbnail_cancel);
g_clear_object(&self->thumbnail_cancel);
// Just let it exit on its own.
g_clear_object(&self->thumbnailer);
g_list_free_full(self->thumbnail_queue, g_free);
self->thumbnail_queue = NULL;
}
// TODO(p): Also collect rescaled images.
GList *missing = NULL, *rescaled = NULL;
// Just let it exit on its own.
g_clear_object(&self->thumbnailer);
g_list_free(self->thumbnail_queue);
self->thumbnail_queue = NULL;
}
static void
thumbnailer_start(FivBrowser *self)
{
thumbnailer_abort(self);
// TODO(p): Leave out all paths containing .cache/thumbnails altogether.
gchar *thumbnails_dir = fiv_io_get_thumbnail_root();
GFile *thumbnails = g_file_new_for_path(thumbnails_dir);
g_free(thumbnails_dir);
GFile *current = g_file_new_for_path(self->path);
gboolean is_a_thumbnail = g_file_has_prefix(current, thumbnails);
g_object_unref(current);
g_object_unref(thumbnails);
if (is_a_thumbnail)
return;
GList *missing = NULL, *lq = NULL;
for (guint i = self->entries->len; i--; ) {
Entry *e = &g_array_index(self->entries, Entry, i);
if (e->icon)
missing = g_list_prepend(missing, g_strdup(e->uri));
Entry *entry = &g_array_index(self->entries, Entry, i);
if (entry->icon)
missing = g_list_prepend(missing, entry);
else if (cairo_surface_get_user_data(
entry->thumbnail, &fiv_io_key_thumbnail_lq))
lq = g_list_prepend(lq, entry);
}
self->thumbnail_queue = g_list_concat(missing, rescaled);
thumbnailer_step(self);
self->thumbnail_queue = g_list_concat(missing, lq);
thumbnailer_next(self);
}
// --- Context menu-------------------------------------------------------------
@ -742,19 +753,13 @@ static void
fiv_browser_finalize(GObject *gobject)
{
FivBrowser *self = FIV_BROWSER(gobject);
thumbnailer_abort(self);
g_free(self->path);
g_array_free(self->entries, TRUE);
g_array_free(self->layouted_rows, TRUE);
cairo_surface_destroy(self->glow);
g_clear_object(&self->pointer);
g_list_free_full(self->thumbnail_queue, g_free);
g_clear_object(&self->thumbnailer);
if (self->thumbnail_cancel) {
g_cancellable_cancel(self->thumbnail_cancel);
g_clear_object(&self->thumbnail_cancel);
}
G_OBJECT_CLASS(fiv_browser_parent_class)->finalize(gobject);
}
@ -816,8 +821,9 @@ fiv_browser_get_preferred_width(GtkWidget *widget, gint *minimum, gint *natural)
GtkBorder padding = {};
gtk_style_context_get_padding(style, GTK_STATE_FLAG_NORMAL, &padding);
*minimum = *natural = g_permitted_width_multiplier * self->item_height +
padding.left + 2 * self->item_border_x + padding.right;
*minimum = *natural =
FIV_IO_WIDE_THUMBNAIL_COEFFICIENT * self->item_height + padding.left +
2 * self->item_border_x + padding.right;
}
static void
@ -1166,6 +1172,9 @@ void
fiv_browser_load(
FivBrowser *self, FivBrowserFilterCallback cb, const char *path)
{
g_return_if_fail(FIV_IS_BROWSER(self));
thumbnailer_abort(self);
g_array_set_size(self->entries, 0);
g_array_set_size(self->layouted_rows, 0);
g_clear_pointer(&self->path, g_free);
@ -1198,5 +1207,5 @@ fiv_browser_load(
g_array_sort(self->entries, entry_compare);
reload_thumbnails(self);
thumbnailer_launch(self);
thumbnailer_start(self);
}

View File

@ -2210,6 +2210,7 @@ load_gdkpixbuf_argb32_unpremultiplied(GdkPixbuf *pixbuf)
p += 4;
}
}
cairo_surface_mark_dirty(surface);
return surface;
}
@ -2288,6 +2289,8 @@ 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_previous;
cairo_user_data_key_t fiv_io_key_thumbnail_lq;
cairo_surface_t *
fiv_io_open(
const gchar *path, FivIoProfile profile, gboolean enhance, GError **error)
@ -2761,8 +2764,21 @@ 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;
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;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@ -2770,13 +2786,14 @@ static const double g_wide_thumbnail_factor = 2;
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 > g_wide_thumbnail_factor * height) {
scale_x = g_wide_thumbnail_factor * row_height / width;
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;
@ -2785,13 +2802,13 @@ rescale_thumbnail(cairo_surface_t *thumbnail, double row_height)
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);
(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);
@ -2804,6 +2821,7 @@ rescale_thumbnail(cairo_surface_t *thumbnail, double row_height)
cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
cairo_paint(cr);
cairo_destroy(cr);
mark_thumbnail_lq(scaled);
return scaled;
}
@ -2847,13 +2865,13 @@ fiv_io_produce_thumbnail(GFile *target, FivIoThumbnailSize size, GError **error)
// 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");
gchar *thumbnails_dir = fiv_io_get_thumbnail_root();
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);
gchar *path = g_strdup_printf("%s/wide-%s/%s.webp", thumbnails_dir,
fiv_io_thumbnail_sizes[use].thumbnail_spec_name, sum);
GError *e = NULL;
while (!fiv_io_save(scaled, scaled, NULL, path, &e)) {
@ -2880,7 +2898,7 @@ fiv_io_produce_thumbnail(GFile *target, FivIoThumbnailSize size, GError **error)
g_free(path);
}
g_free(cache_dir);
g_free(thumbnails_dir);
g_free(sum);
g_free(uri);
cairo_surface_destroy(surface);
@ -3052,44 +3070,52 @@ fiv_io_lookup_thumbnail(GFile *target, FivIoThumbnailSize size)
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");
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++) {
int use = size + 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/thumbnails/wide-%s/%s.webp", cache_dir, name, sum);
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)
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/thumbnails/%s/%s.png", cache_dir, name, sum);
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)
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(cache_dir);
g_free(thumbnails_dir);
g_free(sum);
g_free(uri);
return result;

View File

@ -67,6 +67,9 @@ extern cairo_user_data_key_t fiv_io_key_page_next;
/// There is no wrap-around. This is a weak pointer.
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(
const gchar *path, FivIoProfile profile, gboolean enhance, GError **error);
cairo_surface_t *fiv_io_open_from_data(const char *data, size_t len,
@ -131,6 +134,13 @@ typedef struct _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);