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 +++++++++++++++++++++++++++++------------------------------ fiv-io.c | 16 +++--- fiv-io.h | 11 ++-- fiv-view.c | 2 +- 4 files changed, 94 insertions(+), 98 deletions(-) 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) diff --git a/fiv-io.c b/fiv-io.c index eefc741..f1fcc01 100644 --- a/fiv-io.c +++ b/fiv-io.c @@ -2077,8 +2077,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) { @@ -2108,15 +2108,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 * @@ -2174,8 +2174,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; diff --git a/fiv-io.h b/fiv-io.h index d86c80a..c8d22c4 100644 --- a/fiv-io.h +++ b/fiv-io.h @@ -28,6 +28,7 @@ typedef struct _FivIoImage FivIoImage; typedef struct _FivIoProfile FivIoProfile; // --- Colour management ------------------------------------------------------- +// Note that without a CMM, all FivIoCmm and FivIoProfile will be returned NULL. GBytes *fiv_io_profile_to_bytes(FivIoProfile *profile); void fiv_io_profile_free(FivIoProfile *self); @@ -37,7 +38,7 @@ 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(); +FivIoCmm *fiv_io_cmm_get_default(void); FivIoProfile *fiv_io_cmm_get_profile( FivIoCmm *self, const void *data, size_t len); @@ -55,15 +56,15 @@ void fiv_io_cmm_cmyk(FivIoCmm *self, FivIoImage *image, FivIoProfile *source, FivIoProfile *target); void fiv_io_cmm_4x16le_direct(FivIoCmm *self, unsigned char *data, int w, int h, FivIoProfile *source, FivIoProfile *target); + void fiv_io_cmm_argb32_premultiply(FivIoCmm *self, - FivIoImage *image, FivIoProfile *source, FivIoProfile *target); + 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_cmm_page(FivIoCmm *self, FivIoImage *page, FivIoProfile *target, void (*frame_cb) (FivIoCmm *, FivIoImage *, FivIoProfile *, FivIoProfile *)); -#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_cmm_any(FivIoCmm *self, FivIoImage *image, FivIoProfile *source, FivIoProfile *target); FivIoImage *fiv_io_cmm_finish(FivIoCmm *self, diff --git a/fiv-view.c b/fiv-view.c index 9f6492a..f248ddc 100644 --- a/fiv-view.c +++ b/fiv-view.c @@ -417,7 +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, - fiv_io_cmm_get_default(), + 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; -- cgit v1.2.3-70-g09d2