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