From 501dc123ada348f18cac7ed05c812b5e633bd2ef Mon Sep 17 00:00:00 2001 From: Přemysl Eric Janouch Date: Sun, 28 Jan 2024 01:13:15 +0100 Subject: WIP: Thread-safe colour management --- fiv-io-cmm.c | 163 +++++++++++++++++++++++++++++------------------------------ 1 file changed, 79 insertions(+), 84 deletions(-) (limited to 'fiv-io-cmm.c') diff --git a/fiv-io-cmm.c b/fiv-io-cmm.c index f2d38f9..b131acf 100644 --- a/fiv-io-cmm.c +++ b/fiv-io-cmm.c @@ -31,6 +31,55 @@ #include #endif // HAVE_LCMS2_FAST_FLOAT +// --- 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 @@ -107,8 +156,6 @@ fiv_io_cmm_class_init(FivIoCmmClass *klass) static void fiv_io_cmm_init(FivIoCmm *self) { - (void) self; - self->context = cmsCreateContext(NULL, self); #ifdef HAVE_LCMS2_FAST_FLOAT if (cmsPluginTHR(self->context, cmsFastFloatExtensions())) @@ -119,7 +166,7 @@ fiv_io_cmm_init(FivIoCmm *self) // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - FivIoCmm * -fiv_io_cmm_get_default() +fiv_io_cmm_get_default(void) { static gsize initialization_value = 0; static FivIoCmm *default_ = NULL; @@ -134,8 +181,7 @@ fiv_io_cmm_get_default() FivIoProfile * fiv_io_cmm_get_profile(FivIoCmm *self, const void *data, size_t len) { - if (!self) - self = fiv_io_cmm_get_default(); + g_return_val_if_fail(self != NULL, NULL); return fiv_io_profile_new(self, cmsOpenProfileFromMemTHR(self->context, data, len)); @@ -144,8 +190,7 @@ fiv_io_cmm_get_profile(FivIoCmm *self, const void *data, size_t len) FivIoProfile * fiv_io_cmm_get_profile_sRGB(FivIoCmm *self) { - if (!self) - self = fiv_io_cmm_get_default(); + g_return_val_if_fail(self != NULL, NULL); return fiv_io_profile_new(self, cmsCreate_sRGBProfileTHR(self->context)); @@ -155,8 +200,7 @@ FivIoProfile * fiv_io_cmm_get_profile_parametric(FivIoCmm *self, double gamma, double whitepoint[2], double primaries[6]) { - if (!self) - self = fiv_io_cmm_get_default(); + g_return_val_if_fail(self != NULL, NULL); const cmsCIExyY cmsWP = {whitepoint[0], whitepoint[1], 1.0}; const cmsCIExyYTRIPLE cmsP = { @@ -202,6 +246,7 @@ fiv_io_cmm_get_profile_parametric(FivIoCmm *, double, double[2], double[6]) } #endif // ! HAVE_LCMS2 +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - FivIoProfile * fiv_io_cmm_get_profile_sRGB_gamma(FivIoCmm *self, double gamma) @@ -220,33 +265,6 @@ fiv_io_cmm_get_profile_from_bytes(FivIoCmm *self, GBytes *bytes) } // --- Image loading ----------------------------------------------------------- - -// 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; - } -} - #ifdef HAVE_LCMS2 // TODO(p): In general, try to use CAIRO_FORMAT_RGB30 or CAIRO_FORMAT_RGBA128F. @@ -259,8 +277,7 @@ void fiv_io_cmm_cmyk(FivIoCmm *self, FivIoImage *image, FivIoProfile *source, FivIoProfile *target) { - if (!self) - self = fiv_io_cmm_get_default(); + g_return_if_fail(target == NULL || self != NULL); cmsHTRANSFORM transform = NULL; if (source && target) { @@ -283,8 +300,7 @@ 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) { - if (!self) - self = fiv_io_cmm_get_default(); + g_return_val_if_fail(target == NULL || self != NULL, false); // TODO(p): We should make this optional. FivIoProfile *src_fallback = NULL; @@ -344,44 +360,6 @@ fiv_io_cmm_4x16le_direct( #endif // ! HAVE_LCMS2 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -void -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); -} - -// 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); - } - } -} - #if defined HAVE_LCMS2 && LCMS_VERSION >= 2130 #define FIV_IO_PROFILE_ARGB32_PREMUL \ @@ -404,12 +382,11 @@ void fiv_io_cmm_argb32_premultiply(FivIoCmm *self, FivIoImage *image, FivIoProfile *source, FivIoProfile *target) { - if (!self) - self = fiv_io_cmm_get_default(); + g_return_if_fail(target == NULL || self != NULL); if (image->format != CAIRO_FORMAT_ARGB32) { fiv_io_cmm_xrgb32(self, image, source, target); - } else if (self->broken_premul) { + } else if (!target || self->broken_premul) { fiv_io_cmm_xrgb32(self, image, source, target); fiv_io_premultiply_argb32(image); } else if (!fiv_io_cmm_rgb_direct(self, image->data, @@ -422,8 +399,11 @@ fiv_io_cmm_argb32_premultiply(FivIoCmm *self, #else // ! HAVE_LCMS2 || LCMS_VERSION < 2130 -// TODO(p): Unpremultiply, transform, repremultiply. Or require lcms2>=2.13. -#define fiv_io_cmm_argb32(self, image, source, target) +static void +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, @@ -434,9 +414,24 @@ fiv_io_cmm_argb32_premultiply(FivIoCmm *self, } #endif // ! HAVE_LCMS2 || LCMS_VERSION < 2130 - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +void +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) -- cgit v1.2.3-70-g09d2