[gimp] plug-ins: add HEIF loading/saving plug-in written by Dirk Farin



commit ba149f17592cc47d15b26430e270a9626f137c4f
Author: Michael Natterer <mitch gimp org>
Date:   Fri May 4 20:30:30 2018 +0200

    plug-ins: add HEIF loading/saving plug-in written by Dirk Farin
    
    Thanks a lot to Dirk for contributing this, added him to AUTHORS.
    
    Import the code from https://github.com/strukturag/heif-gimp-plugin.git
    as of today. Merged the files into a single-file plug-in. Changed
    the code a lot to match our coding style, but only formatting,
    no logic changes.
    
    Still uses deprecated GimpDrawable API and no GIO, but I wanted to do
    actual code changes separately from the initial import. Also disabled
    metadata support because updating that to GimpMetadata was too much
    for the initial import.

 AUTHORS                        |    1 +
 authors.xml                    |    1 +
 configure.ac                   |   26 ++
 plug-ins/common/.gitignore     |    2 +
 plug-ins/common/Makefile.am    |   23 +
 plug-ins/common/file-heif.c    |  969 ++++++++++++++++++++++++++++++++++++++++
 plug-ins/common/gimprc.common  |    1 +
 plug-ins/common/plugin-defs.pl |    1 +
 po-plug-ins/POTFILES.in        |    1 +
 9 files changed, 1025 insertions(+), 0 deletions(-)
---
diff --git a/AUTHORS b/AUTHORS
index fd8aa0d..cbe34ae 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -76,6 +76,7 @@ The following people have contributed code to GIMP:
  Ell
  Morton Eriksen
  Larry Ewing
+ Dirk Farin
  Pedro Alonso Ferrer
  Nick Fetchak
  Piotr Filiciak
diff --git a/authors.xml b/authors.xml
index 040dda8..4ae3efd 100644
--- a/authors.xml
+++ b/authors.xml
@@ -84,6 +84,7 @@
   <contributor role="author" last-active="2.0">Morton Eriksen</contributor>
   <contributor role="author" last-active="2.0">Larry Ewing</contributor>
   <contributor role="documenter" last-active="2.6">Alessandro Falappa</contributor>
+  <contributor role="author" last-active="2.10">Dirk Farin</contributor>
   <contributor role="author" last-active="2.4">Pedro Alonso Ferrer</contributor>
   <contributor role="author" last-active="1.2">Nick Fetchak</contributor>
   <contributor role="author" last-active="2.4">Piotr Filiciak</contributor>
diff --git a/configure.ac b/configure.ac
index a4f2581..38b0dab 100644
--- a/configure.ac
+++ b/configure.ac
@@ -81,6 +81,7 @@ m4_define([intltool_required_version], [0.40.1])
 m4_define([perl_required_version], [5.10.0])
 m4_define([python2_required_version], [2.5.0])
 m4_define([webp_required_version], [0.6.0])
+m4_define([libheif_required_version], [1.1.0])
 
 # Current test considers only 2 version numbers. If we update the recommended
 # version of gettext with more version numbers, please update the tests.
@@ -167,6 +168,7 @@ INTLTOOL_REQUIRED_VERSION=intltool_required_version
 PERL_REQUIRED_VERSION=perl_required_version
 PYTHON2_REQUIRED_VERSION=python2_required_version
 WEBP_REQUIRED_VERSION=webp_required_version
+LIBHEIF_REQUIRED_VERSION=libheif_required_version
 XGETTEXT_RECOMMENDED_VERSION=xgettext_recommended_version
 AC_SUBST(GLIB_REQUIRED_VERSION)
 AC_SUBST(GDK_PIXBUF_REQUIRED_VERSION)
@@ -197,6 +199,7 @@ AC_SUBST(INTLTOOL_REQUIRED_VERSION)
 AC_SUBST(PERL_REQUIRED_VERSION)
 AC_SUBST(PYTHON2_REQUIRED_VERSION)
 AC_SUBST(WEBP_REQUIRED_VERSION)
+AC_SUBST(LIBHEIF_REQUIRED_VERSION)
 AC_SUBST(XGETTEXT_RECOMMENDED_VERSION)
 
 # The symbol GIMP_UNSTABLE is defined above for substitution in
@@ -1664,6 +1667,28 @@ fi
 AM_CONDITIONAL(HAVE_WEBP, test "x$have_webp" = xyes)
 
 
+###################
+# Check for libheif
+###################
+
+AC_ARG_WITH(libheif, [  --without-libheif       build without libheif support])
+
+have_libheif=no
+if test "x$with_libheif" != xno; then
+  have_libheif=yes
+  PKG_CHECK_MODULES(LIBHEIF, libheif >= libheif_required_version,
+    FILE_HEIF='file-heif$(EXEEXT)',
+    [have_libheif="no (libheif not found)"])
+fi
+
+if test "x$have_libheif" = xyes; then
+  MIME_TYPES="$MIME_TYPES;image/heif;image/heic"
+fi
+
+AC_SUBST(FILE_HEIF)
+AM_CONDITIONAL(HAVE_LIBHEIF, test "x$have_libheif" = xyes)
+
+
 ######################
 # Check for libmypaint
 ######################
