diff options
| author | Přemysl Eric Janouch <p@janouch.name> | 2022-06-04 22:47:44 +0200 | 
|---|---|---|
| committer | Přemysl Eric Janouch <p@janouch.name> | 2022-06-04 23:14:15 +0200 | 
| commit | 024b5117b4fdbd3193afab40c071abd11b85a013 (patch) | |
| tree | aa0f9154f2640d17d0b864015c79bacafcbcf0f4 | |
| parent | ac6b606ccc85c2fca02b6199ba138fd85f04489c (diff) | |
| download | fiv-024b5117b4fdbd3193afab40c071abd11b85a013.tar.gz fiv-024b5117b4fdbd3193afab40c071abd11b85a013.tar.xz fiv-024b5117b4fdbd3193afab40c071abd11b85a013.zip  | |
Get rid of our spng dependency
Thumbnails can be properly loaded using Wuffs now.
| -rw-r--r-- | README.adoc | 2 | ||||
| -rw-r--r-- | fiv-io.c | 166 | ||||
| -rw-r--r-- | fiv-io.h | 4 | ||||
| -rw-r--r-- | fiv-thumbnail.c | 144 | ||||
| -rw-r--r-- | meson.build | 5 | ||||
| -rw-r--r-- | subprojects/spng.wrap | 8 | 
6 files changed, 189 insertions, 140 deletions
diff --git a/README.adoc b/README.adoc index 143c0c8..d245a0f 100644 --- a/README.adoc +++ b/README.adoc @@ -38,7 +38,7 @@ Building and Running  --------------------  Build dependencies: Meson, pkg-config +  Runtime dependencies: gtk+-3.0, glib>=2.64, pixman-1, shared-mime-info, -libturbojpeg, libwebp, spng>=0.7.0 + +libturbojpeg, libwebp +  Optional dependencies: lcms2, LibRaw, librsvg-2.0, xcursor, libheif, libtiff,  ExifTool, resvg (unstable API, needs to be requested explicitly) @@ -582,6 +582,9 @@ load_wuffs_frame(struct load_wuffs_frame_context *ctx, GError **error)  		return false;  	} +	// TODO(p): Maybe pre-clear with +	// wuffs_base__frame_config__background_color(&fc). +  	// 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 && @@ -785,7 +788,7 @@ open_wuffs(wuffs_base__image_decoder *dec, wuffs_base__io_buffer src,  	struct load_wuffs_frame_context ctx = {  		.dec = dec, .src = &src, .target = ioctx->screen_profile}; -	// TODO(p): PNG text chunks (Wuffs #58). +	// TODO(p): PNG text chunks, like we do with PNG thumbnails.  	// TODO(p): See if something could and should be done about  	// https://www.w3.org/TR/png-hdr-pq/  	wuffs_base__image_decoder__set_report_metadata( @@ -969,6 +972,166 @@ open_wuffs_using(wuffs_base__image_decoder *(*allocate)(),  	return surface;  } +// --- Wuffs for PNG thumbnails ------------------------------------------------ + +static bool +pull_metadata_kvp(wuffs_png__decoder *dec, wuffs_base__io_buffer *src, +	GHashTable *texts, gchar **key, GError **error) +{ +	wuffs_base__more_information minfo = {}; +	GBytes *bytes = NULL; +	if (!(bytes = pull_metadata( +			wuffs_png__decoder__upcast_as__wuffs_base__image_decoder(dec), +			src, &minfo, error))) +		return false; + +	switch (wuffs_base__more_information__metadata__fourcc(&minfo)) { +	case WUFFS_BASE__FOURCC__KVPK: +		g_assert(*key == NULL); +		*key = g_strndup( +			g_bytes_get_data(bytes, NULL), g_bytes_get_size(bytes)); +		break; +	case WUFFS_BASE__FOURCC__KVPV: +		g_assert(*key != NULL); +		g_hash_table_insert(texts, *key, g_strndup( +			g_bytes_get_data(bytes, NULL), g_bytes_get_size(bytes))); +		*key = NULL; +	} + +	g_bytes_unref(bytes); +	return true; +} + +// An uncomplicated variant of fiv_io_open(), might be up for refactoring. +cairo_surface_t * +fiv_io_open_png_thumbnail(const char *path, GError **error) +{ +	wuffs_png__decoder dec = {}; +	wuffs_base__status status = wuffs_png__decoder__initialize( +		&dec, sizeof dec, WUFFS_VERSION, WUFFS_INITIALIZE__ALREADY_ZEROED); +	if (!wuffs_base__status__is_ok(&status)) { +		set_error(error, wuffs_base__status__message(&status)); +		return NULL; +	} + +	gchar *data = NULL; +	gsize len = 0; +	if (!g_file_get_contents(path, &data, &len, error)) +		return NULL; + +	wuffs_base__io_buffer src = +		wuffs_base__ptr_u8__reader((uint8_t *) data, len, TRUE); +	wuffs_png__decoder__set_report_metadata( +		&dec, WUFFS_BASE__FOURCC__KVP, true); + +	wuffs_base__image_config cfg = {}; +	wuffs_base__slice_u8 workbuf = {}; +	cairo_surface_t *surface = NULL; +	bool success = false; + +	GHashTable *texts = +		g_hash_table_new_full(g_str_hash, g_str_equal, g_free, g_free); +	gchar *key = NULL; +	while (true) { +		status = wuffs_png__decoder__decode_image_config(&dec, &cfg, &src); +		if (wuffs_base__status__is_ok(&status)) +			break; + +		if (status.repr != wuffs_base__note__metadata_reported) { +			set_error(error, wuffs_base__status__message(&status)); +			goto fail; +		} +		if (!pull_metadata_kvp(&dec, &src, texts, &key, error)) +			goto fail; +	} + +	g_assert(key == NULL); + +	uint32_t width = wuffs_base__pixel_config__width(&cfg.pixcfg); +	uint32_t height = wuffs_base__pixel_config__height(&cfg.pixcfg); +	if (width > INT16_MAX || height > INT16_MAX) { +		set_error(error, "image dimensions overflow"); +		goto fail; +	} + +	wuffs_base__pixel_config__set(&cfg.pixcfg, +		WUFFS_BASE__PIXEL_FORMAT__BGRA_PREMUL, +		WUFFS_BASE__PIXEL_SUBSAMPLING__NONE, width, height); + +	uint64_t workbuf_len_max_incl = +		wuffs_png__decoder__workbuf_len(&dec).max_incl; +	if (workbuf_len_max_incl) { +		workbuf = wuffs_base__malloc_slice_u8(malloc, workbuf_len_max_incl); +		if (!workbuf.ptr) { +			set_error(error, "failed to allocate a work buffer"); +			goto fail; +		} +	} + +	surface = cairo_image_surface_create( +		wuffs_base__image_config__first_frame_is_opaque(&cfg) +			? CAIRO_FORMAT_RGB24 +			: CAIRO_FORMAT_ARGB32, +		width, height); + +	cairo_status_t surface_status = cairo_surface_status(surface); +	if (surface_status != CAIRO_STATUS_SUCCESS) { +		set_error(error, cairo_status_to_string(surface_status)); +		goto fail; +	} + +	wuffs_base__pixel_buffer pb = {}; +	status = wuffs_base__pixel_buffer__set_from_slice(&pb, &cfg.pixcfg, +		wuffs_base__make_slice_u8(cairo_image_surface_get_data(surface), +			cairo_image_surface_get_stride(surface) * +				cairo_image_surface_get_height(surface))); +	if (!wuffs_base__status__is_ok(&status)) { +		set_error(error, wuffs_base__status__message(&status)); +		goto fail; +	} + +	status = wuffs_png__decoder__decode_frame(&dec, &pb, &src, +		WUFFS_BASE__PIXEL_BLEND__SRC, workbuf, NULL); +	if (!wuffs_base__status__is_ok(&status)) { +		set_error(error, wuffs_base__status__message(&status)); +		goto fail; +	} + +	// The specification does not say where the required metadata should be, +	// it could very well be broken up into two parts. +	wuffs_base__frame_config fc = {}; +	while (true) { +		// Not interested in APNG, might even throw an error in that case. +		status = wuffs_png__decoder__decode_frame_config(&dec, &fc, &src); +		if (status.repr == wuffs_base__note__end_of_data || +			wuffs_base__status__is_ok(&status)) +			break; + +		if (status.repr != wuffs_base__note__metadata_reported) { +			set_error(error, wuffs_base__status__message(&status)); +			goto fail; +		} +		if (!pull_metadata_kvp(&dec, &src, texts, &key, error)) +			goto fail; +	} + +	g_assert(key == NULL); + +	cairo_surface_mark_dirty(surface); +	cairo_surface_set_user_data(surface, &fiv_io_key_text, +		g_hash_table_ref(texts), (cairo_destroy_func_t) g_hash_table_unref); +	success = true; + +fail: +	if (!success) +		g_clear_pointer(&surface, cairo_surface_destroy); + +	free(workbuf.ptr); +	g_free(data); +	g_hash_table_unref(texts); +	return surface; +} +  // --- JPEG --------------------------------------------------------------------  static GBytes * @@ -2555,6 +2718,7 @@ cairo_user_data_key_t fiv_io_key_orientation;  cairo_user_data_key_t fiv_io_key_icc;  cairo_user_data_key_t fiv_io_key_xmp;  cairo_user_data_key_t fiv_io_key_thum; +cairo_user_data_key_t fiv_io_key_text;  cairo_user_data_key_t fiv_io_key_frame_next;  cairo_user_data_key_t fiv_io_key_frame_previous; @@ -53,6 +53,9 @@ extern cairo_user_data_key_t fiv_io_key_icc;  extern cairo_user_data_key_t fiv_io_key_xmp;  /// GBytes with a WebP's THUM chunk, used for our thumbnails.  extern cairo_user_data_key_t fiv_io_key_thum; +/// GHashTable with key-value pairs from PNG's tEXt, zTXt, iTXt chunks. +/// Currently only read by fiv_io_open_png_thumbnail(). +extern cairo_user_data_key_t fiv_io_key_text;  /// The next frame in a sequence, as a surface, in a chain, pre-composited.  /// There is no wrap-around. @@ -95,6 +98,7 @@ typedef struct {  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); +cairo_surface_t *fiv_io_open_png_thumbnail(const char *path, GError **error);  // --- Thumbnail passing utilities --------------------------------------------- diff --git a/fiv-thumbnail.c b/fiv-thumbnail.c index 44f2735..c6e1896 100644 --- a/fiv-thumbnail.c +++ b/fiv-thumbnail.c @@ -18,7 +18,6 @@  #include "config.h"  #include <glib/gstdio.h> -#include <spng.h>  #include <webp/demux.h>  #include <webp/encode.h>  #include <webp/mux.h> @@ -446,138 +445,33 @@ read_wide_thumbnail(  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -static int  // tri-state -check_spng_thumbnail_texts(struct spng_text *texts, uint32_t texts_len, -	const gchar *target, time_t mtime) -{ -	// May contain Thumb::Image::Width Thumb::Image::Height, -	// but those aren't interesting currently (would be for fast previews). -	bool need_uri = true, need_mtime = true; -	for (uint32_t i = 0; i < texts_len; i++) { -		struct spng_text *text = texts + i; -		if (!strcmp(text->keyword, THUMB_URI)) { -			need_uri = false; -			if (strcmp(target, text->text)) -				return false; -		} -		if (!strcmp(text->keyword, THUMB_MTIME)) { -			need_mtime = false; -			if (atol(text->text) != mtime) -				return false; -		} -	} -	return need_uri || need_mtime ? -1 : true; -} - -static int  // tri-state -check_spng_thumbnail(spng_ctx *ctx, const gchar *target, time_t mtime, int *err) -{ -	uint32_t texts_len = 0; -	if ((*err = spng_get_text(ctx, NULL, &texts_len))) -		return false; - -	int result = false; -	struct spng_text *texts = g_malloc0_n(texts_len, sizeof *texts); -	if (!(*err = spng_get_text(ctx, texts, &texts_len))) -		result = check_spng_thumbnail_texts(texts, texts_len, target, mtime); -	g_free(texts); -	return result; -} -  static cairo_surface_t * -read_spng_thumbnail( +read_png_thumbnail(  	const gchar *path, const gchar *uri, time_t mtime, GError **error)  { -	FILE *fp; -	cairo_surface_t *result = NULL; -	if (!(fp = fopen(path, "rb"))) { -		set_error(error, g_strerror(errno)); +	cairo_surface_t *surface = fiv_io_open_png_thumbnail(path, error); +	if (!surface)  		return NULL; -	} - -	errno = 0; -	spng_ctx *ctx = spng_ctx_new(0); -	if (!ctx) { -		set_error(error, g_strerror(errno)); -		goto fail_init; -	} - -	int err; -	size_t size = 0; -	if ((err = spng_set_png_file(ctx, fp)) || -		(err = spng_set_image_limits(ctx, INT16_MAX, INT16_MAX)) || -		(err = spng_decoded_image_size(ctx, SPNG_FMT_RGBA8, &size))) { -		set_error(error, spng_strerror(err)); -		goto fail; -	} -	if (check_spng_thumbnail(ctx, uri, mtime, &err) == false) { -		set_error(error, err ? spng_strerror(err) : "mismatch"); -		goto fail; -	} - -	struct spng_ihdr ihdr = {}; -	struct spng_trns trns = {}; -	spng_get_ihdr(ctx, &ihdr); -	bool may_be_translucent = !spng_get_trns(ctx, &trns) || -		ihdr.color_type == SPNG_COLOR_TYPE_GRAYSCALE_ALPHA || -		ihdr.color_type == SPNG_COLOR_TYPE_TRUECOLOR_ALPHA; - -	cairo_surface_t *surface = cairo_image_surface_create( -		may_be_translucent ? CAIRO_FORMAT_ARGB32 : CAIRO_FORMAT_RGB24, -		ihdr.width, ihdr.height); - -	cairo_status_t surface_status = cairo_surface_status(surface); -	if (surface_status != CAIRO_STATUS_SUCCESS) { -		set_error(error, cairo_status_to_string(surface_status)); -		goto fail_data; -	} - -	uint32_t *data = (uint32_t *) cairo_image_surface_get_data(surface); -	g_assert((size_t) cairo_image_surface_get_stride(surface) * -		cairo_image_surface_get_height(surface) == size); -	cairo_surface_flush(surface); -	if ((err = spng_decode_image(ctx, data, size, SPNG_FMT_RGBA8, -		SPNG_DECODE_TRNS | SPNG_DECODE_GAMMA))) { -		set_error(error, spng_strerror(err)); -		goto fail_data; -	} - -	// The specification does not say where the required metadata should be, -	// it could very well be broken up into two parts. -	if (check_spng_thumbnail(ctx, uri, mtime, &err) != true) { -		set_error( -			error, err ? spng_strerror(err) : "mismatch or not a thumbnail"); -		goto fail_data; +	GHashTable *texts = cairo_surface_get_user_data(surface, &fiv_io_key_text); +	if (!texts) { +		set_error(error, "not a thumbnail"); +		cairo_surface_destroy(surface); +		return NULL;  	} -	// pixman can be mildly abused to do this operation, but it won't be faster. -	if (may_be_translucent) { -		for (size_t i = size / sizeof *data; i--; ) { -			const uint8_t *unit = (const uint8_t *) &data[i]; -			uint32_t a = unit[3], -				b = PREMULTIPLY8(a, unit[2]), -				g = PREMULTIPLY8(a, unit[1]), -				r = PREMULTIPLY8(a, unit[0]); -			data[i] = a << 24 | r << 16 | g << 8 | b; -		} -	} else { -		for (size_t i = size / sizeof *data; i--; ) { -			uint32_t rgba = g_ntohl(data[i]); -			data[i] = rgba << 24 | rgba >> 8; -		} +	// May contain Thumb::Image::Width Thumb::Image::Height, +	// but those aren't interesting currently (would be for fast previews). +	const char *text_uri = g_hash_table_lookup(texts, THUMB_URI); +	const char *text_mtime = g_hash_table_lookup(texts, THUMB_MTIME); +	if (!text_uri || strcmp(text_uri, uri) || +		!text_mtime || atol(text_mtime) != mtime) { +		set_error(error, "mismatch or not a thumbnail"); +		cairo_surface_destroy(surface); +		return NULL;  	} -	cairo_surface_mark_dirty((result = surface)); - -fail_data: -	if (!result) -		cairo_surface_destroy(surface); -fail: -	spng_ctx_free(ctx); -fail_init: -	fclose(fp); -	return result; +	return surface;  }  cairo_surface_t * @@ -616,7 +510,7 @@ fiv_thumbnail_lookup(char *uri, gint64 mtime_msec, FivThumbnailSize size)  		gchar *path =  			g_strdup_printf("%s/%s/%s.png", thumbnails_dir, name, sum); -		result = read_spng_thumbnail(path, uri, mtime_msec / 1000, &error); +		result = read_png_thumbnail(path, uri, mtime_msec / 1000, &error);  		if (error) {  			g_debug("%s: %s", path, error->message);  			g_clear_error(&error); diff --git a/meson.build b/meson.build index 38afe47..b885e2e 100644 --- a/meson.build +++ b/meson.build @@ -39,11 +39,6 @@ dependencies = [  	dependency('libwebpdemux'),  	dependency('libwebpdecoder', required : false),  	dependency('libwebpmux'), -	# https://github.com/google/wuffs/issues/58 -	dependency('spng', version : '>=0.7.0', -		default_options: 'default_library=static', -		# fallback : ['spng', 'spng_dep'], -	),  	lcms2,  	libjpegqs, diff --git a/subprojects/spng.wrap b/subprojects/spng.wrap deleted file mode 100644 index 93904f2..0000000 --- a/subprojects/spng.wrap +++ /dev/null @@ -1,8 +0,0 @@ -[wrap-file] -directory = libspng-0.7.2 -source_url = https://github.com/randy408/libspng/archive/refs/tags/v0.7.2.tar.gz -source_filename = libspng-0.7.2.tar.gz -source_hash = 4acf25571d31f540d0b7ee004f5461d68158e0a13182505376805da99f4ccc4e - -[provide] -spng = spng_dep  | 
