Fix #981: Missing color profile support for AVIF/HEIF and possible others formats
authorColin Clark <colin.clark@cclark.uk>
Sun, 13 Nov 2022 10:33:32 +0000 (10:33 +0000)
committerColin Clark <colin.clark@cclark.uk>
Sun, 13 Nov 2022 10:33:32 +0000 (10:33 +0000)
https://github.com/BestImageViewer/geeqie/issues/981

src/color-man.cc
src/color-man.h
src/image.cc
src/thumb-standard.cc

index 553be6d..226cd2e 100644 (file)
@@ -490,6 +490,322 @@ void color_man_update(void)
        color_man_cache_reset();
 }
 
+#ifdef HAVE_HEIF
+#include <libheif/heif.h>
+#include <math.h>
+
+static cmsToneCurve* colorspaces_create_transfer(int32_t size, double (*fct)(double))
+{
+       float *values = static_cast<float *>(g_malloc(sizeof(float) * size));
+
+       for(int32_t i = 0; i < size; ++i)
+               {
+               const double x = (float)i / (size - 1);
+               const double y = MIN(fct(x), 1.0f);
+               values[i] = (float)y;
+               }
+
+       cmsToneCurve* result = cmsBuildTabulatedToneCurveFloat(NULL, size, values);
+       g_free(values);
+       return result;
+}
+
+// https://www.itu.int/dms_pubrec/itu-r/rec/bt/R-REC-BT.2100-2-201807-I!!PDF-F.pdf
+// Hybrid Log-Gamma
+static double HLG_fct(double x)
+{
+       static const double Beta  = 0.04;
+       static const double RA    = 5.591816309728916; // 1.0 / A where A = 0.17883277
+       static const double B     = 0.28466892; // 1.0 - 4.0 * A
+       static const double C     = 0.5599107295; // 0,5 –aln(4a)
+
+       double e = MAX(x * (1.0 - Beta) + Beta, 0.0);
+
+       if(e == 0.0) return 0.0;
+
+       const double sign = e;
+       e = fabs(e);
+
+       double res = 0.0;
+
+       if(e <= 0.5)
+               {
+               res = e * e / 3.0;
+               }
+       else
+               {
+               res = (exp((e - C) * RA) + B) / 12.0;
+               }
+
+       return copysign(res, sign);
+}
+
+static double PQ_fct(double x)
+{
+       static const double M1 = 2610.0 / 16384.0;
+       static const double M2 = (2523.0 / 4096.0) * 128.0;
+       static const double C1 = 3424.0 / 4096.0;
+       static const double C2 = (2413.0 / 4096.0) * 32.0;
+       static const double C3 = (2392.0 / 4096.0) * 32.0;
+
+       if(x == 0.0) return 0.0;
+       const double sign = x;
+       x = fabs(x);
+
+       const double xpo = pow(x, 1.0 / M2);
+       const double num = MAX(xpo - C1, 0.0);
+       const double den = C2 - C3 * xpo;
+       const double res = pow(num / den, 1.0 / M1);
+
+       return copysign(res, sign);
+}
+
+/**
+ * @brief
+ * @param nclx
+ * @param profile_len
+ * @returns
+ *
+ * Copied from: gimp/libgimpcolor/gimpcolorprofile.c
+ */
+static guchar *nclx_to_lcms_profile(const struct heif_color_profile_nclx *nclx, guint *profile_len)
+{
+       const gchar *primaries_name = "";
+       const gchar *trc_name = "";
+       cmsHPROFILE *profile = NULL;
+       cmsCIExyY whitepoint;
+       cmsCIExyYTRIPLE primaries;
+       cmsToneCurve *curve[3];
+       cmsUInt32Number size;
+       guint8 *data = NULL;
+
+       cmsFloat64Number srgb_parameters[5] =
+       { 2.4, 1.0 / 1.055,  0.055 / 1.055, 1.0 / 12.92, 0.04045 };
+
+       cmsFloat64Number rec709_parameters[5] =
+       { 2.2, 1.0 / 1.099,  0.099 / 1.099, 1.0 / 4.5, 0.081 };
+
+       if (nclx == NULL)
+               {
+               return NULL;
+               }
+
+       if (nclx->color_primaries == heif_color_primaries_unspecified)
+               {
+               return NULL;
+               }
+
+       whitepoint.x = nclx->color_primary_white_x;
+       whitepoint.y = nclx->color_primary_white_y;
+       whitepoint.Y = 1.0f;
+
+       primaries.Red.x = nclx->color_primary_red_x;
+       primaries.Red.y = nclx->color_primary_red_y;
+       primaries.Red.Y = 1.0f;
+
+       primaries.Green.x = nclx->color_primary_green_x;
+       primaries.Green.y = nclx->color_primary_green_y;
+       primaries.Green.Y = 1.0f;
+
+       primaries.Blue.x = nclx->color_primary_blue_x;
+       primaries.Blue.y = nclx->color_primary_blue_y;
+       primaries.Blue.Y = 1.0f;
+
+       switch (nclx->color_primaries)
+               {
+               case heif_color_primaries_ITU_R_BT_709_5:
+                       primaries_name = "BT.709";
+                       break;
+               case   heif_color_primaries_ITU_R_BT_470_6_System_M:
+                       primaries_name = "BT.470-6 System M";
+                       break;
+               case heif_color_primaries_ITU_R_BT_470_6_System_B_G:
+                       primaries_name = "BT.470-6 System BG";
+                       break;
+               case heif_color_primaries_ITU_R_BT_601_6:
+                       primaries_name = "BT.601";
+                       break;
+               case heif_color_primaries_SMPTE_240M:
+                       primaries_name = "SMPTE 240M";
+                       break;
+               case heif_color_primaries_generic_film:
+                       primaries_name = "Generic film";
+                       break;
+               case heif_color_primaries_ITU_R_BT_2020_2_and_2100_0:
+                       primaries_name = "BT.2020";
+                       break;
+               case heif_color_primaries_SMPTE_ST_428_1:
+                       primaries_name = "SMPTE ST 428-1";
+                       break;
+               case heif_color_primaries_SMPTE_RP_431_2:
+                       primaries_name = "SMPTE RP 431-2";
+                       break;
+               case heif_color_primaries_SMPTE_EG_432_1:
+                       primaries_name = "SMPTE EG 432-1 (DCI P3)";
+                       break;
+               case heif_color_primaries_EBU_Tech_3213_E:
+                       primaries_name = "EBU Tech. 3213-E";
+                       break;
+               default:
+                       log_printf("nclx unsupported color_primaries value: %d\n", nclx->color_primaries);
+                       return NULL;
+                       break;
+               }
+
+       DEBUG_1("nclx primaries: %s: ", primaries_name);
+
+       switch (nclx->transfer_characteristics)
+               {
+               case heif_transfer_characteristic_ITU_R_BT_709_5:
+                       curve[0] = curve[1] = curve[2] = cmsBuildParametricToneCurve(NULL, 4, rec709_parameters);
+                       profile = static_cast<cmsHPROFILE *>(cmsCreateRGBProfile(&whitepoint, &primaries, curve));
+                       cmsFreeToneCurve(curve[0]);
+                       trc_name = "Rec709 RGB";
+                       break;
+               case heif_transfer_characteristic_ITU_R_BT_470_6_System_M:
+                       curve[0] = curve[1] = curve[2] = cmsBuildGamma (NULL, 2.2f);
+                       profile = static_cast<cmsHPROFILE *>(cmsCreateRGBProfile(&whitepoint, &primaries, curve));
+                       cmsFreeToneCurve(curve[0]);
+                       trc_name = "Gamma2.2 RGB";
+                       break;
+               case heif_transfer_characteristic_ITU_R_BT_470_6_System_B_G:
+                       curve[0] = curve[1] = curve[2] = cmsBuildGamma (NULL, 2.8f);
+                       profile = static_cast<cmsHPROFILE *>(cmsCreateRGBProfile(&whitepoint, &primaries, curve));
+                       cmsFreeToneCurve(curve[0]);
+                       trc_name = "Gamma2.8 RGB";
+                       break;
+               case heif_transfer_characteristic_linear:
+                       curve[0] = curve[1] = curve[2] = cmsBuildGamma (NULL, 1.0f);
+                       profile = static_cast<cmsHPROFILE *>(cmsCreateRGBProfile(&whitepoint, &primaries, curve));
+                       cmsFreeToneCurve(curve[0]);
+                       trc_name = "linear RGB";
+                       break;
+               case heif_transfer_characteristic_ITU_R_BT_2100_0_HLG:
+                       curve[0] = curve[1] = curve[2] = colorspaces_create_transfer(4096, HLG_fct);
+                       profile = static_cast<cmsHPROFILE *>(cmsCreateRGBProfile(&whitepoint, &primaries, curve));
+                       cmsFreeToneCurve(curve[0]);
+                       trc_name = "HLG Rec2020 RGB";
+                       break;
+               case heif_transfer_characteristic_ITU_R_BT_2100_0_PQ:
+                       curve[0] = curve[1] = curve[2] = colorspaces_create_transfer(4096, PQ_fct);
+                       profile = static_cast<cmsHPROFILE *>(cmsCreateRGBProfile(&whitepoint, &primaries, curve));
+                       cmsFreeToneCurve(curve[0]);
+                       trc_name = "PQ Rec2020 RGB";
+                       break;
+               case heif_transfer_characteristic_IEC_61966_2_1:
+               /* same as default */
+               default:
+                       curve[0] = curve[1] = curve[2] = cmsBuildParametricToneCurve(NULL, 4, srgb_parameters);
+                       profile = static_cast<cmsHPROFILE *>(cmsCreateRGBProfile(&whitepoint, &primaries, curve));
+                       cmsFreeToneCurve(curve[0]);
+                       trc_name = "sRGB-TRC RGB";
+                       break;
+               }
+
+       DEBUG_1("nclx transfer characteristic: %s", trc_name);
+
+       if (profile)
+               {
+               if (cmsSaveProfileToMem(profile, NULL, &size))
+                       {
+                       data = static_cast<guint8 *>(g_malloc(size));
+                       if (cmsSaveProfileToMem(profile, data, &size))
+                               {
+                               *profile_len = size;
+                               }
+                       cmsCloseProfile(profile);
+                       return (guchar *)data;
+                       }
+               else
+                       {
+                       cmsCloseProfile(profile);
+                       return NULL;
+                       }
+               }
+       else
+               {
+               return NULL;
+               }
+}
+
+guchar *heif_color_profile(FileData *fd, guint *profile_len)
+{
+       struct heif_context* ctx;
+       struct heif_error error_code;
+       struct heif_image_handle* handle;
+       struct heif_color_profile_nclx *nclxcp;
+       gint profile_type;
+       guchar *profile;
+       cmsUInt32Number size;
+       guint8 *data = NULL;
+
+       ctx = heif_context_alloc();
+       error_code = heif_context_read_from_file(ctx, fd->path, NULL);
+
+       if (error_code.code)
+               {
+               log_printf("warning: heif reader error: %s\n", error_code.message);
+               heif_context_free(ctx);
+               return NULL;
+               }
+
+       error_code = heif_context_get_primary_image_handle(ctx, &handle);
+       if (error_code.code)
+               {
+               log_printf("warning: heif reader error: %s\n", error_code.message);
+               heif_context_free(ctx);
+               return NULL;
+               }
+
+       nclxcp = heif_nclx_color_profile_alloc();
+       profile_type = heif_image_handle_get_color_profile_type(handle);
+
+       if (profile_type == heif_color_profile_type_prof)
+               {
+               size = heif_image_handle_get_raw_color_profile_size(handle);
+               *profile_len = size;
+               data = static_cast<guint8 *>(g_malloc0(size));
+               error_code = heif_image_handle_get_raw_color_profile(handle, data);
+               if (error_code.code)
+                       {
+                       log_printf("warning: heif reader error: %s\n", error_code.message);
+                       heif_context_free(ctx);
+                       heif_nclx_color_profile_free(nclxcp);
+                       return NULL;
+                       }
+
+               DEBUG_1("heif color profile type: prof");
+               heif_context_free(ctx);
+               heif_nclx_color_profile_free(nclxcp);
+
+               return (guchar *)data;
+               }
+       else
+               {
+               error_code = heif_image_handle_get_nclx_color_profile(handle, &nclxcp);
+               if (error_code.code)
+                       {
+                       log_printf("warning: heif reader error: %s\n", error_code.message);
+                       heif_context_free(ctx);
+                       heif_nclx_color_profile_free(nclxcp);
+                       return NULL;
+                       }
+
+               profile = nclx_to_lcms_profile(nclxcp, profile_len);
+               }
+
+       heif_context_free(ctx);
+       heif_nclx_color_profile_free(nclxcp);
+
+       return (guchar *)profile;
+}
+#else
+guchar *heif_color_profile(FileData *UNUSED(fd), guint *UNUSED(profile_len))
+{
+       return NULL;
+}
+#endif
+
 #else /* define HAVE_LCMS */
 /*** color support not enabled ***/
 