@@ -2762,6 +2787,7 @@ Optional Plug-Ins:
   MNG:                     $have_libmng
   OpenEXR:                 $have_openexr
   WebP:                    $have_webp
+  Heif:                    $have_libheif
   PDF (export):            $have_cairo_pdf
   Print:                   $enable_print
   Python 2:                $enable_python
diff --git a/plug-ins/common/.gitignore b/plug-ins/common/.gitignore
index a5e50cf..13a727e 100644
--- a/plug-ins/common/.gitignore
+++ b/plug-ins/common/.gitignore
@@ -78,6 +78,8 @@
 /file-glob.exe
 /file-header
 /file-header.exe
+/file-heif
+/file-heif.exe
 /file-html-table
 /file-html-table.exe
 /file-jp2-load
diff --git a/plug-ins/common/Makefile.am b/plug-ins/common/Makefile.am
index ef81323..07762c9 100644
--- a/plug-ins/common/Makefile.am
+++ b/plug-ins/common/Makefile.am
@@ -87,6 +87,7 @@ libexec_PROGRAMS = \
        file-gih \
        file-glob \
        file-header \
+       $(FILE_HEIF) \
        file-html-table \
        $(FILE_JP2_LOAD) \
        $(FILE_MNG) \
@@ -144,6 +145,7 @@ libexec_PROGRAMS = \
 
 EXTRA_PROGRAMS = \
        file-aa \
+       file-heif \
        file-jp2-load \
        file-mng \
        file-pdf-save \
@@ -821,6 +823,27 @@ file_header_LDADD = \
        $(INTLLIBS)             \
        $(file_header_RC)
 
+file_heif_CFLAGS = $(LIBHEIF_CFLAGS)
+
+file_heif_SOURCES = \
+       file-heif.c
+
+file_heif_LDADD = \
+       $(libgimpui)            \
+       $(libgimpwidgets)       \
+       $(libgimpmodule)        \
+       $(libgimp)              \
+       $(libgimpmath)          \
+       $(libgimpconfig)        \
+       $(libgimpcolor)         \
+       $(libgimpbase)          \
+       $(GTK_LIBS)             \
+       $(GEGL_LIBS)            \
+       $(LIBHEIF_LIBS)         \
+       $(RT_LIBS)              \
+       $(INTLLIBS)             \
+       $(file_heif_RC)
+
 file_html_table_SOURCES = \
        file-html-table.c
 
