Make thumbnailers pass back raw images
This commit is contained in:
parent
a28fbf25bc
commit
04ec292caf
|
@ -504,13 +504,23 @@ reload_thumbnails(FivBrowser *self)
|
||||||
|
|
||||||
// --- Minion management -------------------------------------------------------
|
// --- Minion management -------------------------------------------------------
|
||||||
|
|
||||||
static gboolean thumbnailer_next(Thumbnailer *thumbnailer);
|
#if !GLIB_CHECK_VERSION(2, 70, 0)
|
||||||
|
#define g_spawn_check_wait_status g_spawn_check_exit_status
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static gboolean thumbnailer_next(Thumbnailer *t);
|
||||||
|
|
||||||
static void
|
static void
|
||||||
thumbnailer_reprocess_entry(FivBrowser *self, Entry *entry)
|
thumbnailer_reprocess_entry(FivBrowser *self, GBytes *output, Entry *entry)
|
||||||
{
|
{
|
||||||
entry_add_thumbnail(entry, self);
|
g_clear_object(&entry->icon);
|
||||||
materialize_icon(self, entry);
|
g_clear_pointer(&entry->thumbnail, cairo_surface_destroy);
|
||||||
|
if (!output || !(entry->thumbnail = rescale_thumbnail(
|
||||||
|
fiv_io_deserialize(output), self->item_height))) {
|
||||||
|
entry_add_thumbnail(entry, self);
|
||||||
|
materialize_icon(self, entry);
|
||||||
|
}
|
||||||
|
|
||||||
gtk_widget_queue_resize(GTK_WIDGET(self));
|
gtk_widget_queue_resize(GTK_WIDGET(self));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -518,64 +528,75 @@ static void
|
||||||
on_thumbnailer_ready(GObject *object, GAsyncResult *res, gpointer user_data)
|
on_thumbnailer_ready(GObject *object, GAsyncResult *res, gpointer user_data)
|
||||||
{
|
{
|
||||||
GSubprocess *subprocess = G_SUBPROCESS(object);
|
GSubprocess *subprocess = G_SUBPROCESS(object);
|
||||||
Thumbnailer *thumbnailer = user_data;
|
Thumbnailer *t = user_data;
|
||||||
|
|
||||||
|
// Reading out pixel data directly from a thumbnailer serves two purposes:
|
||||||
|
// 1. it avoids pointless delays with large thumbnail sizes,
|
||||||
|
// 2. it enables thumbnailing things that cannot be placed in the cache.
|
||||||
GError *error = NULL;
|
GError *error = NULL;
|
||||||
if (!g_subprocess_wait_check_finish(subprocess, res, &error)) {
|
GBytes *out = NULL;
|
||||||
|
if (!g_subprocess_communicate_finish(subprocess, res, &out, NULL, &error)) {
|
||||||
if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
|
if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
|
||||||
g_error_free(error);
|
g_error_free(error);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!g_subprocess_get_if_exited(subprocess) ||
|
} else if (!g_subprocess_get_if_exited(subprocess)) {
|
||||||
g_subprocess_get_exit_status(subprocess) != EXIT_FAILURE)
|
// If it exited, it probably printed its own message.
|
||||||
g_warning("%s", error->message);
|
g_spawn_check_wait_status(g_subprocess_get_status(subprocess), &error);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
g_warning("%s", error->message);
|
||||||
g_error_free(error);
|
g_error_free(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
g_return_if_fail(subprocess == thumbnailer->minion);
|
g_return_if_fail(subprocess == t->minion);
|
||||||
|
|
||||||
gboolean succeeded = g_subprocess_get_if_exited(subprocess) &&
|
gboolean succeeded = g_subprocess_get_if_exited(subprocess) &&
|
||||||
g_subprocess_get_exit_status(subprocess) == EXIT_SUCCESS;
|
g_subprocess_get_exit_status(subprocess) == EXIT_SUCCESS;
|
||||||
g_clear_object(&thumbnailer->minion);
|
g_clear_object(&t->minion);
|
||||||
if (!thumbnailer->target) {
|
if (!t->target) {
|
||||||
g_warning("finished thumbnailing an unknown image");
|
g_warning("finished thumbnailing an unknown image");
|
||||||
|
g_clear_pointer(&out, g_bytes_unref);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (succeeded)
|
if (succeeded)
|
||||||
thumbnailer_reprocess_entry(thumbnailer->self, thumbnailer->target);
|
thumbnailer_reprocess_entry(t->self, out, t->target);
|
||||||
|
else
|
||||||
|
g_clear_pointer(&out, g_bytes_unref);
|
||||||
|
|
||||||
thumbnailer->target = NULL;
|
t->target = NULL;
|
||||||
thumbnailer_next(thumbnailer);
|
thumbnailer_next(t);
|
||||||
}
|
}
|
||||||
|
|
||||||
static gboolean
|
static gboolean
|
||||||
thumbnailer_next(Thumbnailer *thumbnailer)
|
thumbnailer_next(Thumbnailer *t)
|
||||||
{
|
{
|
||||||
// TODO(p): Ideally, try to keep the minions alive.
|
// TODO(p): Try to keep the minions alive (stdout will be a problem).
|
||||||
FivBrowser *self = thumbnailer->self;
|
FivBrowser *self = t->self;
|
||||||
GList *link = self->thumbnailers_queue;
|
GList *link = self->thumbnailers_queue;
|
||||||
if (!link)
|
if (!link)
|
||||||
return FALSE;
|
return FALSE;
|
||||||
|
|
||||||
thumbnailer->target = link->data;
|
t->target = link->data;
|
||||||
self->thumbnailers_queue =
|
self->thumbnailers_queue =
|
||||||
g_list_delete_link(self->thumbnailers_queue, self->thumbnailers_queue);
|
g_list_delete_link(self->thumbnailers_queue, self->thumbnailers_queue);
|
||||||
|
|
||||||
GError *error = NULL;
|
GError *error = NULL;
|
||||||
thumbnailer->minion = g_subprocess_new(G_SUBPROCESS_FLAGS_NONE, &error,
|
t->minion = g_subprocess_new(G_SUBPROCESS_FLAGS_STDOUT_PIPE, &error,
|
||||||
PROJECT_NAME, "--thumbnail",
|
PROJECT_NAME, "--thumbnail",
|
||||||
fiv_thumbnail_sizes[self->item_size].thumbnail_spec_name, "--",
|
fiv_thumbnail_sizes[self->item_size].thumbnail_spec_name, "--",
|
||||||
thumbnailer->target->uri, NULL);
|
t->target->uri, NULL);
|
||||||
if (error) {
|
if (error) {
|
||||||
g_warning("%s", error->message);
|
g_warning("%s", error->message);
|
||||||
g_error_free(error);
|
g_error_free(error);
|
||||||
return FALSE;
|
return FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
thumbnailer->cancel = g_cancellable_new();
|
t->cancel = g_cancellable_new();
|
||||||
g_subprocess_wait_check_async(thumbnailer->minion, thumbnailer->cancel,
|
g_subprocess_communicate_async(
|
||||||
on_thumbnailer_ready, thumbnailer);
|
t->minion, NULL, t->cancel, on_thumbnailer_ready, t);
|
||||||
return TRUE;
|
return TRUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -588,15 +609,15 @@ thumbnailers_abort(FivBrowser *self)
|
||||||
self->thumbnailers_queue = NULL;
|
self->thumbnailers_queue = NULL;
|
||||||
|
|
||||||
for (size_t i = 0; i < self->thumbnailers_len; i++) {
|
for (size_t i = 0; i < self->thumbnailers_len; i++) {
|
||||||
Thumbnailer *thumbnailer = self->thumbnailers + i;
|
Thumbnailer *t = self->thumbnailers + i;
|
||||||
if (thumbnailer->cancel) {
|
if (t->cancel) {
|
||||||
g_cancellable_cancel(thumbnailer->cancel);
|
g_cancellable_cancel(t->cancel);
|
||||||
g_clear_object(&thumbnailer->cancel);
|
g_clear_object(&t->cancel);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Just let them exit on their own.
|
// Just let them exit on their own.
|
||||||
g_clear_object(&thumbnailer->minion);
|
g_clear_object(&t->minion);
|
||||||
thumbnailer->target = NULL;
|
t->target = NULL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
66
fiv-io.c
66
fiv-io.c
|
@ -2703,13 +2703,77 @@ fiv_io_open_from_data(
|
||||||
return surface;
|
return surface;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Thumbnail passing utilities ---------------------------------------------
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
int width, height, stride, format;
|
||||||
|
} CairoHeader;
|
||||||
|
|
||||||
|
void
|
||||||
|
fiv_io_serialize_to_stdout(cairo_surface_t *surface)
|
||||||
|
{
|
||||||
|
if (!surface || cairo_surface_get_type(surface) != CAIRO_SURFACE_TYPE_IMAGE)
|
||||||
|
return;
|
||||||
|
|
||||||
|
#ifdef G_OS_UNIX
|
||||||
|
// Common courtesy, this is never what the user wants.
|
||||||
|
if (isatty(fileno(stdout)))
|
||||||
|
return;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
CairoHeader h = {
|
||||||
|
.width = cairo_image_surface_get_width(surface),
|
||||||
|
.height = cairo_image_surface_get_height(surface),
|
||||||
|
.stride = cairo_image_surface_get_stride(surface),
|
||||||
|
.format = cairo_image_surface_get_format(surface),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Cairo lets pixman initialize image surfaces.
|
||||||
|
// pixman allocates stride * height, not omitting those trailing bytes.
|
||||||
|
const unsigned char *data = cairo_image_surface_get_data(surface);
|
||||||
|
if (fwrite(&h, sizeof h, 1, stdout) == 1)
|
||||||
|
fwrite(data, 1, h.stride * h.height, stdout);
|
||||||
|
}
|
||||||
|
|
||||||
|
cairo_surface_t *
|
||||||
|
fiv_io_deserialize(GBytes *bytes)
|
||||||
|
{
|
||||||
|
CairoHeader h = {};
|
||||||
|
GByteArray *array = g_bytes_unref_to_array(bytes);
|
||||||
|
if (array->len < sizeof h) {
|
||||||
|
g_byte_array_unref(array);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
h = *(CairoHeader *) array->data;
|
||||||
|
if (h.width < 1 || h.height < 1 || h.stride < h.width ||
|
||||||
|
G_MAXSIZE / (gsize) h.stride < (gsize) h.height ||
|
||||||
|
array->len - sizeof h < (gsize) h.stride * (gsize) h.height) {
|
||||||
|
g_byte_array_unref(array);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
cairo_surface_t *surface = cairo_image_surface_create_for_data(
|
||||||
|
array->data + sizeof h, h.format, h.width, h.height, h.stride);
|
||||||
|
if (cairo_surface_status(surface) != CAIRO_STATUS_SUCCESS) {
|
||||||
|
cairo_surface_destroy(surface);
|
||||||
|
g_byte_array_unref(array);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
static cairo_user_data_key_t key;
|
||||||
|
cairo_surface_set_user_data(
|
||||||
|
surface, &key, array, (cairo_destroy_func_t) g_byte_array_unref);
|
||||||
|
return surface;
|
||||||
|
}
|
||||||
|
|
||||||
// --- Filesystem --------------------------------------------------------------
|
// --- Filesystem --------------------------------------------------------------
|
||||||
|
|
||||||
#include "xdg.h"
|
#include "xdg.h"
|
||||||
|
|
||||||
#include <fnmatch.h>
|
#include <fnmatch.h>
|
||||||
|
|
||||||
typedef struct _ModelEntry {
|
typedef struct {
|
||||||
gchar *uri; ///< GIO URI
|
gchar *uri; ///< GIO URI
|
||||||
gchar *collate_key; ///< Collate key for the filename
|
gchar *collate_key; ///< Collate key for the filename
|
||||||
gint64 mtime_msec; ///< Modification time in milliseconds
|
gint64 mtime_msec; ///< Modification time in milliseconds
|
||||||
|
|
5
fiv-io.h
5
fiv-io.h
|
@ -96,6 +96,11 @@ cairo_surface_t *fiv_io_open(const FivIoOpenContext *ctx, GError **error);
|
||||||
cairo_surface_t *fiv_io_open_from_data(
|
cairo_surface_t *fiv_io_open_from_data(
|
||||||
const char *data, size_t len, const FivIoOpenContext *ctx, GError **error);
|
const char *data, size_t len, const FivIoOpenContext *ctx, GError **error);
|
||||||
|
|
||||||
|
// --- Thumbnail passing utilities ---------------------------------------------
|
||||||
|
|
||||||
|
void fiv_io_serialize_to_stdout(cairo_surface_t *surface);
|
||||||
|
cairo_surface_t *fiv_io_deserialize(GBytes *bytes);
|
||||||
|
|
||||||
// --- Filesystem --------------------------------------------------------------
|
// --- Filesystem --------------------------------------------------------------
|
||||||
|
|
||||||
typedef enum _FivIoModelSort {
|
typedef enum _FivIoModelSort {
|
||||||
|
|
|
@ -230,7 +230,8 @@ save_thumbnail(cairo_surface_t *thumbnail, const char *path, GString *thum)
|
||||||
}
|
}
|
||||||
|
|
||||||
gboolean
|
gboolean
|
||||||
fiv_thumbnail_produce(GFile *target, FivThumbnailSize max_size, GError **error)
|
fiv_thumbnail_produce(GFile *target, FivThumbnailSize max_size,
|
||||||
|
cairo_surface_t **max_size_surface, GError **error)
|
||||||
{
|
{
|
||||||
g_return_val_if_fail(max_size >= FIV_THUMBNAIL_SIZE_MIN &&
|
g_return_val_if_fail(max_size >= FIV_THUMBNAIL_SIZE_MIN &&
|
||||||
max_size <= FIV_THUMBNAIL_SIZE_MAX, FALSE);
|
max_size <= FIV_THUMBNAIL_SIZE_MAX, FALSE);
|
||||||
|
@ -306,8 +307,12 @@ fiv_thumbnail_produce(GFile *target, FivThumbnailSize max_size, GError **error)
|
||||||
gchar *path = g_strdup_printf("%s/wide-%s/%s.webp", thumbnails_dir,
|
gchar *path = g_strdup_printf("%s/wide-%s/%s.webp", thumbnails_dir,
|
||||||
fiv_thumbnail_sizes[use].thumbnail_spec_name, sum);
|
fiv_thumbnail_sizes[use].thumbnail_spec_name, sum);
|
||||||
save_thumbnail(scaled, path, thum);
|
save_thumbnail(scaled, path, thum);
|
||||||
cairo_surface_destroy(scaled);
|
|
||||||
g_free(path);
|
g_free(path);
|
||||||
|
|
||||||
|
if (!*max_size_surface)
|
||||||
|
*max_size_surface = scaled;
|
||||||
|
else
|
||||||
|
cairo_surface_destroy(scaled);
|
||||||
}
|
}
|
||||||
|
|
||||||
g_string_free(thum, TRUE);
|
g_string_free(thum, TRUE);
|
||||||
|
|
|
@ -56,8 +56,9 @@ extern cairo_user_data_key_t fiv_thumbnail_key_lq;
|
||||||
gchar *fiv_thumbnail_get_root(void);
|
gchar *fiv_thumbnail_get_root(void);
|
||||||
|
|
||||||
/// Generates wide thumbnails of up to the specified size, saves them in cache.
|
/// Generates wide thumbnails of up to the specified size, saves them in cache.
|
||||||
gboolean fiv_thumbnail_produce(
|
/// Returns the surface used for the maximum size (if the pointer was NULL).
|
||||||
GFile *target, FivThumbnailSize max_size, GError **error);
|
gboolean fiv_thumbnail_produce(GFile *target, FivThumbnailSize max_size,
|
||||||
|
cairo_surface_t **max_size_surface, GError **error);
|
||||||
|
|
||||||
/// Retrieves a thumbnail of the most appropriate quality and resolution
|
/// Retrieves a thumbnail of the most appropriate quality and resolution
|
||||||
/// for the target file.
|
/// for the target file.
|
||||||
|
|
7
fiv.c
7
fiv.c
|
@ -1822,9 +1822,14 @@ main(int argc, char *argv[])
|
||||||
exit_fatal("unknown thumbnail size: %s", thumbnail_size);
|
exit_fatal("unknown thumbnail size: %s", thumbnail_size);
|
||||||
|
|
||||||
GFile *target = g_file_new_for_commandline_arg(path_arg);
|
GFile *target = g_file_new_for_commandline_arg(path_arg);
|
||||||
if (!fiv_thumbnail_produce(target, size, &error))
|
cairo_surface_t *surface = NULL;
|
||||||
|
if (!fiv_thumbnail_produce(target, size, &surface, &error))
|
||||||
exit_fatal("%s", error->message);
|
exit_fatal("%s", error->message);
|
||||||
g_object_unref(target);
|
g_object_unref(target);
|
||||||
|
if (surface) {
|
||||||
|
fiv_io_serialize_to_stdout(surface);
|
||||||
|
cairo_surface_destroy(surface);
|
||||||
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue