Compare commits
6 Commits
336053f24d
...
aaa7cb93c3
Author | SHA1 | Date | |
---|---|---|---|
aaa7cb93c3 | |||
d4b51f07b5 | |||
720464327c | |||
ad1ff06aff | |||
af2eb411d9 | |||
004919cbc5 |
119
fiv-browser.c
119
fiv-browser.c
@ -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);
|
||||
}
|
||||
|
66
fiv-io.c
66
fiv-io.c
@ -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;
|
||||
|
10
fiv-io.h
10
fiv-io.h
@ -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);
|
||||
|
Loading…
x
Reference in New Issue
Block a user