[gimp] Updated HEIF plug-in



commit 01e1587ec1909ce05a4e64c58606e0dfb4e68804
Author: Daniel Novomesky <dnovomesky gmail com>
Date:   Wed Aug 19 16:36:18 2020 +0200

    Updated HEIF plug-in
    
    AVIF import and export (need libheif >= 1.8.0)
    High bit (10/12) import and export
    Auto pre-select suggested output bit depth in save_dialog
    NCLX color profile import (link with lcms)
    Metadata import (link with gexiv2)
    Plugin will not return GIMP_PDB_SUCCESS in case of failure to import file.

 plug-ins/common/Makefile.am |    2 +
 plug-ins/common/file-heif.c | 1056 ++++++++++++++++++++++++++++++++++++++-----
 plug-ins/common/meson.build |    2 +-
 3 files changed, 940 insertions(+), 120 deletions(-)
---
diff --git a/plug-ins/common/Makefile.am b/plug-ins/common/Makefile.am
index aa7dc474a3..d5035e2766 100644
--- a/plug-ins/common/Makefile.am
+++ b/plug-ins/common/Makefile.am
@@ -774,10 +774,12 @@ file_heif_LDADD = \
        $(libgimpcolor)         \
        $(libgimpbase)          \
        $(GTK_LIBS)             \
+       $(GEXIV2_LIBS)          \
        $(GEGL_LIBS)            \
        $(LIBHEIF_LIBS)         \
        $(RT_LIBS)              \
        $(INTLLIBS)             \
+       $(LCMS_LIBS)            \
        $(file_heif_RC)
 
 file_html_table_SOURCES = \
diff --git a/plug-ins/common/file-heif.c b/plug-ins/common/file-heif.c
index e17dcba607..45de152287 100644
--- a/plug-ins/common/file-heif.c
+++ b/plug-ins/common/file-heif.c
@@ -19,6 +19,8 @@
 #include "config.h"
 
 #include <libheif/heif.h>
+#include <lcms2.h>
+#include <gexiv2/gexiv2.h>
 
 #include <libgimp/gimp.h>
 #include <libgimp/gimpui.h>
@@ -28,6 +30,7 @@
 
 #define LOAD_PROC      "file-heif-load"
 #define SAVE_PROC      "file-heif-save"
+#define SAVE_PROC_AV1  "file-heif-av1-save"
 #define PLUG_IN_BINARY "file-heif"
 
 
@@ -68,20 +71,33 @@ static GimpValueArray * heif_save             (GimpProcedure        *procedure,
                                                const GimpValueArray *args,
                                                gpointer              run_data);
 
+#if LIBHEIF_HAVE_VERSION(1,8,0)
+static GimpValueArray * heif_av1_save         (GimpProcedure        *procedure,
+                                               GimpRunMode           run_mode,
+                                               GimpImage            *image,
+                                               gint                  n_drawables,
+                                               GimpDrawable        **drawables,
+                                               GFile                *file,
+                                               const GimpValueArray *args,
+                                               gpointer              run_data);
+#endif
+
 static GimpImage      * load_image            (GFile               *file,
                                                gboolean             interactive,
                                                GimpPDBStatusType    *status,
                                                GError              **error);
-static gboolean         save_image            (GFile                *file,
-                                               GimpImage            *image,
-                                               GimpDrawable         *drawable,
-                                               GObject              *config,
-                                               GError              **error);
+static gboolean         save_image            (GFile                        *file,
+                                               GimpImage                    *image,
+                                               GimpDrawable                 *drawable,
+                                               GObject                      *config,
+                                               GError                      **error,
+                                               enum heif_compression_format  compression);
 
 static gboolean         load_dialog           (struct heif_context  *heif,
                                                uint32_t             *selected_image);
 static gboolean         save_dialog           (GimpProcedure        *procedure,
-                                               GObject              *config);
+                                               GObject              *config,
+                                               GimpImage            *image);
 
 
 G_DEFINE_TYPE (Heif, heif, GIMP_TYPE_PLUG_IN)
@@ -110,13 +126,15 @@ heif_query_procedures (GimpPlugIn *plug_in)
 
   list = g_list_append (list, g_strdup (LOAD_PROC));
   list = g_list_append (list, g_strdup (SAVE_PROC));
