2 * Copyright (C) 2006 John Ellis
3 * Copyright (C) 2008 - 2016 The Geeqie Team
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
17 * You should have received a copy of the GNU General Public License along
18 * with this program; if not, write to the Free Software Foundation, Inc.,
19 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
23 #include "color-man.h"
27 #include "ui-fileops.h"
32 /*** color support enabled ***/
41 struct ColorManCache {
42 cmsHPROFILE profile_in;
43 cmsHPROFILE profile_out;
44 cmsHTRANSFORM transform;
46 ColorManProfileType profile_in_type;
47 gchar *profile_in_file;
49 ColorManProfileType profile_out_type;
50 gchar *profile_out_file;
57 /* pixels to transform per idle call */
58 #define COLOR_MAN_CHUNK_SIZE 81900
61 static void color_man_lib_init()
63 static gboolean init_done = FALSE;
65 if (init_done) return;
69 cmsErrorAction(LCMS_ERROR_IGNORE);
73 static cmsHPROFILE color_man_create_adobe_comp()
75 /* ClayRGB1998 is AdobeRGB compatible */
76 #include "ClayRGB1998_icc.h"
77 return cmsOpenProfileFromMem(ClayRGB1998_icc, ClayRGB1998_icc_len);
81 *-------------------------------------------------------------------
82 * color transform cache
83 *-------------------------------------------------------------------
86 static GList *cm_cache_list = nullptr;
89 static void color_man_cache_ref(ColorManCache *cc)
96 static void color_man_cache_unref(ColorManCache *cc)
101 if (cc->refcount < 1)
103 if (cc->transform) cmsDeleteTransform(cc->transform);
104 if (cc->profile_in) cmsCloseProfile(cc->profile_in);
105 if (cc->profile_out) cmsCloseProfile(cc->profile_out);
107 g_free(cc->profile_in_file);
108 g_free(cc->profile_out_file);
114 static cmsHPROFILE color_man_cache_load_profile(ColorManProfileType type, const gchar *file,
115 guchar *data, guint data_len)
117 cmsHPROFILE profile = nullptr;
121 case COLOR_PROFILE_FILE:
126 pathl = path_from_utf8(file);
127 profile = cmsOpenProfileFromFile(pathl, "r");
131 case COLOR_PROFILE_SRGB:
132 profile = cmsCreate_sRGBProfile();
134 case COLOR_PROFILE_ADOBERGB:
135 profile = color_man_create_adobe_comp();
137 case COLOR_PROFILE_MEM:
140 profile = cmsOpenProfileFromMem(data, data_len);
143 case COLOR_PROFILE_NONE:
151 static ColorManCache *color_man_cache_new(ColorManProfileType in_type, const gchar *in_file,
152 guchar *in_data, guint in_data_len,
153 ColorManProfileType out_type, const gchar *out_file,
154 guchar *out_data, guint out_data_len,
159 color_man_lib_init();
161 cc = g_new0(ColorManCache, 1);
164 cc->profile_in_type = in_type;
165 cc->profile_in_file = g_strdup(in_file);
167 cc->profile_out_type = out_type;
168 cc->profile_out_file = g_strdup(out_file);
170 cc->has_alpha = has_alpha;
172 cc->profile_in = color_man_cache_load_profile(cc->profile_in_type, cc->profile_in_file,
173 in_data, in_data_len);
174 cc->profile_out = color_man_cache_load_profile(cc->profile_out_type, cc->profile_out_file,
175 out_data, out_data_len);
177 if (!cc->profile_in || !cc->profile_out)
179 DEBUG_1("failed to load color profile for %s: %d %s",
180 (!cc->profile_in) ? "input" : "screen",
181 (!cc->profile_in) ? cc->profile_in_type : cc->profile_out_type,
182 (!cc->profile_in) ? cc->profile_in_file : cc->profile_out_file);
184 color_man_cache_unref(cc);
188 cc->transform = cmsCreateTransform(cc->profile_in,
189 (has_alpha) ? TYPE_RGBA_8 : TYPE_RGB_8,
191 (has_alpha) ? TYPE_RGBA_8 : TYPE_RGB_8,
192 options->color_profile.render_intent, 0);
196 DEBUG_1("failed to create color profile transform");
198 color_man_cache_unref(cc);
202 if (cc->profile_in_type != COLOR_PROFILE_MEM && cc->profile_out_type != COLOR_PROFILE_MEM )
204 cm_cache_list = g_list_append(cm_cache_list, cc);
205 color_man_cache_ref(cc);
211 static void color_man_cache_free(ColorManCache *cc)
215 cm_cache_list = g_list_remove(cm_cache_list, cc);
216 color_man_cache_unref(cc);
219 static void color_man_cache_reset()
221 while (cm_cache_list)
225 cc = static_cast<ColorManCache *>(cm_cache_list->data);
226 color_man_cache_free(cc);
230 static ColorManCache *color_man_cache_find(ColorManProfileType in_type, const gchar *in_file,
231 ColorManProfileType out_type, const gchar *out_file,
236 work = cm_cache_list;
240 gboolean match = FALSE;
242 cc = static_cast<ColorManCache *>(work->data);
245 if (cc->profile_in_type == in_type &&
246 cc->profile_out_type == out_type &&
247 cc->has_alpha == has_alpha)
252 if (match && cc->profile_in_type == COLOR_PROFILE_FILE)
254 match = (cc->profile_in_file && in_file &&
255 strcmp(cc->profile_in_file, in_file) == 0);
257 if (match && cc->profile_out_type == COLOR_PROFILE_FILE)
259 match = (cc->profile_out_file && out_file &&
260 strcmp(cc->profile_out_file, out_file) == 0);
263 if (match) return cc;
269 static ColorManCache *color_man_cache_get(ColorManProfileType in_type, const gchar *in_file,
270 guchar *in_data, guint in_data_len,
271 ColorManProfileType out_type, const gchar *out_file,
272 guchar *out_data, guint out_data_len,
277 cc = color_man_cache_find(in_type, in_file, out_type, out_file, has_alpha);
280 color_man_cache_ref(cc);
284 return color_man_cache_new(in_type, in_file, in_data, in_data_len,
285 out_type, out_file, out_data, out_data_len, has_alpha);
290 *-------------------------------------------------------------------
292 *-------------------------------------------------------------------
295 #pragma GCC diagnostic push
296 #pragma GCC diagnostic ignored "-Wunused-function"
297 static void color_man_done_unused(ColorMan *cm, ColorManReturnType type)
301 cm->func_done(cm, type, cm->func_done_data);
304 #pragma GCC diagnostic pop
306 void color_man_correct_region(ColorMan *cm, GdkPixbuf *pixbuf, gint x, gint y, gint w, gint h)
312 gint pixbuf_width, pixbuf_height;
315 pixbuf_width = gdk_pixbuf_get_width(pixbuf);
316 pixbuf_height = gdk_pixbuf_get_height(pixbuf);
318 cc = static_cast<ColorManCache *>(cm->profile);
320 pix = gdk_pixbuf_get_pixels(pixbuf);
321 rs = gdk_pixbuf_get_rowstride(pixbuf);
323 /** @FIXME: x,y expected to be = 0. Maybe this is not the right place for scaling */
324 w = w * scale_factor();
325 h = h * scale_factor();
327 w = MIN(w, pixbuf_width - x);
328 h = MIN(h, pixbuf_height - y);
330 pix += x * ((cc->has_alpha) ? 4 : 3);
331 for (i = 0; i < h; i++)
335 pbuf = pix + ((y + i) * rs);
337 cmsDoTransform(cc->transform, pbuf, pbuf, w);
342 #pragma GCC diagnostic push
343 #pragma GCC diagnostic ignored "-Wunused-function"
344 static gboolean color_man_idle_cb_unused(gpointer data)
346 ColorMan *cm = static_cast<ColorMan *>(data);
350 if (!cm->pixbuf) return FALSE;
353 cm->pixbuf != image_get_pixbuf(cm->imd))
356 color_man_done_unused(cm, COLOR_RETURN_IMAGE_CHANGED);
360 width = gdk_pixbuf_get_width(cm->pixbuf);
361 height = gdk_pixbuf_get_height(cm->pixbuf);
363 if (cm->row > height)
365 if (!cm->incremental_sync && cm->imd)
367 image_area_changed(cm->imd, 0, 0, width, height);
371 color_man_done_unused(cm, COLOR_RETURN_SUCCESS);
375 rh = COLOR_MAN_CHUNK_SIZE / width + 1;
376 color_man_correct_region(cm, cm->pixbuf, 0, cm->row, width, rh);
377 if (cm->incremental_sync && cm->imd) image_area_changed(cm->imd, 0, cm->row, width, rh);
382 #pragma GCC diagnostic pop
384 static ColorMan *color_man_new_real(ImageWindow *imd, GdkPixbuf *pixbuf,
385 ColorManProfileType input_type, const gchar *input_file,
386 guchar *input_data, guint input_data_len,
387 ColorManProfileType screen_type, const gchar *screen_file,
388 guchar *screen_data, guint screen_data_len)
393 if (imd) pixbuf = image_get_pixbuf(imd);
395 cm = g_new0(ColorMan, 1);
398 if (cm->pixbuf) g_object_ref(cm->pixbuf);
400 has_alpha = pixbuf ? gdk_pixbuf_get_has_alpha(pixbuf) : FALSE;
402 cm->profile = color_man_cache_get(input_type, input_file, input_data, input_data_len,
403 screen_type, screen_file, screen_data, screen_data_len, has_alpha);
413 ColorMan *color_man_new(ImageWindow *imd, GdkPixbuf *pixbuf,
414 ColorManProfileType input_type, const gchar *input_file,
415 ColorManProfileType screen_type, const gchar *screen_file,
416 guchar *screen_data, guint screen_data_len)
418 return color_man_new_real(imd, pixbuf,
419 input_type, input_file, nullptr, 0,
420 screen_type, screen_file, screen_data, screen_data_len);
423 #pragma GCC diagnostic push
424 #pragma GCC diagnostic ignored "-Wunused-function"
425 void color_man_start_bg_unused(ColorMan *cm, ColorMan::DoneFunc done_func, gpointer done_data)
427 cm->func_done = done_func;
428 cm->func_done_data = done_data;
429 cm->idle_id = g_idle_add(color_man_idle_cb_unused, cm);
431 #pragma GCC diagnostic pop
433 ColorMan *color_man_new_embedded(ImageWindow *imd, GdkPixbuf *pixbuf,
434 guchar *input_data, guint input_data_len,
435 ColorManProfileType screen_type, const gchar *screen_file,
436 guchar *screen_data, guint screen_data_len)
438 return color_man_new_real(imd, pixbuf,
439 COLOR_PROFILE_MEM, nullptr, input_data, input_data_len,
440 screen_type, screen_file, screen_data, screen_data_len);
443 static gchar *color_man_get_profile_name(ColorManProfileType type, cmsHPROFILE profile)
447 case COLOR_PROFILE_SRGB:
448 return g_strdup(_("sRGB"));
449 case COLOR_PROFILE_ADOBERGB:
450 return g_strdup(_("Adobe RGB compatible"));
452 case COLOR_PROFILE_MEM:
453 case COLOR_PROFILE_FILE:
459 cmsGetProfileInfoASCII(profile, cmsInfoDescription, "en", "US", buffer, 20);
460 buffer[19] = '\0'; /* Just to be sure */
461 return g_strdup(buffer);
463 return g_strdup(cmsTakeProductName(profile));
466 return g_strdup(_("Custom profile"));
468 case COLOR_PROFILE_NONE:
474 gboolean color_man_get_status(ColorMan *cm, gchar **image_profile, gchar **screen_profile)
477 if (!cm) return FALSE;
479 cc = static_cast<ColorManCache *>(cm->profile);
481 if (image_profile) *image_profile = color_man_get_profile_name(cc->profile_in_type, cc->profile_in);
482 if (screen_profile) *screen_profile = color_man_get_profile_name(cc->profile_out_type, cc->profile_out);
486 void color_man_free(ColorMan *cm)
490 if (cm->idle_id) g_source_remove(cm->idle_id);
491 if (cm->pixbuf) g_object_unref(cm->pixbuf);
493 color_man_cache_unref(static_cast<ColorManCache *>(cm->profile));
498 void color_man_update()
500 color_man_cache_reset();
505 #include <libheif/heif.h>
507 static cmsToneCurve* colorspaces_create_transfer(int32_t size, double (*fct)(double))
509 std::vector<float> values;
510 values.reserve(size);
511 for(int32_t i = 0; i < size; ++i)
513 const double x = static_cast<float>(i) / (size - 1);
514 const double y = MIN(fct(x), 1.0f);
515 values.push_back(static_cast<float>(y));
518 return cmsBuildTabulatedToneCurveFloat(nullptr, size, values.data());
521 // https://www.itu.int/dms_pubrec/itu-r/rec/bt/R-REC-BT.2100-2-201807-I!!PDF-F.pdf
523 static double HLG_fct(double x)
525 static const double Beta = 0.04;
526 static const double RA = 5.591816309728916; // 1.0 / A where A = 0.17883277
527 static const double B = 0.28466892; // 1.0 - 4.0 * A
528 static const double C = 0.5599107295; // 0,5 –aln(4a)
530 double e = MAX(x * (1.0 - Beta) + Beta, 0.0);
532 if(e == 0.0) return 0.0;
534 const double sign = e;
545 res = (exp((e - C) * RA) + B) / 12.0;
548 return copysign(res, sign);
551 static double PQ_fct(double x)
553 static const double M1 = 2610.0 / 16384.0;
554 static const double M2 = (2523.0 / 4096.0) * 128.0;
555 static const double C1 = 3424.0 / 4096.0;
556 static const double C2 = (2413.0 / 4096.0) * 32.0;
557 static const double C3 = (2392.0 / 4096.0) * 32.0;
559 if(x == 0.0) return 0.0;
560 const double sign = x;
563 const double xpo = pow(x, 1.0 / M2);
564 const double num = MAX(xpo - C1, 0.0);
565 const double den = C2 - C3 * xpo;
566 const double res = pow(num / den, 1.0 / M1);
568 return copysign(res, sign);
577 * Copied from: gimp/libgimpcolor/gimpcolorprofile.c
579 static guchar *nclx_to_lcms_profile(const struct heif_color_profile_nclx *nclx, guint *profile_len)
581 const gchar *primaries_name = "";
582 const gchar *trc_name = "";
583 cmsHPROFILE *profile = nullptr;
584 cmsCIExyY whitepoint;
585 cmsCIExyYTRIPLE primaries;
586 cmsToneCurve *curve[3];
587 cmsUInt32Number size;
588 guint8 *data = nullptr;
590 cmsFloat64Number srgb_parameters[5] =
591 { 2.4, 1.0 / 1.055, 0.055 / 1.055, 1.0 / 12.92, 0.04045 };
593 cmsFloat64Number rec709_parameters[5] =
594 { 2.2, 1.0 / 1.099, 0.099 / 1.099, 1.0 / 4.5, 0.081 };
601 if (nclx->color_primaries == heif_color_primaries_unspecified)
606 whitepoint.x = nclx->color_primary_white_x;
607 whitepoint.y = nclx->color_primary_white_y;
610 primaries.Red.x = nclx->color_primary_red_x;
611 primaries.Red.y = nclx->color_primary_red_y;
612 primaries.Red.Y = 1.0f;
614 primaries.Green.x = nclx->color_primary_green_x;
615 primaries.Green.y = nclx->color_primary_green_y;
616 primaries.Green.Y = 1.0f;
618 primaries.Blue.x = nclx->color_primary_blue_x;
619 primaries.Blue.y = nclx->color_primary_blue_y;
620 primaries.Blue.Y = 1.0f;
622 switch (nclx->color_primaries)
624 case heif_color_primaries_ITU_R_BT_709_5:
625 primaries_name = "BT.709";
627 case heif_color_primaries_ITU_R_BT_470_6_System_M:
628 primaries_name = "BT.470-6 System M";
630 case heif_color_primaries_ITU_R_BT_470_6_System_B_G:
631 primaries_name = "BT.470-6 System BG";
633 case heif_color_primaries_ITU_R_BT_601_6:
634 primaries_name = "BT.601";
636 case heif_color_primaries_SMPTE_240M:
637 primaries_name = "SMPTE 240M";
639 case heif_color_primaries_generic_film:
640 primaries_name = "Generic film";
642 case heif_color_primaries_ITU_R_BT_2020_2_and_2100_0:
643 primaries_name = "BT.2020";
645 case heif_color_primaries_SMPTE_ST_428_1:
646 primaries_name = "SMPTE ST 428-1";
648 case heif_color_primaries_SMPTE_RP_431_2:
649 primaries_name = "SMPTE RP 431-2";
651 case heif_color_primaries_SMPTE_EG_432_1:
652 primaries_name = "SMPTE EG 432-1 (DCI P3)";
654 case heif_color_primaries_EBU_Tech_3213_E:
655 primaries_name = "EBU Tech. 3213-E";
658 log_printf("nclx unsupported color_primaries value: %d\n", nclx->color_primaries);
663 DEBUG_1("nclx primaries: %s: ", primaries_name);
665 switch (nclx->transfer_characteristics)
667 case heif_transfer_characteristic_ITU_R_BT_709_5:
668 curve[0] = curve[1] = curve[2] = cmsBuildParametricToneCurve(nullptr, 4, rec709_parameters);
669 profile = static_cast<cmsHPROFILE *>(cmsCreateRGBProfile(&whitepoint, &primaries, curve));
670 cmsFreeToneCurve(curve[0]);
671 trc_name = "Rec709 RGB";
673 case heif_transfer_characteristic_ITU_R_BT_470_6_System_M:
674 curve[0] = curve[1] = curve[2] = cmsBuildGamma (nullptr, 2.2f);
675 profile = static_cast<cmsHPROFILE *>(cmsCreateRGBProfile(&whitepoint, &primaries, curve));
676 cmsFreeToneCurve(curve[0]);
677 trc_name = "Gamma2.2 RGB";
679 case heif_transfer_characteristic_ITU_R_BT_470_6_System_B_G:
680 curve[0] = curve[1] = curve[2] = cmsBuildGamma (nullptr, 2.8f);
681 profile = static_cast<cmsHPROFILE *>(cmsCreateRGBProfile(&whitepoint, &primaries, curve));
682 cmsFreeToneCurve(curve[0]);
683 trc_name = "Gamma2.8 RGB";
685 case heif_transfer_characteristic_linear:
686 curve[0] = curve[1] = curve[2] = cmsBuildGamma (nullptr, 1.0f);
687 profile = static_cast<cmsHPROFILE *>(cmsCreateRGBProfile(&whitepoint, &primaries, curve));
688 cmsFreeToneCurve(curve[0]);
689 trc_name = "linear RGB";
691 case heif_transfer_characteristic_ITU_R_BT_2100_0_HLG:
692 curve[0] = curve[1] = curve[2] = colorspaces_create_transfer(4096, HLG_fct);
693 profile = static_cast<cmsHPROFILE *>(cmsCreateRGBProfile(&whitepoint, &primaries, curve));
694 cmsFreeToneCurve(curve[0]);
695 trc_name = "HLG Rec2020 RGB";
697 case heif_transfer_characteristic_ITU_R_BT_2100_0_PQ:
698 curve[0] = curve[1] = curve[2] = colorspaces_create_transfer(4096, PQ_fct);
699 profile = static_cast<cmsHPROFILE *>(cmsCreateRGBProfile(&whitepoint, &primaries, curve));
700 cmsFreeToneCurve(curve[0]);
701 trc_name = "PQ Rec2020 RGB";
703 case heif_transfer_characteristic_IEC_61966_2_1:
704 /* same as default */
706 curve[0] = curve[1] = curve[2] = cmsBuildParametricToneCurve(nullptr, 4, srgb_parameters);
707 profile = static_cast<cmsHPROFILE *>(cmsCreateRGBProfile(&whitepoint, &primaries, curve));
708 cmsFreeToneCurve(curve[0]);
709 trc_name = "sRGB-TRC RGB";
713 DEBUG_1("nclx transfer characteristic: %s", trc_name);
717 if (cmsSaveProfileToMem(profile, nullptr, &size))
719 data = static_cast<guint8 *>(g_malloc(size));
720 if (cmsSaveProfileToMem(profile, data, &size))
724 cmsCloseProfile(profile);
725 return static_cast<guchar *>(data);
729 cmsCloseProfile(profile);
739 guchar *heif_color_profile(FileData *fd, guint *profile_len)
741 struct heif_context* ctx;
742 struct heif_error error_code;
743 struct heif_image_handle* handle;
744 struct heif_color_profile_nclx *nclxcp;
747 cmsUInt32Number size;
748 guint8 *data = nullptr;
750 ctx = heif_context_alloc();
751 error_code = heif_context_read_from_file(ctx, fd->path, nullptr);
755 log_printf("warning: heif reader error: %s\n", error_code.message);
756 heif_context_free(ctx);
760 error_code = heif_context_get_primary_image_handle(ctx, &handle);
763 log_printf("warning: heif reader error: %s\n", error_code.message);
764 heif_context_free(ctx);
768 nclxcp = heif_nclx_color_profile_alloc();
769 profile_type = heif_image_handle_get_color_profile_type(handle);
771 if (profile_type == heif_color_profile_type_prof)
773 size = heif_image_handle_get_raw_color_profile_size(handle);
775 data = static_cast<guint8 *>(g_malloc0(size));
776 error_code = heif_image_handle_get_raw_color_profile(handle, data);
779 log_printf("warning: heif reader error: %s\n", error_code.message);
780 heif_context_free(ctx);
781 heif_nclx_color_profile_free(nclxcp);
785 DEBUG_1("heif color profile type: prof");
786 heif_context_free(ctx);
787 heif_nclx_color_profile_free(nclxcp);
789 return static_cast<guchar *>(data);
793 error_code = heif_image_handle_get_nclx_color_profile(handle, &nclxcp);
796 log_printf("warning: heif reader error: %s\n", error_code.message);
797 heif_context_free(ctx);
798 heif_nclx_color_profile_free(nclxcp);
802 profile = nclx_to_lcms_profile(nclxcp, profile_len);
805 heif_context_free(ctx);
806 heif_nclx_color_profile_free(nclxcp);
811 guchar *heif_color_profile(FileData *, guint *)
817 #else /* define HAVE_LCMS */
818 /*** color support not enabled ***/
821 ColorMan *color_man_new(ImageWindow *, GdkPixbuf *,
822 ColorManProfileType, const gchar *,
823 ColorManProfileType, const gchar *,
830 ColorMan *color_man_new_embedded(ImageWindow *, GdkPixbuf *,
832 ColorManProfileType, const gchar *,
839 void color_man_free(ColorMan *)
844 void color_man_update()
849 void color_man_correct_region(ColorMan *, GdkPixbuf *, gint, gint, gint, gint)
854 gboolean color_man_get_status(ColorMan *, gchar **, gchar **)
859 guchar *heif_color_profile(FileData *, guint *)
864 #endif /* define HAVE_LCMS */
865 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */