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