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"
26 #include "ui-fileops.h"
31 /*** color support enabled ***/
40 struct ColorManCache {
41 cmsHPROFILE profile_in;
42 cmsHPROFILE profile_out;
43 cmsHTRANSFORM transform;
45 ColorManProfileType profile_in_type;
46 gchar *profile_in_file;
48 ColorManProfileType profile_out_type;
49 gchar *profile_out_file;
56 /* pixels to transform per idle call */
57 #define COLOR_MAN_CHUNK_SIZE 81900
60 static void color_man_lib_init()
62 static gboolean init_done = FALSE;
64 if (init_done) return;
68 cmsErrorAction(LCMS_ERROR_IGNORE);
72 static cmsHPROFILE color_man_create_adobe_comp()
74 /* ClayRGB1998 is AdobeRGB compatible */
75 #include "ClayRGB1998_icc.h"
76 return cmsOpenProfileFromMem(ClayRGB1998_icc, ClayRGB1998_icc_len);
80 *-------------------------------------------------------------------
81 * color transform cache
82 *-------------------------------------------------------------------
85 static GList *cm_cache_list = nullptr;
88 static void color_man_cache_ref(ColorManCache *cc)
95 static void color_man_cache_unref(ColorManCache *cc)
100 if (cc->refcount < 1)
102 if (cc->transform) cmsDeleteTransform(cc->transform);
103 if (cc->profile_in) cmsCloseProfile(cc->profile_in);
104 if (cc->profile_out) cmsCloseProfile(cc->profile_out);
106 g_free(cc->profile_in_file);
107 g_free(cc->profile_out_file);
113 static cmsHPROFILE color_man_cache_load_profile(ColorManProfileType type, const gchar *file,
114 guchar *data, guint data_len)
116 cmsHPROFILE profile = nullptr;
120 case COLOR_PROFILE_FILE:
125 pathl = path_from_utf8(file);
126 profile = cmsOpenProfileFromFile(pathl, "r");
130 case COLOR_PROFILE_SRGB:
131 profile = cmsCreate_sRGBProfile();
133 case COLOR_PROFILE_ADOBERGB:
134 profile = color_man_create_adobe_comp();
136 case COLOR_PROFILE_MEM:
139 profile = cmsOpenProfileFromMem(data, data_len);
142 case COLOR_PROFILE_NONE:
150 static ColorManCache *color_man_cache_new(ColorManProfileType in_type, const gchar *in_file,
151 guchar *in_data, guint in_data_len,
152 ColorManProfileType out_type, const gchar *out_file,
153 guchar *out_data, guint out_data_len,
158 color_man_lib_init();
160 cc = g_new0(ColorManCache, 1);
163 cc->profile_in_type = in_type;
164 cc->profile_in_file = g_strdup(in_file);
166 cc->profile_out_type = out_type;
167 cc->profile_out_file = g_strdup(out_file);
169 cc->has_alpha = has_alpha;
171 cc->profile_in = color_man_cache_load_profile(cc->profile_in_type, cc->profile_in_file,
172 in_data, in_data_len);
173 cc->profile_out = color_man_cache_load_profile(cc->profile_out_type, cc->profile_out_file,
174 out_data, out_data_len);
176 if (!cc->profile_in || !cc->profile_out)
178 DEBUG_1("failed to load color profile for %s: %d %s",
179 (!cc->profile_in) ? "input" : "screen",
180 (!cc->profile_in) ? cc->profile_in_type : cc->profile_out_type,
181 (!cc->profile_in) ? cc->profile_in_file : cc->profile_out_file);
183 color_man_cache_unref(cc);
187 cc->transform = cmsCreateTransform(cc->profile_in,
188 (has_alpha) ? TYPE_RGBA_8 : TYPE_RGB_8,
190 (has_alpha) ? TYPE_RGBA_8 : TYPE_RGB_8,
191 options->color_profile.render_intent, 0);
195 DEBUG_1("failed to create color profile transform");
197 color_man_cache_unref(cc);
201 if (cc->profile_in_type != COLOR_PROFILE_MEM && cc->profile_out_type != COLOR_PROFILE_MEM )
203 cm_cache_list = g_list_append(cm_cache_list, cc);
204 color_man_cache_ref(cc);
210 static void color_man_cache_free(ColorManCache *cc)
214 cm_cache_list = g_list_remove(cm_cache_list, cc);
215 color_man_cache_unref(cc);
218 static void color_man_cache_reset()
220 while (cm_cache_list)
224 cc = static_cast<ColorManCache *>(cm_cache_list->data);
225 color_man_cache_free(cc);
229 static ColorManCache *color_man_cache_find(ColorManProfileType in_type, const gchar *in_file,
230 ColorManProfileType out_type, const gchar *out_file,
235 work = cm_cache_list;
239 gboolean match = FALSE;
241 cc = static_cast<ColorManCache *>(work->data);
244 if (cc->profile_in_type == in_type &&
245 cc->profile_out_type == out_type &&
246 cc->has_alpha == has_alpha)
251 if (match && cc->profile_in_type == COLOR_PROFILE_FILE)
253 match = (cc->profile_in_file && in_file &&
254 strcmp(cc->profile_in_file, in_file) == 0);
256 if (match && cc->profile_out_type == COLOR_PROFILE_FILE)
258 match = (cc->profile_out_file && out_file &&
259 strcmp(cc->profile_out_file, out_file) == 0);
262 if (match) return cc;
268 static ColorManCache *color_man_cache_get(ColorManProfileType in_type, const gchar *in_file,
269 guchar *in_data, guint in_data_len,
270 ColorManProfileType out_type, const gchar *out_file,
271 guchar *out_data, guint out_data_len,
276 cc = color_man_cache_find(in_type, in_file, out_type, out_file, has_alpha);
279 color_man_cache_ref(cc);
283 return color_man_cache_new(in_type, in_file, in_data, in_data_len,
284 out_type, out_file, out_data, out_data_len, has_alpha);
289 *-------------------------------------------------------------------
291 *-------------------------------------------------------------------
294 #pragma GCC diagnostic push
295 #pragma GCC diagnostic ignored "-Wunused-function"
296 static void color_man_done_unused(ColorMan *cm, ColorManReturnType type)
300 cm->func_done(cm, type, cm->func_done_data);
303 #pragma GCC diagnostic pop
305 void color_man_correct_region(ColorMan *cm, GdkPixbuf *pixbuf, gint x, gint y, gint w, gint h)
311 gint pixbuf_width, pixbuf_height;
314 pixbuf_width = gdk_pixbuf_get_width(pixbuf);
315 pixbuf_height = gdk_pixbuf_get_height(pixbuf);
317 cc = static_cast<ColorManCache *>(cm->profile);
319 pix = gdk_pixbuf_get_pixels(pixbuf);
320 rs = gdk_pixbuf_get_rowstride(pixbuf);
322 /** @FIXME: x,y expected to be = 0. Maybe this is not the right place for scaling */
323 w = w * scale_factor();
324 h = h * scale_factor();
326 w = MIN(w, pixbuf_width - x);
327 h = MIN(h, pixbuf_height - y);
329 pix += x * ((cc->has_alpha) ? 4 : 3);
330 for (i = 0; i < h; i++)
334 pbuf = pix + ((y + i) * rs);
336 cmsDoTransform(cc->transform, pbuf, pbuf, w);
341 #pragma GCC diagnostic push
342 #pragma GCC diagnostic ignored "-Wunused-function"
343 static gboolean color_man_idle_cb_unused(gpointer data)
345 ColorMan *cm = static_cast<ColorMan *>(data);
349 if (!cm->pixbuf) return FALSE;
352 cm->pixbuf != image_get_pixbuf(cm->imd))
355 color_man_done_unused(cm, COLOR_RETURN_IMAGE_CHANGED);
359 width = gdk_pixbuf_get_width(cm->pixbuf);
360 height = gdk_pixbuf_get_height(cm->pixbuf);
362 if (cm->row > height)
364 if (!cm->incremental_sync && cm->imd)
366 image_area_changed(cm->imd, 0, 0, width, height);
370 color_man_done_unused(cm, COLOR_RETURN_SUCCESS);
374 rh = COLOR_MAN_CHUNK_SIZE / width + 1;
375 color_man_correct_region(cm, cm->pixbuf, 0, cm->row, width, rh);
376 if (cm->incremental_sync && cm->imd) image_area_changed(cm->imd, 0, cm->row, width, rh);
381 #pragma GCC diagnostic pop
383 static ColorMan *color_man_new_real(ImageWindow *imd, GdkPixbuf *pixbuf,
384 ColorManProfileType input_type, const gchar *input_file,
385 guchar *input_data, guint input_data_len,
386 ColorManProfileType screen_type, const gchar *screen_file,
387 guchar *screen_data, guint screen_data_len)
392 if (imd) pixbuf = image_get_pixbuf(imd);
394 cm = g_new0(ColorMan, 1);
397 if (cm->pixbuf) g_object_ref(cm->pixbuf);
399 has_alpha = pixbuf ? gdk_pixbuf_get_has_alpha(pixbuf) : FALSE;
401 cm->profile = color_man_cache_get(input_type, input_file, input_data, input_data_len,
402 screen_type, screen_file, screen_data, screen_data_len, has_alpha);
412 ColorMan *color_man_new(ImageWindow *imd, GdkPixbuf *pixbuf,
413 ColorManProfileType input_type, const gchar *input_file,
414 ColorManProfileType screen_type, const gchar *screen_file,
415 guchar *screen_data, guint screen_data_len)
417 return color_man_new_real(imd, pixbuf,
418 input_type, input_file, nullptr, 0,
419 screen_type, screen_file, screen_data, screen_data_len);
422 #pragma GCC diagnostic push
423 #pragma GCC diagnostic ignored "-Wunused-function"
424 void color_man_start_bg_unused(ColorMan *cm, ColorManDoneFunc done_func, gpointer done_data)
426 cm->func_done = done_func;
427 cm->func_done_data = done_data;
428 cm->idle_id = g_idle_add(color_man_idle_cb_unused, cm);
430 #pragma GCC diagnostic pop
432 ColorMan *color_man_new_embedded(ImageWindow *imd, GdkPixbuf *pixbuf,
433 guchar *input_data, guint input_data_len,
434 ColorManProfileType screen_type, const gchar *screen_file,
435 guchar *screen_data, guint screen_data_len)
437 return color_man_new_real(imd, pixbuf,
438 COLOR_PROFILE_MEM, nullptr, input_data, input_data_len,
439 screen_type, screen_file, screen_data, screen_data_len);
442 static gchar *color_man_get_profile_name(ColorManProfileType type, cmsHPROFILE profile)
446 case COLOR_PROFILE_SRGB:
447 return g_strdup(_("sRGB"));
448 case COLOR_PROFILE_ADOBERGB:
449 return g_strdup(_("Adobe RGB compatible"));
451 case COLOR_PROFILE_MEM:
452 case COLOR_PROFILE_FILE:
458 cmsGetProfileInfoASCII(profile, cmsInfoDescription, "en", "US", buffer, 20);
459 buffer[19] = '\0'; /* Just to be sure */
460 return g_strdup(buffer);
462 return g_strdup(cmsTakeProductName(profile));
465 return g_strdup(_("Custom profile"));
467 case COLOR_PROFILE_NONE:
473 gboolean color_man_get_status(ColorMan *cm, gchar **image_profile, gchar **screen_profile)
476 if (!cm) return FALSE;
478 cc = static_cast<ColorManCache *>(cm->profile);
480 if (image_profile) *image_profile = color_man_get_profile_name(cc->profile_in_type, cc->profile_in);
481 if (screen_profile) *screen_profile = color_man_get_profile_name(cc->profile_out_type, cc->profile_out);
485 void color_man_free(ColorMan *cm)
489 if (cm->idle_id) g_source_remove(cm->idle_id);
490 if (cm->pixbuf) g_object_unref(cm->pixbuf);
492 color_man_cache_unref(static_cast<ColorManCache *>(cm->profile));
497 void color_man_update()
499 color_man_cache_reset();
504 #include <libheif/heif.h>
506 static cmsToneCurve* colorspaces_create_transfer(int32_t size, double (*fct)(double))
508 std::vector<float> values;
509 values.reserve(size);
510 for(int32_t i = 0; i < size; ++i)
512 const double x = static_cast<float>(i) / (size - 1);
513 const double y = MIN(fct(x), 1.0f);
514 values[i] = static_cast<float>(y);
517 return cmsBuildTabulatedToneCurveFloat(nullptr, size, values.data());
520 // https://www.itu.int/dms_pubrec/itu-r/rec/bt/R-REC-BT.2100-2-201807-I!!PDF-F.pdf
522 static double HLG_fct(double x)
524 static const double Beta = 0.04;
525 static const double RA = 5.591816309728916; // 1.0 / A where A = 0.17883277
526 static const double B = 0.28466892; // 1.0 - 4.0 * A
527 static const double C = 0.5599107295; // 0,5 –aln(4a)
529 double e = MAX(x * (1.0 - Beta) + Beta, 0.0);
531 if(e == 0.0) return 0.0;
533 const double sign = e;
544 res = (exp((e - C) * RA) + B) / 12.0;
547 return copysign(res, sign);
550 static double PQ_fct(double x)
552 static const double M1 = 2610.0 / 16384.0;
553 static const double M2 = (2523.0 / 4096.0) * 128.0;
554 static const double C1 = 3424.0 / 4096.0;
555 static const double C2 = (2413.0 / 4096.0) * 32.0;
556 static const double C3 = (2392.0 / 4096.0) * 32.0;
558 if(x == 0.0) return 0.0;
559 const double sign = x;
562 const double xpo = pow(x, 1.0 / M2);
563 const double num = MAX(xpo - C1, 0.0);
564 const double den = C2 - C3 * xpo;
565 const double res = pow(num / den, 1.0 / M1);
567 return copysign(res, sign);
576 * Copied from: gimp/libgimpcolor/gimpcolorprofile.c
578 static guchar *nclx_to_lcms_profile(const struct heif_color_profile_nclx *nclx, guint *profile_len)
580 const gchar *primaries_name = "";
581 const gchar *trc_name = "";
582 cmsHPROFILE *profile = nullptr;
583 cmsCIExyY whitepoint;
584 cmsCIExyYTRIPLE primaries;
585 cmsToneCurve *curve[3];
586 cmsUInt32Number size;
587 guint8 *data = nullptr;
589 cmsFloat64Number srgb_parameters[5] =
590 { 2.4, 1.0 / 1.055, 0.055 / 1.055, 1.0 / 12.92, 0.04045 };
592 cmsFloat64Number rec709_parameters[5] =
593 { 2.2, 1.0 / 1.099, 0.099 / 1.099, 1.0 / 4.5, 0.081 };
600 if (nclx->color_primaries == heif_color_primaries_unspecified)
605 whitepoint.x = nclx->color_primary_white_x;
606 whitepoint.y = nclx->color_primary_white_y;
609 primaries.Red.x = nclx->color_primary_red_x;
610 primaries.Red.y = nclx->color_primary_red_y;
611 primaries.Red.Y = 1.0f;
613 primaries.Green.x = nclx->color_primary_green_x;
614 primaries.Green.y = nclx->color_primary_green_y;
615 primaries.Green.Y = 1.0f;
617 primaries.Blue.x = nclx->color_primary_blue_x;
618 primaries.Blue.y = nclx->color_primary_blue_y;
619 primaries.Blue.Y = 1.0f;
621 switch (nclx->color_primaries)
623 case heif_color_primaries_ITU_R_BT_709_5:
624 primaries_name = "BT.709";
626 case heif_color_primaries_ITU_R_BT_470_6_System_M:
627 primaries_name = "BT.470-6 System M";
629 case heif_color_primaries_ITU_R_BT_470_6_System_B_G:
630 primaries_name = "BT.470-6 System BG";
632 case heif_color_primaries_ITU_R_BT_601_6:
633 primaries_name = "BT.601";
635 case heif_color_primaries_SMPTE_240M:
636 primaries_name = "SMPTE 240M";
638 case heif_color_primaries_generic_film:
639 primaries_name = "Generic film";
641 case heif_color_primaries_ITU_R_BT_2020_2_and_2100_0:
642 primaries_name = "BT.2020";
644 case heif_color_primaries_SMPTE_ST_428_1:
645 primaries_name = "SMPTE ST 428-1";
647 case heif_color_primaries_SMPTE_RP_431_2:
648 primaries_name = "SMPTE RP 431-2";
650 case heif_color_primaries_SMPTE_EG_432_1:
651 primaries_name = "SMPTE EG 432-1 (DCI P3)";
653 case heif_color_primaries_EBU_Tech_3213_E:
654 primaries_name = "EBU Tech. 3213-E";
657 log_printf("nclx unsupported color_primaries value: %d\n", nclx->color_primaries);
662 DEBUG_1("nclx primaries: %s: ", primaries_name);
664 switch (nclx->transfer_characteristics)
666 case heif_transfer_characteristic_ITU_R_BT_709_5:
667 curve[0] = curve[1] = curve[2] = cmsBuildParametricToneCurve(nullptr, 4, rec709_parameters);
668 profile = static_cast<cmsHPROFILE *>(cmsCreateRGBProfile(&whitepoint, &primaries, curve));
669 cmsFreeToneCurve(curve[0]);
670 trc_name = "Rec709 RGB";
672 case heif_transfer_characteristic_ITU_R_BT_470_6_System_M:
673 curve[0] = curve[1] = curve[2] = cmsBuildGamma (nullptr, 2.2f);
674 profile = static_cast<cmsHPROFILE *>(cmsCreateRGBProfile(&whitepoint, &primaries, curve));
675 cmsFreeToneCurve(curve[0]);
676 trc_name = "Gamma2.2 RGB";
678 case heif_transfer_characteristic_ITU_R_BT_470_6_System_B_G:
679 curve[0] = curve[1] = curve[2] = cmsBuildGamma (nullptr, 2.8f);
680 profile = static_cast<cmsHPROFILE *>(cmsCreateRGBProfile(&whitepoint, &primaries, curve));
681 cmsFreeToneCurve(curve[0]);
682 trc_name = "Gamma2.8 RGB";
684 case heif_transfer_characteristic_linear:
685 curve[0] = curve[1] = curve[2] = cmsBuildGamma (nullptr, 1.0f);
686 profile = static_cast<cmsHPROFILE *>(cmsCreateRGBProfile(&whitepoint, &primaries, curve));
687 cmsFreeToneCurve(curve[0]);
688 trc_name = "linear RGB";
690 case heif_transfer_characteristic_ITU_R_BT_2100_0_HLG:
691 curve[0] = curve[1] = curve[2] = colorspaces_create_transfer(4096, HLG_fct);
692 profile = static_cast<cmsHPROFILE *>(cmsCreateRGBProfile(&whitepoint, &primaries, curve));
693 cmsFreeToneCurve(curve[0]);
694 trc_name = "HLG Rec2020 RGB";
696 case heif_transfer_characteristic_ITU_R_BT_2100_0_PQ:
697 curve[0] = curve[1] = curve[2] = colorspaces_create_transfer(4096, PQ_fct);
698 profile = static_cast<cmsHPROFILE *>(cmsCreateRGBProfile(&whitepoint, &primaries, curve));
699 cmsFreeToneCurve(curve[0]);
700 trc_name = "PQ Rec2020 RGB";
702 case heif_transfer_characteristic_IEC_61966_2_1:
703 /* same as default */
705 curve[0] = curve[1] = curve[2] = cmsBuildParametricToneCurve(nullptr, 4, srgb_parameters);
706 profile = static_cast<cmsHPROFILE *>(cmsCreateRGBProfile(&whitepoint, &primaries, curve));
707 cmsFreeToneCurve(curve[0]);
708 trc_name = "sRGB-TRC RGB";
712 DEBUG_1("nclx transfer characteristic: %s", trc_name);
716 if (cmsSaveProfileToMem(profile, nullptr, &size))
718 data = static_cast<guint8 *>(g_malloc(size));
719 if (cmsSaveProfileToMem(profile, data, &size))
723 cmsCloseProfile(profile);
724 return static_cast<guchar *>(data);
728 cmsCloseProfile(profile);
738 guchar *heif_color_profile(FileData *fd, guint *profile_len)
740 struct heif_context* ctx;
741 struct heif_error error_code;
742 struct heif_image_handle* handle;
743 struct heif_color_profile_nclx *nclxcp;
746 cmsUInt32Number size;
747 guint8 *data = nullptr;
749 ctx = heif_context_alloc();
750 error_code = heif_context_read_from_file(ctx, fd->path, nullptr);
754 log_printf("warning: heif reader error: %s\n", error_code.message);
755 heif_context_free(ctx);
759 error_code = heif_context_get_primary_image_handle(ctx, &handle);
762 log_printf("warning: heif reader error: %s\n", error_code.message);
763 heif_context_free(ctx);
767 nclxcp = heif_nclx_color_profile_alloc();
768 profile_type = heif_image_handle_get_color_profile_type(handle);
770 if (profile_type == heif_color_profile_type_prof)
772 size = heif_image_handle_get_raw_color_profile_size(handle);
774 data = static_cast<guint8 *>(g_malloc0(size));
775 error_code = heif_image_handle_get_raw_color_profile(handle, data);
778 log_printf("warning: heif reader error: %s\n", error_code.message);
779 heif_context_free(ctx);
780 heif_nclx_color_profile_free(nclxcp);
784 DEBUG_1("heif color profile type: prof");
785 heif_context_free(ctx);
786 heif_nclx_color_profile_free(nclxcp);
788 return static_cast<guchar *>(data);
792 error_code = heif_image_handle_get_nclx_color_profile(handle, &nclxcp);
795 log_printf("warning: heif reader error: %s\n", error_code.message);
796 heif_context_free(ctx);
797 heif_nclx_color_profile_free(nclxcp);
801 profile = nclx_to_lcms_profile(nclxcp, profile_len);
804 heif_context_free(ctx);
805 heif_nclx_color_profile_free(nclxcp);
810 guchar *heif_color_profile(FileData *, guint *)
816 #else /* define HAVE_LCMS */
817 /*** color support not enabled ***/
820 ColorMan *color_man_new(ImageWindow *, GdkPixbuf *,
821 ColorManProfileType, const gchar *,
822 ColorManProfileType, const gchar *,
829 ColorMan *color_man_new_embedded(ImageWindow *, GdkPixbuf *,
831 ColorManProfileType, const gchar *,
838 void color_man_free(ColorMan *)
843 void color_man_update()
848 void color_man_correct_region(ColorMan *, GdkPixbuf *, gint, gint, gint, gint)
853 gboolean color_man_get_status(ColorMan *, gchar **, gchar **)
858 guchar *heif_color_profile(FileData *, guint *)
863 #endif /* define HAVE_LCMS */
864 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */