diff options
| -rw-r--r-- | fastiv-io.c | 392 | ||||
| -rw-r--r-- | fastiv-io.h | 31 | 
2 files changed, 240 insertions, 183 deletions
| diff --git a/fastiv-io.c b/fastiv-io.c index e68b39b..c6f835e 100644 --- a/fastiv-io.c +++ b/fastiv-io.c @@ -102,30 +102,6 @@ fastiv_io_all_supported_media_types(void)  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -GType -fastiv_io_thumbnail_size_get_type(void) -{ -	static gsize guard; -	if (g_once_init_enter(&guard)) { -#define XX(name, value, dir) {FASTIV_IO_THUMBNAIL_SIZE_ ## name, \ -	"FASTIV_IO_THUMBNAIL_SIZE_" #name, #name}, -		static const GEnumValue values[] = {FASTIV_IO_THUMBNAIL_SIZES(XX) {}}; -#undef XX -		GType type = g_enum_register_static( -			g_intern_static_string("FastivIoThumbnailSize"), values); -		g_once_init_leave(&guard, type); -	} -	return guard; -} - -#define XX(name, value, dir) {value, dir}, -FastivIoThumbnailSizeInfo -	fastiv_io_thumbnail_sizes[FASTIV_IO_THUMBNAIL_SIZE_COUNT] = { -		FASTIV_IO_THUMBNAIL_SIZES(XX)}; -#undef XX - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  #define FASTIV_IO_ERROR fastiv_io_error_quark()  G_DEFINE_QUARK(fastiv-io-error-quark, fastiv_io_error) @@ -171,7 +147,7 @@ pull_passthrough(const wuffs_base__more_information *minfo,  	return true;  } -static GByteArray * +static GBytes *  pull_metadata(wuffs_base__image_decoder *dec, wuffs_base__io_buffer *src,  	wuffs_base__more_information *minfo, GError **error)  { @@ -205,7 +181,7 @@ pull_metadata(wuffs_base__image_decoder *dec, wuffs_base__io_buffer *src,  			wuffs_base__io_buffer__reader_pointer(&dst),  			wuffs_base__io_buffer__reader_length(&dst));  		if (wuffs_base__status__is_ok(&status)) -			return array; +			return g_byte_array_free_to_bytes(array);  		if (status.repr != wuffs_base__suspension__even_more_information &&  			status.repr != wuffs_base__suspension__short_write) { @@ -219,6 +195,148 @@ fail:  	return NULL;  } +struct load_wuffs_frame_context { +	wuffs_base__image_decoder *dec;     ///< Wuffs decoder abstraction +	wuffs_base__io_buffer *src;         ///< Wuffs source buffer +	wuffs_base__image_config cfg;       ///< Wuffs image configuration +	wuffs_base__slice_u8 workbuf;       ///< Work buffer for Wuffs +	uint32_t width;                     ///< Copied from cfg.pixcfg +	uint32_t height;                    ///< Copied from cfg.pixcfg +	cairo_format_t cairo_format;        ///< Target format for surfaces +	bool pack_16_10;                    ///< Custom copying swizzle for RGB30 +	bool expand_16_float;               ///< Custom copying swizzle for RGBA128F +	GBytes *meta_exif;                  ///< Reference-counted Exif +	GBytes *meta_iccp;                  ///< Reference-counted ICC profile + +	cairo_surface_t *result;            ///< The resulting surface (referenced) +	cairo_surface_t *result_tail;       ///< The final animation frame +}; + +static bool +load_wuffs_frame(struct load_wuffs_frame_context *ctx, GError **error) +{ +	wuffs_base__frame_config fc = {0}; +	wuffs_base__status status = +		wuffs_base__image_decoder__decode_frame_config(ctx->dec, &fc, ctx->src); +	if (status.repr == wuffs_base__note__end_of_data && ctx->result) +		return false; +	if (!wuffs_base__status__is_ok(&status)) { +		set_error(error, wuffs_base__status__message(&status)); +		return false; +	} + +	bool success = false; +	unsigned char *targetbuf = NULL; +	cairo_surface_t *surface = +		cairo_image_surface_create(ctx->cairo_format, ctx->width, ctx->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; +	} + +	// CAIRO_STRIDE_ALIGNMENT is 4 bytes, so there will be no padding with +	// ARGB/BGR/XRGB/BGRX. This function does not support a stride different +	// from the width, maybe Wuffs internals do not either. +	unsigned char *surface_data = cairo_image_surface_get_data(surface); +	int surface_stride = cairo_image_surface_get_stride(surface); +	wuffs_base__pixel_buffer pb = {0}; +	if (ctx->expand_16_float) { +		uint32_t targetbuf_size = ctx->height * ctx->width * 64; +		targetbuf = g_malloc(targetbuf_size); +		status = wuffs_base__pixel_buffer__set_from_slice(&pb, &ctx->cfg.pixcfg, +			wuffs_base__make_slice_u8(targetbuf, targetbuf_size)); +	} else if (ctx->pack_16_10) { +		uint32_t targetbuf_size = ctx->height * ctx->width * 16; +		targetbuf = g_malloc(targetbuf_size); +		status = wuffs_base__pixel_buffer__set_from_slice(&pb, &ctx->cfg.pixcfg, +			wuffs_base__make_slice_u8(targetbuf, targetbuf_size)); +	} else { +		status = wuffs_base__pixel_buffer__set_from_slice(&pb, &ctx->cfg.pixcfg, +			wuffs_base__make_slice_u8(surface_data, +				surface_stride * cairo_image_surface_get_height(surface))); +	} +	if (!wuffs_base__status__is_ok(&status)) { +		set_error(error, wuffs_base__status__message(&status)); +		goto fail; +	} + +	// Starting to modify pixel data directly. Probably an unnecessary call. +	cairo_surface_flush(surface); + +	// TODO(p): Composite/combine frames as intended. +	// wuffs_base__image_config__first_frame_is_opaque() is problematic here. +	status = wuffs_base__image_decoder__decode_frame(ctx->dec, &pb, ctx->src, +		WUFFS_BASE__PIXEL_BLEND__SRC, ctx->workbuf, NULL); +	if (!wuffs_base__status__is_ok(&status)) { +		set_error(error, wuffs_base__status__message(&status)); +		goto fail; +	} + +	if (ctx->expand_16_float) { +		g_debug("Wuffs to Cairo RGBA128F"); +		uint16_t *in = (uint16_t *) targetbuf; +		float *out = (float *) surface_data; +		for (uint32_t y = 0; y < ctx->height; y++) { +			for (uint32_t x = 0; x < ctx->width; x++) { +				float b = *in++ / 65535., g = *in++ / 65535., +					r = *in++ / 65535., a = *in++ / 65535.; +				*out++ = r * a; +				*out++ = g * a; +				*out++ = b * a; +				*out++ = a; +			} +		} +	} else if (ctx->pack_16_10) { +		g_debug("Wuffs to Cairo RGB30"); +		uint16_t *in = (uint16_t *) targetbuf; +		uint32_t *out = (uint32_t *) surface_data; +		for (uint32_t y = 0; y < ctx->height; y++) { +			for (uint32_t x = 0; x < ctx->width; x++) { +				uint16_t b = *in++, g = *in++, r = *in++, x = *in++; +				*out++ = (x >> 14) << 30 | +					(r >> 6) << 20 | (g >> 6) << 10 | (b >> 6); +			} +		} +	} + +	// Pixel data has been written, need to let Cairo know. +	cairo_surface_mark_dirty(surface); + +	if (ctx->meta_exif) +		cairo_surface_set_user_data(surface, &fastiv_io_key_exif, +			g_bytes_ref(ctx->meta_exif), (cairo_destroy_func_t) g_bytes_unref); +	if (ctx->meta_iccp) +		cairo_surface_set_user_data(surface, &fastiv_io_key_icc, +			g_bytes_ref(ctx->meta_iccp), (cairo_destroy_func_t) g_bytes_unref); + +	cairo_surface_set_user_data(surface, &fastiv_io_key_loops, +		(void *) (uintptr_t) wuffs_base__image_decoder__num_animation_loops( +			ctx->dec), NULL); +	cairo_surface_set_user_data(surface, &fastiv_io_key_frame_duration, +		(void *) (intptr_t) (wuffs_base__frame_config__duration(&fc) / +			WUFFS_BASE__FLICKS_PER_MILLISECOND), NULL); + +	if (ctx->result_tail) +		cairo_surface_set_user_data(ctx->result_tail, &fastiv_io_key_frame_next, +			surface, (cairo_destroy_func_t) cairo_surface_destroy); +	else +		ctx->result = surface; + +	success = true; +	ctx->result_tail = surface; + +fail: +	if (!success) { +		cairo_surface_destroy(surface); +		g_clear_pointer(&ctx->result, cairo_surface_destroy); +		ctx->result_tail = NULL; +	} + +	g_free(targetbuf); +	return success; +} +  // https://github.com/google/wuffs/blob/main/example/gifplayer/gifplayer.c  // is pure C, and a good reference. I can't use the auxiliary libraries,  // since they depend on C++, which is undesirable. @@ -226,82 +344,79 @@ static cairo_surface_t *  open_wuffs(  	wuffs_base__image_decoder *dec, wuffs_base__io_buffer src, GError **error)  { -	cairo_surface_t *result = NULL; +	struct load_wuffs_frame_context ctx = {.dec = dec, .src = &src};  	// TODO(p): PNG also has sRGB and gAMA, as well as text chunks (Wuffs #58).  	// The former two use WUFFS_BASE__MORE_INFORMATION__FLAVOR__METADATA_PARSED. -	GBytes *meta_exif = NULL;  	wuffs_base__image_decoder__set_report_metadata( -		dec, WUFFS_BASE__FOURCC__EXIF, true); - -	GBytes *meta_iccp = NULL; +		ctx.dec, WUFFS_BASE__FOURCC__EXIF, true);  	wuffs_base__image_decoder__set_report_metadata( -		dec, WUFFS_BASE__FOURCC__ICCP, true); +		ctx.dec, WUFFS_BASE__FOURCC__ICCP, true); -	wuffs_base__image_config cfg = {}; -	wuffs_base__status status = {};  	while (true) { -		status = wuffs_base__image_decoder__decode_image_config( -			dec, &cfg, &src); +		wuffs_base__status status = +			wuffs_base__image_decoder__decode_image_config( +				ctx.dec, &ctx.cfg, ctx.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_preread; +			goto fail;  		}  		wuffs_base__more_information minfo = {}; -		GByteArray *buffer = NULL; -		if (!(buffer = pull_metadata(dec, &src, &minfo, error))) -			goto fail_preread; +		GBytes *bytes = NULL; +		if (!(bytes = pull_metadata(ctx.dec, ctx.src, &minfo, error))) +			goto fail;  		switch (wuffs_base__more_information__metadata__fourcc(&minfo)) {  		case WUFFS_BASE__FOURCC__EXIF: -			if (meta_exif) { +			if (ctx.meta_exif) {  				g_warning("ignoring repeated Exif");  				break;  			} -			meta_exif = g_byte_array_free_to_bytes(buffer); +			ctx.meta_exif = bytes;  			continue;  		case WUFFS_BASE__FOURCC__ICCP: -			if (meta_iccp) { +			if (ctx.meta_iccp) {  				g_warning("ignoring repeated ICC profile");  				break;  			} -			meta_iccp = g_byte_array_free_to_bytes(buffer); +			ctx.meta_iccp = bytes;  			continue;  		} -		g_byte_array_unref(buffer); +		g_bytes_unref(bytes);  	} -	if (!wuffs_base__image_config__is_valid(&cfg)) { +	// This, at least currently, seems excessive. +	if (!wuffs_base__image_config__is_valid(&ctx.cfg)) {  		set_error(error, "invalid Wuffs image configuration"); -		goto fail_preread; +		goto fail;  	}  	// We need to check because of the Cairo API. -	uint32_t width = wuffs_base__pixel_config__width(&cfg.pixcfg); -	uint32_t height = wuffs_base__pixel_config__height(&cfg.pixcfg); -	if (width > INT_MAX || height > INT_MAX) { +	ctx.width = wuffs_base__pixel_config__width(&ctx.cfg.pixcfg); +	ctx.height = wuffs_base__pixel_config__height(&ctx.cfg.pixcfg); +	if (ctx.width > INT_MAX || ctx.height > INT_MAX) {  		set_error(error, "image dimensions overflow"); -		goto fail_preread; +		goto fail;  	}  	// Wuffs maps tRNS to BGRA in `decoder.decode_trns?`, we should be fine.  	// wuffs_base__pixel_format__transparency() doesn't reflect the image file. -	bool opaque = wuffs_base__image_config__first_frame_is_opaque(&cfg); +	bool opaque = wuffs_base__image_config__first_frame_is_opaque(&ctx.cfg);  	// Wuffs' API is kind of awful--we want to catch deep RGB and deep grey.  	wuffs_base__pixel_format srcfmt = -		wuffs_base__pixel_config__pixel_format(&cfg.pixcfg); +		wuffs_base__pixel_config__pixel_format(&ctx.cfg.pixcfg);  	uint32_t bpp = wuffs_base__pixel_format__bits_per_pixel(&srcfmt);  	// Cairo doesn't support transparency with RGB30, so no premultiplication. -	bool pack_16_10 = opaque && (bpp > 24 || (bpp < 24 && bpp > 8)); +	ctx.pack_16_10 = opaque && (bpp > 24 || (bpp < 24 && bpp > 8));  #ifdef FASTIV_CAIRO_RGBA128F -	bool expand_16_float = !opaque && (bpp > 24 || (bpp < 24 && bpp > 8)); +	ctx.expand_16_float = !opaque && (bpp > 24 || (bpp < 24 && bpp > 8));  #endif  // FASTIV_CAIRO_RGBA128F  	// In Wuffs, /doc/note/pixel-formats.md declares "memory order", which, @@ -315,148 +430,47 @@ open_wuffs(  	// CAIRO_FORMAT_ARGB32: "The 32-bit quantities are stored native-endian.  	// Pre-multiplied alpha is used." CAIRO_FORMAT_RGB{24,30} are analogous. -	cairo_format_t cairo_format = CAIRO_FORMAT_ARGB32; +	ctx.cairo_format = CAIRO_FORMAT_ARGB32;  #ifdef FASTIV_CAIRO_RGBA128F -	if (expand_16_float) { +	if (ctx.expand_16_float) {  		wuffs_format = WUFFS_BASE__PIXEL_FORMAT__BGRA_NONPREMUL_4X16LE; -		cairo_format = CAIRO_FORMAT_RGBA128F; +		ctx.cairo_format = CAIRO_FORMAT_RGBA128F;  	} else  #endif  // FASTIV_CAIRO_RGBA128F -	if (pack_16_10) { -		// TODO(p): Make Wuffs support RGB30 as a destination format; +	if (ctx.pack_16_10) { +		// TODO(p): Make Wuffs support A2RGB30 as a destination format;  		// in general, 16-bit depth swizzlers are stubbed.  		// See also wuffs_base__pixel_swizzler__prepare__*().  		wuffs_format = WUFFS_BASE__PIXEL_FORMAT__BGRA_NONPREMUL_4X16LE; -		cairo_format = CAIRO_FORMAT_RGB30; +		ctx.cairo_format = CAIRO_FORMAT_RGB30;  	} else if (opaque) {  		// BGRX doesn't have as wide swizzler support, namely in GIF.  		wuffs_format = WUFFS_BASE__PIXEL_FORMAT__BGRA_NONPREMUL; -		cairo_format = CAIRO_FORMAT_RGB24; +		ctx.cairo_format = CAIRO_FORMAT_RGB24;  	} -	wuffs_base__pixel_config__set(&cfg.pixcfg, wuffs_format, -		WUFFS_BASE__PIXEL_SUBSAMPLING__NONE, width, height); +	wuffs_base__pixel_config__set(&ctx.cfg.pixcfg, wuffs_format, +		WUFFS_BASE__PIXEL_SUBSAMPLING__NONE, ctx.width, ctx.height); -	wuffs_base__slice_u8 workbuf = {0};  	uint64_t workbuf_len_max_incl = -		wuffs_base__image_decoder__workbuf_len(dec).max_incl; +		wuffs_base__image_decoder__workbuf_len(ctx.dec).max_incl;  	if (workbuf_len_max_incl) { -		workbuf = wuffs_base__malloc_slice_u8(malloc, workbuf_len_max_incl); -		if (!workbuf.ptr) { +		ctx.workbuf = wuffs_base__malloc_slice_u8(malloc, workbuf_len_max_incl); +		if (!ctx.workbuf.ptr) {  			set_error(error, "failed to allocate a work buffer"); -			goto fail_preread; -		} -	} - -	unsigned char *targetbuf = NULL; -	cairo_surface_t *surface = -		cairo_image_surface_create(cairo_format, 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; -	} - -	// CAIRO_STRIDE_ALIGNMENT is 4 bytes, so there will be no padding with -	// ARGB/BGR/XRGB/BGRX.  This function does not support a stride different -	// from the width, maybe Wuffs internals do not either. -	unsigned char *surface_data = cairo_image_surface_get_data(surface); -	int surface_stride = cairo_image_surface_get_stride(surface); -	wuffs_base__pixel_buffer pb = {0}; -#ifdef FASTIV_CAIRO_RGBA128F -	if (expand_16_float) { -		uint32_t targetbuf_size = height * width * 64; -		targetbuf = g_malloc(targetbuf_size); -		status = wuffs_base__pixel_buffer__set_from_slice(&pb, &cfg.pixcfg, -			wuffs_base__make_slice_u8(targetbuf, targetbuf_size)); -	} else -#endif  // FASTIV_CAIRO_RGBA128F -	if (pack_16_10) { -		uint32_t targetbuf_size = height * width * 16; -		targetbuf = g_malloc(targetbuf_size); -		status = wuffs_base__pixel_buffer__set_from_slice(&pb, &cfg.pixcfg, -			wuffs_base__make_slice_u8(targetbuf, targetbuf_size)); -	} else { -		status = wuffs_base__pixel_buffer__set_from_slice(&pb, &cfg.pixcfg, -			wuffs_base__make_slice_u8(surface_data, -				surface_stride * cairo_image_surface_get_height(surface))); -	} -	if (!wuffs_base__status__is_ok(&status)) { -		set_error(error, wuffs_base__status__message(&status)); -		goto fail; -	} - -#if 0  // We're not using this right now. -	wuffs_base__frame_config fc = {0}; -	status = wuffs_png__decoder__decode_frame_config(&dec, &fc, &src); -	if (!wuffs_base__status__is_ok(&status)) { -		set_error(error, wuffs_base__status__message(&status)); -		goto fail; -	} -#endif - -	// Starting to modify pixel data directly. Probably an unnecessary call. -	cairo_surface_flush(surface); - -	status = wuffs_base__image_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; -	} - -#ifdef FASTIV_CAIRO_RGBA128F -	if (expand_16_float) { -		g_debug("Wuffs to Cairo RGBA128F"); -		uint16_t *in = (uint16_t *) targetbuf; -		float *out = (float *) surface_data; -		for (uint32_t y = 0; y < height; y++) { -			for (uint32_t x = 0; x < width; x++) { -				float b = *in++ / 65535., g = *in++ / 65535., -					r = *in++ / 65535., a = *in++ / 65535.; -				*out++ = r * a; -				*out++ = g * a; -				*out++ = b * a; -				*out++ = a; -			} -		} -	} else -#endif  // FASTIV_CAIRO_RGBA128F -	if (pack_16_10) { -		g_debug("Wuffs to Cairo RGB30"); -		uint16_t *in = (uint16_t *) targetbuf; -		uint32_t *out = (uint32_t *) surface_data; -		for (uint32_t y = 0; y < height; y++) { -			for (uint32_t x = 0; x < width; x++) { -				uint16_t b = *in++, g = *in++, r = *in++, x = *in++; -				*out++ = (x >> 14) << 30 | -					(r >> 6) << 20 | (g >> 6) << 10 | (b >> 6); -			} +			goto fail;  		}  	} -	// Pixel data has been written, need to let Cairo know. -	cairo_surface_mark_dirty((result = surface)); - -	if (meta_exif) -		cairo_surface_set_user_data(surface, &fastiv_io_key_exif, -			g_bytes_ref(meta_exif), (cairo_destroy_func_t) g_bytes_unref); -	if (meta_iccp) -		cairo_surface_set_user_data(surface, &fastiv_io_key_icc, -			g_bytes_ref(meta_iccp), (cairo_destroy_func_t) g_bytes_unref); +	while (load_wuffs_frame(&ctx, error)) +		;  fail: -	if (!result) -		cairo_surface_destroy(surface); - -	g_free(targetbuf); -	free(workbuf.ptr); - -fail_preread: -	g_clear_pointer(&meta_exif, g_bytes_unref); -	g_clear_pointer(&meta_iccp, g_bytes_unref); -	return result; +	free(ctx.workbuf.ptr); +	g_clear_pointer(&ctx.meta_exif, g_bytes_unref); +	g_clear_pointer(&ctx.meta_iccp, g_bytes_unref); +	return ctx.result;  }  static cairo_surface_t * @@ -986,6 +1000,10 @@ open_gdkpixbuf(const gchar *data, gsize len, GError **error)  cairo_user_data_key_t fastiv_io_key_exif;  cairo_user_data_key_t fastiv_io_key_icc; +cairo_user_data_key_t fastiv_io_key_frame_next; +cairo_user_data_key_t fastiv_io_key_frame_duration; +cairo_user_data_key_t fastiv_io_key_loops; +  cairo_surface_t *  fastiv_io_open(const gchar *path, GError **error)  { @@ -1091,6 +1109,30 @@ fastiv_io_open_from_data(const char *data, size_t len, const gchar *path,  // --- Thumbnails -------------------------------------------------------------- +GType +fastiv_io_thumbnail_size_get_type(void) +{ +	static gsize guard; +	if (g_once_init_enter(&guard)) { +#define XX(name, value, dir) {FASTIV_IO_THUMBNAIL_SIZE_ ## name, \ +	"FASTIV_IO_THUMBNAIL_SIZE_" #name, #name}, +		static const GEnumValue values[] = {FASTIV_IO_THUMBNAIL_SIZES(XX) {}}; +#undef XX +		GType type = g_enum_register_static( +			g_intern_static_string("FastivIoThumbnailSize"), values); +		g_once_init_leave(&guard, type); +	} +	return guard; +} + +#define XX(name, value, dir) {value, dir}, +FastivIoThumbnailSizeInfo +	fastiv_io_thumbnail_sizes[FASTIV_IO_THUMBNAIL_SIZE_COUNT] = { +		FASTIV_IO_THUMBNAIL_SIZES(XX)}; +#undef XX + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +  #ifndef __linux__  #define st_mtim st_mtimespec  #endif  // ! __linux__ diff --git a/fastiv-io.h b/fastiv-io.h index aa21a33..aed235b 100644 --- a/fastiv-io.h +++ b/fastiv-io.h @@ -25,6 +25,29 @@ extern const char *fastiv_io_supported_media_types[];  char **fastiv_io_all_supported_media_types(void); +// Userdata are typically attached to all Cairo surfaces in an animation. + +/// GBytes with plain Exif data. +extern cairo_user_data_key_t fastiv_io_key_exif; +/// GBytes with plain ICC profile data. +extern cairo_user_data_key_t fastiv_io_key_icc; + +/// The next frame in a sequence, as a surface, in a chain, pre-composited. +/// There is no wrap-around. +extern cairo_user_data_key_t fastiv_io_key_frame_next; +/// Frame duration in milliseconds as an intptr_t. +extern cairo_user_data_key_t fastiv_io_key_frame_duration; +/// How many times to repeat the animation, or zero for +inf, as an uintptr_t. +extern cairo_user_data_key_t fastiv_io_key_loops; + +cairo_surface_t *fastiv_io_open(const gchar *path, GError **error); +cairo_surface_t *fastiv_io_open_from_data( +	const char *data, size_t len, const gchar *path, GError **error); + +int fastiv_io_filecmp(GFile *f1, GFile *f2); + +// --- Thumbnails -------------------------------------------------------------- +  // And this is how you avoid glib-mkenums.  typedef enum _FastivIoThumbnailSize {  #define FASTIV_IO_THUMBNAIL_SIZES(XX) \ @@ -52,13 +75,5 @@ typedef struct _FastivIoThumbnailSizeInfo {  extern FastivIoThumbnailSizeInfo  	fastiv_io_thumbnail_sizes[FASTIV_IO_THUMBNAIL_SIZE_COUNT]; -extern cairo_user_data_key_t fastiv_io_key_exif; -extern cairo_user_data_key_t fastiv_io_key_icc; - -cairo_surface_t *fastiv_io_open(const gchar *path, GError **error); -cairo_surface_t *fastiv_io_open_from_data( -	const char *data, size_t len, const gchar *path, GError **error);  cairo_surface_t *fastiv_io_lookup_thumbnail(  	const gchar *target, FastivIoThumbnailSize size); - -int fastiv_io_filecmp(GFile *f1, GFile *f2); | 
