diff options
| -rw-r--r-- | fiv-io-cmm.c (renamed from fiv-io-profile.c) | 429 | ||||
| -rw-r--r-- | fiv-io.c | 77 | ||||
| -rw-r--r-- | fiv-io.h | 55 | ||||
| -rw-r--r-- | fiv-thumbnail.c | 10 | ||||
| -rw-r--r-- | fiv-view.c | 8 | ||||
| -rw-r--r-- | fiv.c | 2 | ||||
| -rw-r--r-- | meson.build | 2 | 
7 files changed, 347 insertions, 236 deletions
| diff --git a/fiv-io-profile.c b/fiv-io-cmm.c index 2c6f345..b131acf 100644 --- a/fiv-io-profile.c +++ b/fiv-io-cmm.c @@ -1,5 +1,5 @@  // -// fiv-profile.c: colour management +// fiv-io-cmm.c: colour management  //  // Copyright (c) 2024, Přemysl Eric Janouch <p@janouch.name>  // @@ -23,6 +23,7 @@  #include "fiv-io.h"  // Colour management must be handled before RGB conversions. +// TODO(p): Make it also possible to use Skia's skcms.  #ifdef HAVE_LCMS2  #include <lcms2.h>  #endif  // HAVE_LCMS2 @@ -30,48 +31,176 @@  #include <lcms2_fast_float.h>  #endif  // HAVE_LCMS2_FAST_FLOAT -// https://github.com/mm2/Little-CMS/issues/430 -static bool g_broken_cms_premul; +// --- CMM-independent transforms ---------------------------------------------- + +// CAIRO_STRIDE_ALIGNMENT is 4 bytes, so there will be no padding with +// ARGB/BGRA/XRGB/BGRX. +static void +trivial_cmyk_to_host_byte_order_argb(unsigned char *p, int len) +{ +	// This CMYK handling has been seen in gdk-pixbuf/JPEG, GIMP/JPEG, skcms. +	// It will typically produce horribly oversaturated results. +	// Assume that all YCCK/CMYK JPEG files use inverted CMYK, as Photoshop +	// does, see https://bugzilla.gnome.org/show_bug.cgi?id=618096 +	while (len--) { +		int c = p[0], m = p[1], y = p[2], k = p[3]; +#if G_BYTE_ORDER == G_LITTLE_ENDIAN +		p[0] = k * y / 255; +		p[1] = k * m / 255; +		p[2] = k * c / 255; +		p[3] = 255; +#else +		p[3] = k * y / 255; +		p[2] = k * m / 255; +		p[1] = k * c / 255; +		p[0] = 255; +#endif +		p += 4; +	} +} + +// From libwebp, verified to exactly match [x * a / 255]. +#define PREMULTIPLY8(a, x) (((uint32_t) (x) * (uint32_t) (a) * 32897U) >> 23) + +void +fiv_io_premultiply_argb32(FivIoImage *image) +{ +	if (image->format != CAIRO_FORMAT_ARGB32) +		return; + +	for (uint32_t y = 0; y < image->height; y++) { +		uint32_t *dstp = (uint32_t *) (image->data + image->stride * y); +		for (uint32_t x = 0; x < image->width; x++) { +			uint32_t argb = dstp[x], a = argb >> 24; +			dstp[x] = a << 24 | +				PREMULTIPLY8(a, 0xFF & (argb >> 16)) << 16 | +				PREMULTIPLY8(a, 0xFF & (argb >>  8)) <<  8 | +				PREMULTIPLY8(a, 0xFF &  argb); +		} +	} +} + +// --- Profiles ---------------------------------------------------------------- +#ifdef HAVE_LCMS2 + +struct _FivIoProfile { +	FivIoCmm *cmm; +	cmsHPROFILE profile; +}; + +GBytes * +fiv_io_profile_to_bytes(FivIoProfile *profile) +{ +	cmsUInt32Number len = 0; +	(void) cmsSaveProfileToMem(profile, NULL, &len); +	gchar *data = g_malloc0(len); +	if (!cmsSaveProfileToMem(profile, data, &len)) { +		g_free(data); +		return NULL; +	} +	return g_bytes_new_take(data, len); +} + +static FivIoProfile * +fiv_io_profile_new(FivIoCmm *cmm, cmsHPROFILE profile) +{ +	FivIoProfile *self = g_new0(FivIoProfile, 1); +	self->cmm = g_object_ref(cmm); +	self->profile = profile; +	return self; +}  void -fiv_io_profile_init(void) +fiv_io_profile_free(FivIoProfile *self) +{ +	cmsCloseProfile(self->profile); +	g_clear_object(&self->cmm); +	g_free(self); +} + +#else  // ! HAVE_LCMS2 + +GBytes *fiv_io_profile_to_bytes(FivIoProfile *) { return NULL; } +void fiv_io_profile_free(FivIoProfile *) {} + +#endif  // ! HAVE_LCMS2 +// --- Contexts ---------------------------------------------------------------- +#ifdef HAVE_LCMS2 + +struct _FivIoCmm { +	GObject parent_instance; +	cmsContext context; + +	// https://github.com/mm2/Little-CMS/issues/430 +	gboolean broken_premul; +}; + +G_DEFINE_TYPE(FivIoCmm, fiv_io_cmm, G_TYPE_OBJECT) + +static void +fiv_io_cmm_finalize(GObject *gobject) +{ +	FivIoCmm *self = FIV_IO_CMM(gobject); +	cmsDeleteContext(self->context); + +	G_OBJECT_CLASS(fiv_io_cmm_parent_class)->finalize(gobject); +} + +static void +fiv_io_cmm_class_init(FivIoCmmClass *klass) +{ +	GObjectClass *object_class = G_OBJECT_CLASS(klass); +	object_class->finalize = fiv_io_cmm_finalize; +} + +static void +fiv_io_cmm_init(FivIoCmm *self)  { -	// TODO(p): Use Little CMS with contexts instead. +	self->context = cmsCreateContext(NULL, self);  #ifdef HAVE_LCMS2_FAST_FLOAT -	if (cmsPluginTHR(NULL, cmsFastFloatExtensions())) -		g_broken_cms_premul = LCMS_VERSION <= 2160; +	if (cmsPluginTHR(self->context, cmsFastFloatExtensions())) +		self->broken_premul = LCMS_VERSION <= 2160;  #endif  // HAVE_LCMS2_FAST_FLOAT  } +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +FivIoCmm * +fiv_io_cmm_get_default(void) +{ +	static gsize initialization_value = 0; +	static FivIoCmm *default_ = NULL; +	if (g_once_init_enter(&initialization_value)) { +		gsize setup_value = 1; +		default_ = g_object_new(FIV_TYPE_IO_CMM, NULL); +		g_once_init_leave(&initialization_value, setup_value); +	} +	return default_; +} +  FivIoProfile * -fiv_io_profile_new(const void *data, size_t len) +fiv_io_cmm_get_profile(FivIoCmm *self, const void *data, size_t len)  { -#ifdef HAVE_LCMS2 -	return cmsOpenProfileFromMemTHR(NULL, data, len); -#else -	(void) data; -	(void) len; -	return NULL; -#endif +	g_return_val_if_fail(self != NULL, NULL); + +	return fiv_io_profile_new(self, +		cmsOpenProfileFromMemTHR(self->context, data, len));  }  FivIoProfile * -fiv_io_profile_new_sRGB(void) +fiv_io_cmm_get_profile_sRGB(FivIoCmm *self)  { -#ifdef HAVE_LCMS2 -	return cmsCreate_sRGBProfileTHR(NULL); -#else -	return NULL; -#endif +	g_return_val_if_fail(self != NULL, NULL); + +	return fiv_io_profile_new(self, +		cmsCreate_sRGBProfileTHR(self->context));  }  FivIoProfile * -fiv_io_profile_new_parametric( +fiv_io_cmm_get_profile_parametric(FivIoCmm *self,  	double gamma, double whitepoint[2], double primaries[6])  { -#ifdef HAVE_LCMS2 -	// TODO(p): Make sure to use the library in a thread-safe manner. -	cmsContext context = NULL; +	g_return_val_if_fail(self != NULL, NULL);  	const cmsCIExyY cmsWP = {whitepoint[0], whitepoint[1], 1.0};  	const cmsCIExyYTRIPLE cmsP = { @@ -80,117 +209,81 @@ fiv_io_profile_new_parametric(  		{primaries[4], primaries[5], 1.0},  	}; -	cmsToneCurve *curve = cmsBuildGamma(context, gamma); +	cmsToneCurve *curve = cmsBuildGamma(self->context, gamma);  	if (!curve)  		return NULL; -	cmsHPROFILE profile = cmsCreateRGBProfileTHR( -		context, &cmsWP, &cmsP, (cmsToneCurve *[3]){curve, curve, curve}); +	cmsHPROFILE profile = cmsCreateRGBProfileTHR(self->context, +		&cmsWP, &cmsP, (cmsToneCurve *[3]){curve, curve, curve});  	cmsFreeToneCurve(curve); -	return profile; -#else -	(void) gamma; -	(void) whitepoint; -	(void) primaries; +	return fiv_io_profile_new(self, profile); +} + +#else  // ! HAVE_LCMS2 + +FivIoCmm * +fiv_io_cmm_get_default() +{  	return NULL; -#endif  }  FivIoProfile * -fiv_io_profile_new_sRGB_gamma(double gamma) +fiv_io_cmm_get_profile(FivIoCmm *, const void *, size_t)  { -	return fiv_io_profile_new_parametric(gamma, -		(double[2]){0.3127, 0.3290}, -		(double[6]){0.6400, 0.3300, 0.3000, 0.6000, 0.1500, 0.0600}); +	return NULL;  }  FivIoProfile * -fiv_io_profile_new_from_bytes(GBytes *bytes) +fiv_io_cmm_get_profile_sRGB(FivIoCmm *)  { -	gsize len = 0; -	gconstpointer p = g_bytes_get_data(bytes, &len); -	return fiv_io_profile_new(p, len); +	return NULL;  } -GBytes * -fiv_io_profile_to_bytes(FivIoProfile *profile) +FivIoProfile * +fiv_io_cmm_get_profile_parametric(FivIoCmm *, double, double[2], double[6])  { -#ifdef HAVE_LCMS2 -	cmsUInt32Number len = 0; -	(void) cmsSaveProfileToMem(profile, NULL, &len); -	gchar *data = g_malloc0(len); -	if (!cmsSaveProfileToMem(profile, data, &len)) { -		g_free(data); -		return NULL; -	} -	return g_bytes_new_take(data, len); -#else -	(void) profile;  	return NULL; -#endif  } -void -fiv_io_profile_free(FivIoProfile *self) +#endif  // ! HAVE_LCMS2 +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +FivIoProfile * +fiv_io_cmm_get_profile_sRGB_gamma(FivIoCmm *self, double gamma)  { -#ifdef HAVE_LCMS2 -	cmsCloseProfile(self); -#else -	(void) self; -#endif +	return fiv_io_cmm_get_profile_parametric(self, gamma, +		(double[2]){0.3127, 0.3290}, +		(double[6]){0.6400, 0.3300, 0.3000, 0.6000, 0.1500, 0.0600}); +} + +FivIoProfile * +fiv_io_cmm_get_profile_from_bytes(FivIoCmm *self, GBytes *bytes) +{ +	gsize len = 0; +	gconstpointer p = g_bytes_get_data(bytes, &len); +	return fiv_io_cmm_get_profile(self, p, len);  }  // --- Image loading ----------------------------------------------------------- +#ifdef HAVE_LCMS2  // TODO(p): In general, try to use CAIRO_FORMAT_RGB30 or CAIRO_FORMAT_RGBA128F. -#ifndef HAVE_LCMS2 -#define FIV_IO_PROFILE_ARGB32 0 -#define FIV_IO_PROFILE_4X16LE 0 -#else  #define FIV_IO_PROFILE_ARGB32 \  	(G_BYTE_ORDER == G_LITTLE_ENDIAN ? TYPE_BGRA_8 : TYPE_ARGB_8)  #define FIV_IO_PROFILE_4X16LE \  	(G_BYTE_ORDER == G_LITTLE_ENDIAN ? TYPE_BGRA_16 : TYPE_BGRA_16_SE) -#endif - -// CAIRO_STRIDE_ALIGNMENT is 4 bytes, so there will be no padding with -// ARGB/BGRA/XRGB/BGRX. -static void -trivial_cmyk_to_host_byte_order_argb(unsigned char *p, int len) -{ -	// This CMYK handling has been seen in gdk-pixbuf/JPEG, GIMP/JPEG, skcms. -	// It will typically produce horribly oversaturated results. -	// Assume that all YCCK/CMYK JPEG files use inverted CMYK, as Photoshop -	// does, see https://bugzilla.gnome.org/show_bug.cgi?id=618096 -	while (len--) { -		int c = p[0], m = p[1], y = p[2], k = p[3]; -#if G_BYTE_ORDER == G_LITTLE_ENDIAN -		p[0] = k * y / 255; -		p[1] = k * m / 255; -		p[2] = k * c / 255; -		p[3] = 255; -#else -		p[3] = k * y / 255; -		p[2] = k * m / 255; -		p[1] = k * c / 255; -		p[0] = 255; -#endif -		p += 4; -	} -}  void -fiv_io_profile_cmyk( +fiv_io_cmm_cmyk(FivIoCmm *self,  	FivIoImage *image, FivIoProfile *source, FivIoProfile *target)  { -#ifndef HAVE_LCMS2 -	(void) source; -	(void) target; -#else +	g_return_if_fail(target == NULL || self != NULL); +  	cmsHTRANSFORM transform = NULL;  	if (source && target) { -		transform = cmsCreateTransformTHR(NULL, source, TYPE_CMYK_8_REV, target, -			FIV_IO_PROFILE_ARGB32, INTENT_PERCEPTUAL, 0); +		transform = cmsCreateTransformTHR(self->context, +			source->profile, TYPE_CMYK_8_REV, +			target->profile, FIV_IO_PROFILE_ARGB32, INTENT_PERCEPTUAL, 0);  	}  	if (transform) {  		cmsDoTransform( @@ -198,129 +291,105 @@ fiv_io_profile_cmyk(  		cmsDeleteTransform(transform);  		return;  	} -#endif  	trivial_cmyk_to_host_byte_order_argb(  		image->data, image->width * image->height);  }  static bool -fiv_io_profile_rgb_direct(unsigned char *data, int w, int h, +fiv_io_cmm_rgb_direct(FivIoCmm *self, unsigned char *data, int w, int h,  	FivIoProfile *source, FivIoProfile *target,  	uint32_t source_format, uint32_t target_format)  { -#ifndef HAVE_LCMS2 -	(void) data; -	(void) w; -	(void) h; -	(void) source; -	(void) source_format; -	(void) target; -	(void) target_format; -	return false; -#else +	g_return_val_if_fail(target == NULL || self != NULL, false); +  	// TODO(p): We should make this optional. -	cmsHPROFILE src_fallback = NULL; +	FivIoProfile *src_fallback = NULL;  	if (target && !source) -		source = src_fallback = cmsCreate_sRGBProfileTHR(NULL); +		source = src_fallback = fiv_io_cmm_get_profile_sRGB(self);  	cmsHTRANSFORM transform = NULL;  	if (source && target) { -		transform = cmsCreateTransformTHR(NULL, -			source, source_format, target, target_format, INTENT_PERCEPTUAL, 0); +		transform = cmsCreateTransformTHR(self->context, +			source->profile, source_format, +			target->profile, target_format, INTENT_PERCEPTUAL, 0);  	}  	if (transform) {  		cmsDoTransform(transform, data, data, w * h);  		cmsDeleteTransform(transform);  	}  	if (src_fallback) -		cmsCloseProfile(src_fallback); +		fiv_io_profile_free(src_fallback);  	return transform != NULL; -#endif  }  static void -fiv_io_profile_xrgb32( +fiv_io_cmm_xrgb32(FivIoCmm *self,  	FivIoImage *image, FivIoProfile *source, FivIoProfile *target)  { -	fiv_io_profile_rgb_direct(image->data, image->width, image->height, +	fiv_io_cmm_rgb_direct(self, image->data, image->width, image->height,  		source, target, FIV_IO_PROFILE_ARGB32, FIV_IO_PROFILE_ARGB32);  }  void -fiv_io_profile_4x16le_direct(unsigned char *data, +fiv_io_cmm_4x16le_direct(FivIoCmm *self, unsigned char *data,  	int w, int h, FivIoProfile *source, FivIoProfile *target)  { -	fiv_io_profile_rgb_direct(data, w, h, source, target, +	fiv_io_cmm_rgb_direct(self, data, w, h, source, target,  		FIV_IO_PROFILE_4X16LE, FIV_IO_PROFILE_4X16LE);  } -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +#else  // ! HAVE_LCMS2  void -fiv_io_profile_page(FivIoImage *page, FivIoProfile *target, -	void (*frame_cb) (FivIoImage *, FivIoProfile *, FivIoProfile *)) +fiv_io_cmm_cmyk(FivIoCmm *, FivIoImage *image, FivIoProfile *, FivIoProfile *)  { -	FivIoProfile *source = NULL; -	if (page->icc) -		source = fiv_io_profile_new_from_bytes(page->icc); - -	// TODO(p): All animations need to be composited in a linear colour space. -	for (FivIoImage *frame = page; frame != NULL; frame = frame->frame_next) -		frame_cb(frame, source, target); - -	if (source) -		fiv_io_profile_free(source); +	trivial_cmyk_to_host_byte_order_argb( +		image->data, image->width * image->height);  } -// From libwebp, verified to exactly match [x * a / 255]. -#define PREMULTIPLY8(a, x) (((uint32_t) (x) * (uint32_t) (a) * 32897U) >> 23) +static void +fiv_io_cmm_xrgb32(FivIoCmm *, FivIoImage *, FivIoProfile *, FivIoProfile *) +{ +}  void -fiv_io_premultiply_argb32(FivIoImage *image) +fiv_io_cmm_4x16le_direct( +	FivIoCmm *, unsigned char *, int, int, FivIoProfile *, FivIoProfile *)  { -	if (image->format != CAIRO_FORMAT_ARGB32) -		return; - -	for (uint32_t y = 0; y < image->height; y++) { -		uint32_t *dstp = (uint32_t *) (image->data + image->stride * y); -		for (uint32_t x = 0; x < image->width; x++) { -			uint32_t argb = dstp[x], a = argb >> 24; -			dstp[x] = a << 24 | -				PREMULTIPLY8(a, 0xFF & (argb >> 16)) << 16 | -				PREMULTIPLY8(a, 0xFF & (argb >>  8)) <<  8 | -				PREMULTIPLY8(a, 0xFF &  argb); -		} -	}  } +#endif  // ! HAVE_LCMS2 +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  #if defined HAVE_LCMS2 && LCMS_VERSION >= 2130  #define FIV_IO_PROFILE_ARGB32_PREMUL \  	(G_BYTE_ORDER == G_LITTLE_ENDIAN ? TYPE_BGRA_8_PREMUL : TYPE_ARGB_8_PREMUL)  static void -fiv_io_profile_argb32(FivIoImage *image, +fiv_io_cmm_argb32(FivIoCmm *self, FivIoImage *image,  	FivIoProfile *source, FivIoProfile *target)  {  	g_return_if_fail(image->format == CAIRO_FORMAT_ARGB32); -	// TODO: With g_no_cms_premultiplication, +	// TODO: With self->broken_premul,  	// this probably also needs to be wrapped in un-premultiplication. -	fiv_io_profile_rgb_direct(image->data, image->width, image->height, +	fiv_io_cmm_rgb_direct(self, image->data, image->width, image->height,  		source, target,  		FIV_IO_PROFILE_ARGB32_PREMUL, FIV_IO_PROFILE_ARGB32_PREMUL);  }  void -fiv_io_profile_argb32_premultiply( +fiv_io_cmm_argb32_premultiply(FivIoCmm *self,  	FivIoImage *image, FivIoProfile *source, FivIoProfile *target)  { +	g_return_if_fail(target == NULL || self != NULL); +  	if (image->format != CAIRO_FORMAT_ARGB32) { -		fiv_io_profile_xrgb32(image, source, target); -	} else if (g_broken_cms_premul) { -		fiv_io_profile_xrgb32(image, source, target); +		fiv_io_cmm_xrgb32(self, image, source, target); +	} else if (!target || self->broken_premul) { +		fiv_io_cmm_xrgb32(self, image, source, target);  		fiv_io_premultiply_argb32(image); -	} else if (!fiv_io_profile_rgb_direct(image->data, +	} else if (!fiv_io_cmm_rgb_direct(self, image->data,  			image->width, image->height, source, target,  			FIV_IO_PROFILE_ARGB32, FIV_IO_PROFILE_ARGB32_PREMUL)) {  		g_debug("failed to create a premultiplying transform"); @@ -330,23 +399,41 @@ fiv_io_profile_argb32_premultiply(  #else  // ! HAVE_LCMS2 || LCMS_VERSION < 2130 -// TODO(p): Unpremultiply, transform, repremultiply. Or require lcms2>=2.13. -#define fiv_io_profile_argb32(surface, source, target) -  static void -fiv_io_profile_argb32_premultiply( +fiv_io_cmm_argb32(FivIoCmm *, FivIoImage *, FivIoProfile *, FivIoProfile *) +{ +	// TODO(p): Unpremultiply, transform, repremultiply. Or require lcms2>=2.13. +} + +void +fiv_io_cmm_argb32_premultiply(FivIoCmm *self,  	FivIoImage *image, FivIoProfile *source, FivIoProfile *target)  { -	fiv_io_profile_xrgb32(image, source, target); +	fiv_io_cmm_xrgb32(self, image, source, target);  	fiv_io_premultiply_argb32(image);  }  #endif  // ! HAVE_LCMS2 || LCMS_VERSION < 2130 -  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -  void -fiv_io_profile_any( +fiv_io_cmm_page(FivIoCmm *self, FivIoImage *page, FivIoProfile *target, +	void (*frame_cb) (FivIoCmm *, FivIoImage *, FivIoProfile *, FivIoProfile *)) +{ +	FivIoProfile *source = NULL; +	if (page->icc) +		source = fiv_io_cmm_get_profile_from_bytes(self, page->icc); + +	// TODO(p): All animations need to be composited in a linear colour space. +	for (FivIoImage *frame = page; frame != NULL; frame = frame->frame_next) +		frame_cb(self, frame, source, target); + +	if (source) +		fiv_io_profile_free(source); +} + +void +fiv_io_cmm_any(FivIoCmm *self,  	FivIoImage *image, FivIoProfile *source, FivIoProfile *target)  {  	// TODO(p): Ensure we do colour management early enough, so that @@ -354,9 +441,9 @@ fiv_io_profile_any(  	// and also for correct alpha compositing.  	switch (image->format) {  	break; case CAIRO_FORMAT_RGB24: -		fiv_io_profile_xrgb32(image, source, target); +		fiv_io_cmm_xrgb32(self, image, source, target);  	break; case CAIRO_FORMAT_ARGB32: -		fiv_io_profile_argb32(image, source, target); +		fiv_io_cmm_argb32(self, image, source, target);  	break; default:  		g_debug("CM attempted on an unsupported surface format");  	} @@ -364,12 +451,12 @@ fiv_io_profile_any(  // TODO(p): Offer better integration, upgrade the bit depth if appropriate.  FivIoImage * -fiv_io_profile_finalize(FivIoImage *image, FivIoProfile *target) +fiv_io_cmm_finish(FivIoCmm *self, FivIoImage *image, FivIoProfile *target)  {  	if (!target)  		return image;  	for (FivIoImage *page = image; page != NULL; page = page->page_next) -		fiv_io_profile_page(page, target, fiv_io_profile_any); +		fiv_io_cmm_page(self, page, target, fiv_io_cmm_any);  	return image;  } @@ -379,6 +379,7 @@ struct load_wuffs_frame_context {  	GBytes *meta_iccp;                  ///< Reference-counted ICC profile  	GBytes *meta_xmp;                   ///< Reference-counted XMP +	FivIoCmm *cmm;                      ///< CMM context, if any  	FivIoProfile *target;               ///< Target device profile, if any  	FivIoProfile *source;               ///< Source colour profile, if any @@ -447,11 +448,12 @@ load_wuffs_frame(struct load_wuffs_frame_context *ctx, GError **error)  	if (ctx->target) {  		if (ctx->expand_16_float || ctx->pack_16_10) { -			fiv_io_profile_4x16le_direct( +			fiv_io_cmm_4x16le_direct(ctx->cmm,  				targetbuf, ctx->width, ctx->height, ctx->source, ctx->target);  			// The first one premultiplies below, the second doesn't need to.  		} else { -			fiv_io_profile_argb32_premultiply(image, ctx->source, ctx->target); +			fiv_io_cmm_argb32_premultiply( +				ctx->cmm, image, ctx->source, ctx->target);  		}  	} @@ -589,7 +591,8 @@ open_wuffs(wuffs_base__image_decoder *dec, wuffs_base__io_buffer src,  	const FivIoOpenContext *ioctx, GError **error)  {  	struct load_wuffs_frame_context ctx = { -		.dec = dec, .src = &src, .target = ioctx->screen_profile}; +		.dec = dec, .src = &src, +		.cmm = ioctx->cmm, .target = ioctx->screen_profile};  	// TODO(p): PNG text chunks, like we do with PNG thumbnails.  	// TODO(p): See if something could and should be done about @@ -673,9 +676,11 @@ open_wuffs(wuffs_base__image_decoder *dec, wuffs_base__io_buffer src,  	// TODO(p): Improve our simplistic PNG handling of: gAMA, cHRM, sRGB.  	if (ctx.target) {  		if (ctx.meta_iccp) -			ctx.source = fiv_io_profile_new_from_bytes(ctx.meta_iccp); +			ctx.source = fiv_io_cmm_get_profile_from_bytes( +				ctx.cmm, ctx.meta_iccp);  		else if (isfinite(gamma) && gamma > 0) -			ctx.source = fiv_io_profile_new_sRGB_gamma(gamma); +			ctx.source = fiv_io_cmm_get_profile_sRGB_gamma( +				ctx.cmm, gamma);  	}  	// Wuffs maps tRNS to BGRA in `decoder.decode_trns?`, we should be fine. @@ -1059,7 +1064,7 @@ parse_exif_profile_subifd(  }  static FivIoProfile * -parse_exif_profile(const void *data, size_t len) +parse_exif_profile(FivIoCmm *cmm, const void *data, size_t len)  {  	struct tiffer T = {};  	if (!tiffer_init(&T, (const uint8_t *) data, len) || !tiffer_next_ifd(&T)) @@ -1091,7 +1096,7 @@ parse_exif_profile(const void *data, size_t len)  	// If sRGB is claimed, assume all parameters are standard.  	if (params.colorspace == Exif_ColorSpace_sRGB) -		return fiv_io_profile_new_sRGB(); +		return fiv_io_cmm_get_profile_sRGB(cmm);  	// AdobeRGB Nikon JPEGs provide all of these.  	if (params.colorspace != Exif_ColorSpace_Uncalibrated || @@ -1100,7 +1105,7 @@ parse_exif_profile(const void *data, size_t len)  		!params.have_primaries)  		return NULL; -	return fiv_io_profile_new_parametric( +	return fiv_io_cmm_get_profile_parametric(cmm,  		params.gamma, params.whitepoint, params.primaries);  } @@ -1255,17 +1260,17 @@ load_jpeg_finalize(FivIoImage *image, bool cmyk,  		g_byte_array_free(meta.icc, TRUE);  	FivIoProfile *source = NULL; -	if (icc_profile) -		source = fiv_io_profile_new( +	if (icc_profile && ctx->cmm) +		source = fiv_io_cmm_get_profile(ctx->cmm,  			g_bytes_get_data(icc_profile, NULL), g_bytes_get_size(icc_profile)); -	else if (image->exif) -		source = parse_exif_profile( +	else if (image->exif && ctx->cmm) +		source = parse_exif_profile(ctx->cmm,  			g_bytes_get_data(image->exif, NULL), g_bytes_get_size(image->exif));  	if (cmyk) -		fiv_io_profile_cmyk(image, source, ctx->screen_profile); +		fiv_io_cmm_cmyk(ctx->cmm, image, source, ctx->screen_profile);  	else -		fiv_io_profile_any(image, source, ctx->screen_profile); +		fiv_io_cmm_any(ctx->cmm, image, source, ctx->screen_profile);  	if (source)  		fiv_io_profile_free(source); @@ -1666,7 +1671,8 @@ open_libwebp(  	WebPDemuxDelete(demux);  	if (ctx->screen_profile) -		fiv_io_profile_argb32_premultiply_page(result, ctx->screen_profile); +		fiv_io_cmm_argb32_premultiply_page( +			ctx->cmm, result, ctx->screen_profile);  fail:  	WebPFreeDecBuffer(&config.output); @@ -2048,7 +2054,7 @@ open_libraw(  out:  	libraw_close(iprc); -	return fiv_io_profile_finalize(result, ctx->screen_profile); +	return fiv_io_cmm_finish(ctx->cmm, result, ctx->screen_profile);  }  #endif  // HAVE_LIBRAW --------------------------------------------------------- @@ -2070,8 +2076,8 @@ load_resvg_destroy(FivIoRenderClosure *closure)  }  static FivIoImage * -load_resvg_render_internal(FivIoRenderClosureResvg *self, -	double scale, FivIoProfile *target, GError **error) +load_resvg_render_internal(FivIoRenderClosureResvg *self, double scale, +	FivIoCmm *cmm, FivIoProfile *target, GError **error)  {  	double w = ceil(self->width * scale), h = ceil(self->height * scale);  	if (w > SHRT_MAX || h > SHRT_MAX) { @@ -2101,15 +2107,15 @@ load_resvg_render_internal(FivIoRenderClosureResvg *self,  		uint32_t rgba = g_ntohl(pixels[i]);  		pixels[i] = rgba << 24 | rgba >> 8;  	} -	return fiv_io_profile_finalize(image, target); +	return fiv_io_cmm_finish(cmm, image, target);  }  static FivIoImage * -load_resvg_render( -	FivIoRenderClosure *closure, FivIoProfile *target, double scale) +load_resvg_render(FivIoRenderClosure *closure, +	FivIoCmm *cmm, FivIoProfile *target, double scale)  {  	FivIoRenderClosureResvg *self = (FivIoRenderClosureResvg *) closure; -	return load_resvg_render_internal(self, scale, target, NULL); +	return load_resvg_render_internal(self, scale, cmm, target, NULL);  }  static const char * @@ -2167,8 +2173,8 @@ open_resvg(  	closure->width = size.width;  	closure->height = size.height; -	FivIoImage *image = -		load_resvg_render_internal(closure, 1., ctx->screen_profile, error); +	FivIoImage *image = load_resvg_render_internal( +		closure, 1., ctx->cmm, ctx->screen_profile, error);  	if (!image) {  		load_resvg_destroy(&closure->parent);  		return NULL; @@ -2198,7 +2204,7 @@ load_librsvg_destroy(FivIoRenderClosure *closure)  static FivIoImage *  load_librsvg_render_internal(FivIoRenderClosureLibrsvg *self, double scale, -	FivIoProfile *target, GError **error) +	FivIoCmm *cmm, FivIoProfile *target, GError **error)  {  	RsvgRectangle viewport = {.x = 0, .y = 0,  		.width = self->width * scale, .height = self->height * scale}; @@ -2225,15 +2231,15 @@ load_librsvg_render_internal(FivIoRenderClosureLibrsvg *self, double scale,  		fiv_io_image_unref(image);  		return NULL;  	} -	return fiv_io_profile_finalize(image, target); +	return fiv_io_cmm_finish(cmm, image, target);  }  static FivIoImage * -load_librsvg_render( -	FivIoRenderClosure *closure, FivIoProfile *target, double scale) +load_librsvg_render(FivIoRenderClosure *closure, +	FivIoCmm *cmm, FivIoProfile *target, double scale)  {  	FivIoRenderClosureLibrsvg *self = (FivIoRenderClosureLibrsvg *) closure; -	return load_librsvg_render_internal(self, scale, target, NULL); +	return load_librsvg_render_internal(self, scale, cmm, target, NULL);  }  static FivIoImage * @@ -2282,8 +2288,8 @@ open_librsvg(  	// librsvg rasterizes filters, so rendering to a recording Cairo surface  	// has been abandoned. -	FivIoImage *image = -		load_librsvg_render_internal(closure, 1., ctx->screen_profile, error); +	FivIoImage *image = load_librsvg_render_internal( +		closure, 1., ctx->cmm, ctx->screen_profile, error);  	if (!image) {  		load_librsvg_destroy(&closure->parent);  		return NULL; @@ -2599,7 +2605,7 @@ open_libheif(  	g_free(ids);  fail_read:  	heif_context_free(ctx); -	return fiv_io_profile_finalize(result, ioctx->screen_profile); +	return fiv_io_cmm_finish(ioctx->cmm, result, ioctx->screen_profile);  }  #endif  // HAVE_LIBHEIF -------------------------------------------------------- @@ -2830,7 +2836,7 @@ fail:  	TIFFSetWarningHandlerExt(whe);  	TIFFSetErrorHandler(eh);  	TIFFSetWarningHandler(wh); -	return fiv_io_profile_finalize(result, ctx->screen_profile); +	return fiv_io_cmm_finish(ctx->cmm, result, ctx->screen_profile);  }  #endif  // HAVE_LIBTIFF -------------------------------------------------------- @@ -2925,9 +2931,10 @@ open_gdkpixbuf(  	g_object_unref(pixbuf);  	if (custom_argb32) -		fiv_io_profile_argb32_premultiply_page(image, ctx->screen_profile); +		fiv_io_cmm_argb32_premultiply_page( +			ctx->cmm, image, ctx->screen_profile);  	else -		image = fiv_io_profile_finalize(image, ctx->screen_profile); +		image = fiv_io_cmm_finish(ctx->cmm, image, ctx->screen_profile);  	return image;  } @@ -25,41 +25,50 @@  typedef enum _FivIoOrientation FivIoOrientation;  typedef struct _FivIoRenderClosure FivIoRenderClosure;  typedef struct _FivIoImage FivIoImage; +typedef struct _FivIoProfile FivIoProfile;  // --- Colour management ------------------------------------------------------- +// Note that without a CMM, all FivIoCmm and FivIoProfile will be returned NULL. -void fiv_io_profile_init(void); - -// TODO(p): Make it also possible to use Skia's skcms. -typedef void *FivIoProfile; -FivIoProfile *fiv_io_profile_new(const void *data, size_t len); -FivIoProfile *fiv_io_profile_new_from_bytes(GBytes *bytes); -FivIoProfile *fiv_io_profile_new_sRGB(void); -FivIoProfile *fiv_io_profile_new_sRGB_gamma(double gamma); -FivIoProfile *fiv_io_profile_new_parametric( -    double gamma, double whitepoint[2], double primaries[6]);  GBytes *fiv_io_profile_to_bytes(FivIoProfile *profile);  void fiv_io_profile_free(FivIoProfile *self);  // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +#define FIV_TYPE_IO_CMM (fiv_io_cmm_get_type()) +G_DECLARE_FINAL_TYPE(FivIoCmm, fiv_io_cmm, FIV, IO_CMM, GObject) + +FivIoCmm *fiv_io_cmm_get_default(void); + +FivIoProfile *fiv_io_cmm_get_profile( +	FivIoCmm *self, const void *data, size_t len); +FivIoProfile *fiv_io_cmm_get_profile_from_bytes(FivIoCmm *self, GBytes *bytes); +FivIoProfile *fiv_io_cmm_get_profile_sRGB(FivIoCmm *self); +FivIoProfile *fiv_io_cmm_get_profile_sRGB_gamma(FivIoCmm *self, double gamma); +FivIoProfile *fiv_io_cmm_get_profile_parametric( +	FivIoCmm *self, double gamma, double whitepoint[2], double primaries[6]); + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +  void fiv_io_premultiply_argb32(FivIoImage *image); -void fiv_io_profile_cmyk( +void fiv_io_cmm_cmyk(FivIoCmm *self,      FivIoImage *image, FivIoProfile *source, FivIoProfile *target); -void fiv_io_profile_4x16le_direct(unsigned char *data, +void fiv_io_cmm_4x16le_direct(FivIoCmm *self, unsigned char *data,      int w, int h, FivIoProfile *source, FivIoProfile *target); -void fiv_io_profile_argb32_premultiply( -    FivIoImage *image, FivIoProfile *source, FivIoProfile *target); -void fiv_io_profile_page(FivIoImage *page, FivIoProfile *target, -	void (*frame_cb) (FivIoImage *, FivIoProfile *, FivIoProfile *)); -#define fiv_io_profile_argb32_premultiply_page(page, target) \ -	fiv_io_profile_page((page), (target), fiv_io_profile_argb32_premultiply) +void fiv_io_cmm_argb32_premultiply(FivIoCmm *self, +	FivIoImage *image, FivIoProfile *source, FivIoProfile *target); +#define fiv_io_cmm_argb32_premultiply_page(cmm, page, target) \ +	fiv_io_cmm_page((cmm), (page), (target), fiv_io_cmm_argb32_premultiply) -void fiv_io_profile_any( +void fiv_io_cmm_page(FivIoCmm *self, FivIoImage *page, FivIoProfile *target, +	void (*frame_cb) (FivIoCmm *, +		FivIoImage *, FivIoProfile *, FivIoProfile *)); +void fiv_io_cmm_any(FivIoCmm *self,  	FivIoImage *image, FivIoProfile *source, FivIoProfile *target); -FivIoImage *fiv_io_profile_finalize(FivIoImage *image, FivIoProfile *target); +FivIoImage *fiv_io_cmm_finish(FivIoCmm *self, +	FivIoImage *image, FivIoProfile *target);  // --- Loading ----------------------------------------------------------------- @@ -84,7 +93,8 @@ enum _FivIoOrientation {  // then loaders could store it in their closures.  struct _FivIoRenderClosure {  	/// The rendering is allowed to fail, returning NULL. -	FivIoImage *(*render)(FivIoRenderClosure *, FivIoProfile *, double scale); +	FivIoImage *(*render)( +		FivIoRenderClosure *, FivIoCmm *, FivIoProfile *, double scale);  	void (*destroy)(FivIoRenderClosure *);  }; @@ -155,7 +165,8 @@ cairo_surface_t *fiv_io_image_to_surface_noref(const FivIoImage *image);  typedef struct {  	const char *uri;                    ///< Source URI -	FivIoProfile screen_profile;        ///< Target colour space or NULL +	FivIoCmm *cmm;                      ///< Colour management module or NULL +	FivIoProfile *screen_profile;       ///< Target colour space or NULL  	int screen_dpi;                     ///< Target DPI  	gboolean enhance;                   ///< Enhance JPEG (currently)  	gboolean first_frame_only;          ///< Only interested in the 1st frame diff --git a/fiv-thumbnail.c b/fiv-thumbnail.c index 6ff1246..80f378e 100644 --- a/fiv-thumbnail.c +++ b/fiv-thumbnail.c @@ -137,10 +137,12 @@ might_be_a_thumbnail(const char *path_or_uri)  static FivIoImage *  render(GFile *target, GBytes *data, gboolean *color_managed, GError **error)  { +	FivIoCmm *cmm = fiv_io_cmm_get_default();  	FivIoOpenContext ctx = {  		.uri = g_file_get_uri(target),  		// Remember to synchronize changes with adjust_thumbnail(). -		.screen_profile = fiv_io_profile_new_sRGB(), +		.cmm = cmm, +		.screen_profile = fiv_io_cmm_get_profile_sRGB(cmm),  		.screen_dpi = 96,  		.first_frame_only = TRUE,  		// Only using this array as a redirect. @@ -182,9 +184,11 @@ adjust_thumbnail(FivIoImage *thumbnail, double row_height)  	FivIoRenderClosure *closure = thumbnail->render;  	if (closure && orientation <= FivIoOrientation0) {  		// Remember to synchronize changes with render(). -		FivIoProfile *screen_profile = fiv_io_profile_new_sRGB(); +		FivIoCmm *cmm = fiv_io_cmm_get_default(); +		FivIoProfile *screen_profile = fiv_io_cmm_get_profile_sRGB(cmm);  		// This API doesn't accept non-uniform scaling; prefer a vertical fit. -		FivIoImage *scaled = closure->render(closure, screen_profile, scale_y); +		FivIoImage *scaled = +			closure->render(closure, cmm, screen_profile, scale_y);  		if (screen_profile)  			fiv_io_profile_free(screen_profile);  		if (scaled) @@ -417,6 +417,7 @@ prescale_page(FivView *self)  	// If it fails, the previous frame pointer may become invalid.  	g_clear_pointer(&self->page_scaled, fiv_io_image_unref);  	self->frame = self->page_scaled = closure->render(closure, +		self->enable_cms ? fiv_io_cmm_get_default() : NULL,  		self->enable_cms ? self->screen_cms_profile : NULL, self->scale);  	if (!self->page_scaled)  		self->frame = self->page; @@ -475,7 +476,8 @@ monitor_cms_profile(GdkWindow *root, int num)  	if (gdk_property_get(root, gdk_atom_intern(atom, FALSE), GDK_NONE, 0,  			8 << 20 /* MiB */, FALSE, &type, &format, &length, &data)) {  		if (format == 8 && length > 0) -			result = fiv_io_profile_new(data, length); +			result = fiv_io_cmm_get_profile( +				fiv_io_cmm_get_default(), data, length);  		g_free(data);  	}  	return result; @@ -525,7 +527,8 @@ reload_screen_cms_profile(FivView *self, GdkWindow *window)  out:  	if (!self->screen_cms_profile) -		self->screen_cms_profile = fiv_io_profile_new_sRGB(); +		self->screen_cms_profile = +			fiv_io_cmm_get_profile_sRGB(fiv_io_cmm_get_default());  }  static void @@ -1381,6 +1384,7 @@ open_without_swapping_in(FivView *self, const char *uri)  {  	FivIoOpenContext ctx = {  		.uri = uri, +		.cmm = self->enable_cms ? fiv_io_cmm_get_default() : NULL,  		.screen_profile = self->enable_cms ? self->screen_cms_profile : NULL,  		.screen_dpi = 96,  // TODO(p): Try to retrieve it from the screen.  		.enhance = self->enhance, @@ -2583,8 +2583,6 @@ on_app_handle_local_options(G_GNUC_UNUSED GApplication *app,  		return 0;  	} -	fiv_io_profile_init(); -  	// Normalize all arguments to URIs, and run thumbnailing modes first.  	for (gsize i = 0; o.args && o.args[i]; i++) {  		GFile *resolved = g_file_new_for_commandline_arg(o.args[i]); diff --git a/meson.build b/meson.build index 2d854e7..bf18196 100644 --- a/meson.build +++ b/meson.build @@ -164,7 +164,7 @@ tiff_tables = custom_target('tiff-tables.h',  )  desktops = ['fiv.desktop', 'fiv-browse.desktop'] -iolib = static_library('fiv-io', 'fiv-io.c', 'fiv-io-profile.c', 'xdg.c', +iolib = static_library('fiv-io', 'fiv-io.c', 'fiv-io-cmm.c', 'xdg.c',  	tiff_tables,  	dependencies : dependencies).extract_all_objects(recursive : true)  exe = executable('fiv', 'fiv.c', 'fiv-view.c', 'fiv-context-menu.c', | 
