From 250f2f0f758a5f3ea0f3174843dbf55fc73b1370 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C5=99emysl=20Eric=20Janouch?= Date: Sat, 27 Jan 2024 23:09:32 +0100 Subject: WIP: Thread-safe colour management --- fiv-io-profile.c | 109 +++++++++++++++++++++++++++++++++++++++----------- fiv-io.c | 10 ++--- fiv-io.h | 8 ++-- fiv-view.c | 1 + fiv.c | 2 - tools/benchmark-raw.c | 2 - 6 files changed, 94 insertions(+), 38 deletions(-) diff --git a/fiv-io-profile.c b/fiv-io-profile.c index 44eb0bf..387b201 100644 --- a/fiv-io-profile.c +++ b/fiv-io-profile.c @@ -30,31 +30,75 @@ #include #endif // HAVE_LCMS2_FAST_FLOAT -// https://github.com/mm2/Little-CMS/issues/430 -static bool g_broken_cms_premul; +// --- Contexts ---------------------------------------------------------------- -void -fiv_io_cmm_init(void) +struct _FivIoCmm { + GObject parent_instance; +#ifdef HAVE_LCMS2 + cmsContext context; + + // https://github.com/mm2/Little-CMS/issues/430 + gboolean broken_premul; +#endif +}; + +G_DEFINE_TYPE(FivIoCmm, fiv_io_cmm, G_TYPE_OBJECT) + +static void +fiv_io_cmm_finalize(GObject *gobject) +{ + FivIoCmm *self = FIV_IO_CMM(gobject); +#ifdef HAVE_LCMS2 + cmsDeleteContext(self->context); +#endif + + 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. + (void) self; + +#ifdef HAVE_LCMS2 + self->context = cmsCreateContext(NULL, self); +#endif #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() { - // TODO: Maintain a singleton for the NULL cmsContext. - return NULL; + 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_cmm_get_profile(FivIoCmm *self, const void *data, size_t len) { + if (!self) + self = fiv_io_cmm_get_default(); + #ifdef HAVE_LCMS2 - return cmsOpenProfileFromMemTHR(NULL, data, len); + return cmsOpenProfileFromMemTHR(self->context, data, len); #else (void) data; (void) len; @@ -65,8 +109,11 @@ 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(); + #ifdef HAVE_LCMS2 - return cmsCreate_sRGBProfileTHR(NULL); + return cmsCreate_sRGBProfileTHR(self->context); #else return NULL; #endif @@ -76,10 +123,10 @@ FivIoProfile * 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; + if (!self) + self = fiv_io_cmm_get_default(); +#ifdef HAVE_LCMS2 const cmsCIExyY cmsWP = {whitepoint[0], whitepoint[1], 1.0}; const cmsCIExyYTRIPLE cmsP = { {primaries[0], primaries[1], 1.0}, @@ -87,12 +134,12 @@ fiv_io_cmm_get_profile_parametric(FivIoCmm *self, {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 @@ -119,6 +166,8 @@ fiv_io_cmm_get_profile_from_bytes(FivIoCmm *self, GBytes *bytes) return fiv_io_cmm_get_profile(self, p, len); } +// --- Profiles ---------------------------------------------------------------- + GBytes * fiv_io_profile_to_bytes(FivIoProfile *profile) { @@ -190,13 +239,18 @@ void fiv_io_cmm_cmyk(FivIoCmm *self, FivIoImage *image, FivIoProfile *source, FivIoProfile *target) { + if (!self) + self = fiv_io_cmm_get_default(); + #ifndef HAVE_LCMS2 + (void) self; (void) source; (void) target; #else cmsHTRANSFORM transform = NULL; if (source && target) { - transform = cmsCreateTransformTHR(NULL, source, TYPE_CMYK_8_REV, target, + transform = cmsCreateTransformTHR(self->context, + source, TYPE_CMYK_8_REV, target, FIV_IO_PROFILE_ARGB32, INTENT_PERCEPTUAL, 0); } if (transform) { @@ -215,7 +269,11 @@ 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(); + #ifndef HAVE_LCMS2 + (void) self; (void) data; (void) w; (void) h; @@ -228,11 +286,11 @@ fiv_io_cmm_rgb_direct(FivIoCmm *self, unsigned char *data, int w, int h, // TODO(p): We should make this optional. cmsHPROFILE src_fallback = NULL; if (target && !source) - source = src_fallback = cmsCreate_sRGBProfileTHR(NULL); + source = src_fallback = cmsCreate_sRGBProfileTHR(self->context); cmsHTRANSFORM transform = NULL; if (source && target) { - transform = cmsCreateTransformTHR(NULL, + transform = cmsCreateTransformTHR(self->context, source, source_format, target, target_format, INTENT_PERCEPTUAL, 0); } if (transform) { @@ -311,7 +369,7 @@ fiv_io_cmm_argb32(FivIoCmm *self, FivIoImage *image, { 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_cmm_rgb_direct(self, image->data, image->width, image->height, source, target, @@ -322,9 +380,12 @@ void fiv_io_cmm_argb32_premultiply(FivIoCmm *self, FivIoImage *image, FivIoProfile *source, FivIoProfile *target) { + if (!self) + self = fiv_io_cmm_get_default(); + if (image->format != CAIRO_FORMAT_ARGB32) { fiv_io_cmm_xrgb32(self, image, source, target); - } else if (g_broken_cms_premul) { + } else if (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, @@ -338,7 +399,7 @@ 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_profile_argb32(surface, source, target) +#define fiv_io_cmm_argb32(self, image, source, target) void fiv_io_cmm_argb32_premultiply(FivIoCmm *self, @@ -371,7 +432,7 @@ fiv_io_cmm_any(FivIoCmm *self, // TODO(p): Offer better integration, upgrade the bit depth if appropriate. FivIoImage * -fiv_io_cmm_finalize(FivIoCmm *self, FivIoImage *image, FivIoProfile *target) +fiv_io_cmm_finish(FivIoCmm *self, FivIoImage *image, FivIoProfile *target) { if (!target) return image; diff --git a/fiv-io.c b/fiv-io.c index be15a18..eefc741 100644 --- a/fiv-io.c +++ b/fiv-io.c @@ -2055,7 +2055,7 @@ open_libraw( out: libraw_close(iprc); - return fiv_io_cmm_finalize(ctx->cmm, result, ctx->screen_profile); + return fiv_io_cmm_finish(ctx->cmm, result, ctx->screen_profile); } #endif // HAVE_LIBRAW --------------------------------------------------------- @@ -2232,7 +2232,7 @@ load_librsvg_render_internal(FivIoRenderClosureLibrsvg *self, double scale, fiv_io_image_unref(image); return NULL; } - return fiv_io_cmm_finalize(cmm, image, target); + return fiv_io_cmm_finish(cmm, image, target); } static FivIoImage * @@ -2606,7 +2606,7 @@ open_libheif( g_free(ids); fail_read: heif_context_free(ctx); - return fiv_io_cmm_finalize(ioctx->cmm, result, ioctx->screen_profile); + return fiv_io_cmm_finish(ioctx->cmm, result, ioctx->screen_profile); } #endif // HAVE_LIBHEIF -------------------------------------------------------- @@ -2837,7 +2837,7 @@ fail: TIFFSetWarningHandlerExt(whe); TIFFSetErrorHandler(eh); TIFFSetWarningHandler(wh); - return fiv_io_cmm_finalize(ctx->cmm, result, ctx->screen_profile); + return fiv_io_cmm_finish(ctx->cmm, result, ctx->screen_profile); } #endif // HAVE_LIBTIFF -------------------------------------------------------- @@ -2935,7 +2935,7 @@ open_gdkpixbuf( fiv_io_cmm_argb32_premultiply_page( ctx->cmm, image, ctx->screen_profile); else - image = fiv_io_cmm_finalize(ctx->cmm, image, ctx->screen_profile); + image = fiv_io_cmm_finish(ctx->cmm, image, ctx->screen_profile); return image; } diff --git a/fiv-io.h b/fiv-io.h index bc88509..94a15e1 100644 --- a/fiv-io.h +++ b/fiv-io.h @@ -28,8 +28,6 @@ typedef struct _FivIoImage FivIoImage; // --- Colour management ------------------------------------------------------- -void fiv_io_cmm_init(void); - // TODO(p): Make it also possible to use Skia's skcms. // TODO(p): Profiles might want to keep references to their CMM contexts. typedef void *FivIoProfile; @@ -39,7 +37,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, CMM, GObject) +G_DECLARE_FINAL_TYPE(FivIoCmm, fiv_io_cmm, FIV, IO_CMM, GObject) FivIoCmm *fiv_io_cmm_get_default(); @@ -70,8 +68,8 @@ void fiv_io_cmm_page(FivIoCmm *self, FivIoImage *page, FivIoProfile *target, void fiv_io_cmm_any(FivIoCmm *self, FivIoImage *image, FivIoProfile *source, FivIoProfile *target); -FivIoImage *fiv_io_cmm_finalize( - FivIoCmm *self, FivIoImage *image, FivIoProfile *target); +FivIoImage *fiv_io_cmm_finish(FivIoCmm *self, + FivIoImage *image, FivIoProfile *target); // --- Loading ----------------------------------------------------------------- diff --git a/fiv-view.c b/fiv-view.c index dfc7a19..9f6492a 100644 --- a/fiv-view.c +++ b/fiv-view.c @@ -1521,6 +1521,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, diff --git a/fiv.c b/fiv.c index 1514d4a..43041b0 100644 --- a/fiv.c +++ b/fiv.c @@ -2583,8 +2583,6 @@ on_app_handle_local_options(G_GNUC_UNUSED GApplication *app, return 0; } - fiv_io_cmm_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/tools/benchmark-raw.c b/tools/benchmark-raw.c index c19760e..a818efa 100644 --- a/tools/benchmark-raw.c +++ b/tools/benchmark-raw.c @@ -410,8 +410,6 @@ main(int argc, char *argv[]) // We don't need to call gdk_cairo_surface_create_from_pixbuf() here, // so don't bother initializing GDK. - fiv_io_cmm_init(); - // A mode to just extract all thumbnails to files for closer inspection. extract_mode = !!getenv("BENCHMARK_RAW_EXTRACT"); -- cgit v1.2.3