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.
22 #include "color-man.h"
27 /*** color support enabled ***/
42 #include "ui-fileops.h"
44 struct ColorManCache {
45 cmsHPROFILE profile_in;
46 cmsHPROFILE profile_out;
47 cmsHTRANSFORM transform;
49 ColorManProfileType profile_in_type;
50 gchar *profile_in_file;
52 ColorManProfileType profile_out_type;
53 gchar *profile_out_file;
60 /* pixels to transform per idle call */
61 #define COLOR_MAN_CHUNK_SIZE 81900
64 static void color_man_lib_init()
66 static gboolean init_done = FALSE;
68 if (init_done) return;
72 cmsErrorAction(LCMS_ERROR_IGNORE);
76 static cmsHPROFILE color_man_create_adobe_comp()
78 /* ClayRGB1998 is AdobeRGB compatible */
79 #include "ClayRGB1998_icc.h"
80 return cmsOpenProfileFromMem(ClayRGB1998_icc, ClayRGB1998_icc_len);
84 *-------------------------------------------------------------------
85 * color transform cache
86 *-------------------------------------------------------------------
89 static GList *cm_cache_list = nullptr;
92 static void color_man_cache_ref(ColorManCache *cc)
99 static void color_man_cache_unref(ColorManCache *cc)
104 if (cc->refcount < 1)
106 if (cc->transform) cmsDeleteTransform(cc->transform);
107 if (cc->profile_in) cmsCloseProfile(cc->profile_in);
108 if (cc->profile_out) cmsCloseProfile(cc->profile_out);
110 g_free(cc->profile_in_file);
111 g_free(cc->profile_out_file);
117 static cmsHPROFILE color_man_cache_load_profile(ColorManProfileType type, const gchar *file,
118 guchar *data, guint data_len)
120 cmsHPROFILE profile = nullptr;
124 case COLOR_PROFILE_FILE:
129 pathl = path_from_utf8(file);
130 profile = cmsOpenProfileFromFile(pathl, "r");
134 case COLOR_PROFILE_SRGB:
135 profile = cmsCreate_sRGBProfile();
137 case COLOR_PROFILE_ADOBERGB:
138 profile = color_man_create_adobe_comp();
140 case COLOR_PROFILE_MEM:
143 profile = cmsOpenProfileFromMem(data, data_len);
146 case COLOR_PROFILE_NONE:
154 static ColorManCache *color_man_cache_new(ColorManProfileType in_type, const gchar *in_file,
155 guchar *in_data, guint in_data_len,
156 ColorManProfileType out_type, const gchar *out_file,
157 guchar *out_data, guint out_data_len,
162 color_man_lib_init();
164 cc = g_new0(ColorManCache, 1);
167 cc->profile_in_type = in_type;
168 cc->profile_in_file = g_strdup(in_file);
170 cc->profile_out_type = out_type;
171 cc->profile_out_file = g_strdup(out_file);
173 cc->has_alpha = has_alpha;
175 cc->profile_in = color_man_cache_load_profile(cc->profile_in_type, cc->profile_in_file,
176 in_data, in_data_len);
177 cc->profile_out = color_man_cache_load_profile(cc->profile_out_type, cc->profile_out_file,
178 out_data, out_data_len);
180 if (!cc->profile_in || !cc->profile_out)
182 DEBUG_1("failed to load color profile for %s: %d %s",
183 (!cc->profile_in) ? "input" : "screen",
184 (!cc->profile_in) ? cc->profile_in_type : cc->profile_out_type,
185 (!cc->profile_in) ? cc->profile_in_file : cc->profile_out_file);
187 color_man_cache_unref(cc);
191 cc->transform = cmsCreateTransform(cc->profile_in,
192 (has_alpha) ? TYPE_RGBA_8 : TYPE_RGB_8,
194 (has_alpha) ? TYPE_RGBA_8 : TYPE_RGB_8,
195 options->color_profile.render_intent, 0);
199 DEBUG_1("failed to create color profile transform");
201 color_man_cache_unref(cc);
205 if (cc->profile_in_type != COLOR_PROFILE_MEM && cc->profile_out_type != COLOR_PROFILE_MEM )
207 cm_cache_list = g_list_append(cm_cache_list, cc);
208 color_man_cache_ref(cc);
214 static void color_man_cache_free(ColorManCache *cc)
218 cm_cache_list = g_list_remove(cm_cache_list, cc);
219 color_man_cache_unref(cc);
222 static void color_man_cache_reset()
224 while (cm_cache_list)
228 cc = static_cast<ColorManCache *>(cm_cache_list->data);
229 color_man_cache_free(cc);
233 static ColorManCache *color_man_cache_find(ColorManProfileType in_type, const gchar *in_file,
234 ColorManProfileType out_type, const gchar *out_file,
239 work = cm_cache_list;
243 gboolean match = FALSE;
245 cc = static_cast<ColorManCache *>(work->data);
248 if (cc->profile_in_type == in_type &&
249 cc->profile_out_type == out_type &&
250 cc->has_alpha == has_alpha)
255 if (match && cc->profile_in_type == COLOR_PROFILE_FILE)
257 match = (cc->profile_in_file && in_file &&
258 strcmp(cc->profile_in_file, in_file) == 0);
260 if (match && cc->profile_out_type == COLOR_PROFILE_FILE)
262 match = (cc->profile_out_file && out_file &&
263 strcmp(cc->profile_out_file, out_file) == 0);
266 if (match) return cc;
272 static ColorManCache *color_man_cache_get(ColorManProfileType in_type, const gchar *in_file,
273 guchar *in_data, guint in_data_len,
274 ColorManProfileType out_type, const gchar *out_file,
275 guchar *out_data, guint out_data_len,
280 cc = color_man_cache_find(in_type, in_file, out_type, out_file, has_alpha);
283 color_man_cache_ref(cc);
287 return color_man_cache_new(in_type, in_file, in_data, in_data_len,
288 out_type, out_file, out_data, out_data_len, has_alpha);
293 *-------------------------------------------------------------------
295 *-------------------------------------------------------------------
298 #pragma GCC diagnostic push
299 #pragma GCC diagnostic ignored "-Wunused-function"
300 static void color_man_done_unused(ColorMan *cm, ColorManReturnType type)
304 cm->func_done(cm, type, cm->func_done_data);
307 #pragma GCC diagnostic pop
309 void color_man_correct_region(ColorMan *cm, GdkPixbuf *pixbuf, gint x, gint y, gint w, gint h)
319 pixbuf_width = gdk_pixbuf_get_width(pixbuf);
320 pixbuf_height = gdk_pixbuf_get_height(pixbuf);
322 cc = static_cast<ColorManCache *>(cm->profile);
324 pix = gdk_pixbuf_get_pixels(pixbuf);
325 rs = gdk_pixbuf_get_rowstride(pixbuf);
327 /** @FIXME: x,y expected to be = 0. Maybe this is not the right place for scaling */
328 w = w * scale_factor();
329 h = h * scale_factor();
331 w = MIN(w, pixbuf_width - x);
332 h = MIN(h, pixbuf_height - y);
334 pix += x * ((cc->has_alpha) ? 4 : 3);
335 for (i = 0; i < h; i++)
339 pbuf = pix + ((y + i) * rs);
341 cmsDoTransform(cc->transform, pbuf, pbuf, w);
346 #pragma GCC diagnostic push
347 #pragma GCC diagnostic ignored "-Wunused-function"
348 static gboolean color_man_idle_cb_unused(gpointer data)
350 auto *cm = static_cast<ColorMan *>(data);
355 if (!cm->pixbuf) return FALSE;
358 cm->pixbuf != image_get_pixbuf(cm->imd))
361 color_man_done_unused(cm, COLOR_RETURN_IMAGE_CHANGED);
365 width = gdk_pixbuf_get_width(cm->pixbuf);
366 height = gdk_pixbuf_get_height(cm->pixbuf);
368 if (cm->row > height)
370 if (!cm->incremental_sync && cm->imd)
372 image_area_changed(cm->imd, 0, 0, width, height);
376 color_man_done_unused(cm, COLOR_RETURN_SUCCESS);
380 rh = COLOR_MAN_CHUNK_SIZE / width + 1;
381 color_man_correct_region(cm, cm->pixbuf, 0, cm->row, width, rh);
382 if (cm->incremental_sync && cm->imd) image_area_changed(cm->imd, 0, cm->row, width, rh);
387 #pragma GCC diagnostic pop
389 static ColorMan *color_man_new_real(ImageWindow *imd, GdkPixbuf *pixbuf,
390 ColorManProfileType input_type, const gchar *input_file,
391 guchar *input_data, guint input_data_len,
392 ColorManProfileType screen_type, const gchar *screen_file,
393 guchar *screen_data, guint screen_data_len)
398 if (imd) pixbuf = image_get_pixbuf(imd);
400 cm = g_new0(ColorMan, 1);
403 if (cm->pixbuf) g_object_ref(cm->pixbuf);
405 has_alpha = pixbuf ? gdk_pixbuf_get_has_alpha(pixbuf) : FALSE;
407 cm->profile = color_man_cache_get(input_type, input_file, input_data, input_data_len,
408 screen_type, screen_file, screen_data, screen_data_len, has_alpha);
418 ColorMan *color_man_new(ImageWindow *imd, GdkPixbuf *pixbuf,
419 ColorManProfileType input_type, const gchar *input_file,
420 ColorManProfileType screen_type, const gchar *screen_file,
421 guchar *screen_data, guint screen_data_len)
423 return color_man_new_real(imd, pixbuf,
424 input_type, input_file, nullptr, 0,
425 screen_type, screen_file, screen_data, screen_data_len);
428 #pragma GCC diagnostic push
429 #pragma GCC diagnostic ignored "-Wunused-function"
430 void color_man_start_bg_unused(ColorMan *cm, ColorMan::DoneFunc done_func, gpointer done_data)
432 cm->func_done = done_func;
433 cm->func_done_data = done_data;
434 cm->idle_id = g_idle_add(color_man_idle_cb_unused, cm);
436 #pragma GCC diagnostic pop
438 ColorMan *color_man_new_embedded(ImageWindow *imd, GdkPixbuf *pixbuf,
439 guchar *input_data, guint input_data_len,
440 ColorManProfileType screen_type, const gchar *screen_file,
441 guchar *screen_data, guint screen_data_len)
443 return color_man_new_real(imd, pixbuf,
444 COLOR_PROFILE_MEM, nullptr, input_data, input_data_len,
445 screen_type, screen_file, screen_data, screen_data_len);
448 static gchar *color_man_get_profile_name(ColorManProfileType type, cmsHPROFILE profile)
452 case COLOR_PROFILE_SRGB:
453 return g_strdup(_("sRGB"));
454 case COLOR_PROFILE_ADOBERGB:
455 return g_strdup(_("Adobe RGB compatible"));
457 case COLOR_PROFILE_MEM:
458 case COLOR_PROFILE_FILE:
464 cmsGetProfileInfoASCII(profile, cmsInfoDescription, "en", "US", buffer, 20);
465 buffer[19] = '\0'; /* Just to be sure */
466 return g_strdup(buffer);
468 return g_strdup(cmsTakeProductName(profile));
471 return g_strdup(_("Custom profile"));
473 case COLOR_PROFILE_NONE:
479 gboolean color_man_get_status(ColorMan *cm, gchar **image_profile, gchar **screen_profile)
482 if (!cm) return FALSE;
484 cc = static_cast<ColorManCache *>(cm->profile);
486 if (image_profile) *image_profile = color_man_get_profile_name(cc->profile_in_type, cc->profile_in);
487 if (screen_profile) *screen_profile = color_man_get_profile_name(cc->profile_out_type, cc->profile_out);
491 void color_man_free(ColorMan *cm)
495 if (cm->idle_id) g_source_remove(cm->idle_id);
496 if (cm->pixbuf) g_object_unref(cm->pixbuf);
498 color_man_cache_unref(static_cast<ColorManCache *>(cm->profile));
503 void color_man_update()
505 color_man_cache_reset();
510 #include <libheif/heif.h>
512 static cmsToneCurve* colorspaces_create_transfer(int32_t size, double (*fct)(double))
514 std::vector<float> values;
515 values.reserve(size);
516 for(int32_t i = 0; i < size; ++i)
518 const double x = static_cast<float>(i) / (size - 1);
519 const double y = MIN(fct(x), 1.0F);
520 values.push_back(static_cast<float>(y));
523 return cmsBuildTabulatedToneCurveFloat(nullptr, size, values.data());
526 // https://www.itu.int/dms_pubrec/itu-r/rec/bt/R-REC-BT.2100-2-201807-I!!PDF-F.pdf
528 static double HLG_fct(double x)
530 static const double Beta = 0.04;
531 static const double RA = 5.591816309728916; // 1.0 / A where A = 0.17883277
532 static const double B = 0.28466892; // 1.0 - 4.0 * A
533 static const double C = 0.5599107295; // 0,5 –aln(4a)
535 double e = MAX(x * (1.0 - Beta) + Beta, 0.0);
537 if(e == 0.0) return 0.0;
539 const double sign = e;
550 res = (exp((e - C) * RA) + B) / 12.0;
553 return copysign(res, sign);
556 static double PQ_fct(double x)
558 static const double M1 = 2610.0 / 16384.0;
559 static const double M2 = (2523.0 / 4096.0) * 128.0;
560 static const double C1 = 3424.0 / 4096.0;
561 static const double C2 = (2413.0 / 4096.0) * 32.0;
562 static const double C3 = (2392.0 / 4096.0) * 32.0;
564 if(x == 0.0) return 0.0;
565 const double sign = x;
568 const double xpo = pow(x, 1.0 / M2);
569 const double num = MAX(xpo - C1, 0.0);
570 const double den = C2 - C3 * xpo;
571 const double res = pow(num / den, 1.0 / M1);
573 return copysign(res, sign);
582 * Copied from: gimp/libgimpcolor/gimpcolorprofile.c
584 static guchar *nclx_to_lcms_profile(const struct heif_color_profile_nclx *nclx, guint *profile_len)
586 const gchar *primaries_name = "";
587 const gchar *trc_name = "";
588 cmsHPROFILE *profile = nullptr;
589 cmsCIExyY whitepoint;
590 cmsCIExyYTRIPLE primaries;
591 cmsToneCurve *curve[3];
592 cmsUInt32Number size;
593 guint8 *data = nullptr;
595 cmsFloat64Number srgb_parameters[5] =
596 { 2.4, 1.0 / 1.055, 0.055 / 1.055, 1.0 / 12.92, 0.04045 };
598 cmsFloat64Number rec709_parameters[5] =
599 { 2.2, 1.0 / 1.099, 0.099 / 1.099, 1.0 / 4.5, 0.081 };
606 if (nclx->color_primaries == heif_color_primaries_unspecified)
611 whitepoint.x = nclx->color_primary_white_x;
612 whitepoint.y = nclx->color_primary_white_y;
615 primaries.Red.x = nclx->color_primary_red_x;
616 primaries.Red.y = nclx->color_primary_red_y;
617 primaries.Red.Y = 1.0F;
619 primaries.Green.x = nclx->color_primary_green_x;
620 primaries.Green.y = nclx->color_primary_green_y;
621 primaries.Green.Y = 1.0F;
623 primaries.Blue.x = nclx->color_primary_blue_x;
624 primaries.Blue.y = nclx->color_primary_blue_y;
625 primaries.Blue.Y = 1.0F;
627 switch (nclx->color_primaries)
629 case heif_color_primaries_ITU_R_BT_709_5:
630 primaries_name = "BT.709";
632 case heif_color_primaries_ITU_R_BT_470_6_System_M:
633 primaries_name = "BT.470-6 System M";
635 case heif_color_primaries_ITU_R_BT_470_6_System_B_G:
636 primaries_name = "BT.470-6 System BG";
638 case heif_color_primaries_ITU_R_BT_601_6:
639 primaries_name = "BT.601";
641 case heif_color_primaries_SMPTE_240M:
642 primaries_name = "SMPTE 240M";
644 case heif_color_primaries_generic_film:
645 primaries_name = "Generic film";
647 case heif_color_primaries_ITU_R_BT_2020_2_and_2100_0:
648 primaries_name = "BT.2020";
650 case heif_color_primaries_SMPTE_ST_428_1:
651 primaries_name = "SMPTE ST 428-1";
653 case heif_color_primaries_SMPTE_RP_431_2:
654 primaries_name = "SMPTE RP 431-2";
656 case heif_color_primaries_SMPTE_EG_432_1:
657 primaries_name = "SMPTE EG 432-1 (DCI P3)";
659 case heif_color_primaries_EBU_Tech_3213_E:
660 primaries_name = "EBU Tech. 3213-E";
663 log_printf("nclx unsupported color_primaries value: %d\n", nclx->color_primaries);
668 DEBUG_1("nclx primaries: %s: ", primaries_name);
670 switch (nclx->transfer_characteristics)
672 case heif_transfer_characteristic_ITU_R_BT_709_5:
673 curve[0] = curve[1] = curve[2] = cmsBuildParametricToneCurve(nullptr, 4, rec709_parameters);
674 profile = static_cast<cmsHPROFILE *>(cmsCreateRGBProfile(&whitepoint, &primaries, curve));
675 cmsFreeToneCurve(curve[0]);
676 trc_name = "Rec709 RGB";
678 case heif_transfer_characteristic_ITU_R_BT_470_6_System_M:
679 curve[0] = curve[1] = curve[2] = cmsBuildGamma (nullptr, 2.2F);
680 profile = static_cast<cmsHPROFILE *>(cmsCreateRGBProfile(&whitepoint, &primaries, curve));
681 cmsFreeToneCurve(curve[0]);
682 trc_name = "Gamma2.2 RGB";
684 case heif_transfer_characteristic_ITU_R_BT_470_6_System_B_G:
685 curve[0] = curve[1] = curve[2] = cmsBuildGamma (nullptr, 2.8F);
686 profile = static_cast<cmsHPROFILE *>(cmsCreateRGBProfile(&whitepoint, &primaries, curve));
687 cmsFreeToneCurve(curve[0]);
688 trc_name = "Gamma2.8 RGB";
690 case heif_transfer_characteristic_linear:
691 curve[0] = curve[1] = curve[2] = cmsBuildGamma (nullptr, 1.0F);
692 profile = static_cast<cmsHPROFILE *>(cmsCreateRGBProfile(&whitepoint, &primaries, curve));
693 cmsFreeToneCurve(curve[0]);
694 trc_name = "linear RGB";
696 case heif_transfer_characteristic_ITU_R_BT_2100_0_HLG:
697 curve[0] = curve[1] = curve[2] = colorspaces_create_transfer(4096, HLG_fct);
698 profile = static_cast<cmsHPROFILE *>(cmsCreateRGBProfile(&whitepoint, &primaries, curve));
699 cmsFreeToneCurve(curve[0]);
700 trc_name = "HLG Rec2020 RGB";
702 case heif_transfer_characteristic_ITU_R_BT_2100_0_PQ:
703 curve[0] = curve[1] = curve[2] = colorspaces_create_transfer(4096, PQ_fct);
704 profile = static_cast<cmsHPROFILE *>(cmsCreateRGBProfile(&whitepoint, &primaries, curve));
705 cmsFreeToneCurve(curve[0]);
706 trc_name = "PQ Rec2020 RGB";
708 case heif_transfer_characteristic_IEC_61966_2_1:
709 /* same as default */
711 curve[0] = curve[1] = curve[2] = cmsBuildParametricToneCurve(nullptr, 4, srgb_parameters);
712 profile = static_cast<cmsHPROFILE *>(cmsCreateRGBProfile(&whitepoint, &primaries, curve));
713 cmsFreeToneCurve(curve[0]);
714 trc_name = "sRGB-TRC RGB";
718 DEBUG_1("nclx transfer characteristic: %s", trc_name);
722 if (cmsSaveProfileToMem(profile, nullptr, &size))
724 data = static_cast<guint8 *>(g_malloc(size));
725 if (cmsSaveProfileToMem(profile, data, &size))
729 cmsCloseProfile(profile);
730 return static_cast<guchar *>(data);
733 cmsCloseProfile(profile);
740 guchar *heif_color_profile(FileData *fd, guint *profile_len)
742 struct heif_context* ctx;
743 struct heif_error error_code;
744 struct heif_image_handle* handle;
745 struct heif_color_profile_nclx *nclxcp;
748 cmsUInt32Number size;
749 guint8 *data = nullptr;
751 ctx = heif_context_alloc();
752 error_code = heif_context_read_from_file(ctx, fd->path, nullptr);
756 log_printf("warning: heif reader error: %s\n", error_code.message);
757 heif_context_free(ctx);
761 error_code = heif_context_get_primary_image_handle(ctx, &handle);
764 log_printf("warning: heif reader error: %s\n", error_code.message);
765 heif_context_free(ctx);
769 nclxcp = heif_nclx_color_profile_alloc();
770 profile_type = heif_image_handle_get_color_profile_type(handle);
772 if (profile_type == heif_color_profile_type_prof)
774 size = heif_image_handle_get_raw_color_profile_size(handle);
776 data = static_cast<guint8 *>(g_malloc0(size));
777 error_code = heif_image_handle_get_raw_color_profile(handle, data);
780 log_printf("warning: heif reader error: %s\n", error_code.message);
781 heif_context_free(ctx);
782 heif_nclx_color_profile_free(nclxcp);
786 DEBUG_1("heif color profile type: prof");
787 heif_context_free(ctx);
788 heif_nclx_color_profile_free(nclxcp);
790 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);
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: */