-
+#if LIBHEIF_HAVE_VERSION(1,8,0)
+  list = g_list_append (list, g_strdup (SAVE_PROC_AV1));
+#endif
   return list;
 }
 
 static GimpProcedure *
 heif_create_procedure (GimpPlugIn  *plug_in,
-                      const gchar *name)
+                       const gchar *name)
 {
   GimpProcedure *procedure = NULL;
 
@@ -143,9 +161,17 @@ heif_create_procedure (GimpPlugIn  *plug_in,
       gimp_file_procedure_set_handles_remote (GIMP_FILE_PROCEDURE (procedure),
                                               TRUE);
       gimp_file_procedure_set_mime_types (GIMP_FILE_PROCEDURE (procedure),
-                                          "image/heif");
+                                          "image/heif"
+#if LIBHEIF_HAVE_VERSION(1,8,0)
+                                          ",image/avif"
+#endif
+                                         );
       gimp_file_procedure_set_extensions (GIMP_FILE_PROCEDURE (procedure),
-                                          "heif,heic");
+                                          "heif,heic"
+#if LIBHEIF_HAVE_VERSION(1,8,0)
+                                          ",avif"
+#endif
+                                         );
 
       /* HEIF is an ISOBMFF format whose "brand" (the value after "ftyp")
        * can be of various values.
@@ -156,7 +182,11 @@ heif_create_procedure (GimpPlugIn  *plug_in,
                                       "4,string,ftyphevc,4,string,ftypheim,"
                                       "4,string,ftypheis,4,string,ftyphevm,"
                                       "4,string,ftyphevs,4,string,ftypmif1,"
-                                      "4,string,ftypmsf1");
+                                      "4,string,ftypmsf1"
+#if LIBHEIF_HAVE_VERSION(1,8,0)
+                                      ",4,string,ftypavif"
+#endif
+                                     );
     }
   else if (! strcmp (name, SAVE_PROC))
     {
@@ -202,17 +232,74 @@ heif_create_procedure (GimpPlugIn  *plug_in,
                                  "Save the image's color profile",
                                  gimp_export_color_profile (),
                                  G_PARAM_READWRITE);
+
+      GIMP_PROC_ARG_INT (procedure, "save-bit-depth",
+                         "Bit depth",
+                         "Bit depth of exported image",
+                         8, 12, 8,
+                         G_PARAM_READWRITE);
     }
+#if LIBHEIF_HAVE_VERSION(1,8,0)
+  else if (! strcmp (name, SAVE_PROC_AV1))
+    {
+      procedure = gimp_save_procedure_new (plug_in, name,
+                                           GIMP_PDB_PROC_TYPE_PLUGIN,
+                                           heif_av1_save, NULL, NULL);
+
+      gimp_procedure_set_image_types (procedure, "RGB*");
+
+      gimp_procedure_set_menu_label (procedure, "HEIF/AVIF");
+
+      gimp_procedure_set_documentation (procedure,
+                                        "Exports AVIF images",
+                                        "Save image in AV1 Image File Format (AVIF)",
+                                        name);
+      gimp_procedure_set_attribution (procedure,
+                                      "Daniel Novomesky <dnovomesky gmail com>",
+                                      "Daniel Novomesky <dnovomesky gmail com>",
+                                      "2020");
+
+      gimp_file_procedure_set_handles_remote (GIMP_FILE_PROCEDURE (procedure),
+                                              TRUE);
+      gimp_file_procedure_set_mime_types (GIMP_FILE_PROCEDURE (procedure),
+                                          "image/avif");
+      gimp_file_procedure_set_extensions (GIMP_FILE_PROCEDURE (procedure),
+                                          "avif");
+
+      GIMP_PROC_ARG_INT (procedure, "quality",
+                         "Quality",
+                         "Quality factor (0 = worst, 100 = best)",
+                         0, 100, 50,
+                         G_PARAM_READWRITE);
+
+      GIMP_PROC_ARG_BOOLEAN (procedure, "lossless",
+                             "Lossless",
+                             "Use lossless compression",
+                             FALSE,
+                             G_PARAM_READWRITE);
+
+      GIMP_PROC_AUX_ARG_BOOLEAN (procedure, "save-color-profile",
+                                 "Save color profile",
+                                 "Save the image's color profile",
+                                 gimp_export_color_profile (),
+                                 G_PARAM_READWRITE);
 
+      GIMP_PROC_ARG_INT (procedure, "save-bit-depth",
+                         "Bit depth",
+                         "Bit depth of exported image",
+                         8, 12, 8,
+                         G_PARAM_READWRITE);
+    }
+#endif
   return procedure;
 }
 
 static GimpValueArray *
 heif_load (GimpProcedure        *procedure,
-          GimpRunMode           run_mode,
-          GFile                *file,
-          const GimpValueArray *args,
-          gpointer              run_data)
+           GimpRunMode           run_mode,
+           GFile                *file,
+           const GimpValueArray *args,
+           gpointer              run_data)
 {
   GimpValueArray    *return_vals;
   GimpPDBStatusType  status = GIMP_PDB_SUCCESS;
@@ -244,13 +331,13 @@ heif_load (GimpProcedure        *procedure,
 
 static GimpValueArray *
 heif_save (GimpProcedure        *procedure,
-          GimpRunMode           run_mode,
-          GimpImage            *image,
-          gint                  n_drawables,
-          GimpDrawable        **drawables,
-          GFile                *file,
-          const GimpValueArray *args,
-          gpointer              run_data)
+           GimpRunMode           run_mode,
+           GimpImage            *image,
+           gint                  n_drawables,
+           GimpDrawable        **drawables,
+           GFile                *file,
+           const GimpValueArray *args,
+           gpointer              run_data)
 {
   GimpProcedureConfig *config;
   GimpPDBStatusType    status = GIMP_PDB_SUCCESS;
@@ -296,14 +383,14 @@ heif_save (GimpProcedure        *procedure,
 
   if (run_mode == GIMP_RUN_INTERACTIVE)
     {
-      if (! save_dialog (procedure, G_OBJECT (config)))
+      if (! save_dialog (procedure, G_OBJECT (config), image))
         status = GIMP_PDB_CANCEL;
     }
 
   if (status == GIMP_PDB_SUCCESS)
     {
       if (! save_image (file, image, drawables[0], G_OBJECT (config),
-                        &error))
+                        &error, heif_compression_HEVC))
         {
           status = GIMP_PDB_EXECUTION_ERROR;
         }
@@ -321,6 +408,87 @@ heif_save (GimpProcedure        *procedure,
   return gimp_procedure_new_return_values (procedure, status, error);
 }
 
+#if LIBHEIF_HAVE_VERSION(1,8,0)
+static GimpValueArray *
+heif_av1_save (GimpProcedure        *procedure,
+               GimpRunMode           run_mode,
+               GimpImage            *image,
+               gint                  n_drawables,
+               GimpDrawable        **drawables,
+               GFile                *file,
+               const GimpValueArray *args,
+               gpointer              run_data)
+{
+  GimpProcedureConfig *config;
+  GimpPDBStatusType    status = GIMP_PDB_SUCCESS;
+  GimpExportReturn     export = GIMP_EXPORT_CANCEL;
+  GError              *error  = NULL;
+
+  INIT_I18N ();
+  gegl_init (NULL, NULL);
+
+  config = gimp_procedure_create_config (procedure);
+  gimp_procedure_config_begin_export (config, image, run_mode,
+                                      args, "image/avif");
+
+  switch (run_mode)
+    {
+    case GIMP_RUN_INTERACTIVE:
+    case GIMP_RUN_WITH_LAST_VALS:
+      gimp_ui_init (PLUG_IN_BINARY);
+
+      export = gimp_export_image (&image, &n_drawables, &drawables, "AVIF",
+                                  GIMP_EXPORT_CAN_HANDLE_RGB |
+                                  GIMP_EXPORT_CAN_HANDLE_ALPHA);
+
+      if (export == GIMP_EXPORT_CANCEL)
+        return gimp_procedure_new_return_values (procedure,
+                                                 GIMP_PDB_CANCEL,
+                                                 NULL);
+      break;
+
+    default:
+      break;
+    }
+
+  if (n_drawables != 1)
+    {
+      g_set_error (&error, G_FILE_ERROR, 0,
+                   _("HEIF format does not support multiple layers."));
+
+      return gimp_procedure_new_return_values (procedure,
+                                               GIMP_PDB_CALLING_ERROR,
+                                               error);
+    }
+
+  if (run_mode == GIMP_RUN_INTERACTIVE)
+    {
+      if (! save_dialog (procedure, G_OBJECT (config), image))
+        status = GIMP_PDB_CANCEL;
+    }
+
+  if (status == GIMP_PDB_SUCCESS)
+    {
+      if (! save_image (file, image, drawables[0], G_OBJECT (config),
+                        &error, heif_compression_AV1))
+        {
+          status = GIMP_PDB_EXECUTION_ERROR;
+        }
+    }
+
+  gimp_procedure_config_end_export (config, image, file, status);
+  g_object_unref (config);
+
+  if (export == GIMP_EXPORT_EXPORT)
+    {
+      gimp_image_delete (image);
+      g_free (drawables);
+    }
+
+  return gimp_procedure_new_return_values (procedure, status, error);
+}
+#endif
+
 static goffset
 get_file_size (GFile   *file,
                GError **error)
@@ -342,6 +510,180 @@ get_file_size (GFile   *file,
   return size;
 }
 
+#if LIBHEIF_HAVE_VERSION(1,8,0)
+static void
+heifplugin_color_profile_set_tag (cmsHPROFILE      profile,
+                                  cmsTagSignature  sig,
+                                  const gchar     *tag)
+{
+  cmsMLU *mlu;
+
+  mlu = cmsMLUalloc (NULL, 1);
+  cmsMLUsetASCII (mlu, "en", "US", tag);
+  cmsWriteTag (profile, sig, mlu);
+  cmsMLUfree (mlu);
+}
+
+static GimpColorProfile *
+nclx_to_gimp_profile (const struct heif_color_profile_nclx *nclx)
+{
+  const gchar *primaries_name = "";
+  const gchar *trc_name = "";
+  cmsHPROFILE profile = NULL;
+  cmsCIExyY whitepoint;
+  cmsCIExyYTRIPLE primaries;
+  cmsToneCurve *curve[3];
+
+  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;
+    }
+
+  if (nclx->color_primaries == heif_color_primaries_ITU_R_BT_709_5)
+    {
+      if (nclx->transfer_characteristics == heif_transfer_characteristic_IEC_61966_2_1)
+        {
+          return gimp_color_profile_new_rgb_srgb();
+        }
+
+      if (nclx->transfer_characteristics == heif_transfer_characteristic_linear)
+        {
+          return gimp_color_profile_new_rgb_srgb_linear();
+        }
+    }
+
+  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 8:
+      primaries_name = "Generic film";
+      break;
+    case 9:
+      primaries_name = "BT.2020";
+      break;
+    case 10:
+      primaries_name = "XYZ";
+      break;
+    case 11:
+      primaries_name = "SMPTE RP 431-2";
+      break;
+    case 12:
+      primaries_name = "SMPTE EG 432-1 (DCI P3)";
+      break;
+    case 22:
+      primaries_name = "EBU Tech. 3213-E";
+      break;
+    default:
+      g_warning ("%s: Unsupported color_primaries value %d.",
+                 G_STRFUNC, nclx->color_primaries);
+      return NULL;
+      break;
+    }
+
+  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 = 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 = 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 = 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 = cmsCreateRGBProfile (&whitepoint, &primaries, curve);
+      cmsFreeToneCurve (curve[0]);
+      trc_name = "linear 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 = cmsCreateRGBProfile (&whitepoint, &primaries, curve);
+      cmsFreeToneCurve (curve[0]);
+      trc_name = "sRGB-TRC RGB";
+      break;
+    }
+
+  if (profile)
+    {
+      GimpColorProfile *new_profile;
+      gchar *description = g_strdup_printf ("%s %s", primaries_name, trc_name);
+
+      heifplugin_color_profile_set_tag (profile, cmsSigProfileDescriptionTag,
+                                        description);
+      heifplugin_color_profile_set_tag (profile, cmsSigDeviceMfgDescTag,
+                                        "GIMP");
+      heifplugin_color_profile_set_tag (profile, cmsSigDeviceModelDescTag,
+                                        description);
+      heifplugin_color_profile_set_tag (profile, cmsSigCopyrightTag,
+                                        "Public Domain");
+
+      new_profile = gimp_color_profile_new_from_lcms_profile (profile, NULL);
+
+      cmsCloseProfile (profile);
+      g_free (description);
+      return new_profile;
+    }
+
+  return NULL;
+}
+#endif
+
 GimpImage *
 load_image (GFile              *file,
             gboolean            interactive,
@@ -369,10 +711,17 @@ load_image (GFile              *file,
   const Babl               *format;
   const guint8             *data;
   gint                      stride;
+  gint                      bit_depth;
+  enum                      heif_chroma chroma;
+  GimpPrecision             precision;
+  gboolean                  load_linear;
+  const char               *encoding;
 
   gimp_progress_init_printf (_("Opening '%s'"),
                              g_file_get_parse_name (file));
 
+  *status = GIMP_PDB_EXECUTION_ERROR;
+
   file_size = get_file_size (file, error);
   if (file_size <= 0)
     return NULL;
@@ -395,6 +744,14 @@ load_image (GFile              *file,
   gimp_progress_update (0.25);
 
   ctx = heif_context_alloc ();
+  if (!ctx)
+    {
+      g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
+                   "cannot allocate heif_context");
+      g_free (file_buffer);
+      g_object_unref (input);
+      return NULL;
+    }
 
   err = heif_context_read_from_memory (ctx, file_buffer, file_size, NULL);
   if (err.code)
@@ -483,24 +840,68 @@ load_image (GFile              *file,
 
   has_alpha = heif_image_handle_has_alpha_channel (handle);
 
+  bit_depth = heif_image_handle_get_luma_bits_per_pixel (handle);
+  if (bit_depth < 0)
+    {
+      g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
+                   "Input image has undefined bit-depth");
+      heif_image_handle_release (handle);
+      heif_context_free (ctx);
+
+      return NULL;
+    }
+
+  if (bit_depth == 8)
+    {
+      if (has_alpha)
+        {
+          chroma = heif_chroma_interleaved_RGBA;
+        }
+      else
+        {
+          chroma = heif_chroma_interleaved_RGB;
+        }
+    }
+  else /* high bit depth */
+    {
+#if ( G_BYTE_ORDER == G_LITTLE_ENDIAN )
+      if (has_alpha)
+        {
+          chroma = heif_chroma_interleaved_RRGGBBAA_LE;
+        }
+      else
+        {
+          chroma = heif_chroma_interleaved_RRGGBB_LE;
+        }
+#else
+      if (has_alpha)
+        {
+          chroma = heif_chroma_interleaved_RRGGBBAA_BE;
+        }
+      else
+        {
+          chroma = heif_chroma_interleaved_RRGGBB_BE;
+        }
+#endif
+    }
+
   err = heif_decode_image (handle,
                            &img,
                            heif_colorspace_RGB,
-                           has_alpha ? heif_chroma_interleaved_RGBA :
-                           heif_chroma_interleaved_RGB,
+                           chroma,
                            NULL);
   if (err.code)
     {
       g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
                    _("Loading HEIF image failed: %s"),
                    err.message);