diff --git a/plug-ins/common/file-heif.c b/plug-ins/common/file-heif.c
new file mode 100644
index 0000000..2da6e46
--- /dev/null
+++ b/plug-ins/common/file-heif.c
@@ -0,0 +1,969 @@
+/*
+ * GIMP HEIF loader / write plugin.
+ * Copyright (c) 2018 struktur AG, Dirk Farin <farin struktur de>
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "config.h"
+
+#include <libheif/heif.h>
+
+#include <libgimp/gimp.h>
+#include <libgimp/gimpui.h>
+
+#include "libgimp/stdplugins-intl.h"
+
+
+#define LOAD_PROC      "file-heif-load"
+#define SAVE_PROC      "file-heif-save"
+#define PLUG_IN_BINARY "file-heif"
+
+
+typedef struct _SaveParams SaveParams;
+
+struct _SaveParams
+{
+  gint     quality;
+  gboolean lossless;
+};
+
+
+/*  local function prototypes  */
+
+static void       query          (void);
+static void       run            (const gchar          *name,
+                                  gint                  nparams,
+                                  const GimpParam      *param,
+                                  gint                 *nreturn_vals,
+                                  GimpParam           **return_vals);
+
+static gint32     load_image     (const gchar         *filename,
+                                  gboolean             interactive,
+                                  GError              **error);
+static gboolean   save_image     (const gchar          *filename,
+                                  gint32                image_ID,
+                                  gint32                drawable_ID,
+                                  const SaveParams     *params,
+                                  GError              **error);
+
+static gboolean   load_dialog    (struct heif_context  *heif,
+                                  uint32_t             *selected_image);
+static gboolean   save_dialog    (SaveParams           *params);
+
+
+GimpPlugInInfo PLUG_IN_INFO =
+{
+  NULL,  /* init_proc  */
+  NULL,  /* quit_proc  */
+  query, /* query_proc */
+  run,   /* run_proc   */
+};
+
+
+MAIN ()
+
+
+static void
+query (void)
+{
+  static const GimpParamDef load_args[] =
+  {
+    { GIMP_PDB_INT32,  "run-mode",     "The run mode { RUN-NONINTERACTIVE (1) }" },
+    { GIMP_PDB_STRING, "filename",     "The name of the file to load" },
+    { GIMP_PDB_STRING, "raw-filename", "The name entered"             }
+  };
+
+  static const GimpParamDef load_return_vals[] =
+  {
+    { GIMP_PDB_IMAGE, "image", "Output image" }
+  };
+
+
+  static const GimpParamDef save_args[] =
+  {
+    { GIMP_PDB_INT32,    "run-mode",     "The run mode { RUN-NONINTERACTIVE (1) }" },
+    { GIMP_PDB_IMAGE,    "image",        "Input image"                  },
+    { GIMP_PDB_DRAWABLE, "drawable",     "Drawable to export"           },
+    { GIMP_PDB_STRING,   "filename",     "The name of the file to export the image in" },
+    { GIMP_PDB_STRING,   "raw-filename", "The name of the file to export the image in" },
+    { GIMP_PDB_INT32,    "quality",      "Quality factor (range: 0-100. 0 = worst, 100 = best)" },
+    { GIMP_PDB_INT32,    "lossless",     "Use lossless compression (0 = lossy, 1 = lossless)" }
+  };
+
+  gimp_install_procedure (LOAD_PROC,
+                         _("Loads HEIF images"),
+                          _("Load image stored in HEIF format (High "
+                            "Efficiency Image File Format). Typical "
+                            "suffices for HEIF files are .heif, .heic."),
+                         "Dirk Farin <farin struktur de>",
+                         "Dirk Farin <farin struktur de>",
+                         "2018",
+                         _("HEIF/HEIC"),
+                         NULL,
+                         GIMP_PLUGIN,
+                         G_N_ELEMENTS (load_args),
+                         G_N_ELEMENTS (load_return_vals),
+                         load_args, load_return_vals);
+
+  gimp_register_load_handler (LOAD_PROC, "heic,heif", "");
+  gimp_register_file_handler_mime (LOAD_PROC, "image/heif");
+
+  gimp_install_procedure (SAVE_PROC,
+                         _("Exports HEIF images"),
+                          _("Save image in HEIF format (High Efficiency "
+                            "Image File Format)."),
+                         "Dirk Farin <farin struktur de>",
+                         "Dirk Farin <farin struktur de>",
+                         "2018",
+                         _("HEIF/HEIC"),
+                         "RGB*",
+                         GIMP_PLUGIN,
+                         G_N_ELEMENTS (save_args), 0,
+                         save_args, NULL);
+
+  gimp_register_save_handler (SAVE_PROC, "heic,heif", "");
+  gimp_register_file_handler_mime (SAVE_PROC, "image/heif");
+}
+
+#define LOAD_HEIF_ERROR  -1
+#define LOAD_HEIF_CANCEL -2
+
+static void
+run (const gchar      *name,
+     gint              n_params,
+     const GimpParam  *param,
+     gint             *nreturn_vals,
+     GimpParam       **return_vals)
+{
+  static GimpParam   values[2];
+  GimpRunMode        run_mode;
+  GimpPDBStatusType  status = GIMP_PDB_SUCCESS;
+  GError            *error  = NULL;
+
+  INIT_I18N ();
+
+  run_mode = param[0].data.d_int32;
+
+  *nreturn_vals = 1;
+  *return_vals  = values;
+
+  values[0].type          = GIMP_PDB_STATUS;
+  values[0].data.d_status = GIMP_PDB_EXECUTION_ERROR;
+
+  if (run_mode == GIMP_RUN_INTERACTIVE)
+    gimp_ui_init (PLUG_IN_BINARY, FALSE);
+
+  if (strcmp (name, LOAD_PROC) == 0)
+    {
+      const gchar *filename;
+      gboolean     interactive;
+
+      if (n_params != 3)
+        status = GIMP_PDB_CALLING_ERROR;
+
+      filename    = param[1].data.d_string;
+      interactive = (run_mode == GIMP_RUN_INTERACTIVE);
+
+      if (status == GIMP_PDB_SUCCESS)
+        {
+          gint32 image_ID;
+
+          image_ID = load_image (filename, interactive, &error);
+
+          if (image_ID >= 0)
+            {
+              *nreturn_vals = 2;
+              values[1].type         = GIMP_PDB_IMAGE;
+              values[1].data.d_image = image_ID;
+            }
+          else if (image_ID == LOAD_HEIF_CANCEL)
+            {
+              status = GIMP_PDB_CANCEL;
+            }
+        }
+    }
+  else if (strcmp(name, SAVE_PROC) == 0)
+    {
+      gint32           image_ID    = param[1].data.d_int32;
+      gint32           drawable_ID = param[2].data.d_int32;
+      GimpExportReturn export      = GIMP_EXPORT_CANCEL;
+      SaveParams       params;
+
+      params.lossless = FALSE;
+      params.quality  = 50;
+
+      switch (run_mode)
+        {
+        case GIMP_RUN_INTERACTIVE:
+        case GIMP_RUN_WITH_LAST_VALS:
+          export = gimp_export_image (&image_ID, &drawable_ID, "HEIF",
+                                      GIMP_EXPORT_CAN_HANDLE_RGB |
+                                      GIMP_EXPORT_CAN_HANDLE_ALPHA);
+
+          if (export == GIMP_EXPORT_CANCEL)
+            {
+              values[0].data.d_status = GIMP_PDB_CANCEL;
+              return;
+            }
+          break;
+
+        default:
+          break;
+        }
+
+      switch (run_mode)
+        {
+        case GIMP_RUN_INTERACTIVE:
+          gimp_get_data (SAVE_PROC, &params);
+
+          if (! save_dialog (&params))
+            status = GIMP_PDB_CANCEL;
+          break;
+
+        case GIMP_RUN_WITH_LAST_VALS:
+          gimp_get_data (SAVE_PROC, &params);
+          break;
+
+        case GIMP_RUN_NONINTERACTIVE:
+          /*  Make sure all the arguments are there!  */
+          if (n_params != 7)
+            {
+              status = GIMP_PDB_CALLING_ERROR;
+            }
+          else
+            {
+              params.quality  = (param[5].data.d_int32);
+              params.lossless = (param[6].data.d_int32);
+            }
+          break;
+        }
+
+      if (status == GIMP_PDB_SUCCESS)
+        {
+
+          if (save_image (param[3].data.d_string, image_ID, drawable_ID,
+                          &params,
+                          &error))
+            {
+              gimp_set_data (SAVE_PROC, &params, sizeof (params));
+            }
+          else
+            {
+              status = GIMP_PDB_EXECUTION_ERROR;
+            }
+        }
+    }
+  else
+    {
+      status = GIMP_PDB_CALLING_ERROR;
+    }
+
+  if (status != GIMP_PDB_SUCCESS && error)
+    {
+      *nreturn_vals = 2;
+      values[1].type          = GIMP_PDB_STRING;
+      values[1].data.d_string = error->message;
+    }
+
+  values[0].data.d_status = status;
+}
+
+gint32
+load_image (const gchar  *filename,
+            gboolean      interactive,
+            GError      **error)
+{
+  struct heif_context      *ctx;
+  struct heif_error         err;
+  struct heif_image_handle *handle = NULL;
+  struct heif_image        *img = NULL;
+  gint                      n_images;
+  heif_item_id              primary;
+  heif_item_id              selected_image;
+  gboolean                  has_alpha;
+  gint                      width;
+  gint                      height;
+  gint32                    image_ID;
+  gint32                    layer_ID;
+  GimpDrawable             *drawable;
+  GimpPixelRgn              rgn_out;
+  const guint8             *data;
+  gint                      stride;
+  gint                      bpp;
+
+  ctx = heif_context_alloc ();
+
+  err = heif_context_read_from_file (ctx, filename, NULL);
+  if (err.code)
+    {
+      g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
+                   _("Loading HEIF image failed: %s"),
+                   err.message);
+      heif_context_free (ctx);
+
+      return -1;
+    }
+
+  /* analyze image content
+   * Is there more than one image? Which image is the primary image?
+   */
+
+  n_images = heif_context_get_number_of_top_level_images (ctx);
+  if (n_images == 0)
+    {
+      g_set_error_literal (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
+                           _("Loading HEIF image failed: "
+                             "Input file contains no readable images"));
+      heif_context_free (ctx);
+
+      return -1;
+    }
+
+  err = heif_context_get_primary_image_ID (ctx, &primary);
+  if (err.code)
+    {
+      g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
+                   _("Loading HEIF image failed: %s"),
+                   err.message);
+      heif_context_free (ctx);
+
+      return -1;
+    }
+
+  /* if primary image is no top level image or not present (invalid
+   * file), just take the first image
+   */
+
+  if (! heif_context_is_top_level_image_ID (ctx, primary))
+    {
+      gint n = heif_context_get_list_of_top_level_image_IDs (ctx, &primary, 1);
+      g_assert (n == 1);
+    }
+
+  selected_image = primary;
+
+  /* if there are several images in the file and we are running
+   * interactive, let the user choose a picture
+   */
+
+  if (interactive && n_images > 1)
+    {
+      if (! load_dialog (ctx, &selected_image))
+        {
+          heif_context_free (ctx);
+
+          return LOAD_HEIF_CANCEL;
+        }
+    }
+
+  /* load the picture */
+
+  err = heif_context_get_image_handle (ctx, selected_image, &handle);
+  if (err.code)
+    {
+      g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
+                   _("Loading HEIF image failed: %s"),
+                   err.message);
+      heif_context_free (ctx);
+
+      return -1;
+    }
+
+  has_alpha = heif_image_handle_has_alpha_channel (handle);
+
+  err = heif_decode_image (handle,
+                           &img,
+                           heif_colorspace_RGB,
+                           has_alpha ? heif_chroma_interleaved_32bit :
+                           heif_chroma_interleaved_24bit,
+                           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);
+
+     return -1;
+    }
+
+  width  = heif_image_get_width  (img, heif_channel_interleaved);
+  height = heif_image_get_height (img, heif_channel_interleaved);
+
+  /* create GIMP image and copy HEIF image into the GIMP image
+   * (converting it to RGB)
+   */
+
+  image_ID = gimp_image_new (width, height, GIMP_RGB);
+  gimp_image_set_filename (image_ID, filename);
+
+  layer_ID = gimp_layer_new (image_ID,
+                             _("image content"),
+                             width, height,
+                             has_alpha ? GIMP_RGBA_IMAGE : GIMP_RGB_IMAGE,
+                             100.0,
+                             gimp_image_get_default_new_layer_mode (image_ID));
+
+  gimp_image_insert_layer (image_ID, layer_ID, -1, 0);
+
+  drawable = gimp_drawable_get (layer_ID);
+
+  gimp_pixel_rgn_init (&rgn_out,
+                       drawable,
+                       0,0,
+                       width, height,
+                       TRUE, TRUE);
+
+  data = heif_image_get_plane_readonly (img, heif_channel_interleaved,
+                                        &stride);
+
+  bpp = heif_image_get_bits_per_pixel (img, heif_channel_interleaved) / 8;
+
+  if (stride == width * bpp)
+    {
+      /* we can transfer the whole image at once */
+
+      gimp_pixel_rgn_set_rect (&rgn_out,
+                               data,
+                               0, 0, width, height);
+    }
+  else
+    {
+      gint y;
+
+      for (y = 0; y < height; y++)
+        {
+          /* stride has some padding, we have to send the image line by line */
+
+          gimp_pixel_rgn_set_row (&rgn_out,
+                                  data + y * stride,
+                                  0, y, width);
+        }
+    }
+
+  if (FALSE)
+    {
+      gint         n_metadata;
+      heif_item_id metadata_id;
+
+      n_metadata =
+        heif_image_handle_get_list_of_metadata_block_IDs (handle,
+                                                          "Exif",
+                                                          &metadata_id, 1);
+
+      if (n_metadata > 0)
+        {
+          size_t      data_size;
+          uint8_t    *data;
+          const gint  heif_exif_skip = 4;
+
+          data_size = heif_image_handle_get_metadata_size (handle,
+                                                           metadata_id);
+          data = g_alloca (data_size);
+
+          err = heif_image_handle_get_metadata (handle, metadata_id, data);
+
+
+          gimp_image_attach_new_parasite (image_ID,
+                                          "exif-data",
+                                          0,
+                                          data_size - heif_exif_skip,
+                                          data + heif_exif_skip);
+        }
+    }
+
+  gimp_drawable_flush (drawable);
+  gimp_drawable_merge_shadow (drawable->drawable_id, TRUE);
+  gimp_drawable_update (drawable->drawable_id,
+                        0, 0, width, height);
+
+  gimp_drawable_detach (drawable);
+
+  heif_image_handle_release (handle);
+  heif_context_free (ctx);
+  heif_image_release (img);
+
+  return image_ID;
+}
+
+static gboolean
+save_image (const gchar       *filename,
+            gint32             image_ID,
+            gint32             drawable_ID,
+            const SaveParams  *params,
+            GError           **error)
+{
+  struct heif_image        *image   = NULL;
+  struct heif_context      *context = heif_context_alloc ();
+  struct heif_encoder      *encoder;
+  struct heif_image_handle *handle;
+  struct heif_error         err;
+  guint8                   *data;
+  gint                      stride;
+  GimpPixelRgn              rgn_in;
+  GimpDrawable             *drawable;
+  gint                      width;
+  gint                      height;
+  gboolean                  has_alpha;
+  gint                      y;
+
+  width  = gimp_drawable_width  (drawable_ID);
+  height = gimp_drawable_height (drawable_ID);
+
+  has_alpha = gimp_drawable_has_alpha (drawable_ID);
+
+  err = heif_image_create (width, height,
+                           heif_colorspace_RGB,
+                           has_alpha ?
+                           heif_chroma_interleaved_32bit :
+                           heif_chroma_interleaved_24bit,
+                           &image);
+
+  heif_image_add_plane (image, heif_channel_interleaved,
+                        width, height, has_alpha ? 32 : 24);
+
+  data = heif_image_get_plane (image, heif_channel_interleaved, &stride);
+
+  drawable = gimp_drawable_get (drawable_ID);
+
+  gimp_pixel_rgn_init (&rgn_in, drawable,
+                       0, 0, width, height, FALSE, FALSE);
+
+  for (y = 0; y < height; y++)
+    {
+      gimp_pixel_rgn_get_row (&rgn_in,
+                              data + y * stride, 0, y, width);
+    }
+
+  gimp_drawable_detach (drawable);
+
+  /*  encode to HEIF file  */
+
+  context = heif_context_alloc ();
+
+  err = heif_context_get_encoder_for_format (context,
+                                             heif_compression_HEVC,
+                                             &encoder);
+
+  heif_encoder_set_lossy_quality (encoder, params->quality);
+  heif_encoder_set_lossless (encoder, params->lossless);
+  /* heif_encoder_set_logging_level (encoder, logging_level); */
+
+  err = heif_context_encode_image (context,
+                                   image,
+                                   encoder,
+                                   NULL,
+                                   &handle);
+  if (err.code != 0)
+    {
+      g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
+                   _("Encoding HEIF image failed: %s"),
+                   err.message);
+      return FALSE;
+    }
+
+  heif_image_handle_release (handle);
+
+  err = heif_context_write_to_file (context, filename);
+
+  if (err.code != 0)
+    {
+      g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
+                   _("Writing HEIF image failed: %s"),
+                   err.message);
+      return FALSE;
+    }
+
+  heif_context_free (context);
+  heif_image_release (image);
+
+  heif_encoder_release (encoder);
+
+  return TRUE;
+}
+
+
+/*  the dialogs  */
+
+#define MAX_THUMBNAIL_SIZE 320
+
+typedef struct _HeifImage HeifImage;
+
+struct _HeifImage
+{
+  uint32_t           ID;
+  gchar              caption[100];
+  struct heif_image *thumbnail;
+  gint               width;
+  gint               height;
+};
+
+static gboolean
+load_thumbnails (struct heif_context *heif,
+                 HeifImage           *images)
+{
+  guint32 *IDs;
+  gint     n_images;
+  gint     i;
+
+  n_images = heif_context_get_number_of_top_level_images (heif);
+
+  /* get list of all (top level) image IDs */
+
+  IDs = g_alloca (n_images * sizeof (guint32));
+
+  heif_context_get_list_of_top_level_image_IDs (heif, IDs, n_images);
+
+
+  /* Load a thumbnail for each image. */
+
+  for (i = 0; i < n_images; i++)
+    {
+      struct heif_image_handle *handle;
+      struct heif_error         err;
+      gint                      width;
+      gint                      height;
+      struct heif_image_handle *thumbnail_handle;
+      heif_item_id              thumbnail_ID;
+      gint                      n_thumbnails;
+      struct heif_image        *thumbnail_img;
+      gint                      thumbnail_width;
+      gint                      thumbnail_height;
+
+      images[i].caption[0] = 0;
+      images[i].thumbnail  = NULL;
+
+      /* get image handle */
+
+      err = heif_context_get_image_handle (heif, IDs[i], &handle);
+      if (err.code)
+        {
+          gimp_message (err.message);
+          continue;
+        }
+
+      /* generate image caption */
+
+      width  = heif_image_handle_get_width  (handle);
+      height = heif_image_handle_get_height (handle);
+
+      if (heif_image_handle_is_primary_image (handle))
+        {
+          g_snprintf (images[i].caption, sizeof (images[i].caption),
+                      "%dx%d (%s)", width, height, _("primary"));
+        }
+      else
+        {
+          g_snprintf (images[i].caption, sizeof (images[i].caption),
+                      "%dx%d", width, height);
+        }
+
+      /* get handle to thumbnail image
+       *
+       * if there is no thumbnail image, just the the image itself
+       * (will be scaled down later)
+       */
+
+      n_thumbnails = heif_image_handle_get_list_of_thumbnail_IDs (handle,
+                                                                  &thumbnail_ID,
+                                                                  1);
+
+      if (n_thumbnails > 0)
+        {
+          err = heif_image_handle_get_thumbnail (handle, thumbnail_ID,
+                                                 &thumbnail_handle);
+          if (err.code)
+            {
+              gimp_message (err.message);
+              continue;
+            }
+        }
+      else
+        {
+          err = heif_context_get_image_handle (heif, IDs[i], &thumbnail_handle);
+          if (err.code)
+            {
+              gimp_message (err.message);
+              continue;
+            }
+        }
+
+      /* decode the thumbnail image */
+
+      err = heif_decode_image (thumbnail_handle,
+                               &thumbnail_img,
+                               heif_colorspace_RGB,
+                               heif_chroma_interleaved_24bit,
+                               NULL);
+      if (err.code)
+        {
+          gimp_message (err.message);
+          continue;
+        }
+
+      /* if thumbnail image size exceeds the maximum, scale it down */
+
+      thumbnail_width  = heif_image_handle_get_width  (thumbnail_handle);
+      thumbnail_height = heif_image_handle_get_height (thumbnail_handle);
+
+      if (thumbnail_width  > MAX_THUMBNAIL_SIZE ||
+          thumbnail_height > MAX_THUMBNAIL_SIZE)
+        {
+          /* compute scaling factor to fit into a max sized box */
+
+          gfloat factor_h = thumbnail_width  / (gfloat) MAX_THUMBNAIL_SIZE;
+          gfloat factor_v = thumbnail_height / (gfloat) MAX_THUMBNAIL_SIZE;
+          gint   new_width, new_height;
+          struct heif_image *scaled_img = NULL;
+
+          if (factor_v > factor_h)
+            {
+              new_height = MAX_THUMBNAIL_SIZE;
+              new_width  = thumbnail_width / factor_v;
+            }
+          else
+            {
+              new_height = thumbnail_height / factor_h;
+              new_width  = MAX_THUMBNAIL_SIZE;
+            }
+
+          /* scale the image */
+
+          err = heif_image_scale_image (thumbnail_img,
+                                        &scaled_img,
+                                        new_width, new_height,
+                                        NULL);
+          if (err.code)
+            {
+              gimp_message (err.message);
+              continue;
+            }
+
+          /* release the old image and only keep the scaled down version */
+
+          heif_image_release (thumbnail_img);
+          thumbnail_img = scaled_img;
+
+          thumbnail_width  = new_width;
+          thumbnail_height = new_height;
+        }
+
+      heif_image_handle_release (thumbnail_handle);
+      heif_image_handle_release (handle);
+
+      /* remember the HEIF thumbnail image (we need it for the GdkPixbuf) */
+
+      images[i].thumbnail = thumbnail_img;
+
+      images[i].width  = thumbnail_width;
+      images[i].height = thumbnail_height;
+    }
+
+  return TRUE;
+}
+
+
+static gboolean
+load_dialog (struct heif_context *heif,
+             uint32_t            *selected_image)
+{
+  GtkWidget    *dialog;
+  GtkWidget    *main_vbox;
+  GtkWidget    *frame;
+  HeifImage    *heif_images;
+  GtkListStore *list_store;
+  GtkTreeIter   iter;
+  GtkWidget    *icon_view;
+  gint          n_images;
+  gint          i;
+  gint          selected_idx = -1;
+  gboolean      run          = FALSE;
+
+  n_images = heif_context_get_number_of_top_level_images (heif);
+
+  heif_images = g_alloca (n_images * sizeof (HeifImage));
+
+  if (! load_thumbnails (heif, heif_images))
+    return FALSE;
+
+  dialog = gimp_dialog_new (_("Load HEIF Image"), PLUG_IN_BINARY,
+                            NULL, 0,
+                            gimp_standard_help_func, LOAD_PROC,
+
+                            GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+                            GTK_STOCK_OK,     GTK_RESPONSE_OK,
+
+                            NULL);
+
+  main_vbox = gtk_vbox_new (FALSE, 12);
+  gtk_container_set_border_width (GTK_CONTAINER (main_vbox), 12);
+  gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))),
+                      main_vbox, TRUE, TRUE, 0);
+
+  frame = gimp_frame_new (_("Select Image"));
+  gtk_box_pack_start (GTK_BOX (main_vbox), frame, FALSE, FALSE, 0);
+  gtk_widget_show (frame);
+
+  /* prepare list store with all thumbnails and caption */
+
+  list_store = gtk_list_store_new (2, G_TYPE_STRING, GDK_TYPE_PIXBUF);
+
+  for (i = 0; i < n_images; i++)
+    {
+      GdkPixbuf    *pixbuf;
+      const guint8 *data;
+      gint          stride;
+
+      gtk_list_store_append (list_store, &iter);
+      gtk_list_store_set (list_store, &iter, 0, heif_images[i].caption, -1);
+
+      data = heif_image_get_plane_readonly (heif_images[i].thumbnail,
+                                            heif_channel_interleaved,
+                                            &stride);
+
+      pixbuf = gdk_pixbuf_new_from_data (data,
+                                         GDK_COLORSPACE_RGB,
+                                         FALSE,
+                                         8,
+                                         heif_images[i].width,
+                                         heif_images[i].height,
+                                         stride,
+                                         NULL,
+                                         NULL);
+
+      gtk_list_store_set (list_store, &iter, 1, pixbuf, -1);
+    }
+
+  icon_view = gtk_icon_view_new ();
+  gtk_icon_view_set_model (GTK_ICON_VIEW (icon_view),
+                           GTK_TREE_MODEL (list_store));
+  gtk_icon_view_set_text_column (GTK_ICON_VIEW (icon_view), 0);
+  gtk_icon_view_set_pixbuf_column (GTK_ICON_VIEW (icon_view), 1);
+  gtk_container_add (GTK_CONTAINER (frame), icon_view);
+  gtk_widget_show (icon_view);
+
+  /* pre-select the primary image */
+
+  for (i = 0; i < n_images; i++)
+    {
+      if (heif_images[i].ID == *selected_image)
+        {
+          selected_idx = i;
+          break;
+        }
+    }
+
+  if (selected_idx != -1)
+    {
+      GtkTreePath *path = gtk_tree_path_new_from_indices (selected_idx, -1);
+
+      gtk_icon_view_select_path (GTK_ICON_VIEW (icon_view), path);
+      gtk_tree_path_free (path);
+    }
+
+  gtk_widget_show (main_vbox);
+  gtk_widget_show (dialog);
+
+  run = (gimp_dialog_run (GIMP_DIALOG (dialog)) == GTK_RESPONSE_OK);
+
+  if (run)
+    {
+      GList *selected_items =
+        gtk_icon_view_get_selected_items (GTK_ICON_VIEW (icon_view));
+
+      if (selected_items)
+        {
+          GtkTreePath *path    = selected_items->data;
+          gint        *indices = gtk_tree_path_get_indices (path);
+
+          *selected_image = heif_images[indices[0]].ID;
+
+          g_list_free_full (selected_items,
+                            (GDestroyNotify) gtk_tree_path_free);
+        }
+    }
+
+  gtk_widget_destroy (dialog);
+
+  /* release thumbnail images */
+
+  for (i = 0 ; i < n_images; i++)
+    heif_image_release (heif_images[i].thumbnail);
+
+  return run;
+}
+
+static void
+lossless_button_toggled (GtkToggleButton *source,
+                         GtkWidget       *slider)
+{
+  gboolean lossless = gtk_toggle_button_get_active (source);
+
+  gtk_widget_set_sensitive (slider, ! lossless);
+}
+
+gboolean
+save_dialog (SaveParams *params)
+{
+  GtkWidget *dialog;
+  GtkWidget *main_vbox;
+  GtkWidget *hbox;
+  GtkWidget *label;
+  GtkWidget *lossless_button;
+  GtkWidget *quality_slider;
+  gboolean   run = FALSE;
+
+  dialog = gimp_export_dialog_new (_("HEIF"), PLUG_IN_BINARY, SAVE_PROC);
+
+  main_vbox = gtk_vbox_new (FALSE, 12);
+  gtk_container_set_border_width (GTK_CONTAINER (main_vbox), 12);
+  gtk_box_pack_start (GTK_BOX (gimp_export_dialog_get_content_area (dialog)),
+                      main_vbox, TRUE, TRUE, 0);
+
+  lossless_button = gtk_check_button_new_with_label (_("Lossless"));
+  gtk_box_pack_start (GTK_BOX (main_vbox), lossless_button, FALSE, FALSE, 0);
+
+  hbox = gtk_hbox_new (FALSE, 6);
+  label = gtk_label_new (_("Quality:"));
+  quality_slider = gtk_hscale_new_with_range (0, 100, 5);
+  gtk_scale_set_value_pos (GTK_SCALE(quality_slider), GTK_POS_RIGHT);
+  gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, TRUE, 0);
+  gtk_box_pack_start (GTK_BOX (hbox), quality_slider, TRUE, TRUE, 0);
+  gtk_box_pack_start (GTK_BOX (main_vbox), hbox, TRUE, TRUE, 0);
+
+  gtk_range_set_value (GTK_RANGE (quality_slider), params->quality);
+  gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON(lossless_button),
+                                params->lossless);
+  gtk_widget_set_sensitive (quality_slider, !params->lossless);
+
+  g_signal_connect (lossless_button, "toggled",
+                    G_CALLBACK (lossless_button_toggled),
+                    quality_slider);
+
+  gtk_widget_show_all (dialog);
+
+  run = (gimp_dialog_run (GIMP_DIALOG (dialog)) == GTK_RESPONSE_OK);
+
+  if (run)
+    {
+      params->quality  = gtk_range_get_value (GTK_RANGE (quality_slider));
+      params->lossless = gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (lossless_button));
+    }
+
+  gtk_widget_destroy (dialog);
+
+  return run;
+}
diff --git a/plug-ins/common/gimprc.common b/plug-ins/common/gimprc.common
index 3cddb15..2c7307c 100644
--- a/plug-ins/common/gimprc.common
+++ b/plug-ins/common/gimprc.common
@@ -36,6 +36,7 @@ file_gif_save_RC = file-gif-save.rc.o
 file_gih_RC = file-gih.rc.o
 file_glob_RC = file-glob.rc.o
 file_header_RC = file-header.rc.o
