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"
30 /*** color support enabled ***/
39 typedef struct _ColorManCache ColorManCache;
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(void)
62 static gboolean init_done = FALSE;
64 if (init_done) return;
68 cmsErrorAction(LCMS_ERROR_IGNORE);
72 static cmsHPROFILE color_man_create_adobe_comp(void)
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 = NULL;
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 = NULL;
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(void)
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 //static void color_man_done(ColorMan *cm, ColorManReturnType type)
298 //cm->func_done(cm, type, cm->func_done_data);
302 void color_man_correct_region(ColorMan *cm, GdkPixbuf *pixbuf, gint x, gint y, gint w, gint h)
308 gint pixbuf_width, pixbuf_height;
311 pixbuf_width = gdk_pixbuf_get_width(pixbuf);
312 pixbuf_height = gdk_pixbuf_get_height(pixbuf);
314 cc = static_cast<ColorManCache *>(cm->profile);
316 pix = gdk_pixbuf_get_pixels(pixbuf);
317 rs = gdk_pixbuf_get_rowstride(pixbuf);
319 /** @FIXME: x,y expected to be = 0. Maybe this is not the right place for scaling */
320 w = w * scale_factor();
321 h = h * scale_factor();
323 w = MIN(w, pixbuf_width - x);
324 h = MIN(h, pixbuf_height - y);
326 pix += x * ((cc->has_alpha) ? 4 : 3);
327 for (i = 0; i < h; i++)
331 pbuf = pix + ((y + i) * rs);
333 cmsDoTransform(cc->transform, pbuf, pbuf, w);
338 //static gboolean color_man_idle_cb(gpointer data)
340 //ColorMan *cm = static_cast<ColorMan *>(data);
341 //gint width, height;
344 //if (!cm->pixbuf) return FALSE;
347 //cm->pixbuf != image_get_pixbuf(cm->imd))
350 //color_man_done(cm, COLOR_RETURN_IMAGE_CHANGED);
354 //width = gdk_pixbuf_get_width(cm->pixbuf);
355 //height = gdk_pixbuf_get_height(cm->pixbuf);
357 //if (cm->row > height)
359 //if (!cm->incremental_sync && cm->imd)
361 //image_area_changed(cm->imd, 0, 0, width, height);
365 //color_man_done(cm, COLOR_RETURN_SUCCESS);
369 //rh = COLOR_MAN_CHUNK_SIZE / width + 1;
370 //color_man_correct_region(cm, cm->pixbuf, 0, cm->row, width, rh);
371 //if (cm->incremental_sync && cm->imd) image_area_changed(cm->imd, 0, cm->row, width, rh);
377 static ColorMan *color_man_new_real(ImageWindow *imd, GdkPixbuf *pixbuf,
378 ColorManProfileType input_type, const gchar *input_file,
379 guchar *input_data, guint input_data_len,
380 ColorManProfileType screen_type, const gchar *screen_file,
381 guchar *screen_data, guint screen_data_len)
386 if (imd) pixbuf = image_get_pixbuf(imd);
388 cm = g_new0(ColorMan, 1);
391 if (cm->pixbuf) g_object_ref(cm->pixbuf);
393 has_alpha = pixbuf ? gdk_pixbuf_get_has_alpha(pixbuf) : FALSE;
395 cm->profile = color_man_cache_get(input_type, input_file, input_data, input_data_len,
396 screen_type, screen_file, screen_data, screen_data_len, has_alpha);
406 ColorMan *color_man_new(ImageWindow *imd, GdkPixbuf *pixbuf,
407 ColorManProfileType input_type, const gchar *input_file,
408 ColorManProfileType screen_type, const gchar *screen_file,
409 guchar *screen_data, guint screen_data_len)
411 return color_man_new_real(imd, pixbuf,
412 input_type, input_file, NULL, 0,
413 screen_type, screen_file, screen_data, screen_data_len);
416 //void color_man_start_bg(ColorMan *cm, ColorManDoneFunc done_func, gpointer done_data)
418 //cm->func_done = done_func;
419 //cm->func_done_data = done_data;
420 //cm->idle_id = g_idle_add(color_man_idle_cb, cm);
423 ColorMan *color_man_new_embedded(ImageWindow *imd, GdkPixbuf *pixbuf,
424 guchar *input_data, guint input_data_len,
425 ColorManProfileType screen_type, const gchar *screen_file,
426 guchar *screen_data, guint screen_data_len)
428 return color_man_new_real(imd, pixbuf,
429 COLOR_PROFILE_MEM, NULL, input_data, input_data_len,
430 screen_type, screen_file, screen_data, screen_data_len);
433 static gchar *color_man_get_profile_name(ColorManProfileType type, cmsHPROFILE profile)
437 case COLOR_PROFILE_SRGB:
438 return g_strdup(_("sRGB"));
439 case COLOR_PROFILE_ADOBERGB:
440 return g_strdup(_("Adobe RGB compatible"));
442 case COLOR_PROFILE_MEM:
443 case COLOR_PROFILE_FILE:
449 cmsGetProfileInfoASCII(profile, cmsInfoDescription, "en", "US", buffer, 20);
450 buffer[19] = '\0'; /* Just to be sure */
451 return g_strdup(buffer);
453 return g_strdup(cmsTakeProductName(profile));
456 return g_strdup(_("Custom profile"));
458 case COLOR_PROFILE_NONE:
464 gboolean color_man_get_status(ColorMan *cm, gchar **image_profile, gchar **screen_profile)
467 if (!cm) return FALSE;
469 cc = static_cast<ColorManCache *>(cm->profile);
471 if (image_profile) *image_profile = color_man_get_profile_name(cc->profile_in_type, cc->profile_in);
472 if (screen_profile) *screen_profile = color_man_get_profile_name(cc->profile_out_type, cc->profile_out);
476 void color_man_free(ColorMan *cm)
480 if (cm->idle_id) g_source_remove(cm->idle_id);
481 if (cm->pixbuf) g_object_unref(cm->pixbuf);
483 color_man_cache_unref(static_cast<ColorManCache *>(cm->profile));
488 void color_man_update(void)
490 color_man_cache_reset();
494 #include <libheif/heif.h>
497 static cmsToneCurve* colorspaces_create_transfer(int32_t size, double (*fct)(double))
499 float *values = static_cast<float *>(g_malloc(sizeof(float) * size));
501 for(int32_t i = 0; i < size; ++i)
503 const double x = (float)i / (size - 1);
504 const double y = MIN(fct(x), 1.0f);
505 values[i] = (float)y;
508 cmsToneCurve* result = cmsBuildTabulatedToneCurveFloat(NULL, size, values);
513 // https://www.itu.int/dms_pubrec/itu-r/rec/bt/R-REC-BT.2100-2-201807-I!!PDF-F.pdf
515 static double HLG_fct(double x)
517 static const double Beta = 0.04;
518 static const double RA = 5.591816309728916; // 1.0 / A where A = 0.17883277
519 static const double B = 0.28466892; // 1.0 - 4.0 * A
520 static const double C = 0.5599107295; // 0,5 –aln(4a)
522 double e = MAX(x * (1.0 - Beta) + Beta, 0.0);
524 if(e == 0.0) return 0.0;
526 const double sign = e;
537 res = (exp((e - C) * RA) + B) / 12.0;
540 return copysign(res, sign);
543 static double PQ_fct(double x)
545 static const double M1 = 2610.0 / 16384.0;
546 static const double M2 = (2523.0 / 4096.0) * 128.0;
547 static const double C1 = 3424.0 / 4096.0;
548 static const double C2 = (2413.0 / 4096.0) * 32.0;
549 static const double C3 = (2392.0 / 4096.0) * 32.0;
551 if(x == 0.0) return 0.0;
552 const double sign = x;
555 const double xpo = pow(x, 1.0 / M2);
556 const double num = MAX(xpo - C1, 0.0);
557 const double den = C2 - C3 * xpo;
558 const double res = pow(num / den, 1.0 / M1);
560 return copysign(res, sign);
569 * Copied from: gimp/libgimpcolor/gimpcolorprofile.c
571 static guchar *nclx_to_lcms_profile(const struct heif_color_profile_nclx *nclx, guint *profile_len)
573 const gchar *primaries_name = "";
574 const gchar *trc_name = "";
575 cmsHPROFILE *profile = NULL;
576 cmsCIExyY whitepoint;
577 cmsCIExyYTRIPLE primaries;
578 cmsToneCurve *curve[3];
579 cmsUInt32Number size;
582 cmsFloat64Number srgb_parameters[5] =
583 { 2.4, 1.0 / 1.055, 0.055 / 1.055, 1.0 / 12.92, 0.04045 };
585 cmsFloat64Number rec709_parameters[5] =
586 { 2.2, 1.0 / 1.099, 0.099 / 1.099, 1.0 / 4.5, 0.081 };
593 if (nclx->color_primaries == heif_color_primaries_unspecified)
598 whitepoint.x = nclx->color_primary_white_x;
599 whitepoint.y = nclx->color_primary_white_y;
602 primaries.Red.x = nclx->color_primary_red_x;
603 primaries.Red.y = nclx->color_primary_red_y;
604 primaries.Red.Y = 1.0f;
606 primaries.Green.x = nclx->color_primary_green_x;
607 primaries.Green.y = nclx->color_primary_green_y;
608 primaries.Green.Y = 1.0f;
610 primaries.Blue.x = nclx->color_primary_blue_x;
611 primaries.Blue.y = nclx->color_primary_blue_y;
612 primaries.Blue.Y = 1.0f;
614 switch (nclx->color_primaries)
616 case heif_color_primaries_ITU_R_BT_709_5:
617 primaries_name = "BT.709";
619 case heif_color_primaries_ITU_R_BT_470_6_System_M:
620 primaries_name = "BT.470-6 System M";
622 case heif_color_primaries_ITU_R_BT_470_6_System_B_G:
623 primaries_name = "BT.470-6 System BG";
625 case heif_color_primaries_ITU_R_BT_601_6:
626 primaries_name = "BT.601";
628 case heif_color_primaries_SMPTE_240M:
629 primaries_name = "SMPTE 240M";
631 case heif_color_primaries_generic_film:
632 primaries_name = "Generic film";
634 case heif_color_primaries_ITU_R_BT_2020_2_and_2100_0:
635 primaries_name = "BT.2020";
637 case heif_color_primaries_SMPTE_ST_428_1:
638 primaries_name = "SMPTE ST 428-1";
640 case heif_color_primaries_SMPTE_RP_431_2:
641 primaries_name = "SMPTE RP 431-2";
643 case heif_color_primaries_SMPTE_EG_432_1:
644 primaries_name = "SMPTE EG 432-1 (DCI P3)";
646 case heif_color_primaries_EBU_Tech_3213_E:
647 primaries_name = "EBU Tech. 3213-E";
650 log_printf("nclx unsupported color_primaries value: %d\n", nclx->color_primaries);
655 DEBUG_1("nclx primaries: %s: ", primaries_name);
657 switch (nclx->transfer_characteristics)
659 case heif_transfer_characteristic_ITU_R_BT_709_5:
660 curve[0] = curve[1] = curve[2] = cmsBuildParametricToneCurve(NULL, 4, rec709_parameters);
661 profile = static_cast<cmsHPROFILE *>(cmsCreateRGBProfile(&whitepoint, &primaries, curve));
662 cmsFreeToneCurve(curve[0]);
663 trc_name = "Rec709 RGB";
665 case heif_transfer_characteristic_ITU_R_BT_470_6_System_M:
666 curve[0] = curve[1] = curve[2] = cmsBuildGamma (NULL, 2.2f);
667 profile = static_cast<cmsHPROFILE *>(cmsCreateRGBProfile(&whitepoint, &primaries, curve));
668 cmsFreeToneCurve(curve[0]);
669 trc_name = "Gamma2.2 RGB";
671 case heif_transfer_characteristic_ITU_R_BT_470_6_System_B_G:
672 curve[0] = curve[1] = curve[2] = cmsBuildGamma (NULL, 2.8f);
673 profile = static_cast<cmsHPROFILE *>(cmsCreateRGBProfile(&whitepoint, &primaries, curve));
674 cmsFreeToneCurve(curve[0]);
675 trc_name = "Gamma2.8 RGB";
677 case heif_transfer_characteristic_linear:
678 curve[0] = curve[1] = curve[2] = cmsBuildGamma (NULL, 1.0f);
679 profile = static_cast<cmsHPROFILE *>(cmsCreateRGBProfile(&whitepoint, &primaries, curve));
680 cmsFreeToneCurve(curve[0]);
681 trc_name = "linear RGB";
683 case heif_transfer_characteristic_ITU_R_BT_2100_0_HLG:
684 curve[0] = curve[1] = curve[2] = colorspaces_create_transfer(4096, HLG_fct);
685 profile = static_cast<cmsHPROFILE *>(cmsCreateRGBProfile(&whitepoint, &primaries, curve));
686 cmsFreeToneCurve(curve[0]);
687 trc_name = "HLG Rec2020 RGB";
689 case heif_transfer_characteristic_ITU_R_BT_2100_0_PQ:
690 curve[0] = curve[1] = curve[2] = colorspaces_create_transfer(4096, PQ_fct);
691 profile = static_cast<cmsHPROFILE *>(cmsCreateRGBProfile(&whitepoint, &primaries, curve));
692 cmsFreeToneCurve(curve[0]);
693 trc_name = "PQ Rec2020 RGB";
695 case heif_transfer_characteristic_IEC_61966_2_1:
696 /* same as default */
698 curve[0] = curve[1] = curve[2] = cmsBuildParametricToneCurve(NULL, 4, srgb_parameters);
699 profile = static_cast<cmsHPROFILE *>(cmsCreateRGBProfile(&whitepoint, &primaries, curve));
700 cmsFreeToneCurve(curve[0]);
701 trc_name = "sRGB-TRC RGB";
705 DEBUG_1("nclx transfer characteristic: %s", trc_name);
709 if (cmsSaveProfileToMem(profile, NULL, &size))
711 data = static_cast<guint8 *>(g_malloc(size));
712 if (cmsSaveProfileToMem(profile, data, &size))
716 cmsCloseProfile(profile);
717 return (guchar *)data;
721 cmsCloseProfile(profile);
731 guchar *heif_color_profile(FileData *fd, guint *profile_len)
733 struct heif_context* ctx;
734 struct heif_error error_code;
735 struct heif_image_handle* handle;
736 struct heif_color_profile_nclx *nclxcp;
739 cmsUInt32Number size;
742 ctx = heif_context_alloc();
743 error_code = heif_context_read_from_file(ctx, fd->path, NULL);
747 log_printf("warning: heif reader error: %s\n", error_code.message);
748 heif_context_free(ctx);
752 error_code = heif_context_get_primary_image_handle(ctx, &handle);
755 log_printf("warning: heif reader error: %s\n", error_code.message);
756 heif_context_free(ctx);
760 nclxcp = heif_nclx_color_profile_alloc();
761 profile_type = heif_image_handle_get_color_profile_type(handle);
763 if (profile_type == heif_color_profile_type_prof)
765 size = heif_image_handle_get_raw_color_profile_size(handle);
767 data = static_cast<guint8 *>(g_malloc0(size));
768 error_code = heif_image_handle_get_raw_color_profile(handle, data);
771 log_printf("warning: heif reader error: %s\n", error_code.message);
772 heif_context_free(ctx);
773 heif_nclx_color_profile_free(nclxcp);
777 DEBUG_1("heif color profile type: prof");
778 heif_context_free(ctx);
779 heif_nclx_color_profile_free(nclxcp);
781 return (guchar *)data;
785 error_code = heif_image_handle_get_nclx_color_profile(handle, &nclxcp);
788 log_printf("warning: heif reader error: %s\n", error_code.message);
789 heif_context_free(ctx);
790 heif_nclx_color_profile_free(nclxcp);
794 profile = nclx_to_lcms_profile(nclxcp, profile_len);
797 heif_context_free(ctx);
798 heif_nclx_color_profile_free(nclxcp);
800 return (guchar *)profile;
803 guchar *heif_color_profile(FileData *UNUSED(fd), guint *UNUSED(profile_len))
809 #else /* define HAVE_LCMS */
810 /*** color support not enabled ***/
813 ColorMan *color_man_new(ImageWindow *UNUSED(imd), GdkPixbuf *UNUSED(pixbuf),
814 ColorManProfileType UNUSED(input_type), const gchar *UNUSED(input_file),
815 ColorManProfileType UNUSED(screen_type), const gchar *UNUSED(screen_file),
816 guchar *UNUSED(screen_data), guint UNUSED(screen_data_len))
822 ColorMan *color_man_new_embedded(ImageWindow *UNUSED(imd), GdkPixbuf *UNUSED(pixbuf),
823 guchar *UNUSED(input_data), guint UNUSED(input_data_len),
824 ColorManProfileType UNUSED(screen_type), const gchar *UNUSED(screen_file),
825 guchar *UNUSED(screen_data), guint UNUSED(screen_data_len))
831 void color_man_free(ColorMan *UNUSED(cm))
836 void color_man_update(void)
841 void color_man_correct_region(ColorMan *UNUSED(cm), GdkPixbuf *UNUSED(pixbuf), gint UNUSED(x), gint UNUSED(y), gint UNUSED(w), gint UNUSED(h))
846 void color_man_start_bg(ColorMan *UNUSED(cm), ColorManDoneFunc UNUSED(done_func), gpointer UNUSED(done_data))
851 gboolean color_man_get_status(ColorMan *UNUSED(cm), gchar **UNUSED(image_profile), gchar **UNUSED(screen_profile))
856 guchar *heif_color_profile(FileData *UNUSED(fd), guint *UNUSED(profile_len))
861 #endif /* define HAVE_LCMS */
862 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */