From 250f2f0f758a5f3ea0f3174843dbf55fc73b1370 Mon Sep 17 00:00:00 2001
From: Přemysl Eric Janouch
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-70-g09d2