Compare commits
2 Commits
231b77e6c0
...
35c1f2c8ba
Author | SHA1 | Date | |
---|---|---|---|
35c1f2c8ba | |||
b973d323ba |
132
fiv-browser.c
132
fiv-browser.c
@ -41,6 +41,17 @@
|
|||||||
// The glow is actually a glowing margin, the border is rendered in two parts.
|
// The glow is actually a glowing margin, the border is rendered in two parts.
|
||||||
//
|
//
|
||||||
|
|
||||||
|
typedef struct entry Entry;
|
||||||
|
typedef struct item Item;
|
||||||
|
typedef struct row Row;
|
||||||
|
|
||||||
|
typedef struct _Thumbnailer {
|
||||||
|
FivBrowser *self; ///< Parent browser
|
||||||
|
Entry *target; ///< Currently processed Entry pointer
|
||||||
|
GSubprocess *minion; ///< A slave for the current queue head
|
||||||
|
GCancellable *cancel; ///< Cancellable handle
|
||||||
|
} Thumbnailer;
|
||||||
|
|
||||||
struct _FivBrowser {
|
struct _FivBrowser {
|
||||||
GtkWidget parent_instance;
|
GtkWidget parent_instance;
|
||||||
|
|
||||||
@ -53,9 +64,9 @@ struct _FivBrowser {
|
|||||||
GArray *layouted_rows; ///< []Row
|
GArray *layouted_rows; ///< []Row
|
||||||
int selected;
|
int selected;
|
||||||
|
|
||||||
GList *thumbnail_queue; ///< Entry pointers
|
Thumbnailer *thumbnailers; ///< Parallelized thumbnailers
|
||||||
GSubprocess *thumbnailer; ///< A slave for the current queue head
|
size_t thumbnailers_len; ///< Thumbnailers array size
|
||||||
GCancellable *thumbnail_cancel; ///< Cancellable handle
|
GList *thumbnailers_queue; ///< Queued up Entry pointers
|
||||||
|
|
||||||
GdkCursor *pointer; ///< Cached pointer cursor
|
GdkCursor *pointer; ///< Cached pointer cursor
|
||||||
cairo_surface_t *glow; ///< CAIRO_FORMAT_A8 mask
|
cairo_surface_t *glow; ///< CAIRO_FORMAT_A8 mask
|
||||||
@ -63,10 +74,6 @@ struct _FivBrowser {
|
|||||||
int item_border_y; ///< T/B .item margin + border
|
int item_border_y; ///< T/B .item margin + border
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef struct entry Entry;
|
|
||||||
typedef struct item Item;
|
|
||||||
typedef struct row Row;
|
|
||||||
|
|
||||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
struct entry {
|
struct entry {
|
||||||
@ -450,9 +457,9 @@ reload_thumbnails(FivBrowser *self)
|
|||||||
gtk_widget_queue_resize(GTK_WIDGET(self));
|
gtk_widget_queue_resize(GTK_WIDGET(self));
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Slave management --------------------------------------------------------
|
// --- Minion management -------------------------------------------------------
|
||||||
|
|
||||||
static void thumbnailer_next(FivBrowser *self);
|
static gboolean thumbnailer_next(Thumbnailer *thumbnailer);
|
||||||
|
|
||||||
static void
|
static void
|
||||||
thumbnailer_reprocess_entry(FivBrowser *self, Entry *entry)
|
thumbnailer_reprocess_entry(FivBrowser *self, Entry *entry)
|
||||||
@ -466,7 +473,8 @@ 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);
|
||||||
FivBrowser *self = FIV_BROWSER(user_data);
|
Thumbnailer *thumbnailer = user_data;
|
||||||
|
|
||||||
GError *error = NULL;
|
GError *error = NULL;
|
||||||
if (!g_subprocess_wait_check_finish(subprocess, res, &error)) {
|
if (!g_subprocess_wait_check_finish(subprocess, res, &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)) {
|
||||||
@ -479,67 +487,78 @@ on_thumbnailer_ready(GObject *object, GAsyncResult *res, gpointer user_data)
|
|||||||
g_error_free(error);
|
g_error_free(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
g_return_if_fail(subprocess == thumbnailer->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(&self->thumbnailer);
|
g_clear_object(&thumbnailer->minion);
|
||||||
if (!self->thumbnail_queue) {
|
if (!thumbnailer->target) {
|
||||||
g_warning("finished thumbnailing an unknown image");
|
g_warning("finished thumbnailing an unknown image");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Entry *entry = self->thumbnail_queue->data;
|
|
||||||
self->thumbnail_queue =
|
|
||||||
g_list_delete_link(self->thumbnail_queue, self->thumbnail_queue);
|
|
||||||
if (succeeded)
|
if (succeeded)
|
||||||
thumbnailer_reprocess_entry(self, entry);
|
thumbnailer_reprocess_entry(thumbnailer->self, thumbnailer->target);
|
||||||
|
|
||||||
thumbnailer_next(self);
|
thumbnailer->target = NULL;
|
||||||
|
thumbnailer_next(thumbnailer);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static gboolean
|
||||||
thumbnailer_next(FivBrowser *self)
|
thumbnailer_next(Thumbnailer *thumbnailer)
|
||||||
{
|
{
|
||||||
// TODO(p): At least launch multiple thumbnailers in parallel.
|
// TODO(p): Ideally, try to keep the minions alive.
|
||||||
// Ideally, try to keep them alive.
|
FivBrowser *self = thumbnailer->self;
|
||||||
GList *link = self->thumbnail_queue;
|
GList *link = self->thumbnailers_queue;
|
||||||
if (!link)
|
if (!link)
|
||||||
return;
|
return FALSE;
|
||||||
|
|
||||||
|
thumbnailer->target = link->data;
|
||||||
|
self->thumbnailers_queue =
|
||||||
|
g_list_delete_link(self->thumbnailers_queue, self->thumbnailers_queue);
|
||||||
|
|
||||||
const Entry *entry = link->data;
|
|
||||||
GError *error = NULL;
|
GError *error = NULL;
|
||||||
self->thumbnailer = g_subprocess_new(G_SUBPROCESS_FLAGS_NONE, &error,
|
thumbnailer->minion = g_subprocess_new(G_SUBPROCESS_FLAGS_NONE, &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, "--",
|
||||||
entry->uri, NULL);
|
thumbnailer->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;
|
return FALSE;
|
||||||
}
|
}
|
||||||
|
|
||||||
self->thumbnail_cancel = g_cancellable_new();
|
thumbnailer->cancel = g_cancellable_new();
|
||||||
g_subprocess_wait_check_async(
|
g_subprocess_wait_check_async(thumbnailer->minion, thumbnailer->cancel,
|
||||||
self->thumbnailer, self->thumbnail_cancel, on_thumbnailer_ready, self);
|
on_thumbnailer_ready, thumbnailer);
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||||
|
|
||||||
|
static void
|
||||||
|
thumbnailers_abort(FivBrowser *self)
|
||||||
|
{
|
||||||
|
g_list_free(self->thumbnailers_queue);
|
||||||
|
self->thumbnailers_queue = NULL;
|
||||||
|
|
||||||
|
for (size_t i = 0; i < self->thumbnailers_len; i++) {
|
||||||
|
Thumbnailer *thumbnailer = self->thumbnailers + i;
|
||||||
|
if (thumbnailer->cancel) {
|
||||||
|
g_cancellable_cancel(thumbnailer->cancel);
|
||||||
|
g_clear_object(&thumbnailer->cancel);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Just let them exit on their own.
|
||||||
|
g_clear_object(&thumbnailer->minion);
|
||||||
|
thumbnailer->target = NULL;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
thumbnailer_abort(FivBrowser *self)
|
thumbnailers_start(FivBrowser *self)
|
||||||
{
|
{
|
||||||
if (self->thumbnail_cancel) {
|
thumbnailers_abort(self);
|
||||||
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(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.
|
// TODO(p): Leave out all paths containing .cache/thumbnails altogether.
|
||||||
gchar *thumbnails_dir = fiv_thumbnail_get_root();
|
gchar *thumbnails_dir = fiv_thumbnail_get_root();
|
||||||
@ -562,8 +581,11 @@ thumbnailer_start(FivBrowser *self)
|
|||||||
lq = g_list_prepend(lq, entry);
|
lq = g_list_prepend(lq, entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
self->thumbnail_queue = g_list_concat(missing, lq);
|
self->thumbnailers_queue = g_list_concat(missing, lq);
|
||||||
thumbnailer_next(self);
|
for (size_t i = 0; i < self->thumbnailers_len; i++) {
|
||||||
|
if (!thumbnailer_next(self->thumbnailers + i))
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Context menu-------------------------------------------------------------
|
// --- Context menu-------------------------------------------------------------
|
||||||
@ -750,7 +772,7 @@ static void
|
|||||||
fiv_browser_finalize(GObject *gobject)
|
fiv_browser_finalize(GObject *gobject)
|
||||||
{
|
{
|
||||||
FivBrowser *self = FIV_BROWSER(gobject);
|
FivBrowser *self = FIV_BROWSER(gobject);
|
||||||
thumbnailer_abort(self);
|
thumbnailers_abort(self);
|
||||||
g_array_free(self->entries, TRUE);
|
g_array_free(self->entries, TRUE);
|
||||||
g_array_free(self->layouted_rows, TRUE);
|
g_array_free(self->layouted_rows, TRUE);
|
||||||
if (self->model) {
|
if (self->model) {
|
||||||
@ -1146,9 +1168,15 @@ fiv_browser_init(FivBrowser *self)
|
|||||||
g_array_set_clear_func(self->entries, (GDestroyNotify) entry_free);
|
g_array_set_clear_func(self->entries, (GDestroyNotify) entry_free);
|
||||||
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);
|
||||||
|
self->selected = -1;
|
||||||
|
|
||||||
|
self->thumbnailers_len = g_get_num_processors();
|
||||||
|
self->thumbnailers =
|
||||||
|
g_malloc0_n(self->thumbnailers_len, sizeof *self->thumbnailers);
|
||||||
|
for (size_t i = 0; i < self->thumbnailers_len; i++)
|
||||||
|
self->thumbnailers[i].self = self;
|
||||||
|
|
||||||
set_item_size(self, FIV_THUMBNAIL_SIZE_NORMAL);
|
set_item_size(self, FIV_THUMBNAIL_SIZE_NORMAL);
|
||||||
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);
|
||||||
|
|
||||||
g_signal_connect_swapped(gtk_settings_get_default(),
|
g_signal_connect_swapped(gtk_settings_get_default(),
|
||||||
@ -1163,7 +1191,7 @@ on_model_files_changed(FivIoModel *model, FivBrowser *self)
|
|||||||
g_return_if_fail(model == self->model);
|
g_return_if_fail(model == self->model);
|
||||||
|
|
||||||
// TODO(p): Later implement arguments.
|
// TODO(p): Later implement arguments.
|
||||||
thumbnailer_abort(self);
|
thumbnailers_abort(self);
|
||||||
g_array_set_size(self->entries, 0);
|
g_array_set_size(self->entries, 0);
|
||||||
g_array_set_size(self->layouted_rows, 0);
|
g_array_set_size(self->layouted_rows, 0);
|
||||||
|
|
||||||
@ -1176,7 +1204,7 @@ on_model_files_changed(FivIoModel *model, FivBrowser *self)
|
|||||||
g_ptr_array_free(files, TRUE);
|
g_ptr_array_free(files, TRUE);
|
||||||
|
|
||||||
reload_thumbnails(self);
|
reload_thumbnails(self);
|
||||||
thumbnailer_start(self);
|
thumbnailers_start(self);
|
||||||
}
|
}
|
||||||
|
|
||||||
GtkWidget *
|
GtkWidget *
|
||||||
|
16
fiv-io.c
16
fiv-io.c
@ -564,10 +564,17 @@ load_wuffs_frame(struct load_wuffs_frame_context *ctx, GError **error)
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Wuffs' test/data/animated-red-blue.gif, e.g., needs this handling.
|
||||||
|
cairo_format_t decode_format = ctx->cairo_format;
|
||||||
|
if (wuffs_base__frame_config__index(&fc) > 0 &&
|
||||||
|
wuffs_base__pixel_config__pixel_format(&ctx->cfg.pixcfg).repr ==
|
||||||
|
WUFFS_BASE__PIXEL_FORMAT__BGRA_NONPREMUL)
|
||||||
|
decode_format = CAIRO_FORMAT_ARGB32;
|
||||||
|
|
||||||
bool success = false;
|
bool success = false;
|
||||||
unsigned char *targetbuf = NULL;
|
unsigned char *targetbuf = NULL;
|
||||||
cairo_surface_t *surface =
|
cairo_surface_t *surface =
|
||||||
cairo_image_surface_create(ctx->cairo_format, ctx->width, ctx->height);
|
cairo_image_surface_create(decode_format, ctx->width, ctx->height);
|
||||||
cairo_status_t surface_status = cairo_surface_status(surface);
|
cairo_status_t surface_status = cairo_surface_status(surface);
|
||||||
if (surface_status != CAIRO_STATUS_SUCCESS) {
|
if (surface_status != CAIRO_STATUS_SUCCESS) {
|
||||||
set_error(error, cairo_status_to_string(surface_status));
|
set_error(error, cairo_status_to_string(surface_status));
|
||||||
@ -659,6 +666,7 @@ load_wuffs_frame(struct load_wuffs_frame_context *ctx, GError **error)
|
|||||||
cairo_surface_mark_dirty(canvas);
|
cairo_surface_mark_dirty(canvas);
|
||||||
|
|
||||||
// Apply that frame's disposal method.
|
// Apply that frame's disposal method.
|
||||||
|
// XXX: We do not expect opaque pictures to receive holes this way.
|
||||||
wuffs_base__rect_ie_u32 bounds =
|
wuffs_base__rect_ie_u32 bounds =
|
||||||
wuffs_base__frame_config__bounds(&ctx->last_fc);
|
wuffs_base__frame_config__bounds(&ctx->last_fc);
|
||||||
// TODO(p): This field needs to be colour-managed.
|
// TODO(p): This field needs to be colour-managed.
|
||||||
@ -686,6 +694,8 @@ load_wuffs_frame(struct load_wuffs_frame_context *ctx, GError **error)
|
|||||||
// TODO(p): Implement, it seems tricky.
|
// TODO(p): Implement, it seems tricky.
|
||||||
// Might need another surface to keep track of the state.
|
// Might need another surface to keep track of the state.
|
||||||
break;
|
break;
|
||||||
|
case WUFFS_BASE__ANIMATION_DISPOSAL__NONE:
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Paint the current frame over that, within its bounds.
|
// Paint the current frame over that, within its bounds.
|
||||||
@ -844,8 +854,6 @@ open_wuffs(wuffs_base__image_decoder *dec, wuffs_base__io_buffer src,
|
|||||||
|
|
||||||
// Wuffs maps tRNS to BGRA in `decoder.decode_trns?`, we should be fine.
|
// Wuffs maps tRNS to BGRA in `decoder.decode_trns?`, we should be fine.
|
||||||
// wuffs_base__pixel_format__transparency() doesn't reflect the image file.
|
// wuffs_base__pixel_format__transparency() doesn't reflect the image file.
|
||||||
// TODO(p): See if wuffs_base__image_config__first_frame_is_opaque() causes
|
|
||||||
// issues with animations, and eventually ensure an alpha-capable format.
|
|
||||||
bool opaque = wuffs_base__image_config__first_frame_is_opaque(&ctx.cfg);
|
bool opaque = wuffs_base__image_config__first_frame_is_opaque(&ctx.cfg);
|
||||||
|
|
||||||
// Wuffs' API is kind of awful--we want to catch wide RGB and wide grey.
|
// Wuffs' API is kind of awful--we want to catch wide RGB and wide grey.
|
||||||
@ -886,6 +894,8 @@ open_wuffs(wuffs_base__image_decoder *dec, wuffs_base__io_buffer src,
|
|||||||
ctx.cairo_format = CAIRO_FORMAT_RGB30;
|
ctx.cairo_format = CAIRO_FORMAT_RGB30;
|
||||||
} else if (opaque) {
|
} else if (opaque) {
|
||||||
// BGRX doesn't have as wide swizzler support, namely in GIF.
|
// BGRX doesn't have as wide swizzler support, namely in GIF.
|
||||||
|
// Moreover, follower frames may still be partly transparent.
|
||||||
|
// Therefore, we choose to keep "wuffs_format" intact.
|
||||||
ctx.cairo_format = CAIRO_FORMAT_RGB24;
|
ctx.cairo_format = CAIRO_FORMAT_RGB24;
|
||||||
} else if (!ctx.target) {
|
} else if (!ctx.target) {
|
||||||
wuffs_format = WUFFS_BASE__PIXEL_FORMAT__BGRA_PREMUL;
|
wuffs_format = WUFFS_BASE__PIXEL_FORMAT__BGRA_PREMUL;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user