Deduplicate cell_renderer_height_override()
[geeqie.git] / src / color-man.cc
1 /*
2  * Copyright (C) 2006 John Ellis
3  * Copyright (C) 2008 - 2016 The Geeqie Team
4  *
5  * Author: John Ellis
6  *
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.
11  *
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.
16  *
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.
20  */
21
22 #include "color-man.h"
23
24 #include <config.h>
25
26 #if HAVE_LCMS
27 /*** color support enabled ***/
28
29 #include <cstdint>
30 #include <cstring>
31 #include <vector>
32
33 #include <glib-object.h>
34
35 #if HAVE_LCMS2
36 #  include <lcms2.h>
37 #else
38 #  include <lcms.h>
39 #endif
40
41 #include "debug.h"
42 #include "filedata.h"
43 #include "image.h"
44 #include "intl.h"
45 #include "options.h"
46 #include "ui-fileops.h"
47
48 struct ColorManCache {
49         cmsHPROFILE   profile_in;
50         cmsHPROFILE   profile_out;
51         cmsHTRANSFORM transform;
52
53         ColorManProfileType profile_in_type;
54         gchar *profile_in_file;
55
56         ColorManProfileType profile_out_type;
57         gchar *profile_out_file;
58
59         gboolean has_alpha;
60
61         gint refcount;
62 };
63
64 /* pixels to transform per idle call */
65 #define COLOR_MAN_CHUNK_SIZE 81900
66
67
68 static void color_man_lib_init()
69 {
70         static gboolean init_done = FALSE;
71
72         if (init_done) return;
73         init_done = TRUE;
74
75 #if !HAVE_LCMS2
76         cmsErrorAction(LCMS_ERROR_IGNORE);
77 #endif
78 }
79
80 static cmsHPROFILE color_man_create_adobe_comp()
81 {
82         /* ClayRGB1998 is AdobeRGB compatible */
83 #include "ClayRGB1998_icc.h" // IWYU pragma: keep
84         return cmsOpenProfileFromMem(ClayRGB1998_icc, ClayRGB1998_icc_len);
85 }
86
87 /*
88  *-------------------------------------------------------------------
89  * color transform cache
90  *-------------------------------------------------------------------
91  */
92
93 static GList *cm_cache_list = nullptr;
94
95
96 static void color_man_cache_ref(ColorManCache *cc)
97 {
98         if (!cc) return;
99
100         cc->refcount++;
101 }
102
103 static void color_man_cache_unref(ColorManCache *cc)
104 {
105         if (!cc) return;
106
107         cc->refcount--;
108         if (cc->refcount < 1)
109                 {
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);
113
114                 g_free(cc->profile_in_file);
115                 g_free(cc->profile_out_file);
116
117                 g_free(cc);
118                 }
119 }
120
121 static cmsHPROFILE color_man_cache_load_profile(ColorManProfileType type, const gchar *file,
122                                                 guchar *data, guint data_len)
123 {
124         cmsHPROFILE profile = nullptr;
125
126         switch (type)
127                 {
128                 case COLOR_PROFILE_FILE:
129                         if (file)
130                                 {
131                                 gchar *pathl;
132
133                                 pathl = path_from_utf8(file);
134                                 profile = cmsOpenProfileFromFile(pathl, "r");
135                                 g_free(pathl);
136                                 }
137                         break;
138                 case COLOR_PROFILE_SRGB:
139                         profile = cmsCreate_sRGBProfile();
140                         break;
141                 case COLOR_PROFILE_ADOBERGB:
142                         profile = color_man_create_adobe_comp();
143                         break;
144                 case COLOR_PROFILE_MEM:
145                         if (data)
146                                 {
147                                 profile = cmsOpenProfileFromMem(data, data_len);
148                                 }
149                         break;
150                 case COLOR_PROFILE_NONE:
151                 default:
152                         break;
153                 }
154
155         return profile;
156 }
157
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,
162                                           gboolean has_alpha)
163 {
164         ColorManCache *cc;
165
166         color_man_lib_init();
167
168         cc = g_new0(ColorManCache, 1);
169         cc->refcount = 1;
170
171         cc->profile_in_type = in_type;
172         cc->profile_in_file = g_strdup(in_file);
173
174         cc->profile_out_type = out_type;
175         cc->profile_out_file = g_strdup(out_file);
176
177         cc->has_alpha = has_alpha;
178
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);
183
184         if (!cc->profile_in || !cc->profile_out)
185                 {
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);
190
191                 color_man_cache_unref(cc);
192                 return nullptr;
193                 }
194
195         cc->transform = cmsCreateTransform(cc->profile_in,
196                                            (has_alpha) ? TYPE_RGBA_8 : TYPE_RGB_8,
197                                            cc->profile_out,
198                                            (has_alpha) ? TYPE_RGBA_8 : TYPE_RGB_8,
199                                            options->color_profile.render_intent, 0);
200
201         if (!cc->transform)
202                 {
203                 DEBUG_1("failed to create color profile transform");
204
205                 color_man_cache_unref(cc);
206                 return nullptr;
207                 }
208
209         if (cc->profile_in_type != COLOR_PROFILE_MEM && cc->profile_out_type != COLOR_PROFILE_MEM )
210                 {
211                 cm_cache_list = g_list_append(cm_cache_list, cc);
212                 color_man_cache_ref(cc);
213                 }
214
215         return cc;
216 }
217
218 static void color_man_cache_free(ColorManCache *cc)
219 {
220         if (!cc) return;
221
222         cm_cache_list = g_list_remove(cm_cache_list, cc);
223         color_man_cache_unref(cc);
224 }
225
226 static void color_man_cache_reset()
227 {
228         while (cm_cache_list)
229                 {
230                 ColorManCache *cc;
231
232                 cc = static_cast<ColorManCache *>(cm_cache_list->data);
233                 color_man_cache_free(cc);
234                 }
235 }
236
237 static ColorManCache *color_man_cache_find(ColorManProfileType in_type, const gchar *in_file,
238                                            ColorManProfileType out_type, const gchar *out_file,
239                                            gboolean has_alpha)
240 {
241         GList *work;
242
243         work = cm_cache_list;
244         while (work)
245                 {
246                 ColorManCache *cc;
247                 gboolean match = FALSE;
248
249                 cc = static_cast<ColorManCache *>(work->data);
250                 work = work->next;
251
252                 if (cc->profile_in_type == in_type &&
253                     cc->profile_out_type == out_type &&
254                     cc->has_alpha == has_alpha)
255                         {
256                         match = TRUE;
257                         }
258
259                 if (match && cc->profile_in_type == COLOR_PROFILE_FILE)
260                         {
261                         match = (cc->profile_in_file && in_file &&
262                                  strcmp(cc->profile_in_file, in_file) == 0);
263                         }
264                 if (match && cc->profile_out_type == COLOR_PROFILE_FILE)
265                         {
266                         match = (cc->profile_out_file && out_file &&
267                                  strcmp(cc->profile_out_file, out_file) == 0);
268                         }
269
270                 if (match) return cc;
271                 }
272
273         return nullptr;
274 }
275
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,
280                                           gboolean has_alpha)
281 {
282         ColorManCache *cc;
283
284         cc = color_man_cache_find(in_type, in_file, out_type, out_file, has_alpha);
285         if (cc)
286                 {
287                 color_man_cache_ref(cc);
288                 return cc;
289                 }
290
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);
293 }
294
295
296 /*
297  *-------------------------------------------------------------------
298  * color manager
299  *-------------------------------------------------------------------
300  */
301
302 #pragma GCC diagnostic push
303 #pragma GCC diagnostic ignored "-Wunused-function"
304 static void color_man_done_unused(ColorMan *cm, ColorManReturnType type)
305 {
306         if (cm->func_done)
307                 {
308                 cm->func_done(cm, type, cm->func_done_data);
309                 }
310 }
311 #pragma GCC diagnostic pop
312
313 void color_man_correct_region(ColorMan *cm, GdkPixbuf *pixbuf, gint x, gint y, gint w, gint h)
314 {
315         ColorManCache *cc;
316         guchar *pix;
317         gint rs;
318         gint i;
319         gint pixbuf_width;
320         gint pixbuf_height;
321
322
323         pixbuf_width = gdk_pixbuf_get_width(pixbuf);
324         pixbuf_height = gdk_pixbuf_get_height(pixbuf);
325
326         cc = static_cast<ColorManCache *>(cm->profile);
327
328         pix = gdk_pixbuf_get_pixels(pixbuf);
329         rs = gdk_pixbuf_get_rowstride(pixbuf);
330
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();
334
335         w = MIN(w, pixbuf_width - x);
336         h = MIN(h, pixbuf_height - y);
337
338         pix += x * ((cc->has_alpha) ? 4 : 3);
339         for (i = 0; i < h; i++)
340                 {
341                 guchar *pbuf;
342
343                 pbuf = pix + ((y + i) * rs);
344
345                 cmsDoTransform(cc->transform, pbuf, pbuf, w);
346                 }
347
348 }
349
350 #pragma GCC diagnostic push
351 #pragma GCC diagnostic ignored "-Wunused-function"
352 static gboolean color_man_idle_cb_unused(gpointer data)
353 {
354         auto *cm = static_cast<ColorMan *>(data);
355         gint width;
356         gint height;
357         gint rh;
358
359         if (!cm->pixbuf) return FALSE;
360
361         if (cm->imd &&
362             cm->pixbuf != image_get_pixbuf(cm->imd))
363                 {
364                 cm->idle_id = 0;
365                 color_man_done_unused(cm, COLOR_RETURN_IMAGE_CHANGED);
366                 return FALSE;
367                 }
368
369         width = gdk_pixbuf_get_width(cm->pixbuf);
370         height = gdk_pixbuf_get_height(cm->pixbuf);
371
372         if (cm->row > height)
373                 {
374                 if (!cm->incremental_sync && cm->imd)
375                         {
376                         image_area_changed(cm->imd, 0, 0, width, height);
377                         }
378
379                 cm->idle_id = 0;
380                 color_man_done_unused(cm, COLOR_RETURN_SUCCESS);
381                 return FALSE;
382                 }
383
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);
387         cm->row += rh;
388
389         return TRUE;
390 }
391 #pragma GCC diagnostic pop
392
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)
398 {
399         ColorMan *cm;
400         gboolean has_alpha;
401
402         if (imd) pixbuf = image_get_pixbuf(imd);
403
404         cm = g_new0(ColorMan, 1);
405         cm->imd = imd;
406         cm->pixbuf = pixbuf;
407         if (cm->pixbuf) g_object_ref(cm->pixbuf);
408
409         has_alpha = pixbuf ? gdk_pixbuf_get_has_alpha(pixbuf) : FALSE;
410
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);
413         if (!cm->profile)
414                 {
415                 color_man_free(cm);
416                 return nullptr;
417                 }
418
419         return cm;
420 }
421
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)
426 {
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);
430 }
431
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)
435 {
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);
439 }
440 #pragma GCC diagnostic pop
441
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)
446 {
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);
450 }
451
452 static gchar *color_man_get_profile_name(ColorManProfileType type, cmsHPROFILE profile)
453 {
454         switch (type)
455                 {
456                 case COLOR_PROFILE_SRGB:
457                         return g_strdup(_("sRGB"));
458                 case COLOR_PROFILE_ADOBERGB:
459                         return g_strdup(_("Adobe RGB compatible"));
460                         break;
461                 case COLOR_PROFILE_MEM:
462                 case COLOR_PROFILE_FILE:
463                         if (profile)
464                                 {
465 #if HAVE_LCMS2
466                                 char buffer[20];
467                                 buffer[0] = '\0';
468                                 cmsGetProfileInfoASCII(profile, cmsInfoDescription, "en", "US", buffer, 20);
469                                 buffer[19] = '\0'; /* Just to be sure */
470                                 return g_strdup(buffer);
471 #else
472                                 return g_strdup(cmsTakeProductName(profile));
473 #endif
474                                 }
475                         return g_strdup(_("Custom profile"));
476                         break;
477                 case COLOR_PROFILE_NONE:
478                 default:
479                         return g_strdup("");
480                 }
481 }
482
483 gboolean color_man_get_status(ColorMan *cm, gchar **image_profile, gchar **screen_profile)
484 {
485         ColorManCache *cc;
486         if (!cm) return FALSE;
487
488         cc = static_cast<ColorManCache *>(cm->profile);
489
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);
492         return TRUE;
493 }
494
495 void color_man_free(ColorMan *cm)
496 {
497         if (!cm) return;
498
499         if (cm->idle_id) g_source_remove(cm->idle_id);
500         if (cm->pixbuf) g_object_unref(cm->pixbuf);
501
502         color_man_cache_unref(static_cast<ColorManCache *>(cm->profile));
503
504         g_free(cm);
505 }
506
507 void color_man_update()
508 {
509         color_man_cache_reset();
510 }
511
512 #if HAVE_HEIF
513 #include <cmath>
514 #include <libheif/heif.h>
515
516 static cmsToneCurve* colorspaces_create_transfer(int32_t size, double (*fct)(double))
517 {
518         std::vector<float> values;
519         values.reserve(size);
520         for(int32_t i = 0; i < size; ++i)
521                 {
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));
525                 }
526
527         return cmsBuildTabulatedToneCurveFloat(nullptr, size, values.data());
528 }
529
530 // https://www.itu.int/dms_pubrec/itu-r/rec/bt/R-REC-BT.2100-2-201807-I!!PDF-F.pdf
531 // Hybrid Log-Gamma
532 static double HLG_fct(double x)
533 {
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)
538
539         double e = MAX(x * (1.0 - Beta) + Beta, 0.0);
540
541         if(e == 0.0) return 0.0;
542
543         const double sign = e;
544         e = fabs(e);
545
546         double res = 0.0;
547
548         if(e <= 0.5)
549                 {
550                 res = e * e / 3.0;
551                 }
552         else
553                 {
554                 res = (exp((e - C) * RA) + B) / 12.0;
555                 }
556
557         return copysign(res, sign);
558 }
559
560 static double PQ_fct(double x)
561 {
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;
567
568         if(x == 0.0) return 0.0;
569         const double sign = x;
570         x = fabs(x);
571
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);
576
577         return copysign(res, sign);
578 }
579
580 /**
581  * @brief
582  * @param nclx
583  * @param profile_len
584  * @returns
585  *
586  * Copied from: gimp/libgimpcolor/gimpcolorprofile.c
587  */
588 static guchar *nclx_to_lcms_profile(const struct heif_color_profile_nclx *nclx, guint *profile_len)
589 {
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;
598
599         cmsFloat64Number srgb_parameters[5] =
600         { 2.4, 1.0 / 1.055,  0.055 / 1.055, 1.0 / 12.92, 0.04045 };
601
602         cmsFloat64Number rec709_parameters[5] =
603         { 2.2, 1.0 / 1.099,  0.099 / 1.099, 1.0 / 4.5, 0.081 };
604
605         if (nclx == nullptr)
606                 {
607                 return nullptr;
608                 }
609
610         if (nclx->color_primaries == heif_color_primaries_unspecified)
611                 {
612                 return nullptr;
613                 }
614
615         whitepoint.x = nclx->color_primary_white_x;
616         whitepoint.y = nclx->color_primary_white_y;
617         whitepoint.Y = 1.0F;
618
619         primaries.Red.x = nclx->color_primary_red_x;
620         primaries.Red.y = nclx->color_primary_red_y;
621         primaries.Red.Y = 1.0F;
622
623         primaries.Green.x = nclx->color_primary_green_x;
624         primaries.Green.y = nclx->color_primary_green_y;
625         primaries.Green.Y = 1.0F;
626
627         primaries.Blue.x = nclx->color_primary_blue_x;
628         primaries.Blue.y = nclx->color_primary_blue_y;
629         primaries.Blue.Y = 1.0F;
630
631         switch (nclx->color_primaries)
632                 {
633                 case heif_color_primaries_ITU_R_BT_709_5:
634                         primaries_name = "BT.709";
635                         break;
636                 case   heif_color_primaries_ITU_R_BT_470_6_System_M:
637                         primaries_name = "BT.470-6 System M";
638                         break;
639                 case heif_color_primaries_ITU_R_BT_470_6_System_B_G:
640                         primaries_name = "BT.470-6 System BG";
641                         break;
642                 case heif_color_primaries_ITU_R_BT_601_6:
643                         primaries_name = "BT.601";
644                         break;
645                 case heif_color_primaries_SMPTE_240M:
646                         primaries_name = "SMPTE 240M";
647                         break;
648                 case heif_color_primaries_generic_film:
649                         primaries_name = "Generic film";
650                         break;
651                 case heif_color_primaries_ITU_R_BT_2020_2_and_2100_0:
652                         primaries_name = "BT.2020";
653                         break;
654                 case heif_color_primaries_SMPTE_ST_428_1:
655                         primaries_name = "SMPTE ST 428-1";
656                         break;
657                 case heif_color_primaries_SMPTE_RP_431_2:
658                         primaries_name = "SMPTE RP 431-2";
659                         break;
660                 case heif_color_primaries_SMPTE_EG_432_1:
661                         primaries_name = "SMPTE EG 432-1 (DCI P3)";
662                         break;
663                 case heif_color_primaries_EBU_Tech_3213_E:
664                         primaries_name = "EBU Tech. 3213-E";
665                         break;
666                 default:
667                         log_printf("nclx unsupported color_primaries value: %d\n", nclx->color_primaries);
668                         return nullptr;
669                         break;
670                 }
671
672         DEBUG_1("nclx primaries: %s: ", primaries_name);
673
674         switch (nclx->transfer_characteristics)
675                 {
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";
681                         break;
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";
687                         break;
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";
693                         break;
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";
699                         break;
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";
705                         break;
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";
711                         break;
712                 case heif_transfer_characteristic_IEC_61966_2_1:
713                 /* same as default */
714                 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";
719                         break;
720                 }
721
722         DEBUG_1("nclx transfer characteristic: %s", trc_name);
723
724         if (profile)
725                 {
726                 if (cmsSaveProfileToMem(profile, nullptr, &size))
727                         {
728                         data = static_cast<guint8 *>(g_malloc(size));
729                         if (cmsSaveProfileToMem(profile, data, &size))
730                                 {
731                                 *profile_len = size;
732                                 }
733                         cmsCloseProfile(profile);
734                         return static_cast<guchar *>(data);
735                         }
736
737                 cmsCloseProfile(profile);
738                 return nullptr;
739                 }
740
741         return nullptr;
742 }
743
744 guchar *heif_color_profile(FileData *fd, guint *profile_len)
745 {
746         struct heif_context* ctx;
747         struct heif_error error_code;
748         struct heif_image_handle* handle;
749         struct heif_color_profile_nclx *nclxcp;
750         gint profile_type;
751         guchar *profile;
752         cmsUInt32Number size;
753         guint8 *data = nullptr;
754
755         ctx = heif_context_alloc();
756         error_code = heif_context_read_from_file(ctx, fd->path, nullptr);
757
758         if (error_code.code)
759                 {
760                 log_printf("warning: heif reader error: %s\n", error_code.message);
761                 heif_context_free(ctx);
762                 return nullptr;
763                 }
764
765         error_code = heif_context_get_primary_image_handle(ctx, &handle);
766         if (error_code.code)
767                 {
768                 log_printf("warning: heif reader error: %s\n", error_code.message);
769                 heif_context_free(ctx);
770                 return nullptr;
771                 }
772
773         nclxcp = heif_nclx_color_profile_alloc();
774         profile_type = heif_image_handle_get_color_profile_type(handle);
775
776         if (profile_type == heif_color_profile_type_prof)
777                 {
778                 size = heif_image_handle_get_raw_color_profile_size(handle);
779                 *profile_len = size;
780                 data = static_cast<guint8 *>(g_malloc0(size));
781                 error_code = heif_image_handle_get_raw_color_profile(handle, data);
782                 if (error_code.code)
783                         {
784                         log_printf("warning: heif reader error: %s\n", error_code.message);
785                         heif_context_free(ctx);
786                         heif_nclx_color_profile_free(nclxcp);
787                         return nullptr;
788                         }
789
790                 DEBUG_1("heif color profile type: prof");
791                 heif_context_free(ctx);
792                 heif_nclx_color_profile_free(nclxcp);
793
794                 return static_cast<guchar *>(data);
795                 }
796
797         error_code = heif_image_handle_get_nclx_color_profile(handle, &nclxcp);
798         if (error_code.code)
799                 {
800                 log_printf("warning: heif reader error: %s\n", error_code.message);
801                 heif_context_free(ctx);
802                 heif_nclx_color_profile_free(nclxcp);
803                 return nullptr;
804                 }
805
806         profile = nclx_to_lcms_profile(nclxcp, profile_len);
807
808         heif_context_free(ctx);
809         heif_nclx_color_profile_free(nclxcp);
810
811         return profile;
812 }
813 #else
814 guchar *heif_color_profile(FileData *, guint *)
815 {
816         return NULL;
817 }
818 #endif
819
820 #else /* define HAVE_LCMS */
821 /*** color support not enabled ***/
822
823
824 ColorMan *color_man_new(ImageWindow *, GdkPixbuf *,
825                         ColorManProfileType, const gchar *,
826                         ColorManProfileType, const gchar *,
827                         guchar *, guint)
828 {
829         /* no op */
830         return nullptr;
831 }
832
833 ColorMan *color_man_new_embedded(ImageWindow *, GdkPixbuf *,
834                                  guchar *, guint,
835                                  ColorManProfileType, const gchar *,
836                                  guchar *, guint)
837 {
838         /* no op */
839         return nullptr;
840 }
841
842 void color_man_free(ColorMan *)
843 {
844         /* no op */
845 }
846
847 void color_man_update()
848 {
849         /* no op */
850 }
851
852 void color_man_correct_region(ColorMan *, GdkPixbuf *, gint, gint, gint, gint)
853 {
854         /* no op */
855 }
856
857 gboolean color_man_get_status(ColorMan *, gchar **, gchar **)
858 {
859         return FALSE;
860 }
861
862 guchar *heif_color_profile(FileData *, guint *)
863 {
864         return nullptr;
865 }
866
867 #endif /* define HAVE_LCMS */
868 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */