[gimp] Updated HEIF plug-in
- From: Jehan <jehanp src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [gimp] Updated HEIF plug-in
- Date: Wed, 26 Aug 2020 12:05:39 +0000 (UTC)
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]