@@ -537,5 +853,10 @@ gboolean color_man_get_status(ColorMan *UNUSED(cm), gchar **UNUSED(image_profile
        return FALSE;
 }
 
+guchar *heif_color_profile(FileData *UNUSED(fd), guint *UNUSED(profile_len))
+{
+       return NULL;
+}
+
 #endif /* define HAVE_LCMS */
 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */
index 8962588..22d5161 100644 (file)
@@ -73,5 +73,6 @@ void color_man_start_bg(ColorMan *cm, ColorManDoneFunc don_func, gpointer done_d
 
 gboolean color_man_get_status(ColorMan *cm, gchar **image_profile, gchar **screen_profile);
 
+guchar *heif_color_profile(FileData *fd, guint *profile_len);
 #endif
 /* vim: set shiftwidth=8 softtabstop=0 cindent cinoptions={1s: */
index 6b79c29..6faa00f 100644 (file)
@@ -457,7 +457,16 @@ static gboolean image_post_process_color(ImageWindow *imd, gint start_row, gbool
 
        if (exif)
                {
-               profile = exif_get_color_profile(exif, &profile_len);
+               if (g_strcmp0(imd->image_fd->format_name, "heif") == 0)
+                       {
+                       profile = heif_color_profile(imd->image_fd, &profile_len);
+                       }
+
+               if (!profile)
+                       {
+                       profile = exif_get_color_profile(exif, &profile_len);
+                       }
+
                if (profile)
                        {
                        if (!imd->color_profile_use_image)
index 2c18ed1..f90ae96 100644 (file)
@@ -410,7 +410,16 @@ void thumb_loader_std_calibrate_pixbuf(FileData *fd, GdkPixbuf *pixbuf) {
 
        if (exif)
                {
-               profile = exif_get_color_profile(exif, &profile_len);
+               if (g_strcmp0(fd->format_name, "heif") == 0)
+                       {
+                       profile = heif_color_profile(fd, &profile_len);
+                       }
+
+               if (!profile)
+                       {
+                       profile = exif_get_color_profile(exif, &profile_len);
+                       }
+
                if (profile)
                        {
                        DEBUG_1("Found embedded color profile");