+file_heif_RC = file-heif.rc.o
 file_html_table_RC = file-html-table.rc.o
 file_jp2_load_RC = file-jp2-load.rc.o
 file_mng_RC = file-mng.rc.o
diff --git a/plug-ins/common/plugin-defs.pl b/plug-ins/common/plugin-defs.pl
index 11a1885..a8b2151 100644
--- a/plug-ins/common/plugin-defs.pl
+++ b/plug-ins/common/plugin-defs.pl
@@ -37,6 +37,7 @@
     'file-gih' => { ui => 1, gegl => 1 },
     'file-glob' => {},
     'file-header' => { ui => 1, gegl => 1 },
+    'file-heif' => { ui => 1, optional => 1, gegl => 1, libs => 'LIBHEIF_LIBS', cflags => 'LIBHEIF_CFLAGS' },
     'file-html-table' => { ui => 1, gegl => 1 },
     'file-jp2-load' => { ui => 1, optional => 1, gegl => 1, libs => 'OPENJPEG_LIBS', cflags => 
'OPENJPEG_CFLAGS' },
     'file-mng' => { ui => 1, gegl => 1, optional => 1, libs => 'MNG_LIBS', cflags => 'MNG_CFLAGS' },
diff --git a/po-plug-ins/POTFILES.in b/po-plug-ins/POTFILES.in
index 378c05b..df491f5 100644
--- a/po-plug-ins/POTFILES.in
+++ b/po-plug-ins/POTFILES.in
@@ -41,6 +41,7 @@ plug-ins/common/file-gif-save.c
 plug-ins/common/file-gih.c
 plug-ins/common/file-glob.c
 plug-ins/common/file-header.c
+plug-ins/common/file-heif.c
 plug-ins/common/file-html-table.c
 plug-ins/common/file-jp2-load.c
 plug-ins/common/file-mng.c


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