cdc7e87158dc3bd38c26e94b473aeb67faa34c99
[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;
313         gint pixbuf_height;
314
315
316         pixbuf_width = gdk_pixbuf_get_width(pixbuf);
317         pixbuf_height = gdk_pixbuf_get_height(pixbuf);
318
319         cc = static_cast<ColorManCache *>(cm->profile);
320
321         pix = gdk_pixbuf_get_pixels(pixbuf);
322         rs = gdk_pixbuf_get_rowstride(pixbuf);
323
324         /** @FIXME: x,y expected to be = 0. Maybe this is not the right place for scaling */
325         w = w * scale_factor();
326         h = h * scale_factor();
327
328         w = MIN(w, pixbuf_width - x);
329         h = MIN(h, pixbuf_height - y);
330
331         pix += x * ((cc->has_alpha) ? 4 : 3);
332         for (i = 0; i < h; i++)
333                 {
334                 guchar *pbuf;
335
336                 pbuf = pix + ((y + i) * rs);
337
338                 cmsDoTransform(cc->transform, pbuf, pbuf, w);
339                 }
340
341 }
342
343 #pragma GCC diagnostic push
344 #pragma GCC diagnostic ignored "-Wunused-function"
345 static gboolean color_man_idle_cb_unused(gpointer data)
346 {
347         auto *cm = static_cast<ColorMan *>(data);
348         gint width;
349         gint height;
350         gint rh;
351
352         if (!cm->pixbuf) return FALSE;
353
354         if (cm->imd &&
355             cm->pixbuf != image_get_pixbuf(cm->imd))
356                 {
357                 cm->idle_id = 0;
358                 color_man_done_unused(cm, COLOR_RETURN_IMAGE_CHANGED);
359                 return FALSE;
360                 }
361
362         width = gdk_pixbuf_get_width(cm->pixbuf);
363         height = gdk_pixbuf_get_height(cm->pixbuf);
364
365         if (cm->row > height)
366                 {
367                 if (!cm->incremental_sync && cm->imd)
368                         {
369                         image_area_changed(cm->imd, 0, 0, width, height);
370                         }
371
372                 cm->idle_id = 0;
373                 color_man_done_unused(cm, COLOR_RETURN_SUCCESS);
374                 return FALSE;
375                 }
376
377         rh = COLOR_MAN_CHUNK_SIZE / width + 1;
378         color_man_correct_region(cm, cm->pixbuf, 0, cm->row, width, rh);
379         if (cm->incremental_sync && cm->imd) image_area_changed(cm->imd, 0, cm->row, width, rh);
380         cm->row += rh;
381
382         return TRUE;
383 }
384 #pragma GCC diagnostic pop
385
386 static ColorMan *color_man_new_real(ImageWindow *imd, GdkPixbuf *pixbuf,
387                                     ColorManProfileType input_type, const gchar *input_file,
388                                     guchar *input_data, guint input_data_len,
389                                     ColorManProfileType screen_type, const gchar *screen_file,
390                                     guchar *screen_data, guint screen_data_len)
391 {
392         ColorMan *cm;
393         gboolean has_alpha;
394
395         if (imd) pixbuf = image_get_pixbuf(imd);
396
397         cm = g_new0(ColorMan, 1);
398         cm->imd = imd;
399         cm->pixbuf = pixbuf;
400         if (cm->pixbuf) g_object_ref(cm->pixbuf);
401
402         has_alpha = pixbuf ? gdk_pixbuf_get_has_alpha(pixbuf) : FALSE;
403
404         cm->profile = color_man_cache_get(input_type, input_file, input_data, input_data_len,
405                                           screen_type, screen_file, screen_data, screen_data_len, has_alpha);
406         if (!cm->profile)
407                 {
408                 color_man_free(cm);
409                 return nullptr;
410                 }
411
412         return cm;
413 }
414
415 ColorMan *color_man_new(ImageWindow *imd, GdkPixbuf *pixbuf,
416                         ColorManProfileType input_type, const gchar *input_file,
417                         ColorManProfileType screen_type, const gchar *screen_file,
418                         guchar *screen_data, guint screen_data_len)
419 {
420         return color_man_new_real(imd, pixbuf,
421                                   input_type, input_file, nullptr, 0,
422                                   screen_type, screen_file, screen_data, screen_data_len);
423 }
424
425 #pragma GCC diagnostic push
426 #pragma GCC diagnostic ignored "-Wunused-function"
427 void color_man_start_bg_unused(ColorMan *cm, ColorMan::DoneFunc done_func, gpointer done_data)
428 {
429         cm->func_done = done_func;
430         cm->func_done_data = done_data;
431         cm->idle_id = g_idle_add(color_man_idle_cb_unused, cm);
432 }
433 #pragma GCC diagnostic pop
434
435 ColorMan *color_man_new_embedded(ImageWindow *imd, GdkPixbuf *pixbuf,
436                                  guchar *input_data, guint input_data_len,
437                                  ColorManProfileType screen_type, const gchar *screen_file,
438                                  guchar *screen_data, guint screen_data_len)
439 {
440         return color_man_new_real(imd, pixbuf,
441                                   COLOR_PROFILE_MEM, nullptr, input_data, input_data_len,
442                                   screen_type, screen_file, screen_data, screen_data_len);
443 }
444
445 static gchar *color_man_get_profile_name(ColorManProfileType type, cmsHPROFILE profile)
446 {
447         switch (type)
448                 {
449                 case COLOR_PROFILE_SRGB:
450                         return g_strdup(_("sRGB"));
451                 case COLOR_PROFILE_ADOBERGB:
452                         return g_strdup(_("Adobe RGB compatible"));
453                         break;
454                 case COLOR_PROFILE_MEM:
455                 case COLOR_PROFILE_FILE:
456                         if (profile)
457                                 {
458 #ifdef HAVE_LCMS2
459                                 char buffer[20];
460                                 buffer[0] = '\0';
461                                 cmsGetProfileInfoASCII(profile, cmsInfoDescription, "en", "US", buffer, 20);
462                                 buffer[19] = '\0'; /* Just to be sure */
463                                 return g_strdup(buffer);
464 #else
465                                 return g_strdup(cmsTakeProductName(profile));
466 #endif
467                                 }
468                         return g_strdup(_("Custom profile"));
469                         break;
470                 case COLOR_PROFILE_NONE:
471                 default:
472                         return g_strdup("");
473                 }
474 }
475
476 gboolean color_man_get_status(ColorMan *cm, gchar **image_profile, gchar **screen_profile)
477 {
478         ColorManCache *cc;
479         if (!cm) return FALSE;
480
481         cc = static_cast<ColorManCache *>(cm->profile);
482
483         if (image_profile) *image_profile = color_man_get_profile_name(cc->profile_in_type, cc->profile_in);
484         if (screen_profile) *screen_profile = color_man_get_profile_name(cc->profile_out_type, cc->profile_out);
485         return TRUE;
486 }
487
488 void color_man_free(ColorMan *cm)
489 {
490         if (!cm) return;
491
492         if (cm->idle_id) g_source_remove(cm->idle_id);
493         if (cm->pixbuf) g_object_unref(cm->pixbuf);
494
495         color_man_cache_unref(static_cast<ColorManCache *>(cm->profile));
496
497         g_free(cm);
498 }
499
500 void color_man_update()
501 {
502         color_man_cache_reset();
503 }
504
505 #ifdef HAVE_HEIF
506 #include <cmath>
507 #include <libheif/heif.h>
508
509 static cmsToneCurve* colorspaces_create_transfer(int32_t size, double (*fct)(double))
510 {
511         std::vector<float> values;
512         values.reserve(size);
513         for(int32_t i = 0; i < size; ++i)
514                 {
515                 const double x = static_cast<float>(i) / (size - 1);
516                 const double y = MIN(fct(x), 1.0F);
517                 values.push_back(static_cast<float>(y));
518                 }
519
520         return cmsBuildTabulatedToneCurveFloat(nullptr, size, values.data());
521 }
522
523 // https://www.itu.int/dms_pubrec/itu-r/rec/bt/R-REC-BT.2100-2-201807-I!!PDF-F.pdf
524 // Hybrid Log-Gamma
525 static double HLG_fct(double x)
526 {
527         static const double Beta  = 0.04;
528         static const double RA    = 5.591816309728916; // 1.0 / A where A = 0.17883277
529         static const double B     = 0.28466892; // 1.0 - 4.0 * A
530         static const double C     = 0.5599107295; // 0,5 â€“aln(4a)
531
532         double e = MAX(x * (1.0 - Beta) + Beta, 0.0);
533
534         if(e == 0.0) return 0.0;
535
536         const double sign = e;
537         e = fabs(e);
538
539         double res = 0.0;
540
541         if(e <= 0.5)
542                 {
543                 res = e * e / 3.0;
544                 }
545         else
546                 {
547                 res = (exp((e - C) * RA) + B) / 12.0;
548                 }
549
550         return copysign(res, sign);
551 }
552
553 static double PQ_fct(double x)
554 {
555         static const double M1 = 2610.0 / 16384.0;
556         static const double M2 = (2523.0 / 4096.0) * 128.0;
557         static const double C1 = 3424.0 / 4096.0;
558         static const double C2 = (2413.0 / 4096.0) * 32.0;
559         static const double C3 = (2392.0 / 4096.0) * 32.0;
560
561         if(x == 0.0) return 0.0;
562         const double sign = x;
563         x = fabs(x);
564
565         const double xpo = pow(x, 1.0 / M2);
566         const double num = MAX(xpo - C1, 0.0);
567         const double den = C2 - C3 * xpo;
568         const double res = pow(num / den, 1.0 / M1);
569
570         return copysign(res, sign);
571 }
572
573 /**
574  * @brief
575  * @param nclx
576  * @param profile_len
577  * @returns
578  *
579  * Copied from: gimp/libgimpcolor/gimpcolorprofile.c
580  */
581 static guchar *nclx_to_lcms_profile(const struct heif_color_profile_nclx *nclx, guint *profile_len)
582 {
583         const gchar *primaries_name = "";
584         const gchar *trc_name = "";
585         cmsHPROFILE *profile = nullptr;
586         cmsCIExyY whitepoint;
587         cmsCIExyYTRIPLE primaries;
588         cmsToneCurve *curve[3];
589         cmsUInt32Number size;
590         guint8 *data = nullptr;
591
592         cmsFloat64Number srgb_parameters[5] =
593         { 2.4, 1.0 / 1.055,  0.055 / 1.055, 1.0 / 12.92, 0.04045 };
594
595         cmsFloat64Number rec709_parameters[5] =
596         { 2.2, 1.0 / 1.099,  0.099 / 1.099, 1.0 / 4.5, 0.081 };
597
598         if (nclx == nullptr)
599                 {
600                 return nullptr;
601                 }
602
603         if (nclx->color_primaries == heif_color_primaries_unspecified)
604                 {
605                 return nullptr;
606                 }
607
608         whitepoint.x = nclx->color_primary_white_x;
609         whitepoint.y = nclx->color_primary_white_y;
610         whitepoint.Y = 1.0F;
611
612         primaries.Red.x = nclx->color_primary_red_x;
613         primaries.Red.y = nclx->color_primary_red_y;
614         primaries.Red.Y = 1.0F;
615
616         primaries.Green.x = nclx->color_primary_green_x;
617         primaries.Green.y = nclx->color_primary_green_y;
618         primaries.Green.Y = 1.0F;
619
620         primaries.Blue.x = nclx->color_primary_blue_x;
621         primaries.Blue.y = nclx->color_primary_blue_y;
622         primaries.Blue.Y = 1.0F;
623
624         switch (nclx->color_primaries)
625                 {
626                 case heif_color_primaries_ITU_R_BT_709_5:
627                         primaries_name = "BT.709";
628                         break;
629                 case   heif_color_primaries_ITU_R_BT_470_6_System_M:
630                         primaries_name = "BT.470-6 System M";
631                         break;
632                 case heif_color_primaries_ITU_R_BT_470_6_System_B_G:
633                         primaries_name = "BT.470-6 System BG";
634                         break;
635                 case heif_color_primaries_ITU_R_BT_601_6:
636                         primaries_name = "BT.601";
637                         break;
638                 case heif_color_primaries_SMPTE_240M:
639                         primaries_name = "SMPTE 240M";
640                         break;
641                 case heif_color_primaries_generic_film:
642                         primaries_name = "Generic film";
643                         break;
644                 case heif_color_primaries_ITU_R_BT_2020_2_and_2100_0:
645                         primaries_name = "BT.2020";
646                         break;
647                 case heif_color_primaries_SMPTE_ST_428_1:
648                         primaries_name = "SMPTE ST 428-1";
649                         break;
650                 case heif_color_primaries_SMPTE_RP_431_2:
651                         primaries_name = "SMPTE RP 431-2";
652                         break;
653                 case heif_color_primaries_SMPTE_EG_432_1:
654                         primaries_name = "SMPTE EG 432-1 (DCI P3)";
655                         break;
656                 case heif_color_primaries_EBU_Tech_3213_E:
657                         primaries_name = "EBU Tech. 3213-E";
658                         break;
659                 default:
660                         log_printf("nclx unsupported color_primaries value: %d\n", nclx->color_primaries);
661                         return nullptr;
662                         break;
663                 }
664
665         DEBUG_1("nclx primaries: %s: ", primaries_name);
666
667         switch (nclx->transfer_characteristics)
668                 {
669                 case heif_transfer_characteristic_ITU_R_BT_709_5:
670                         curve[0] = curve[1] = curve[2] = cmsBuildParametricToneCurve(nullptr, 4, rec709_parameters);
671                         profile = static_cast<cmsHPROFILE *>(cmsCreateRGBProfile(&whitepoint, &primaries, curve));
672                         cmsFreeToneCurve(curve[0]);
673                         trc_name = "Rec709 RGB";
674                         break;
675                 case heif_transfer_characteristic_ITU_R_BT_470_6_System_M:
676                         curve[0] = curve[1] = curve[2] = cmsBuildGamma (nullptr, 2.2F);
677                         profile = static_cast<cmsHPROFILE *>(cmsCreateRGBProfile(&whitepoint, &primaries, curve));
678                         cmsFreeToneCurve(curve[0]);
679                         trc_name = "Gamma2.2 RGB";
680                         break;
681                 case heif_transfer_characteristic_ITU_R_BT_470_6_System_B_G:
682                         curve[0] = curve[1] = curve[2] = cmsBuildGamma (nullptr, 2.8F);
683                         profile = static_cast<cmsHPROFILE *>(cmsCreateRGBProfile(&whitepoint, &primaries, curve));
684                         cmsFreeToneCurve(curve[0]);
685                         trc_name = "Gamma2.8 RGB";
686                         break;
687                 case heif_transfer_characteristic_linear:
688                         curve[0] = curve[1] = curve[2] = cmsBuildGamma (nullptr, 1.0F);
689                         profile = static_cast<cmsHPROFILE *>(cmsCreateRGBProfile(&whitepoint, &primaries, curve));
690                         cmsFreeToneCurve(curve[0]);
691                         trc_name = "linear RGB";
692                         break;
693                 case heif_transfer_characteristic_ITU_R_BT_2100_0_HLG:
694                         curve[0] = curve[1] = curve[2] = colorspaces_create_transfer(4096, HLG_fct);
695                         profile = static_cast<cmsHPROFILE *>(cmsCreateRGBProfile(&whitepoint, &primaries, curve));
696                         cmsFreeToneCurve(curve[0]);
697                         trc_name = "HLG Rec2020 RGB";
698                         break;
699                 case heif_transfer_characteristic_ITU_R_BT_2100_0_PQ:
700                         curve[0] = curve[1] = curve[2] = colorspaces_create_transfer(4096, PQ_fct);
701                         profile = static_cast<cmsHPROFILE *>(cmsCreateRGBProfile(&whitepoint, &primaries, curve));
702                         cmsFreeToneCurve(curve[0]);
703                         trc_name = "PQ Rec2020 RGB";
704                         break;
705                 case heif_transfer_characteristic_IEC_61966_2_1:
706                 /* same as default */
707                 default:
708                         curve[0] = curve[1] = curve[2] = cmsBuildParametricToneCurve(nullptr, 4, srgb_parameters);
709                         profile = static_cast<cmsHPROFILE *>(cmsCreateRGBProfile(&whitepoint, &primaries, curve));
710                         cmsFreeToneCurve(curve[0]);
711                         trc_name = "sRGB-TRC RGB";
712                         break;
713                 }
714
715         DEBUG_1("nclx transfer characteristic: %s", trc_name);
716
717         if (profile)
718                 {
719                 if (cmsSaveProfileToMem(profile, nullptr, &size))
720                         {
721                         data = static_cast<guint8 *>(g_malloc(size));
722                         if (cmsSaveProfileToMem(profile, data, &size))
723                                 {
724                                 *profile_len = size;
725                                 }
726                         cmsCloseProfile(profile);
727                         return static_cast<guchar *>(data);
728                         }
729
730                 cmsCloseProfile(profile);
731                 return nullptr;
732                 }
733
734         return nullptr;
735 }
736
737 guchar *heif_color_profile(FileData *fd, guint *profile_len)
738 {
739         struct heif_context* ctx;
740         struct heif_error error_code;
741         struct heif_image_handle* handle;
742         struct heif_color_profile_nclx *nclxcp;
743         gint profile_type;
744         guchar *profile;
745         cmsUInt32Number size;
746         guint8 *data = nullptr;
747
748         ctx = heif_context_alloc();
749         error_code = heif_context_read_from_file(ctx, fd->path, nullptr);
750
751         if (error_code.code)
752                 {
753                 log_printf("warning: heif reader error: %s\n", error_code.message);
754                 heif_context_free(ctx);
755                 return nullptr;
756                 }
757
758         error_code = heif_context_get_primary_image_handle(ctx, &handle);
759         if (error_code.code)
760                 {
761                 log_printf("warning: heif reader error: %s\n", error_code.message);
762                 heif_context_free(ctx);
763                 return nullptr;
764                 }
765
766         nclxcp = heif_nclx_color_profile_alloc();
767         profile_type = heif_image_handle_get_color_profile_type(handle);
768
769         if (profile_type == heif_color_profile_type_prof)
770                 {
771                 size = heif_image_handle_get_raw_color_profile_size(handle);
772                 *profile_len = size;
773                 data = static_cast<guint8 *>(g_malloc0(size));
774                 error_code = heif_image_handle_get_raw_color_profile(handle, data);
775                 if (error_code.code)
776                         {
777                         log_printf("warning: heif reader error: %s\n", error_code.message);
778                         heif_context_free(ctx);
779                         heif_nclx_color_profile_free(nclxcp);
780                         return nullptr;
781                         }
782
783                 DEBUG_1("heif color profile type: prof");
784                 heif_context_free(ctx);
785                 heif_nclx_color_profile_free(nclxcp);
786
787                 return static_cast<guchar *>(data);
788                 }
789
790         error_code = heif_image_handle_get_nclx_color_profile(handle, &nclxcp);
791         if (error_code.code)
792                 {
793                 log_printf("warning: heif reader error: %s\n", error_code.message);
794                 heif_context_free(ctx);
795                 heif_nclx_color_profile_free(nclxcp);
796                 return nullptr;
797                 }
798
799         profile = nclx_to_lcms_profile(nclxcp, profile_len);
800
801         heif_context_free(ctx);
802         heif_nclx_color_profile_free(nclxcp);
803
804         return profile;
805 }
806 #else
807 guchar *heif_color_profile(FileData *, guint *)
808 {
809         return NULL;
810 }
811 #endif
812
813 #else /* define HAVE_LCMS */
814 /*** color support not enabled ***/
815
816
817 ColorMan *color_man_new(ImageWindow *, GdkPixbuf *,
818                         ColorManProfileType, const gchar *,
819                         ColorManProfileType, const gchar *,
820                         guchar *, guint)
821 {
822         /* no op */
823         return nullptr;
824 }
825
826 ColorMan *color_man_new_embedded(ImageWindow *, GdkPixbuf *,
827                                  guchar *, guint,
828                                  ColorManProfileType, const gchar *,
829                                  guchar *, guint)
830 {
831         /* no op */
832         return nullptr;
833 }
834
835 void color_man_free(ColorMan *)
836 {
837         /* no op */
838 }
839
840 void color_man_update()
841 {
842         /* no op */
843 }
844
845 void color_man_correct_region(ColorMan *, GdkPixbuf *, gint, gint, gint, gint)
846 {
847         /* no op */
848 }
849
850 gboolean color_man_get_status(ColorMan *, gchar **, gchar **)
851 {
852         return FALSE;
853 }
854
855 guchar *heif_color_profile(FileData *, guint *)
856 {
857         return nullptr;
858 }
859
860 #endif /* define HAVE_LCMS */
861 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */