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 -------------------------------------------------------
 | ||||
| 
 | ||||
| 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 | ||||
| thumbnailer_reprocess_entry(FivBrowser *self, Entry *entry) | ||||
| thumbnailer_reprocess_entry(FivBrowser *self, GBytes *output, Entry *entry) | ||||
| { | ||||
| 	entry_add_thumbnail(entry, self); | ||||
| 	materialize_icon(self, entry); | ||||
| 	g_clear_object(&entry->icon); | ||||
| 	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)); | ||||
| } | ||||
| 
 | ||||
| @ -518,64 +528,75 @@ static void | ||||
| on_thumbnailer_ready(GObject *object, GAsyncResult *res, gpointer user_data) | ||||
| { | ||||
| 	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; | ||||
| 	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)) { | ||||
| 			g_error_free(error); | ||||
| 			return; | ||||
| 		} | ||||
| 		if (!g_subprocess_get_if_exited(subprocess) || | ||||
| 			g_subprocess_get_exit_status(subprocess) != EXIT_FAILURE) | ||||
| 			g_warning("%s", error->message); | ||||
| 	} else if (!g_subprocess_get_if_exited(subprocess)) { | ||||
| 		// If it exited, it probably printed its own message.
 | ||||
| 		g_spawn_check_wait_status(g_subprocess_get_status(subprocess), &error); | ||||
| 	} | ||||
| 
 | ||||
| 	if (error) { | ||||
| 		g_warning("%s", error->message); | ||||
| 		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) && | ||||
| 		g_subprocess_get_exit_status(subprocess) == EXIT_SUCCESS; | ||||
| 	g_clear_object(&thumbnailer->minion); | ||||
| 	if (!thumbnailer->target) { | ||||
| 	g_clear_object(&t->minion); | ||||
| 	if (!t->target) { | ||||
| 		g_warning("finished thumbnailing an unknown image"); | ||||
| 		g_clear_pointer(&out, g_bytes_unref); | ||||
| 		return; | ||||
| 	} | ||||
| 
 | ||||
| 	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; | ||||
| 	thumbnailer_next(thumbnailer); | ||||
| 	t->target = NULL; | ||||
| 	thumbnailer_next(t); | ||||
| } | ||||
| 
 | ||||
| static gboolean | ||||
| thumbnailer_next(Thumbnailer *thumbnailer) | ||||
| thumbnailer_next(Thumbnailer *t) | ||||
| { | ||||
| 	// TODO(p): Ideally, try to keep the minions alive.
 | ||||
| 	FivBrowser *self = thumbnailer->self; | ||||
| 	// TODO(p): Try to keep the minions alive (stdout will be a problem).
 | ||||
| 	FivBrowser *self = t->self; | ||||
| 	GList *link = self->thumbnailers_queue; | ||||
| 	if (!link) | ||||
| 		return FALSE; | ||||
| 
 | ||||
| 	thumbnailer->target = link->data; | ||||
| 	t->target = link->data; | ||||
| 	self->thumbnailers_queue = | ||||
| 		g_list_delete_link(self->thumbnailers_queue, self->thumbnailers_queue); | ||||
| 
 | ||||
| 	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", | ||||
| 		fiv_thumbnail_sizes[self->item_size].thumbnail_spec_name, "--", | ||||
| 		thumbnailer->target->uri, NULL); | ||||
| 		t->target->uri, NULL); | ||||
| 	if (error) { | ||||
| 		g_warning("%s", error->message); | ||||
| 		g_error_free(error); | ||||
| 		return FALSE; | ||||
| 	} | ||||
| 
 | ||||
| 	thumbnailer->cancel = g_cancellable_new(); | ||||
| 	g_subprocess_wait_check_async(thumbnailer->minion, thumbnailer->cancel, | ||||
| 		on_thumbnailer_ready, thumbnailer); | ||||
| 	t->cancel = g_cancellable_new(); | ||||
| 	g_subprocess_communicate_async( | ||||
| 		t->minion, NULL, t->cancel, on_thumbnailer_ready, t); | ||||
| 	return TRUE; | ||||
| } | ||||
| 
 | ||||
| @ -588,15 +609,15 @@ thumbnailers_abort(FivBrowser *self) | ||||
| 	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); | ||||
| 		Thumbnailer *t = self->thumbnailers + i; | ||||
| 		if (t->cancel) { | ||||
| 			g_cancellable_cancel(t->cancel); | ||||
| 			g_clear_object(&t->cancel); | ||||
| 		} | ||||
| 
 | ||||
| 		// Just let them exit on their own.
 | ||||
| 		g_clear_object(&thumbnailer->minion); | ||||
| 		thumbnailer->target = NULL; | ||||
| 		g_clear_object(&t->minion); | ||||
| 		t->target = NULL; | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
|  | ||||
							
								
								
									
										66
									
								
								fiv-io.c
									
									
									
									
									
								
							
							
						
						
									
										66
									
								
								fiv-io.c
									
									
									
									
									
								
							| @ -2703,13 +2703,77 @@ fiv_io_open_from_data( | ||||
| 	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 --------------------------------------------------------------
 | ||||
| 
 | ||||
| #include "xdg.h" | ||||
| 
 | ||||
| #include <fnmatch.h> | ||||
| 
 | ||||
| typedef struct _ModelEntry { | ||||
| typedef struct { | ||||
| 	gchar *uri;                         ///< GIO URI
 | ||||
| 	gchar *collate_key;                 ///< Collate key for the filename
 | ||||
| 	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( | ||||
| 	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 --------------------------------------------------------------
 | ||||
| 
 | ||||
| typedef enum _FivIoModelSort { | ||||
|  | ||||
| @ -230,7 +230,8 @@ save_thumbnail(cairo_surface_t *thumbnail, const char *path, GString *thum) | ||||
| } | ||||
| 
 | ||||
| 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 && | ||||
| 		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, | ||||
| 			fiv_thumbnail_sizes[use].thumbnail_spec_name, sum); | ||||
| 		save_thumbnail(scaled, path, thum); | ||||
| 		cairo_surface_destroy(scaled); | ||||
| 		g_free(path); | ||||
| 
 | ||||
| 		if (!*max_size_surface) | ||||
| 			*max_size_surface = scaled; | ||||
| 		else | ||||
| 			cairo_surface_destroy(scaled); | ||||
| 	} | ||||
| 
 | ||||
| 	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); | ||||
| 
 | ||||
| /// Generates wide thumbnails of up to the specified size, saves them in cache.
 | ||||
| gboolean fiv_thumbnail_produce( | ||||
| 	GFile *target, FivThumbnailSize max_size, GError **error); | ||||
| /// Returns the surface used for the maximum size (if the pointer was NULL).
 | ||||
| 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
 | ||||
| /// 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); | ||||
| 
 | ||||
| 		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); | ||||
| 		g_object_unref(target); | ||||
| 		if (surface) { | ||||
| 			fiv_io_serialize_to_stdout(surface); | ||||
| 			cairo_surface_destroy(surface); | ||||
| 		} | ||||
| 		return 0; | ||||
| 	} | ||||
| 
 | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user