-     heif_image_handle_release (handle);
-     heif_context_free (ctx);
+      heif_image_handle_release (handle);
+      heif_context_free (ctx);
 
-     return NULL;
+      return NULL;
     }
 
-#ifdef HAVE_LIBHEIF_1_4_0
+#if LIBHEIF_HAVE_VERSION(1,4,0)
   switch (heif_image_handle_get_color_profile_type (handle))
     {
     case heif_color_profile_type_not_present:
@@ -510,34 +911,49 @@ load_image (GFile              *file,
       /* I am unsure, but it looks like both these types represent an
        * ICC color profile. XXX
        */
-        {
-          void   *profile_data;
-          size_t  profile_size;
+      {
+        void   *profile_data;
+        size_t  profile_size;
 
-          profile_size = heif_image_handle_get_raw_color_profile_size (handle);
-          profile_data = g_malloc0 (profile_size);
-          err = heif_image_handle_get_raw_color_profile (handle, profile_data);
+        profile_size = heif_image_handle_get_raw_color_profile_size (handle);
+        profile_data = g_malloc0 (profile_size);
+        err = heif_image_handle_get_raw_color_profile (handle, profile_data);
 
-          if (err.code)
-            g_warning ("%s: color profile loading failed and discarded.",
-                       G_STRFUNC);
-          else
-            profile = gimp_color_profile_new_from_icc_profile ((guint8 *) profile_data,
-                                                               profile_size, NULL);
+        if (err.code)
+          g_warning ("%s: ICC profile loading failed and discarded.",
+                     G_STRFUNC);
+        else
+          profile = gimp_color_profile_new_from_icc_profile ((guint8 *) profile_data,
+                                                             profile_size, NULL);
 
-          g_free (profile_data);
-        }
+        g_free (profile_data);
+      }
       break;
+#if LIBHEIF_HAVE_VERSION(1,8,0)
+    case heif_color_profile_type_nclx:
+      {
+        struct heif_color_profile_nclx *nclx = NULL;
 
+        err = heif_image_handle_get_nclx_color_profile (handle, &nclx);
+        if (err.code)
+          {
+            g_warning ("%s: NCLX profile loading failed and discarded.",
+                       G_STRFUNC);
+          }
+        else
+          {
+            profile = nclx_to_gimp_profile (nclx);
+            heif_nclx_color_profile_free (nclx);
+          }
+      }
+      break;
+#endif
     default:
-      /* heif_color_profile_type_nclx (what is that?) and any future
-       * profile type which we don't support in GIMP (yet).
-       */
       g_warning ("%s: unknown color profile type has been discarded.",
                  G_STRFUNC);
       break;
     }
-#endif /* HAVE_LIBHEIF_1_4_0 */
+#endif /* LIBHEIF_HAVE_VERSION(1,4,0) */
 
   gimp_progress_update (0.75);
 
@@ -548,11 +964,60 @@ load_image (GFile              *file,
    * (converting it to RGB)
    */
 
-  image = gimp_image_new (width, height, GIMP_RGB);
+  if (profile)
+    {
+      load_linear = gimp_color_profile_is_linear (profile);
+    }
+  else
+    {
+      load_linear = FALSE;
+    }
+
+  if (load_linear)
+    {
+      if (bit_depth == 8)
+        {
+          precision = GIMP_PRECISION_U8_LINEAR;
+          encoding = has_alpha ? "RGBA u8" : "RGB u8";
+        }
+      else
+        {
+          precision = GIMP_PRECISION_U16_LINEAR;
+          encoding = has_alpha ? "RGBA u16" : "RGB u16";
+        }
+    }
+  else /* non-linear profiles */
+    {
+      if (bit_depth == 8)
+        {
+          precision = GIMP_PRECISION_U8_NON_LINEAR;
+          encoding = has_alpha ? "R'G'B'A u8" : "R'G'B' u8";
+        }
+      else
+        {
+          precision = GIMP_PRECISION_U16_NON_LINEAR;
+          encoding = has_alpha ? "R'G'B'A u16" : "R'G'B' u16";
+        }
+    }
+
+  image = gimp_image_new_with_precision (width, height, GIMP_RGB, precision);
   gimp_image_set_file (image, file);
 
   if (profile)
-    gimp_image_set_color_profile (image, profile);
+    {
+      if (gimp_color_profile_is_rgb (profile))
+        {
+          gimp_image_set_color_profile (image, profile);
+        }
+      else if (gimp_color_profile_is_gray (profile))
+        {
+          g_warning ("Gray ICC profile was not applied to the imported image.");
+        }
+      else
+        {
+          g_warning ("ICC profile was not applied to the imported image.");
+        }
+    }
 
   layer = gimp_layer_new (image,
                           _("image content"),
@@ -565,19 +1030,86 @@ load_image (GFile              *file,
 
   buffer = gimp_drawable_get_buffer (GIMP_DRAWABLE (layer));
 
-  if (has_alpha)
-    format = babl_format_with_space ("R'G'B'A u8",
-                                     gegl_buffer_get_format (buffer));
-  else
-    format = babl_format_with_space ("R'G'B' u8",
-                                     gegl_buffer_get_format (buffer));
-
   data = heif_image_get_plane_readonly (img, heif_channel_interleaved,
                                         &stride);
 
-  gegl_buffer_set (buffer,
-                   GEGL_RECTANGLE (0, 0, width, height),
-                   0, format, data, stride);
+  format = babl_format_with_space (encoding,
+                                   gegl_buffer_get_format (buffer));
+
+  if (bit_depth == 8)
+    {
+      gegl_buffer_set (buffer,
+                       GEGL_RECTANGLE (0, 0, width, height),
+                       0, format, data, stride);
+    }
+  else /* high bit depth */
+    {
+      uint16_t       *data16;
+      const uint16_t *src16;
+      uint16_t       *dest16;
+      gint            x, y, rowentries;
+      int             tmp_pixelval;
+
+      if (has_alpha)
+        {
+          rowentries = width * 4;
+        }
+      else /* no alpha */
+        {
+          rowentries = width * 3;
+        }
+
+      data16 = g_malloc_n (height, rowentries * 2);
+      dest16 = data16;
+
+      switch (bit_depth)
+        {
+        case 10:
+          for (y = 0; y < height; y++)
+            {
+              src16 = (const uint16_t *) (y * stride + data);
+              for (x = 0; x < rowentries; x++)
+                {
+                  tmp_pixelval = (int) ( ( (float) (0x03ff & (*src16)) / 1023.0f) * 65535.0f + 0.5f);
+                  *dest16 = CLAMP (tmp_pixelval, 0, 65535);
+                  dest16++;
+                  src16++;
+                }
+            }
+          break;
+        case 12:
+          for (y = 0; y < height; y++)
+            {
+              src16 = (const uint16_t *) (y * stride + data);
+              for (x = 0; x < rowentries; x++)
+                {
+                  tmp_pixelval = (int) ( ( (float) (0x0fff & (*src16))  / 4095.0f) * 65535.0f + 0.5f);
+                  *dest16 = CLAMP (tmp_pixelval, 0, 65535);
+                  dest16++;
+                  src16++;
+                }
+            }
+          break;
+        default:
+          for (y = 0; y < height; y++)
+            {
+              src16 = (const uint16_t *) (y * stride + data);
+              for (x = 0; x < rowentries; x++)
+                {
+                  *dest16 = *src16;
+                  dest16++;
+                  src16++;
+                }
+            }
+          break;
+        }
+
+      gegl_buffer_set (buffer,
+                       GEGL_RECTANGLE (0, 0, width, height),
+                       0, format, data16, GEGL_AUTO_ROWSTRIDE);
+
+      g_free (data16);
+    }
 
   g_object_unref (buffer);
 
@@ -609,37 +1141,78 @@ load_image (GFile              *file,
 
     n_metadata =
       heif_image_handle_get_list_of_metadata_block_IDs (handle,
-                                                        "XMP",
+                                                        "mime",
                                                         &metadata_id, 1);
     if (n_metadata > 0)
       {
-        xmp_data_size = heif_image_handle_get_metadata_size (handle,
-                                                             metadata_id);
-        xmp_data = g_alloca (xmp_data_size);
-
-        err = heif_image_handle_get_metadata (handle, metadata_id, xmp_data);
-        if (err.code != 0)
+        if (g_strcmp0 (
+              heif_image_handle_get_metadata_content_type (handle, metadata_id),
+              "application/rdf+xml") == 0)
           {
-            xmp_data      = NULL;
-            xmp_data_size = 0;
+            xmp_data_size = heif_image_handle_get_metadata_size (handle,
+                            metadata_id);
+            xmp_data = g_alloca (xmp_data_size);
+
+            err = heif_image_handle_get_metadata (handle, metadata_id, xmp_data);
+            if (err.code != 0)
+              {
+                xmp_data      = NULL;
+                xmp_data_size = 0;
+              }
           }
       }
 
     if (exif_data || xmp_data)
       {
         GimpMetadata          *metadata = gimp_metadata_new ();
-        GimpMetadataLoadFlags  flags    = GIMP_METADATA_LOAD_ALL;
+        GimpMetadataLoadFlags  flags    = GIMP_METADATA_LOAD_COMMENT | GIMP_METADATA_LOAD_RESOLUTION;
 
         if (exif_data)
-          gimp_metadata_set_from_exif (metadata,
-                                       exif_data, exif_data_size, NULL);
+          {
+            const guint8 tiffHeaderBE[4] = { 'M', 'M', 0, 42 };
+            const guint8 tiffHeaderLE[4] = { 'I', 'I', 42, 0 };
+            GExiv2Metadata *exif_metadata = GEXIV2_METADATA (metadata);
+            const guint8 *tiffheader = exif_data;
+            glong new_exif_size = exif_data_size;
+
+            while (new_exif_size >= 4)  /*Searching for TIFF Header*/
+              {
+                if (tiffheader[0] == tiffHeaderBE[0] && tiffheader[1] == tiffHeaderBE[1] &&
+                    tiffheader[2] == tiffHeaderBE[2] && tiffheader[2] == tiffHeaderBE[2])
+                  {
+                    break;
+                  }
+                if (tiffheader[0] == tiffHeaderLE[0] && tiffheader[1] == tiffHeaderLE[1] &&
+                    tiffheader[2] == tiffHeaderLE[2] && tiffheader[2] == tiffHeaderLE[2])
+                  {
+                    break;
+                  }
+                new_exif_size--;
+                tiffheader++;
+              }
+
+            if (new_exif_size > 4)   /* TIFF header + some data found*/
+              {
+                if (! gexiv2_metadata_open_buf (exif_metadata, tiffheader, new_exif_size, error))
+                  {
+                    g_printerr ("%s: Failed to set EXIF metadata: %s\n", G_STRFUNC, (*error)->message);
+                    g_clear_error (error);
+                  }
+              }
+            else
+              {
+                g_printerr ("%s: EXIF metadata not set\n", G_STRFUNC);
+              }
+          }
 
         if (xmp_data)
-          gimp_metadata_set_from_xmp (metadata,
-                                      xmp_data, xmp_data_size, NULL);
-
-        if (profile)
-          flags &= ~GIMP_METADATA_LOAD_COLORSPACE;
+          {
+            if (!gimp_metadata_set_from_xmp (metadata, xmp_data, xmp_data_size, error))
+              {
+                g_printerr ("%s: Failed to set XMP metadata: %s\n", G_STRFUNC, (*error)->message);
+                g_clear_error (error);
+              }
+          }
 
         gimp_image_metadata_load_finish (image, "image/heif",
                                          metadata, flags,
@@ -656,6 +1229,10 @@ load_image (GFile              *file,
 
   gimp_progress_update (1.0);
 
+  if (image)
+    {
+      *status = GIMP_PDB_SUCCESS;
+    }
   return image;
 }
 
@@ -670,7 +1247,7 @@ write_callback (struct heif_context *ctx,
   struct heif_error  heif_error;
 
   heif_error.code    = heif_error_Ok;
-  heif_error.subcode = heif_error_Ok;
+  heif_error.subcode = heif_suberror_Unspecified;
   heif_error.message = "";
 
   if (! g_output_stream_write_all (output, data, size, NULL, NULL, &error))
@@ -683,11 +1260,12 @@ write_callback (struct heif_context *ctx,
 }
 
 static gboolean
-save_image (GFile         *file,
-            GimpImage     *image,
-            GimpDrawable  *drawable,
-            GObject       *config,
-            GError       **error)
+save_image (GFile                        *file,
+            GimpImage                    *image,
+            GimpDrawable                 *drawable,
+            GObject                      *config,
+            GError                      **error,
+            enum heif_compression_format  compression)
 {
   struct heif_image        *h_image = NULL;
   struct heif_context      *context = heif_context_alloc ();
@@ -709,10 +1287,21 @@ save_image (GFile         *file,
   gboolean                  lossless;
   gint                      quality;
   gboolean                  save_profile;
+  gint                      save_bit_depth = 8;
+
+  if (!context)
+    {
+      g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
+                   "cannot allocate heif_context");
+      return NULL;
+    }
 
   g_object_get (config,
                 "lossless",           &lossless,
                 "quality",            &quality,
+#if LIBHEIF_HAVE_VERSION(1,8,0)
+                "save-bit-depth",     &save_bit_depth,
+#endif
                 "save-color-profile", &save_profile,
                 NULL);
 
@@ -724,14 +1313,53 @@ save_image (GFile         *file,
 
   has_alpha = gimp_drawable_has_alpha (drawable);
 
-  err = heif_image_create (width, height,
-                           heif_colorspace_RGB,
-                           has_alpha ?
-                           heif_chroma_interleaved_RGBA :
-                           heif_chroma_interleaved_RGB,
-                           &h_image);
+  switch (save_bit_depth)
+    {
+    case 8:
+      err = heif_image_create (width, height,
+                               heif_colorspace_RGB,
+                               has_alpha ?
+                               heif_chroma_interleaved_RGBA :
+                               heif_chroma_interleaved_RGB,
+                               &h_image);
+      break;
+    case 10:
+    case 12:
+#if ( G_BYTE_ORDER == G_LITTLE_ENDIAN )
+      err = heif_image_create (width, height,
+                               heif_colorspace_RGB,
+                               has_alpha ?
+                               heif_chroma_interleaved_RRGGBBAA_LE :
+                               heif_chroma_interleaved_RRGGBB_LE,
+                               &h_image);
+#else
+      err = heif_image_create (width, height,
+                               heif_colorspace_RGB,
+                               has_alpha ?
+                               heif_chroma_interleaved_RRGGBBAA_BE :
+                               heif_chroma_interleaved_RRGGBB_BE,
+                               &h_image);
+#endif
+      break;
+    default:
+      g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
+                   "Unsupported bit depth: %d",
+                   save_bit_depth);
+      heif_context_free (context);
+      return FALSE;
+      break;
+    }
 
-#ifdef HAVE_LIBHEIF_1_4_0
+  if (err.code != 0)
+    {
+      g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
+                   _("Encoding HEIF image failed: %s"),
+                   err.message);
+      heif_context_free (context);
+      return FALSE;
+    }
+
+#if LIBHEIF_HAVE_VERSION(1,4,0)
   if (save_profile)
     {
       GimpColorProfile *profile = NULL;
@@ -780,48 +1408,171 @@ save_image (GFile         *file,
 
       g_object_unref (profile);
     }
-#endif /* HAVE_LIBHEIF_1_4_0 */
+  else
+    {
+#if LIBHEIF_HAVE_VERSION(1,8,0)
+      /* We save as sRGB */
+      struct heif_color_profile_nclx nclx_profile;
+
+      nclx_profile.color_primaries = heif_color_primaries_ITU_R_BT_709_5;
+      nclx_profile.transfer_characteristics = heif_transfer_characteristic_IEC_61966_2_1;
+      nclx_profile.matrix_coefficients = heif_matrix_coefficients_ITU_R_BT_601_6;
+      nclx_profile.full_range_flag = 1;
+
+      heif_image_set_nclx_color_profile (h_image, &nclx_profile);
+
+      space = babl_space ("sRGB");
+      out_linear = FALSE;
+#endif
+    }
+#endif /* LIBHEIF_HAVE_VERSION(1,4,0) */
 
   if (! space)
     space = gimp_drawable_get_format (drawable);
 
-  heif_image_add_plane (h_image, heif_channel_interleaved,
-                        width, height, has_alpha ? 32 : 24);
 
-  data = heif_image_get_plane (h_image, heif_channel_interleaved, &stride);
+  if (save_bit_depth > 8)
+    {
+      uint16_t       *data16;
+      const uint16_t *src16;
+      uint16_t       *dest16;
+      gint            x, y, rowentries;
+      int             tmp_pixelval;
+
+      if (has_alpha)
+        {
+          rowentries = width * 4;
 
-  buffer = gimp_drawable_get_buffer (drawable);
+          if (out_linear)
+            encoding = "RGBA u16";
+          else
+            encoding = "R'G'B'A u16";
+        }
+      else /* no alpha */
+        {
+          rowentries = width * 3;
 
-  if (has_alpha)
-    {
-      if (out_linear)
-        encoding = "RGBA u8";
-      else
-        encoding = "R'G'B'A u8";
+          if (out_linear)
+            encoding = "RGB u16";
+          else
+            encoding = "R'G'B' u16";
+        }
+
+      data16 = g_malloc_n (height, rowentries * 2);
+      src16 = data16;
+
+      format = babl_format_with_space (encoding, space);
+
+      buffer = gimp_drawable_get_buffer (drawable);
+
+      gegl_buffer_get (buffer,
+                       GEGL_RECTANGLE (0, 0, width, height),
+                       1.0, format, data16, GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
+
+      g_object_unref (buffer);
+
+      heif_image_add_plane (h_image, heif_channel_interleaved,
+                            width, height, save_bit_depth);
+
+      data = heif_image_get_plane (h_image, heif_channel_interleaved, &stride);
+
+      switch (save_bit_depth)
+        {
+        case 10:
+          for (y = 0; y < height; y++)
+            {
+              dest16 = (uint16_t *) (y * stride + data);
+              for (x = 0; x < rowentries; x++)
+                {
+                  tmp_pixelval = (int) ( ( (float) (*src16) / 65535.0f) * 1023.0f + 0.5f);
+                  *dest16 = CLAMP (tmp_pixelval, 0, 1023);
+                  dest16++;
+                  src16++;
+                }
+            }
+          break;
+        case 12:
+          for (y = 0; y < height; y++)
+            {
+              dest16 = (uint16_t *) (y * stride + data);
+              for (x = 0; x < rowentries; x++)
+                {
+                  tmp_pixelval = (int) ( ( (float) (*src16) / 65535.0f) * 4095.0f + 0.5f);
+                  *dest16 = CLAMP (tmp_pixelval, 0, 4095);
+                  dest16++;
+                  src16++;
+                }
+            }
+          break;
+        default:
+          for (y = 0; y < height; y++)
+            {
+              dest16 = (uint16_t *) (y * stride + data);
+              for (x = 0; x < rowentries; x++)
+                {
+                  *dest16 = *src16;
+                  dest16++;
+                  src16++;
+                }
+            }
+          break;
+        }
+      g_free (data16);
     }
-  else
+  else /* save_bit_depth == 8 */
     {
-      if (out_linear)
-        encoding = "RGB u8";
+#if LIBHEIF_HAVE_VERSION(1,8,0)
+      heif_image_add_plane (h_image, heif_channel_interleaved,
+                            width, height, 8);
+#else
+      /* old style settings */
+      heif_image_add_plane (h_image, heif_channel_interleaved,
+                            width, height, has_alpha ? 32 : 24);
+#endif
+
+      data = heif_image_get_plane (h_image, heif_channel_interleaved, &stride);
+
+      buffer = gimp_drawable_get_buffer (drawable);
+
+      if (has_alpha)
+        {
+          if (out_linear)
+            encoding = "RGBA u8";
+          else
+            encoding = "R'G'B'A u8";
+        }
       else
-        encoding = "R'G'B' u8";
-    }
-  format = babl_format_with_space (encoding, space);
+        {
+          if (out_linear)
+            encoding = "RGB u8";
+          else
+            encoding = "R'G'B' u8";
+        }
+      format = babl_format_with_space (encoding, space);
 
-  gegl_buffer_get (buffer,
-                   GEGL_RECTANGLE (0, 0, width, height),
-                   1.0, format, data, stride, GEGL_ABYSS_NONE);
+      gegl_buffer_get (buffer,
+                       GEGL_RECTANGLE (0, 0, width, height),
+                       1.0, format, data, stride, GEGL_ABYSS_NONE);
 
-  g_object_unref (buffer);
+      g_object_unref (buffer);
+    }
 
   gimp_progress_update (0.33);
 
   /*  encode to HEIF file  */
-
   err = heif_context_get_encoder_for_format (context,
-                                             heif_compression_HEVC,
+                                             compression,
                                              &encoder);
 
+  if (err.code != 0)
+    {
+      g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
+                   "Unable to find suitable HEIF encoder");
+      heif_image_release (h_image);
+      heif_context_free (context);
+      return FALSE;
+  }
+
   heif_encoder_set_lossy_quality (encoder, quality);
   heif_encoder_set_lossless (encoder, lossless);
   /* heif_encoder_set_logging_level (encoder, logging_level); */
@@ -836,6 +1587,9 @@ save_image (GFile         *file,
       g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
                    _("Encoding HEIF image failed: %s"),
                    err.message);
+      heif_encoder_release (encoder);
+      heif_image_release (h_image);
+      heif_context_free (context);
       return FALSE;
     }
 
@@ -850,7 +1604,12 @@ save_image (GFile         *file,
                                             NULL, FALSE, G_FILE_CREATE_NONE,
                                             NULL, error));
   if (! output)
-    return FALSE;
+    {
+      heif_encoder_release (encoder);
+      heif_image_release (h_image);
+      heif_context_free (context);
+      return FALSE;
+    }
 
   err = heif_context_write (context, &writer, output);
 
@@ -865,15 +1624,18 @@ save_image (GFile         *file,
       g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
                    _("Writing HEIF image failed: %s"),
                    err.message);
+
+      heif_encoder_release (encoder);
+      heif_image_release (h_image);
+      heif_context_free (context);
       return FALSE;
     }
 
   g_object_unref (output);
 
-  heif_context_free (context);
-  heif_image_release (h_image);
-
   heif_encoder_release (encoder);
+  heif_image_release (h_image);
+  heif_context_free (context);
 
   gimp_progress_update (1.0);
 
@@ -1233,13 +1995,20 @@ load_dialog (struct heif_context *heif,
 
 gboolean
 save_dialog (GimpProcedure *procedure,
-             GObject       *config)
+             GObject       *config,
+             GimpImage     *image)
 {
   GtkWidget *dialog;
   GtkWidget *main_vbox;
   GtkWidget *grid;
   GtkWidget *button;
   GtkWidget *frame;
+#if LIBHEIF_HAVE_VERSION(1,8,0)
+  GtkWidget *grid2;
+  GtkListStore  *store;
+  GtkWidget     *combo;
+  gint           save_bit_depth = 8;
+#endif
   gboolean   run;
 
   dialog = gimp_procedure_dialog_new (procedure,
@@ -1276,7 +2045,56 @@ save_dialog (GimpProcedure *procedure,
                              1, 10, 0,
                              FALSE, 0, 0);
 
-#ifdef HAVE_LIBHEIF_1_4_0
+#if LIBHEIF_HAVE_VERSION(1,8,0)
+  g_object_get (config,
+                "save-bit-depth", &save_bit_depth,
+                NULL);
+
+  switch (gimp_image_get_precision (image))
+    {
+    case GIMP_PRECISION_U8_LINEAR:
+    case GIMP_PRECISION_U8_NON_LINEAR:
+    case GIMP_PRECISION_U8_PERCEPTUAL:
+      /* image is 8bit depth */
+      if (save_bit_depth > 8)
+        {
+          save_bit_depth = 8;
+          g_object_set (config,
+                        "save-bit-depth", save_bit_depth,
+                        NULL);
+        }
+      break;
+    default:
+      /* high bit depth */
+      if (save_bit_depth < 10)
+        {
+          save_bit_depth = 10;
+          g_object_set (config,
+                        "save-bit-depth", save_bit_depth,
+                        NULL);
+        }
+      break;
+    }
+
+  grid2 = gtk_grid_new ();
+  gtk_grid_set_column_spacing (GTK_GRID (grid2), 6);
+  gtk_box_pack_start (GTK_BOX (main_vbox), grid2, FALSE, FALSE, 0);
+  gtk_widget_show (grid2);
+
+  store = gimp_int_store_new ("8 bit/channel",    8,
+                              "10 bit/channel (HDR)",  10,
+                              "12 bit/channel (HDR)", 12,
+                              NULL);
+
+  combo = gimp_prop_int_combo_box_new (config, "save-bit-depth",
+                                       GIMP_INT_STORE (store));
+  g_object_unref (store);
+  gimp_grid_attach_aligned (GTK_GRID (grid2), 0, 1,
+                            "Bit depth:", 0.0, 0.5,
+                            combo, 2);
+#endif
+
+#if LIBHEIF_HAVE_VERSION(1,4,0)
   button = gimp_prop_check_button_new (config, "save-color-profile",
                                        _("Save color _profile"));
   gtk_box_pack_start (GTK_BOX (main_vbox), button, FALSE, FALSE, 0);
diff --git a/plug-ins/common/meson.build b/plug-ins/common/meson.build
index 8b41c30478..1e11436d0f 100644
--- a/plug-ins/common/meson.build
+++ b/plug-ins/common/meson.build
@@ -93,7 +93,7 @@ endif
 
 if libheif.found()
   common_plugins_list += { 'name': 'file-heif',
-    'deps': [ gtk3, gegl, libheif, ],
+    'deps': [ gtk3, gegl, libheif, gexiv2, lcms, ],
   }
 endif
 



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]