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 ***/
33 #include <glib-object.h>
46 #include "ui-fileops.h"
48 struct ColorManCache {
49 cmsHPROFILE profile_in;
50 cmsHPROFILE profile_out;
51 cmsHTRANSFORM transform;
53 ColorManProfileType profile_in_type;
54 gchar *profile_in_file;
56 ColorManProfileType profile_out_type;
57 gchar *profile_out_file;
64 /* pixels to transform per idle call */
65 #define COLOR_MAN_CHUNK_SIZE 81900
68 static void color_man_lib_init()
70 static gboolean init_done = FALSE;
72 if (init_done) return;
76 cmsErrorAction(LCMS_ERROR_IGNORE);
80 static cmsHPROFILE color_man_create_adobe_comp()
82 /* ClayRGB1998 is AdobeRGB compatible */
83 #include "ClayRGB1998_icc.h" // IWYU pragma: keep
84 return cmsOpenProfileFromMem(ClayRGB1998_icc, ClayRGB1998_icc_len);
88 *-------------------------------------------------------------------
89 * color transform cache
90 *-------------------------------------------------------------------
93 static GList *cm_cache_list = nullptr;
96 static void color_man_cache_ref(ColorManCache *cc)
103 static void color_man_cache_unref(ColorManCache *cc)
108 if (cc->refcount < 1)
110 if (cc->transform) cmsDeleteTransform(cc->transform);
111 if (cc->profile_in) cmsCloseProfile(cc->profile_in);
112 if (cc->profile_out) cmsCloseProfile(cc->profile_out);
114 g_free(cc->profile_in_file);
115 g_free(cc->profile_out_file);
121 static cmsHPROFILE color_man_cache_load_profile(ColorManProfileType type, const gchar *file,
122 guchar *data, guint data_len)
124 cmsHPROFILE profile = nullptr;
128 case COLOR_PROFILE_FILE:
133 pathl = path_from_utf8(file);
134 profile = cmsOpenProfileFromFile(pathl, "r");
138 case COLOR_PROFILE_SRGB:
139 profile = cmsCreate_sRGBProfile();
141 case COLOR_PROFILE_ADOBERGB:
142 profile = color_man_create_adobe_comp();
144 case COLOR_PROFILE_MEM:
147 profile = cmsOpenProfileFromMem(data, data_len);
150 case COLOR_PROFILE_NONE:
158 static ColorManCache *color_man_cache_new(ColorManProfileType in_type, const gchar *in_file,
159 guchar *in_data, guint in_data_len,
160 ColorManProfileType out_type, const gchar *out_file,
161 guchar *out_data, guint out_data_len,
166 color_man_lib_init();
168 cc = g_new0(ColorManCache, 1);
171 cc->profile_in_type = in_type;
172 cc->profile_in_file = g_strdup(in_file);
174 cc->profile_out_type = out_type;
175 cc->profile_out_file = g_strdup(out_file);
177 cc->has_alpha = has_alpha;
179 cc->profile_in = color_man_cache_load_profile(cc->profile_in_type, cc->profile_in_file,
180 in_data, in_data_len);
181 cc->profile_out = color_man_cache_load_profile(cc->profile_out_type, cc->profile_out_file,
182 out_data, out_data_len);
184 if (!cc->profile_in || !cc->profile_out)
186 DEBUG_1("failed to load color profile for %s: %d %s",
187 (!cc->profile_in) ? "input" : "screen",
188 (!cc->profile_in) ? cc->profile_in_type : cc->profile_out_type,
189 (!cc->profile_in) ? cc->profile_in_file : cc->profile_out_file);
191 color_man_cache_unref(cc);
195 cc->transform = cmsCreateTransform(cc->profile_in,
196 (has_alpha) ? TYPE_RGBA_8 : TYPE_RGB_8,
198 (has_alpha) ? TYPE_RGBA_8 : TYPE_RGB_8,
199 options->color_profile.render_intent, 0);
203 DEBUG_1("failed to create color profile transform");
205 color_man_cache_unref(cc);
209 if (cc->profile_in_type != COLOR_PROFILE_MEM && cc->profile_out_type != COLOR_PROFILE_MEM )
211 cm_cache_list = g_list_append(cm_cache_list, cc);
212 color_man_cache_ref(cc);
218 static void color_man_cache_free(ColorManCache *cc)
222 cm_cache_list = g_list_remove(cm_cache_list, cc);
223 color_man_cache_unref(cc);
226 static void color_man_cache_reset()
228 while (cm_cache_list)
232 cc = static_cast<ColorManCache *>(cm_cache_list->data);
233 color_man_cache_free(cc);
237 static ColorManCache *color_man_cache_find(ColorManProfileType in_type, const gchar *in_file,
238 ColorManProfileType out_type, const gchar *out_file,
243 work = cm_cache_list;
247 gboolean match = FALSE;
249 cc = static_cast<ColorManCache *>(work->data);
252 if (cc->profile_in_type == in_type &&
253 cc->profile_out_type == out_type &&
254 cc->has_alpha == has_alpha)
259 if (match && cc->profile_in_type == COLOR_PROFILE_FILE)
261 match = (cc->profile_in_file && in_file &&
262 strcmp(cc->profile_in_file, in_file) == 0);
264 if (match && cc->profile_out_type == COLOR_PROFILE_FILE)
266 match = (cc->profile_out_file && out_file &&
267 strcmp(cc->profile_out_file, out_file) == 0);
270 if (match) return cc;
276 static ColorManCache *color_man_cache_get(ColorManProfileType in_type, const gchar *in_file,
277 guchar *in_data, guint in_data_len,
278 ColorManProfileType out_type, const gchar *out_file,
279 guchar *out_data, guint out_data_len,
284 cc = color_man_cache_find(in_type, in_file, out_type, out_file, has_alpha);
287 color_man_cache_ref(cc);
291 return color_man_cache_new(in_type, in_file, in_data, in_data_len,
292 out_type, out_file, out_data, out_data_len, has_alpha);
297 *-------------------------------------------------------------------
299 *-------------------------------------------------------------------
302 #pragma GCC diagnostic push
303 #pragma GCC diagnostic ignored "-Wunused-function"
304 static void color_man_done_unused(ColorMan *cm, ColorManReturnType type)
308 cm->func_done(cm, type, cm->func_done_data);
311 #pragma GCC diagnostic pop
313 void color_man_correct_region(ColorMan *cm, GdkPixbuf *pixbuf, gint x, gint y, gint w, gint h)
323 pixbuf_width = gdk_pixbuf_get_width(pixbuf);
324 pixbuf_height = gdk_pixbuf_get_height(pixbuf);
326 cc = static_cast<ColorManCache *>(cm->profile);
328 pix = gdk_pixbuf_get_pixels(pixbuf);
329 rs = gdk_pixbuf_get_rowstride(pixbuf);
331 /** @FIXME: x,y expected to be = 0. Maybe this is not the right place for scaling */
332 w = w * scale_factor();
333 h = h * scale_factor();
335 w = MIN(w, pixbuf_width - x);
336 h = MIN(h, pixbuf_height - y);
338 pix += x * ((cc->has_alpha) ? 4 : 3);
339 for (i = 0; i < h; i++)
343 pbuf = pix + ((y + i) * rs);
345 cmsDoTransform(cc->transform, pbuf, pbuf, w);
350 #pragma GCC diagnostic push
351 #pragma GCC diagnostic ignored "-Wunused-function"
352 static gboolean color_man_idle_cb_unused(gpointer data)
354 auto *cm = static_cast<ColorMan *>(data);
359 if (!cm->pixbuf) return FALSE;
362 cm->pixbuf != image_get_pixbuf(cm->imd))
365 color_man_done_unused(cm, COLOR_RETURN_IMAGE_CHANGED);
369 width = gdk_pixbuf_get_width(cm->pixbuf);
370 height = gdk_pixbuf_get_height(cm->pixbuf);
372 if (cm->row > height)
374 if (!cm->incremental_sync && cm->imd)
376 image_area_changed(cm->imd, 0, 0, width, height);
380 color_man_done_unused(cm, COLOR_RETURN_SUCCESS);
384 rh = COLOR_MAN_CHUNK_SIZE / width + 1;
385 color_man_correct_region(cm, cm->pixbuf, 0, cm->row, width, rh);
386 if (cm->incremental_sync && cm->imd) image_area_changed(cm->imd, 0, cm->row, width, rh);
391 #pragma GCC diagnostic pop
393 static ColorMan *color_man_new_real(ImageWindow *imd, GdkPixbuf *pixbuf,
394 ColorManProfileType input_type, const gchar *input_file,
395 guchar *input_data, guint input_data_len,
396 ColorManProfileType screen_type, const gchar *screen_file,
397 guchar *screen_data, guint screen_data_len)
402 if (imd) pixbuf = image_get_pixbuf(imd);
404 cm = g_new0(ColorMan, 1);
407 if (cm->pixbuf) g_object_ref(cm->pixbuf);
409 has_alpha = pixbuf ? gdk_pixbuf_get_has_alpha(pixbuf) : FALSE;
411 cm->profile = color_man_cache_get(input_type, input_file, input_data, input_data_len,
412 screen_type, screen_file, screen_data, screen_data_len, has_alpha);
422 ColorMan *color_man_new(ImageWindow *imd, GdkPixbuf *pixbuf,
423 ColorManProfileType input_type, const gchar *input_file,
424 ColorManProfileType screen_type, const gchar *screen_file,
425 guchar *screen_data, guint screen_data_len)
427 return color_man_new_real(imd, pixbuf,
428 input_type, input_file, nullptr, 0,
429 screen_type, screen_file, screen_data, screen_data_len);
432 #pragma GCC diagnostic push
433 #pragma GCC diagnostic ignored "-Wunused-function"
434 void color_man_start_bg_unused(ColorMan *cm, ColorMan::DoneFunc done_func, gpointer done_data)
436 cm->func_done = done_func;
437 cm->func_done_data = done_data;
438 cm->idle_id = g_idle_add(color_man_idle_cb_unused, cm);
440 #pragma GCC diagnostic pop
442 ColorMan *color_man_new_embedded(ImageWindow *imd, GdkPixbuf *pixbuf,
443 guchar *input_data, guint input_data_len,
444 ColorManProfileType screen_type, const gchar *screen_file,
445 guchar *screen_data, guint screen_data_len)
447 return color_man_new_real(imd, pixbuf,
448 COLOR_PROFILE_MEM, nullptr, input_data, input_data_len,
449 screen_type, screen_file, screen_data, screen_data_len);
452 static gchar *color_man_get_profile_name(ColorManProfileType type, cmsHPROFILE profile)
456 case COLOR_PROFILE_SRGB:
457 return g_strdup(_("sRGB"));
458 case COLOR_PROFILE_ADOBERGB:
459 return g_strdup(_("Adobe RGB compatible"));
461 case COLOR_PROFILE_MEM:
462 case COLOR_PROFILE_FILE:
468 cmsGetProfileInfoASCII(profile, cmsInfoDescription, "en", "US", buffer, 20);
469 buffer[19] = '\0'; /* Just to be sure */
470 return g_strdup(buffer);
472 return g_strdup(cmsTakeProductName(profile));
475 return g_strdup(_("Custom profile"));
477 case COLOR_PROFILE_NONE:
483 gboolean color_man_get_status(ColorMan *cm, gchar **image_profile, gchar **screen_profile)
486 if (!cm) return FALSE;
488 cc = static_cast<ColorManCache *>(cm->profile);
490 if (image_profile) *image_profile = color_man_get_profile_name(cc->profile_in_type, cc->profile_in);
491 if (screen_profile) *screen_profile = color_man_get_profile_name(cc->profile_out_type, cc->profile_out);
495 void color_man_free(ColorMan *cm)
499 if (cm->idle_id) g_source_remove(cm->idle_id);
500 if (cm->pixbuf) g_object_unref(cm->pixbuf);
502 color_man_cache_unref(static_cast<ColorManCache *>(cm->profile));
507 void color_man_update()
509 color_man_cache_reset();
514 #include <libheif/heif.h>
516 static cmsToneCurve* colorspaces_create_transfer(int32_t size, double (*fct)(double))
518 std::vector<float> values;
519 values.reserve(size);
520 for(int32_t i = 0; i < size; ++i)
522 const double x = static_cast<float>(i) / (size - 1);
523 const double y = MIN(fct(x), 1.0F);
524 values.push_back(static_cast<float>(y));
527 return cmsBuildTabulatedToneCurveFloat(nullptr, size, values.data());
530 // https://www.itu.int/dms_pubrec/itu-r/rec/bt/R-REC-BT.2100-2-201807-I!!PDF-F.pdf
532 static double HLG_fct(double x)
534 static const double Beta = 0.04;
535 static const double RA = 5.591816309728916; // 1.0 / A where A = 0.17883277
536 static const double B = 0.28466892; // 1.0 - 4.0 * A
537 static const double C = 0.5599107295; // 0,5 –aln(4a)
539 double e = MAX(x * (1.0 - Beta) + Beta, 0.0);
541 if(e == 0.0) return 0.0;
543 const double sign = e;
554 res = (exp((e - C) * RA) + B) / 12.0;
557 return copysign(res, sign);
560 static double PQ_fct(double x)
562 static const double M1 = 2610.0 / 16384.0;
563 static const double M2 = (2523.0 / 4096.0) * 128.0;
564 static const double C1 = 3424.0 / 4096.0;
565 static const double C2 = (2413.0 / 4096.0) * 32.0;
566 static const double C3 = (2392.0 / 4096.0) * 32.0;
568 if(x == 0.0) return 0.0;
569 const double sign = x;
572 const double xpo = pow(x, 1.0 / M2);
573 const double num = MAX(xpo - C1, 0.0);
574 const double den = C2 - C3 * xpo;
575 const double res = pow(num / den, 1.0 / M1);
577 return copysign(res, sign);
586 * Copied from: gimp/libgimpcolor/gimpcolorprofile.c
588 static guchar *nclx_to_lcms_profile(const struct heif_color_profile_nclx *nclx, guint *profile_len)
590 const gchar *primaries_name = "";
591 const gchar *trc_name = "";
592 cmsHPROFILE *profile = nullptr;
593 cmsCIExyY whitepoint;
594 cmsCIExyYTRIPLE primaries;
595 cmsToneCurve *curve[3];
596 cmsUInt32Number size;
597 guint8 *data = nullptr;
599 cmsFloat64Number srgb_parameters[5] =
600 { 2.4, 1.0 / 1.055, 0.055 / 1.055, 1.0 / 12.92, 0.04045 };
602 cmsFloat64Number rec709_parameters[5] =
603 { 2.2, 1.0 / 1.099, 0.099 / 1.099, 1.0 / 4.5, 0.081 };
610 if (nclx->color_primaries == heif_color_primaries_unspecified)
615 whitepoint.x = nclx->color_primary_white_x;
616 whitepoint.y = nclx->color_primary_white_y;
619 primaries.Red.x = nclx->color_primary_red_x;
620 primaries.Red.y = nclx->color_primary_red_y;
621 primaries.Red.Y = 1.0F;
623 primaries.Green.x = nclx->color_primary_green_x;
624 primaries.Green.y = nclx->color_primary_green_y;
625 primaries.Green.Y = 1.0F;
627 primaries.Blue.x = nclx->color_primary_blue_x;
628 primaries.Blue.y = nclx->color_primary_blue_y;
629 primaries.Blue.Y = 1.0F;
631 switch (nclx->color_primaries)
633 case heif_color_primaries_ITU_R_BT_709_5:
634 primaries_name = "BT.709";
636 case heif_color_primaries_ITU_R_BT_470_6_System_M:
637 primaries_name = "BT.470-6 System M";
639 case heif_color_primaries_ITU_R_BT_470_6_System_B_G:
640 primaries_name = "BT.470-6 System BG";
642 case heif_color_primaries_ITU_R_BT_601_6:
643 primaries_name = "BT.601";
645 case heif_color_primaries_SMPTE_240M:
646 primaries_name = "SMPTE 240M";
648 case heif_color_primaries_generic_film:
649 primaries_name = "Generic film";
651 case heif_color_primaries_ITU_R_BT_2020_2_and_2100_0:
652 primaries_name = "BT.2020";
654 case heif_color_primaries_SMPTE_ST_428_1:
655 primaries_name = "SMPTE ST 428-1";
657 case heif_color_primaries_SMPTE_RP_431_2:
658 primaries_name = "SMPTE RP 431-2";
660 case heif_color_primaries_SMPTE_EG_432_1:
661 primaries_name = "SMPTE EG 432-1 (DCI P3)";
663 case heif_color_primaries_EBU_Tech_3213_E:
664 primaries_name = "EBU Tech. 3213-E";
667 log_printf("nclx unsupported color_primaries value: %d\n", nclx->color_primaries);
672 DEBUG_1("nclx primaries: %s: ", primaries_name);
674 switch (nclx->transfer_characteristics)
676 case heif_transfer_characteristic_ITU_R_BT_709_5:
677 curve[0] = curve[1] = curve[2] = cmsBuildParametricToneCurve(nullptr, 4, rec709_parameters);
678 profile = static_cast<cmsHPROFILE *>(cmsCreateRGBProfile(&whitepoint, &primaries, curve));
679 cmsFreeToneCurve(curve[0]);
680 trc_name = "Rec709 RGB";
682 case heif_transfer_characteristic_ITU_R_BT_470_6_System_M:
683 curve[0] = curve[1] = curve[2] = cmsBuildGamma (nullptr, 2.2F);
684 profile = static_cast<cmsHPROFILE *>(cmsCreateRGBProfile(&whitepoint, &primaries, curve));
685 cmsFreeToneCurve(curve[0]);
686 trc_name = "Gamma2.2 RGB";
688 case heif_transfer_characteristic_ITU_R_BT_470_6_System_B_G:
689 curve[0] = curve[1] = curve[2] = cmsBuildGamma (nullptr, 2.8F);
690 profile = static_cast<cmsHPROFILE *>(cmsCreateRGBProfile(&whitepoint, &primaries, curve));
691 cmsFreeToneCurve(curve[0]);
692 trc_name = "Gamma2.8 RGB";
694 case heif_transfer_characteristic_linear:
695 curve[0] = curve[1] = curve[2] = cmsBuildGamma (nullptr, 1.0F);
696 profile = static_cast<cmsHPROFILE *>(cmsCreateRGBProfile(&whitepoint, &primaries, curve));
697 cmsFreeToneCurve(curve[0]);
698 trc_name = "linear RGB";
700 case heif_transfer_characteristic_ITU_R_BT_2100_0_HLG:
701 curve[0] = curve[1] = curve[2] = colorspaces_create_transfer(4096, HLG_fct);
702 profile = static_cast<cmsHPROFILE *>(cmsCreateRGBProfile(&whitepoint, &primaries, curve));
703 cmsFreeToneCurve(curve[0]);
704 trc_name = "HLG Rec2020 RGB";
706 case heif_transfer_characteristic_ITU_R_BT_2100_0_PQ:
707 curve[0] = curve[1] = curve[2] = colorspaces_create_transfer(4096, PQ_fct);
708 profile = static_cast<cmsHPROFILE *>(cmsCreateRGBProfile(&whitepoint, &primaries, curve));
709 cmsFreeToneCurve(curve[0]);
710 trc_name = "PQ Rec2020 RGB";
712 case heif_transfer_characteristic_IEC_61966_2_1:
713 /* same as default */
715 curve[0] = curve[1] = curve[2] = cmsBuildParametricToneCurve(nullptr, 4, srgb_parameters);
716 profile = static_cast<cmsHPROFILE *>(cmsCreateRGBProfile(&whitepoint, &primaries, curve));
717 cmsFreeToneCurve(curve[0]);
718 trc_name = "sRGB-TRC RGB";
722 DEBUG_1("nclx transfer characteristic: %s", trc_name);
726 if (cmsSaveProfileToMem(profile, nullptr, &size))
728 data = static_cast<guint8 *>(g_malloc(size));
729 if (cmsSaveProfileToMem(profile, data, &size))
733 cmsCloseProfile(profile);
734 return static_cast<guchar *>(data);
737 cmsCloseProfile(profile);
744 guchar *heif_color_profile(FileData *fd, guint *profile_len)
746 struct heif_context* ctx;
747 struct heif_error error_code;
748 struct heif_image_handle* handle;
749 struct heif_color_profile_nclx *nclxcp;
752 cmsUInt32Number size;
753 guint8 *data = nullptr;
755 ctx = heif_context_alloc();
756 error_code = heif_context_read_from_file(ctx, fd->path, nullptr);
760 log_printf("warning: heif reader error: %s\n", error_code.message);
761 heif_context_free(ctx);
765 error_code = heif_context_get_primary_image_handle(ctx, &handle);
768 log_printf("warning: heif reader error: %s\n", error_code.message);
769 heif_context_free(ctx);
773 nclxcp = heif_nclx_color_profile_alloc();
774 profile_type = heif_image_handle_get_color_profile_type(handle);
776 if (profile_type == heif_color_profile_type_prof)
778 size = heif_image_handle_get_raw_color_profile_size(handle);
780 data = static_cast<guint8 *>(g_malloc0(size));
781 error_code = heif_image_handle_get_raw_color_profile(handle, data);
784 log_printf("warning: heif reader error: %s\n", error_code.message);
785 heif_context_free(ctx);
786 heif_nclx_color_profile_free(nclxcp);
790 DEBUG_1("heif color profile type: prof");
791 heif_context_free(ctx);
792 heif_nclx_color_profile_free(nclxcp);
794 return static_cast<guchar *>(data);
797 error_code = heif_image_handle_get_nclx_color_profile(handle, &nclxcp);
800 log_printf("warning: heif reader error: %s\n", error_code.message);
801 heif_context_free(ctx);
802 heif_nclx_color_profile_free(nclxcp);
806 profile = nclx_to_lcms_profile(nclxcp, profile_len);
808 heif_context_free(ctx);
809 heif_nclx_color_profile_free(nclxcp);
814 guchar *heif_color_profile(FileData *, guint *)
820 #else /* define HAVE_LCMS */
821 /*** color support not enabled ***/
824 ColorMan *color_man_new(ImageWindow *, GdkPixbuf *,
825 ColorManProfileType, const gchar *,
826 ColorManProfileType, const gchar *,
833 ColorMan *color_man_new_embedded(ImageWindow *, GdkPixbuf *,
835 ColorManProfileType, const gchar *,
842 void color_man_free(ColorMan *)
847 void color_man_update()
852 void color_man_correct_region(ColorMan *, GdkPixbuf *, gint, gint, gint, gint)
857 gboolean color_man_get_status(ColorMan *, gchar **, gchar **)
862 guchar *heif_color_profile(FileData *, guint *)
867 #endif /* define HAVE_LCMS */
868 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */