diff options
| -rw-r--r-- | README.adoc | 2 | ||||
| -rw-r--r-- | fiv-io.c | 123 | 
2 files changed, 95 insertions, 30 deletions
diff --git a/README.adoc b/README.adoc index 0749a55..b364e4f 100644 --- a/README.adoc +++ b/README.adoc @@ -6,7 +6,7 @@ raw photos, HEIC, AVIF, WebP, SVG, X11 cursors and TIFF, or whatever gdk-pixbuf  loads.  Its development status can be summarized as '`beta`'.  E.g., colour management -is partial, and certain GIFs animate wrong. +is a bit incomplete, and certain GIFs animate wrong.  Non-goals  --------- @@ -198,6 +198,14 @@ fiv_io_profile_new_sRGB(void)  #endif  } +static FivIoProfile +fiv_io_profile_new_from_bytes(GBytes *bytes) +{ +	gsize len = 0; +	gconstpointer p = g_bytes_get_data(bytes, &len); +	return fiv_io_profile_new(p, len); +} +  void  fiv_io_profile_free(FivIoProfile self)  { @@ -211,8 +219,10 @@ fiv_io_profile_free(FivIoProfile self)  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  // TODO(p): In general, try to use CAIRO_FORMAT_RGB30 or CAIRO_FORMAT_RGBA128F. -#define FIV_IO_LCMS2_ARGB \ +#define FIV_IO_LCMS2_ARGB32 \  	(G_BYTE_ORDER == G_LITTLE_ENDIAN ? TYPE_BGRA_8 : TYPE_ARGB_8) +#define FIV_IO_LCMS2_4X16LE \ +	(G_BYTE_ORDER == G_LITTLE_ENDIAN ? TYPE_BGRA_16 : TYPE_BGRA_16_SE)  // CAIRO_STRIDE_ALIGNMENT is 4 bytes, so there will be no padding with  // ARGB/BGRA/XRGB/BGRX. @@ -254,7 +264,7 @@ fiv_io_profile_cmyk(  	cmsHTRANSFORM transform = NULL;  	if (source && target) {  		transform = cmsCreateTransform(source, TYPE_CMYK_8_REV, target, -			FIV_IO_LCMS2_ARGB, INTENT_PERCEPTUAL, 0); +			FIV_IO_LCMS2_ARGB32, INTENT_PERCEPTUAL, 0);  	}  	if (transform) {  		cmsDoTransform(transform, data, data, w * h); @@ -266,18 +276,56 @@ fiv_io_profile_cmyk(  }  static void -fiv_io_profile_xrgb32( -	cairo_surface_t *surface, FivIoProfile source, FivIoProfile target) +fiv_io_profile_xrgb32_direct(unsigned char *data, int w, int h, +	FivIoProfile source, FivIoProfile target)  {  #ifndef HAVE_LCMS2 -	(void) surface; +	(void) data; +	(void) w; +	(void) h;  	(void) source;  	(void) target;  #else +	// TODO(p): We should make this optional. +	cmsHPROFILE src_fallback = NULL; +	if (target && !source) +		source = src_fallback = cmsCreate_sRGBProfile(); + +	cmsHTRANSFORM transform = NULL; +	if (source && target) { +		transform = cmsCreateTransform(source, FIV_IO_LCMS2_ARGB32, target, +			FIV_IO_LCMS2_ARGB32, INTENT_PERCEPTUAL, 0); +	} +	if (transform) { +		cmsDoTransform(transform, data, data, w * h); +		cmsDeleteTransform(transform); +	} +	if (src_fallback) +		cmsCloseProfile(src_fallback); +#endif +} + +static void +fiv_io_profile_xrgb32( +	cairo_surface_t *surface, FivIoProfile source, FivIoProfile target) +{  	unsigned char *data = cairo_image_surface_get_data(surface);  	int w = cairo_image_surface_get_width(surface);  	int h = cairo_image_surface_get_height(surface); +	fiv_io_profile_xrgb32_direct(data, w, h, source, target); +} +static void +fiv_io_profile_4x16le_direct( +	unsigned char *data, int w, int h, FivIoProfile source, FivIoProfile target) +{ +#ifndef HAVE_LCMS2 +	(void) data; +	(void) w; +	(void) h; +	(void) source; +	(void) target; +#else  	// TODO(p): We should make this optional.  	cmsHPROFILE src_fallback = NULL;  	if (target && !source) @@ -285,8 +333,8 @@ fiv_io_profile_xrgb32(  	cmsHTRANSFORM transform = NULL;  	if (source && target) { -		transform = cmsCreateTransform(source, FIV_IO_LCMS2_ARGB, target, -			FIV_IO_LCMS2_ARGB, INTENT_PERCEPTUAL, 0); +		transform = cmsCreateTransform(source, FIV_IO_LCMS2_4X16LE, target, +			FIV_IO_LCMS2_4X16LE, INTENT_PERCEPTUAL, 0);  	}  	if (transform) {  		cmsDoTransform(transform, data, data, w * h); @@ -303,14 +351,11 @@ static void  fiv_io_profile_xrgb32_page(cairo_surface_t *page, FivIoProfile target)  {  	GBytes *bytes = NULL; -	gsize len = 0; -	gconstpointer p = NULL;  	FivIoProfile source = NULL; -	if ((bytes = cairo_surface_get_user_data(page, &fiv_io_key_icc)) && -		(p = g_bytes_get_data(bytes, &len))) -		source = fiv_io_profile_new(p, len); +	if ((bytes = cairo_surface_get_user_data(page, &fiv_io_key_icc))) +		source = fiv_io_profile_new_from_bytes(bytes); -	// TODO(p): Animations need to be composited in a linear colour space. +	// TODO(p): All animations need to be composited in a linear colour space.  	for (cairo_surface_t *frame = page; frame != NULL;  		frame = cairo_surface_get_user_data(frame, &fiv_io_key_frame_next))  		fiv_io_profile_xrgb32(frame, source, target); @@ -348,6 +393,8 @@ fiv_io_premultiply_argb32(cairo_surface_t *surface)  	int h = cairo_image_surface_get_height(surface);  	unsigned char *data = cairo_image_surface_get_data(surface);  	int stride = cairo_image_surface_get_stride(surface); +	if (cairo_image_surface_get_format(surface) != CAIRO_FORMAT_ARGB32) +		return;  	for (int y = 0; y < h; y++) {  		uint32_t *dstp = (uint32_t *) (data + stride * y); @@ -454,6 +501,9 @@ struct load_wuffs_frame_context {  	GBytes *meta_iccp;                  ///< Reference-counted ICC profile  	GBytes *meta_xmp;                   ///< Reference-counted XMP +	FivIoProfile target;                ///< Target device profile, if any +	FivIoProfile source;                ///< Source colour profile, if any +  	cairo_surface_t *result;            ///< The resulting surface (referenced)  	cairo_surface_t *result_tail;       ///< The final animation frame  }; @@ -487,13 +537,8 @@ load_wuffs_frame(struct load_wuffs_frame_context *ctx, GError **error)  	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; +	if (ctx->expand_16_float || ctx->pack_16_10) { +		uint32_t targetbuf_size = ctx->height * ctx->width * 8;  		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)); @@ -517,6 +562,18 @@ load_wuffs_frame(struct load_wuffs_frame_context *ctx, GError **error)  		goto fail;  	} +	if (ctx->target) { +		if (ctx->expand_16_float || ctx->pack_16_10) { +			fiv_io_profile_4x16le_direct( +				targetbuf, ctx->width, ctx->height, ctx->source, ctx->target); +			// The first one premultiplies below, the second doesn't need to. +		} else { +			fiv_io_profile_xrgb32_direct(surface_data, ctx->width, ctx->height, +				ctx->source, ctx->target); +			fiv_io_premultiply_argb32(surface); +		} +	} +  	if (ctx->expand_16_float) {  		g_debug("Wuffs to Cairo RGBA128F");  		uint16_t *in = (uint16_t *) targetbuf; @@ -561,6 +618,7 @@ load_wuffs_frame(struct load_wuffs_frame_context *ctx, GError **error)  		// Apply that frame's disposal method.  		wuffs_base__rect_ie_u32 bounds =  			wuffs_base__frame_config__bounds(&ctx->last_fc); +		// TODO(p): This field needs to be colour-managed.  		wuffs_base__color_u32_argb_premul bg =  			wuffs_base__frame_config__background_color(&ctx->last_fc); @@ -650,10 +708,11 @@ fail:  // is pure C, and a good reference. I can't use the auxiliary libraries,  // since they depend on C++, which is undesirable.  static cairo_surface_t * -open_wuffs( -	wuffs_base__image_decoder *dec, wuffs_base__io_buffer src, GError **error) +open_wuffs(wuffs_base__image_decoder *dec, wuffs_base__io_buffer src, +	FivIoProfile profile, GError **error)  { -	struct load_wuffs_frame_context ctx = {.dec = dec, .src = &src}; +	struct load_wuffs_frame_context ctx = { +		.dec = dec, .src = &src, .target = profile};  	// 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. @@ -720,13 +779,16 @@ open_wuffs(  		goto fail;  	} +	if (ctx.target && ctx.meta_iccp) +		ctx.source = fiv_io_profile_new_from_bytes(ctx.meta_iccp); +  	// Wuffs maps tRNS to BGRA in `decoder.decode_trns?`, we should be fine.  	// 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); -	// Wuffs' API is kind of awful--we want to catch deep RGB and deep grey. +	// Wuffs' API is kind of awful--we want to catch wide RGB and wide grey.  	wuffs_base__pixel_format srcfmt =  		wuffs_base__pixel_config__pixel_format(&ctx.cfg.pixcfg);  	uint32_t bpp = wuffs_base__pixel_format__bits_per_pixel(&srcfmt); @@ -744,7 +806,7 @@ open_wuffs(  	// XXX: WUFFS_BASE__PIXEL_FORMAT__ARGB_PREMUL is not expressible, only RGBA.  	// Wuffs doesn't support big-endian architectures at all, we might want to  	// fall back to spng in such cases, or do a second conversion. -	uint32_t wuffs_format = WUFFS_BASE__PIXEL_FORMAT__BGRA_PREMUL; +	uint32_t wuffs_format = WUFFS_BASE__PIXEL_FORMAT__BGRA_NONPREMUL;  	// CAIRO_FORMAT_ARGB32: "The 32-bit quantities are stored native-endian.  	// Pre-multiplied alpha is used." CAIRO_FORMAT_RGB{24,30} are analogous. @@ -764,8 +826,9 @@ open_wuffs(  		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;  		ctx.cairo_format = CAIRO_FORMAT_RGB24; +	} else if (!ctx.target) { +		wuffs_format = WUFFS_BASE__PIXEL_FORMAT__BGRA_PREMUL;  	}  	wuffs_base__pixel_config__set(&ctx.cfg.pixcfg, wuffs_format, @@ -794,6 +857,7 @@ fail:  	g_clear_pointer(&ctx.meta_exif, g_bytes_unref);  	g_clear_pointer(&ctx.meta_iccp, g_bytes_unref);  	g_clear_pointer(&ctx.meta_xmp, g_bytes_unref); +	g_clear_pointer(&ctx.source, fiv_io_profile_free);  	return ctx.result;  } @@ -807,10 +871,11 @@ open_wuffs_using(wuffs_base__image_decoder *(*allocate)(),  		return NULL;  	} -	cairo_surface_t *surface = open_wuffs( -		dec, wuffs_base__ptr_u8__reader((uint8_t *) data, len, TRUE), error); +	cairo_surface_t *surface = +		open_wuffs(dec, wuffs_base__ptr_u8__reader((uint8_t *) data, len, TRUE), +			profile, error);  	free(dec); -	return fiv_io_profile_finalize(surface, profile); +	return surface;  }  // --- JPEG --------------------------------------------------------------------  | 
