2018-05-04 20:30:30 +02:00
|
|
|
/*
|
|
|
|
* 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
|
2018-07-11 23:27:07 +02:00
|
|
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
2018-05-04 20:30:30 +02:00
|
|
|
*/
|
|
|
|
|
|
|
|
#include "config.h"
|
|
|
|
|
|
|
|
#include <libheif/heif.h>
|
2020-08-19 16:36:18 +02:00
|
|
|
#include <lcms2.h>
|
|
|
|
#include <gexiv2/gexiv2.h>
|
2020-09-17 13:48:03 +02:00
|
|
|
#include <sys/time.h>
|
2018-05-04 20:30:30 +02:00
|
|
|
|
|
|
|
#include <libgimp/gimp.h>
|
|
|
|
#include <libgimp/gimpui.h>
|
|
|
|
|
|
|
|
#include "libgimp/stdplugins-intl.h"
|
|
|
|
|
|
|
|
|
|
|
|
#define LOAD_PROC "file-heif-load"
|
2020-10-20 14:16:53 +02:00
|
|
|
#define LOAD_PROC_AV1 "file-heif-av1-load"
|
2018-05-04 20:30:30 +02:00
|
|
|
#define SAVE_PROC "file-heif-save"
|
2020-08-19 16:36:18 +02:00
|
|
|
#define SAVE_PROC_AV1 "file-heif-av1-save"
|
2018-05-04 20:30:30 +02:00
|
|
|
#define PLUG_IN_BINARY "file-heif"
|
|
|
|
|
2020-09-17 13:48:03 +02:00
|
|
|
typedef struct
|
|
|
|
{
|
|
|
|
gchar *tag;
|
|
|
|
gint type;
|
|
|
|
} XmpStructs;
|
2018-05-04 20:30:30 +02:00
|
|
|
|
2020-10-15 17:38:24 +02:00
|
|
|
typedef enum _HeifpluginEncoderSpeed
|
|
|
|
{
|
|
|
|
HEIFPLUGIN_ENCODER_SPEED_SLOW = 0,
|
|
|
|
HEIFPLUGIN_ENCODER_SPEED_BALANCED = 1,
|
|
|
|
HEIFPLUGIN_ENCODER_SPEED_FASTER = 2
|
|
|
|
} HeifpluginEncoderSpeed;
|
|
|
|
|
|
|
|
typedef enum _HeifpluginExportFormat
|
|
|
|
{
|
|
|
|
HEIFPLUGIN_EXPORT_FORMAT_RGB = 0,
|
|
|
|
HEIFPLUGIN_EXPORT_FORMAT_YUV444 = 1,
|
|
|
|
HEIFPLUGIN_EXPORT_FORMAT_YUV422 = 2,
|
|
|
|
HEIFPLUGIN_EXPORT_FORMAT_YUV420 = 3
|
|
|
|
} HeifpluginExportFormat;
|
|
|
|
|
2019-08-24 10:23:01 +02:00
|
|
|
typedef struct _Heif Heif;
|
|
|
|
typedef struct _HeifClass HeifClass;
|
|
|
|
|
|
|
|
struct _Heif
|
|
|
|
{
|
|
|
|
GimpPlugIn parent_instance;
|
|
|
|
};
|
|
|
|
|
|
|
|
struct _HeifClass
|
2018-05-04 20:30:30 +02:00
|
|
|
{
|
2019-08-24 10:23:01 +02:00
|
|
|
GimpPlugInClass parent_class;
|
2018-05-04 20:30:30 +02:00
|
|
|
};
|
|
|
|
|
|
|
|
|
2019-08-24 10:23:01 +02:00
|
|
|
#define HEIF_TYPE (heif_get_type ())
|
|
|
|
#define HEIF (obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), HEIF_TYPE, Heif))
|
|
|
|
|
|
|
|
GType heif_get_type (void) G_GNUC_CONST;
|
|
|
|
|
|
|
|
static GList * heif_query_procedures (GimpPlugIn *plug_in);
|
|
|
|
static GimpProcedure * heif_create_procedure (GimpPlugIn *plug_in,
|
|
|
|
const gchar *name);
|
|
|
|
|
|
|
|
static GimpValueArray * heif_load (GimpProcedure *procedure,
|
|
|
|
GimpRunMode run_mode,
|
|
|
|
GFile *file,
|
|
|
|
const GimpValueArray *args,
|
|
|
|
gpointer run_data);
|
|
|
|
static GimpValueArray * heif_save (GimpProcedure *procedure,
|
|
|
|
GimpRunMode run_mode,
|
|
|
|
GimpImage *image,
|
2020-04-14 11:46:17 +02:00
|
|
|
gint n_drawables,
|
|
|
|
GimpDrawable **drawables,
|
2019-08-24 10:23:01 +02:00
|
|
|
GFile *file,
|
|
|
|
const GimpValueArray *args,
|
|
|
|
gpointer run_data);
|
|
|
|
|
2020-08-19 16:36:18 +02:00
|
|
|
#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
|
|
|
|
|
2019-08-24 10:23:01 +02:00
|
|
|
static GimpImage * load_image (GFile *file,
|
|
|
|
gboolean interactive,
|
|
|
|
GimpPDBStatusType *status,
|
|
|
|
GError **error);
|
2020-08-19 16:36:18 +02:00
|
|
|
static gboolean save_image (GFile *file,
|
|
|
|
GimpImage *image,
|
|
|
|
GimpDrawable *drawable,
|
|
|
|
GObject *config,
|
|
|
|
GError **error,
|
2020-09-17 13:48:03 +02:00
|
|
|
enum heif_compression_format compression,
|
|
|
|
GimpMetadata *metadata);
|
2019-08-24 10:23:01 +02:00
|
|
|
|
|
|
|
static gboolean load_dialog (struct heif_context *heif,
|
|
|
|
uint32_t *selected_image);
|
2019-09-24 19:35:49 +02:00
|
|
|
static gboolean save_dialog (GimpProcedure *procedure,
|
2020-08-19 16:36:18 +02:00
|
|
|
GObject *config,
|
|
|
|
GimpImage *image);
|
2019-08-24 10:23:01 +02:00
|
|
|
|
|
|
|
|
|
|
|
G_DEFINE_TYPE (Heif, heif, GIMP_TYPE_PLUG_IN)
|
|
|
|
|
|
|
|
GIMP_MAIN (HEIF_TYPE)
|
2018-05-04 20:30:30 +02:00
|
|
|
|
|
|
|
|
|
|
|
static void
|
2019-08-24 10:23:01 +02:00
|
|
|
heif_class_init (HeifClass *klass)
|
2018-05-04 20:30:30 +02:00
|
|
|
{
|
2019-08-24 10:23:01 +02:00
|
|
|
GimpPlugInClass *plug_in_class = GIMP_PLUG_IN_CLASS (klass);
|
2018-05-04 20:30:30 +02:00
|
|
|
|
2019-08-24 10:23:01 +02:00
|
|
|
plug_in_class->query_procedures = heif_query_procedures;
|
|
|
|
plug_in_class->create_procedure = heif_create_procedure;
|
|
|
|
}
|
2018-05-04 20:30:30 +02:00
|
|
|
|
2019-08-24 10:23:01 +02:00
|
|
|
static void
|
|
|
|
heif_init (Heif *heif)
|
|
|
|
{
|
|
|
|
}
|
2018-05-04 20:30:30 +02:00
|
|
|
|
2019-08-24 10:23:01 +02:00
|
|
|
static GList *
|
|
|
|
heif_query_procedures (GimpPlugIn *plug_in)
|
|
|
|
{
|
|
|
|
GList *list = NULL;
|
|
|
|
|
2020-10-20 14:16:53 +02:00
|
|
|
if (heif_have_decoder_for_format (heif_compression_HEVC))
|
|
|
|
{
|
|
|
|
list = g_list_append (list, g_strdup (LOAD_PROC));
|
|
|
|
}
|
|
|
|
|
|
|
|
if (heif_have_encoder_for_format (heif_compression_HEVC))
|
|
|
|
{
|
|
|
|
list = g_list_append (list, g_strdup (SAVE_PROC));
|
|
|
|
}
|
2020-08-19 16:36:18 +02:00
|
|
|
#if LIBHEIF_HAVE_VERSION(1,8,0)
|
2020-10-20 14:16:53 +02:00
|
|
|
if (heif_have_decoder_for_format (heif_compression_AV1))
|
|
|
|
{
|
|
|
|
list = g_list_append (list, g_strdup (LOAD_PROC_AV1));
|
|
|
|
}
|
|
|
|
|
|
|
|
if (heif_have_encoder_for_format (heif_compression_AV1))
|
|
|
|
{
|
|
|
|
list = g_list_append (list, g_strdup (SAVE_PROC_AV1));
|
|
|
|
}
|
2020-08-19 16:36:18 +02:00
|
|
|
#endif
|
2019-08-24 10:23:01 +02:00
|
|
|
return list;
|
2018-05-04 20:30:30 +02:00
|
|
|
}
|
|
|
|
|
2019-08-24 10:23:01 +02:00
|
|
|
static GimpProcedure *
|
|
|
|
heif_create_procedure (GimpPlugIn *plug_in,
|
2020-08-19 16:36:18 +02:00
|
|
|
const gchar *name)
|
2019-08-24 10:23:01 +02:00
|
|
|
{
|
|
|
|
GimpProcedure *procedure = NULL;
|
2018-05-04 20:30:30 +02:00
|
|
|
|
2019-08-24 10:23:01 +02:00
|
|
|
if (! strcmp (name, LOAD_PROC))
|
|
|
|
{
|
2019-08-30 12:52:28 +02:00
|
|
|
procedure = gimp_load_procedure_new (plug_in, name,
|
|
|
|
GIMP_PDB_PROC_TYPE_PLUGIN,
|
2019-08-24 10:23:01 +02:00
|
|
|
heif_load, NULL, NULL);
|
|
|
|
|
|
|
|
gimp_procedure_set_menu_label (procedure, N_("HEIF/HEIC"));
|
|
|
|
|
|
|
|
gimp_procedure_set_documentation (procedure,
|
|
|
|
_("Loads HEIF images"),
|
|
|
|
_("Load image stored in HEIF format (High "
|
|
|
|
"Efficiency Image File Format). Typical "
|
|
|
|
"suffices for HEIF files are .heif, "
|
|
|
|
".heic."),
|
|
|
|
name);
|
|
|
|
gimp_procedure_set_attribution (procedure,
|
|
|
|
"Dirk Farin <farin@struktur.de>",
|
|
|
|
"Dirk Farin <farin@struktur.de>",
|
|
|
|
"2018");
|
|
|
|
|
|
|
|
gimp_file_procedure_set_handles_remote (GIMP_FILE_PROCEDURE (procedure),
|
|
|
|
TRUE);
|
|
|
|
gimp_file_procedure_set_mime_types (GIMP_FILE_PROCEDURE (procedure),
|
2020-10-20 14:16:53 +02:00
|
|
|
"image/heif");
|
2019-08-24 10:23:01 +02:00
|
|
|
gimp_file_procedure_set_extensions (GIMP_FILE_PROCEDURE (procedure),
|
2020-10-20 14:16:53 +02:00
|
|
|
"heif,heic");
|
2019-08-24 10:23:01 +02:00
|
|
|
|
|
|
|
/* HEIF is an ISOBMFF format whose "brand" (the value after "ftyp")
|
|
|
|
* can be of various values.
|
|
|
|
* See also: https://gitlab.gnome.org/GNOME/gimp/issues/2209
|
|
|
|
*/
|
|
|
|
gimp_file_procedure_set_magics (GIMP_FILE_PROCEDURE (procedure),
|
|
|
|
"4,string,ftypheic,4,string,ftypheix,"
|
|
|
|
"4,string,ftyphevc,4,string,ftypheim,"
|
|
|
|
"4,string,ftypheis,4,string,ftyphevm,"
|
|
|
|
"4,string,ftyphevs,4,string,ftypmif1,"
|
2020-10-20 14:16:53 +02:00
|
|
|
"4,string,ftypmsf1");
|
2019-08-24 10:23:01 +02:00
|
|
|
}
|
|
|
|
else if (! strcmp (name, SAVE_PROC))
|
|
|
|
{
|
2019-08-30 12:52:28 +02:00
|
|
|
procedure = gimp_save_procedure_new (plug_in, name,
|
|
|
|
GIMP_PDB_PROC_TYPE_PLUGIN,
|
2019-08-24 10:23:01 +02:00
|
|
|
heif_save, NULL, NULL);
|
|
|
|
|
|
|
|
gimp_procedure_set_image_types (procedure, "RGB*");
|
|
|
|
|
|
|
|
gimp_procedure_set_menu_label (procedure, N_("HEIF/HEIC"));
|
|
|
|
|
|
|
|
gimp_procedure_set_documentation (procedure,
|
|
|
|
_("Exports HEIF images"),
|
|
|
|
_("Save image in HEIF format (High "
|
|
|
|
"Efficiency Image File Format)."),
|
|
|
|
name);
|
|
|
|
gimp_procedure_set_attribution (procedure,
|
|
|
|
"Dirk Farin <farin@struktur.de>",
|
|
|
|
"Dirk Farin <farin@struktur.de>",
|
|
|
|
"2018");
|
|
|
|
|
|
|
|
gimp_file_procedure_set_handles_remote (GIMP_FILE_PROCEDURE (procedure),
|
|
|
|
TRUE);
|
|
|
|
gimp_file_procedure_set_mime_types (GIMP_FILE_PROCEDURE (procedure),
|
|
|
|
"image/heif");
|
|
|
|
gimp_file_procedure_set_extensions (GIMP_FILE_PROCEDURE (procedure),
|
|
|
|
"heif,heic");
|
|
|
|
|
|
|
|
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);
|
2019-09-24 19:35:49 +02:00
|
|
|
|
2019-09-25 12:50:29 +02:00
|
|
|
GIMP_PROC_AUX_ARG_BOOLEAN (procedure, "save-color-profile",
|
|
|
|
"Save color profile",
|
2019-09-24 19:35:49 +02:00
|
|
|
"Save the image's color profile",
|
|
|
|
gimp_export_color_profile (),
|
|
|
|
G_PARAM_READWRITE);
|
2020-08-19 16:36:18 +02:00
|
|
|
|
|
|
|
GIMP_PROC_ARG_INT (procedure, "save-bit-depth",
|
|
|
|
"Bit depth",
|
|
|
|
"Bit depth of exported image",
|
|
|
|
8, 12, 8,
|
|
|
|
G_PARAM_READWRITE);
|
2020-09-17 13:48:03 +02:00
|
|
|
|
2020-10-15 17:38:24 +02:00
|
|
|
GIMP_PROC_ARG_INT (procedure, "pixel-format",
|
|
|
|
"Pixel format",
|
|
|
|
"Format of color sub-sampling",
|
|
|
|
HEIFPLUGIN_EXPORT_FORMAT_RGB, HEIFPLUGIN_EXPORT_FORMAT_YUV420,
|
|
|
|
HEIFPLUGIN_EXPORT_FORMAT_YUV420,
|
|
|
|
G_PARAM_READWRITE);
|
|
|
|
|
|
|
|
GIMP_PROC_ARG_INT (procedure, "encoder-speed",
|
|
|
|
"Encoder speed",
|
|
|
|
"Tradeoff between speed and compression",
|
|
|
|
HEIFPLUGIN_ENCODER_SPEED_SLOW, HEIFPLUGIN_ENCODER_SPEED_FASTER,
|
|
|
|
HEIFPLUGIN_ENCODER_SPEED_BALANCED,
|
|
|
|
G_PARAM_READWRITE);
|
|
|
|
|
2020-09-17 13:48:03 +02:00
|
|
|
GIMP_PROC_ARG_BOOLEAN (procedure, "save-exif",
|
|
|
|
"Save Exif",
|
|
|
|
"Toggle saving Exif data",
|
|
|
|
gimp_export_exif (),
|
|
|
|
G_PARAM_READWRITE);
|
|
|
|
|
|
|
|
GIMP_PROC_ARG_BOOLEAN (procedure, "save-xmp",
|
|
|
|
"Save XMP",
|
|
|
|
"Toggle saving XMP data",
|
|
|
|
gimp_export_xmp (),
|
|
|
|
G_PARAM_READWRITE);
|
2019-08-24 10:23:01 +02:00
|
|
|
}
|
2020-08-19 16:36:18 +02:00
|
|
|
#if LIBHEIF_HAVE_VERSION(1,8,0)
|
2020-10-20 14:16:53 +02:00
|
|
|
else if (! strcmp (name, LOAD_PROC_AV1))
|
|
|
|
{
|
|
|
|
procedure = gimp_load_procedure_new (plug_in, name,
|
|
|
|
GIMP_PDB_PROC_TYPE_PLUGIN,
|
|
|
|
heif_load, NULL, NULL);
|
|
|
|
|
|
|
|
gimp_procedure_set_menu_label (procedure, "HEIF/AVIF");
|
|
|
|
|
|
|
|
gimp_procedure_set_documentation (procedure,
|
|
|
|
_("Loads AVIF images"),
|
|
|
|
_("Load image stored 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_file_procedure_set_magics (GIMP_FILE_PROCEDURE (procedure),
|
|
|
|
"4,string,ftypmif1,4,string,ftypavif");
|
|
|
|
|
|
|
|
gimp_file_procedure_set_priority (GIMP_FILE_PROCEDURE (procedure), 100);
|
|
|
|
}
|
2020-08-19 16:36:18 +02:00
|
|
|
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,
|
2020-10-20 14:16:53 +02:00
|
|
|
_("Exports AVIF images"),
|
|
|
|
_("Save image in AV1 Image File Format (AVIF)"),
|
2020-08-19 16:36:18 +02:00
|
|
|
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");
|
|
|
|
|
2020-10-20 14:16:53 +02:00
|
|
|
gimp_file_procedure_set_priority (GIMP_FILE_PROCEDURE (procedure), 100);
|
|
|
|
|
2020-08-19 16:36:18 +02:00
|
|
|
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);
|
2019-08-24 10:23:01 +02:00
|
|
|
|
2020-08-19 16:36:18 +02:00
|
|
|
GIMP_PROC_ARG_INT (procedure, "save-bit-depth",
|
|
|
|
"Bit depth",
|
|
|
|
"Bit depth of exported image",
|
|
|
|
8, 12, 8,
|
|
|
|
G_PARAM_READWRITE);
|
2020-09-17 13:48:03 +02:00
|
|
|
|
2020-10-15 17:38:24 +02:00
|
|
|
GIMP_PROC_ARG_INT (procedure, "pixel-format",
|
|
|
|
"Pixel format",
|
|
|
|
"Format of color sub-sampling",
|
|
|
|
HEIFPLUGIN_EXPORT_FORMAT_RGB, HEIFPLUGIN_EXPORT_FORMAT_YUV420,
|
|
|
|
HEIFPLUGIN_EXPORT_FORMAT_YUV420,
|
|
|
|
G_PARAM_READWRITE);
|
|
|
|
|
|
|
|
GIMP_PROC_ARG_INT (procedure, "encoder-speed",
|
|
|
|
"Encoder speed",
|
|
|
|
"Tradeoff between speed and compression",
|
|
|
|
HEIFPLUGIN_ENCODER_SPEED_SLOW, HEIFPLUGIN_ENCODER_SPEED_FASTER,
|
|
|
|
HEIFPLUGIN_ENCODER_SPEED_BALANCED,
|
|
|
|
G_PARAM_READWRITE);
|
|
|
|
|
2020-09-17 13:48:03 +02:00
|
|
|
GIMP_PROC_ARG_BOOLEAN (procedure, "save-exif",
|
|
|
|
"Save Exif",
|
|
|
|
"Toggle saving Exif data",
|
|
|
|
gimp_export_exif (),
|
|
|
|
G_PARAM_READWRITE);
|
|
|
|
|
|
|
|
GIMP_PROC_ARG_BOOLEAN (procedure, "save-xmp",
|
|
|
|
"Save XMP",
|
|
|
|
"Toggle saving XMP data",
|
|
|
|
gimp_export_xmp (),
|
|
|
|
G_PARAM_READWRITE);
|
2020-08-19 16:36:18 +02:00
|
|
|
}
|
|
|
|
#endif
|
2019-08-24 10:23:01 +02:00
|
|
|
return procedure;
|
|
|
|
}
|
|
|
|
|
|
|
|
static GimpValueArray *
|
|
|
|
heif_load (GimpProcedure *procedure,
|
2020-08-19 16:36:18 +02:00
|
|
|
GimpRunMode run_mode,
|
|
|
|
GFile *file,
|
|
|
|
const GimpValueArray *args,
|
|
|
|
gpointer run_data)
|
2018-05-04 20:30:30 +02:00
|
|
|
{
|
2019-08-24 10:23:01 +02:00
|
|
|
GimpValueArray *return_vals;
|
2018-05-04 20:30:30 +02:00
|
|
|
GimpPDBStatusType status = GIMP_PDB_SUCCESS;
|
2019-08-24 10:23:01 +02:00
|
|
|
GimpImage *image;
|
|
|
|
gboolean interactive;
|
|
|
|
GError *error = NULL;
|
2018-05-04 20:30:30 +02:00
|
|
|
|
|
|
|
INIT_I18N ();
|
2018-05-04 21:14:23 +02:00
|
|
|
gegl_init (NULL, NULL);
|
2018-05-04 20:30:30 +02:00
|
|
|
|
2019-08-24 10:23:01 +02:00
|
|
|
interactive = (run_mode == GIMP_RUN_INTERACTIVE);
|
2018-05-04 20:30:30 +02:00
|
|
|
|
2019-08-24 10:23:01 +02:00
|
|
|
if (interactive)
|
2019-09-20 19:39:00 +02:00
|
|
|
gimp_ui_init (PLUG_IN_BINARY);
|
2018-05-04 20:30:30 +02:00
|
|
|
|
2019-08-24 10:23:01 +02:00
|
|
|
image = load_image (file, interactive, &status, &error);
|
2018-05-04 20:30:30 +02:00
|
|
|
|
2019-08-24 10:23:01 +02:00
|
|
|
if (! image)
|
|
|
|
return gimp_procedure_new_return_values (procedure, status, error);
|
2018-05-04 20:30:30 +02:00
|
|
|
|
2019-08-24 10:23:01 +02:00
|
|
|
return_vals = gimp_procedure_new_return_values (procedure,
|
|
|
|
GIMP_PDB_SUCCESS,
|
|
|
|
NULL);
|
2018-05-04 20:30:30 +02:00
|
|
|
|
2019-08-24 10:23:01 +02:00
|
|
|
GIMP_VALUES_SET_IMAGE (return_vals, 1, image);
|
2018-05-04 20:30:30 +02:00
|
|
|
|
2019-08-24 10:23:01 +02:00
|
|
|
return return_vals;
|
|
|
|
}
|
2018-05-04 20:30:30 +02:00
|
|
|
|
2019-08-24 10:23:01 +02:00
|
|
|
static GimpValueArray *
|
|
|
|
heif_save (GimpProcedure *procedure,
|
2020-08-19 16:36:18 +02:00
|
|
|
GimpRunMode run_mode,
|
|
|
|
GimpImage *image,
|
|
|
|
gint n_drawables,
|
|
|
|
GimpDrawable **drawables,
|
|
|
|
GFile *file,
|
|
|
|
const GimpValueArray *args,
|
|
|
|
gpointer run_data)
|
2019-08-24 10:23:01 +02:00
|
|
|
{
|
2019-10-09 23:50:14 +02:00
|
|
|
GimpProcedureConfig *config;
|
|
|
|
GimpPDBStatusType status = GIMP_PDB_SUCCESS;
|
|
|
|
GimpExportReturn export = GIMP_EXPORT_CANCEL;
|
2020-09-17 13:48:03 +02:00
|
|
|
GimpMetadata *metadata;
|
2019-10-09 23:50:14 +02:00
|
|
|
GError *error = NULL;
|
2018-05-05 12:53:39 +02:00
|
|
|
|
2019-08-24 10:23:01 +02:00
|
|
|
INIT_I18N ();
|
|
|
|
gegl_init (NULL, NULL);
|
2018-05-04 20:30:30 +02:00
|
|
|
|
2019-09-26 00:44:52 +02:00
|
|
|
config = gimp_procedure_create_config (procedure);
|
2020-09-17 13:48:03 +02:00
|
|
|
metadata = gimp_procedure_config_begin_export (config, image, run_mode,
|
|
|
|
args, "image/heif");
|
2018-05-04 20:30:30 +02:00
|
|
|
|
2019-08-24 10:23:01 +02:00
|
|
|
switch (run_mode)
|
|
|
|
{
|
|
|
|
case GIMP_RUN_INTERACTIVE:
|
|
|
|
case GIMP_RUN_WITH_LAST_VALS:
|
2019-09-20 19:39:00 +02:00
|
|
|
gimp_ui_init (PLUG_IN_BINARY);
|
2019-09-24 19:35:49 +02:00
|
|
|
|
2020-04-14 11:46:17 +02:00
|
|
|
export = gimp_export_image (&image, &n_drawables, &drawables, "HEIF",
|
2019-08-24 10:23:01 +02:00
|
|
|
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;
|
2018-05-04 20:30:30 +02:00
|
|
|
|
2019-08-24 10:23:01 +02:00
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
2018-05-04 20:30:30 +02:00
|
|
|
|
2020-04-14 11:46:17 +02:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2019-09-24 19:35:49 +02:00
|
|
|
if (run_mode == GIMP_RUN_INTERACTIVE)
|
|
|
|
{
|
2020-08-19 16:36:18 +02:00
|
|
|
if (! save_dialog (procedure, G_OBJECT (config), image))
|
2019-08-24 10:23:01 +02:00
|
|
|
status = GIMP_PDB_CANCEL;
|
|
|
|
}
|
2019-07-01 16:39:13 +02:00
|
|
|
|
2019-09-24 19:35:49 +02:00
|
|
|
if (status == GIMP_PDB_SUCCESS)
|
2019-08-24 10:23:01 +02:00
|
|
|
{
|
2020-04-14 11:46:17 +02:00
|
|
|
if (! save_image (file, image, drawables[0], G_OBJECT (config),
|
2020-09-17 13:48:03 +02:00
|
|
|
&error, heif_compression_HEVC, metadata))
|
2019-09-24 19:35:49 +02:00
|
|
|
{
|
|
|
|
status = GIMP_PDB_EXECUTION_ERROR;
|
|
|
|
}
|
2018-05-04 20:30:30 +02:00
|
|
|
}
|
|
|
|
|
2019-10-09 23:50:14 +02:00
|
|
|
gimp_procedure_config_end_export (config, image, file, status);
|
2019-09-24 19:35:49 +02:00
|
|
|
g_object_unref (config);
|
|
|
|
|
|
|
|
if (export == GIMP_EXPORT_EXPORT)
|
2020-04-14 11:46:17 +02:00
|
|
|
{
|
|
|
|
gimp_image_delete (image);
|
|
|
|
g_free (drawables);
|
|
|
|
}
|
2018-05-04 20:30:30 +02:00
|
|
|
|
2019-08-24 10:23:01 +02:00
|
|
|
return gimp_procedure_new_return_values (procedure, status, error);
|
2018-05-04 20:30:30 +02:00
|
|
|
}
|
|
|
|
|
2020-08-19 16:36:18 +02:00
|
|
|
#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;
|
2020-09-17 13:48:03 +02:00
|
|
|
GimpMetadata *metadata;
|
2020-08-19 16:36:18 +02:00
|
|
|
GError *error = NULL;
|
|
|
|
|
|
|
|
INIT_I18N ();
|
|
|
|
gegl_init (NULL, NULL);
|
|
|
|
|
|
|
|
config = gimp_procedure_create_config (procedure);
|
2020-09-17 13:48:03 +02:00
|
|
|
metadata = gimp_procedure_config_begin_export (config, image, run_mode,
|
|
|
|
args, "image/avif");
|
2020-08-19 16:36:18 +02:00
|
|
|
|
|
|
|
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),
|
2020-09-17 13:48:03 +02:00
|
|
|
&error, heif_compression_AV1, metadata))
|
2020-08-19 16:36:18 +02:00
|
|
|
{
|
|
|
|
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
|
|
|
|
|
2018-05-05 12:53:39 +02:00
|
|
|
static goffset
|
|
|
|
get_file_size (GFile *file,
|
|
|
|
GError **error)
|
|
|
|
{
|
|
|
|
GFileInfo *info;
|
|
|
|
goffset size = 1;
|
|
|
|
|
|
|
|
info = g_file_query_info (file,
|
|
|
|
G_FILE_ATTRIBUTE_STANDARD_SIZE,
|
|
|
|
G_FILE_QUERY_INFO_NONE,
|
|
|
|
NULL, error);
|
|
|
|
if (info)
|
|
|
|
{
|
|
|
|
size = g_file_info_get_size (info);
|
|
|
|
|
|
|
|
g_object_unref (info);
|
|
|
|
}
|
|
|
|
|
|
|
|
return size;
|
|
|
|
}
|
|
|
|
|
2020-08-19 16:36:18 +02:00
|
|
|
#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
|
|
|
|
|
2019-08-24 10:23:01 +02:00
|
|
|
GimpImage *
|
|
|
|
load_image (GFile *file,
|
|
|
|
gboolean interactive,
|
|
|
|
GimpPDBStatusType *status,
|
|
|
|
GError **error)
|
2018-05-04 20:30:30 +02:00
|
|
|
{
|
2018-05-05 12:53:39 +02:00
|
|
|
GInputStream *input;
|
|
|
|
goffset file_size;
|
|
|
|
guchar *file_buffer;
|
|
|
|
gsize bytes_read;
|
2018-05-04 20:30:30 +02:00
|
|
|
struct heif_context *ctx;
|
|
|
|
struct heif_error err;
|
2019-07-01 19:14:28 +02:00
|
|
|
struct heif_image_handle *handle = NULL;
|
|
|
|
struct heif_image *img = NULL;
|
2019-04-01 15:29:58 +02:00
|
|
|
GimpColorProfile *profile = NULL;
|
2018-05-04 20:30:30 +02:00
|
|
|
gint n_images;
|
|
|
|
heif_item_id primary;
|
|
|
|
heif_item_id selected_image;
|
|
|
|
gboolean has_alpha;
|
|
|
|
gint width;
|
|
|
|
gint height;
|
2019-08-24 10:23:01 +02:00
|
|
|
GimpImage *image;
|
|
|
|
GimpLayer *layer;
|
2018-05-04 21:14:23 +02:00
|
|
|
GeglBuffer *buffer;
|
|
|
|
const Babl *format;
|
2018-05-04 20:30:30 +02:00
|
|
|
const guint8 *data;
|
|
|
|
gint stride;
|
2020-09-08 12:00:32 +02:00
|
|
|
gint bit_depth = 8;
|
|
|
|
enum heif_chroma chroma = heif_chroma_interleaved_RGB;
|
2020-08-19 16:36:18 +02:00
|
|
|
GimpPrecision precision;
|
|
|
|
gboolean load_linear;
|
|
|
|
const char *encoding;
|
2018-05-04 20:30:30 +02:00
|
|
|
|
2018-05-05 12:53:39 +02:00
|
|
|
gimp_progress_init_printf (_("Opening '%s'"),
|
|
|
|
g_file_get_parse_name (file));
|
|
|
|
|
2020-08-19 16:36:18 +02:00
|
|
|
*status = GIMP_PDB_EXECUTION_ERROR;
|
|
|
|
|
2018-05-05 12:53:39 +02:00
|
|
|
file_size = get_file_size (file, error);
|
|
|
|
if (file_size <= 0)
|
2019-08-24 10:23:01 +02:00
|
|
|
return NULL;
|
2018-05-05 12:53:39 +02:00
|
|
|
|
|
|
|
input = G_INPUT_STREAM (g_file_read (file, NULL, error));
|
|
|
|
if (! input)
|
2019-08-24 10:23:01 +02:00
|
|
|
return NULL;
|
2018-05-05 12:53:39 +02:00
|
|
|
|
|
|
|
file_buffer = g_malloc (file_size);
|
|
|
|
|
|
|
|
if (! g_input_stream_read_all (input, file_buffer, file_size,
|
|
|
|
&bytes_read, NULL, error) &&
|
|
|
|
bytes_read == 0)
|
|
|
|
{
|
|
|
|
g_free (file_buffer);
|
|
|
|
g_object_unref (input);
|
2019-08-24 10:23:01 +02:00
|
|
|
return NULL;
|
2018-05-05 12:53:39 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
gimp_progress_update (0.25);
|
|
|
|
|
2018-05-04 20:30:30 +02:00
|
|
|
ctx = heif_context_alloc ();
|
2020-08-19 16:36:18 +02:00
|
|
|
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;
|
|
|
|
}
|
2018-05-04 20:30:30 +02:00
|
|
|
|
2018-05-05 12:53:39 +02:00
|
|
|
err = heif_context_read_from_memory (ctx, file_buffer, file_size, NULL);
|
2018-05-04 20:30:30 +02:00
|
|
|
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);
|
2018-05-05 12:53:39 +02:00
|
|
|
g_free (file_buffer);
|
|
|
|
g_object_unref (input);
|
2018-05-04 20:30:30 +02:00
|
|
|
|
2019-08-24 10:23:01 +02:00
|
|
|
return NULL;
|
2018-05-04 20:30:30 +02:00
|
|
|
}
|
|
|
|
|
2018-05-05 12:53:39 +02:00
|
|
|
g_free (file_buffer);
|
|
|
|
g_object_unref (input);
|
|
|
|
|
|
|
|
gimp_progress_update (0.5);
|
|
|
|
|
2018-05-04 20:30:30 +02:00
|
|
|
/* 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);
|
|
|
|
|
2019-08-24 10:23:01 +02:00
|
|
|
return NULL;
|
2018-05-04 20:30:30 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
2019-08-24 10:23:01 +02:00
|
|
|
return NULL;
|
2018-05-04 20:30:30 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/* 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);
|
|
|
|
|
2019-08-24 10:23:01 +02:00
|
|
|
*status = GIMP_PDB_CANCEL;
|
|
|
|
|
|
|
|
return NULL;
|
2018-05-04 20:30:30 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* 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);
|
|
|
|
|
2019-08-24 10:23:01 +02:00
|
|
|
return NULL;
|
2018-05-04 20:30:30 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
has_alpha = heif_image_handle_has_alpha_channel (handle);
|
|
|
|
|
2020-09-08 12:00:32 +02:00
|
|
|
#if LIBHEIF_HAVE_VERSION(1,8,0)
|
2020-08-19 16:36:18 +02:00
|
|
|
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;
|
|
|
|
}
|
2020-09-08 12:00:32 +02:00
|
|
|
#endif
|
2020-08-19 16:36:18 +02:00
|
|
|
|
|
|
|
if (bit_depth == 8)
|
|
|
|
{
|
|
|
|
if (has_alpha)
|
|
|
|
{
|
|
|
|
chroma = heif_chroma_interleaved_RGBA;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
chroma = heif_chroma_interleaved_RGB;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else /* high bit depth */
|
|
|
|
{
|
2020-09-08 12:00:32 +02:00
|
|
|
#if LIBHEIF_HAVE_VERSION(1,8,0)
|
2020-08-19 16:36:18 +02:00
|
|
|
#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;
|
|
|
|
}
|
2020-09-08 12:00:32 +02:00
|
|
|
#endif
|
2020-08-19 16:36:18 +02:00
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
2018-05-04 20:30:30 +02:00
|
|
|
err = heif_decode_image (handle,
|
|
|
|
&img,
|
|
|
|
heif_colorspace_RGB,
|
2020-08-19 16:36:18 +02:00
|
|
|
chroma,
|
2018-05-04 20:30:30 +02:00
|
|
|
NULL);
|
|
|
|
if (err.code)
|
|
|
|
{
|
|
|
|
g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
|
|
|
|
_("Loading HEIF image failed: %s"),
|
|
|
|
err.message);
|
2020-08-19 16:36:18 +02:00
|
|
|
heif_image_handle_release (handle);
|
|
|
|
heif_context_free (ctx);
|
2018-05-04 20:30:30 +02:00
|
|
|
|
2020-08-19 16:36:18 +02:00
|
|
|
return NULL;
|
2018-05-04 20:30:30 +02:00
|
|
|
}
|
|
|
|
|
2020-08-19 16:36:18 +02:00
|
|
|
#if LIBHEIF_HAVE_VERSION(1,4,0)
|
2019-04-01 15:29:58 +02:00
|
|
|
switch (heif_image_handle_get_color_profile_type (handle))
|
|
|
|
{
|
|
|
|
case heif_color_profile_type_not_present:
|
|
|
|
break;
|
|
|
|
case heif_color_profile_type_rICC:
|
|
|
|
case heif_color_profile_type_prof:
|
|
|
|
/* I am unsure, but it looks like both these types represent an
|
|
|
|
* ICC color profile. XXX
|
|
|
|
*/
|
2020-08-19 16:36:18 +02:00
|
|
|
{
|
|
|
|
void *profile_data;
|
|
|
|
size_t profile_size;
|
2019-04-01 15:29:58 +02:00
|
|
|
|
2020-08-19 16:36:18 +02:00
|
|
|
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);
|
2019-04-01 15:29:58 +02:00
|
|
|
|
2020-08-19 16:36:18 +02:00
|
|
|
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);
|
2019-04-01 15:29:58 +02:00
|
|
|
|
2020-08-19 16:36:18 +02:00
|
|
|
g_free (profile_data);
|
|
|
|
}
|
2019-04-01 15:29:58 +02:00
|
|
|
break;
|
2020-08-19 16:36:18 +02:00
|
|
|
#if LIBHEIF_HAVE_VERSION(1,8,0)
|
|
|
|
case heif_color_profile_type_nclx:
|
|
|
|
{
|
|
|
|
struct heif_color_profile_nclx *nclx = NULL;
|
2019-06-25 13:20:07 +02:00
|
|
|
|
2020-08-19 16:36:18 +02:00
|
|
|
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
|
2019-04-01 15:29:58 +02:00
|
|
|
default:
|
|
|
|
g_warning ("%s: unknown color profile type has been discarded.",
|
|
|
|
G_STRFUNC);
|
|
|
|
break;
|
|
|
|
}
|
2020-08-19 16:36:18 +02:00
|
|
|
#endif /* LIBHEIF_HAVE_VERSION(1,4,0) */
|
2019-04-01 15:29:58 +02:00
|
|
|
|
2018-05-05 12:53:39 +02:00
|
|
|
gimp_progress_update (0.75);
|
|
|
|
|
2018-05-04 20:30:30 +02:00
|
|
|
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)
|
|
|
|
*/
|
|
|
|
|
2020-08-19 16:36:18 +02:00
|
|
|
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);
|
2019-09-11 21:48:34 +02:00
|
|
|
gimp_image_set_file (image, file);
|
2018-05-04 20:30:30 +02:00
|
|
|
|
2019-04-01 15:29:58 +02:00
|
|
|
if (profile)
|
2020-08-19 16:36:18 +02:00
|
|
|
{
|
|
|
|
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.");
|
|
|
|
}
|
|
|
|
}
|
2019-04-01 15:29:58 +02:00
|
|
|
|
2019-08-24 10:23:01 +02:00
|
|
|
layer = gimp_layer_new (image,
|
|
|
|
_("image content"),
|
|
|
|
width, height,
|
|
|
|
has_alpha ? GIMP_RGBA_IMAGE : GIMP_RGB_IMAGE,
|
|
|
|
100.0,
|
|
|
|
gimp_image_get_default_new_layer_mode (image));
|
2018-05-04 20:30:30 +02:00
|
|
|
|
2019-08-24 10:23:01 +02:00
|
|
|
gimp_image_insert_layer (image, layer, NULL, 0);
|
2018-05-04 20:30:30 +02:00
|
|
|
|
2019-08-24 10:23:01 +02:00
|
|
|
buffer = gimp_drawable_get_buffer (GIMP_DRAWABLE (layer));
|
2018-05-04 20:30:30 +02:00
|
|
|
|
|
|
|
data = heif_image_get_plane_readonly (img, heif_channel_interleaved,
|
|
|
|
&stride);
|
|
|
|
|
2020-08-19 16:36:18 +02:00
|
|
|
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);
|
|
|
|
}
|
2018-05-04 20:30:30 +02:00
|
|
|
|
2018-05-04 21:14:23 +02:00
|
|
|
g_object_unref (buffer);
|
2018-05-04 20:30:30 +02:00
|
|
|
|
2018-05-05 14:38:27 +02:00
|
|
|
{
|
|
|
|
size_t exif_data_size = 0;
|
|
|
|
uint8_t *exif_data = NULL;
|
|
|
|
size_t xmp_data_size = 0;
|
|
|
|
uint8_t *xmp_data = NULL;
|
|
|
|
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)
|
|
|
|
{
|
|
|
|
exif_data_size = heif_image_handle_get_metadata_size (handle,
|
|
|
|
metadata_id);
|
|
|
|
exif_data = g_alloca (exif_data_size);
|
|
|
|
|
|
|
|
err = heif_image_handle_get_metadata (handle, metadata_id, exif_data);
|
|
|
|
if (err.code != 0)
|
|
|
|
{
|
|
|
|
exif_data = NULL;
|
|
|
|
exif_data_size = 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
n_metadata =
|
|
|
|
heif_image_handle_get_list_of_metadata_block_IDs (handle,
|
2020-08-19 16:36:18 +02:00
|
|
|
"mime",
|
2018-05-05 14:38:27 +02:00
|
|
|
&metadata_id, 1);
|
|
|
|
if (n_metadata > 0)
|
|
|
|
{
|
2020-08-19 16:36:18 +02:00
|
|
|
if (g_strcmp0 (
|
|
|
|
heif_image_handle_get_metadata_content_type (handle, metadata_id),
|
|
|
|
"application/rdf+xml") == 0)
|
2018-05-05 14:38:27 +02:00
|
|
|
{
|
2020-08-19 16:36:18 +02:00
|
|
|
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;
|
|
|
|
}
|
2018-05-05 14:38:27 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (exif_data || xmp_data)
|
|
|
|
{
|
2019-06-20 18:08:59 +02:00
|
|
|
GimpMetadata *metadata = gimp_metadata_new ();
|
2020-08-19 16:36:18 +02:00
|
|
|
GimpMetadataLoadFlags flags = GIMP_METADATA_LOAD_COMMENT | GIMP_METADATA_LOAD_RESOLUTION;
|
2018-05-05 14:38:27 +02:00
|
|
|
|
|
|
|
if (exif_data)
|
2020-08-19 16:36:18 +02:00
|
|
|
{
|
|
|
|
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] &&
|
2020-10-08 01:35:26 +02:00
|
|
|
tiffheader[2] == tiffHeaderBE[2] && tiffheader[3] == tiffHeaderBE[3])
|
2020-08-19 16:36:18 +02:00
|
|
|
{
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (tiffheader[0] == tiffHeaderLE[0] && tiffheader[1] == tiffHeaderLE[1] &&
|
2020-10-08 01:35:26 +02:00
|
|
|
tiffheader[2] == tiffHeaderLE[2] && tiffheader[3] == tiffHeaderLE[3])
|
2020-08-19 16:36:18 +02:00
|
|
|
{
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
2018-05-05 14:38:27 +02:00
|
|
|
|
|
|
|
if (xmp_data)
|
2020-08-19 16:36:18 +02:00
|
|
|
{
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
2019-06-20 18:08:59 +02:00
|
|
|
|
2019-08-24 10:23:01 +02:00
|
|
|
gimp_image_metadata_load_finish (image, "image/heif",
|
app, libgimp, plug-ins: move Orientation metadata handling into core.
Orientation is now handled by core code, just next to profile conversion
handling.
One of the first consequence is that we don't need to have a non-GUI
version gimp_image_metadata_load_finish_batch() in libgimp, next to a
GUI version of the gimp_image_metadata_load_finish() function in
libgimpui. This makes for simpler API.
Also a plug-in which wishes to get access to the rotation dialog
provided by GIMP without loading ligimpui/GTK+ (for whatever reason)
will still have the feature.
The main advantage is that the "Don't ask me again" feature is now
handled by a settings in `Preferences > Image Import & Export` as the
"Metadata rotation policy". Until now it was saved as a global parasite,
which made it virtually non-editable once you checked it once (no easy
way to edit parasites except by scripts). So say you refused the
rotation once while checking "Don't ask again", and GIMP will forever
discard the rotation metadata without giving you a sane way to change
your mind. Of course, I could have passed the settings to plug-ins
through the PDB, but I find it a lot better to simply handle such
settings core-side.
The dialog code is basically the same as an app/dialogs/ as it was in
libgimp, with the minor improvement that it now takes the scale ratio
into account (basically the maximum thumbnail size will be bigger on
higher density displays).
Only downside of the move to the core is that this rotation dialog is
raised only when you open an image from the core, not as a PDB call. So
a plug-in which makes say a "file-jpeg-load" PDB call, even in
INTERACTIVE run mode, won't have rotation processed. Note that this was
already the same for embedded color profile conversion. This can be
wanted or not. Anyway some additional libgimp calls might be of interest
to explicitly call the core dialogs.
2020-09-23 19:59:09 +02:00
|
|
|
metadata, flags);
|
2018-05-05 14:38:27 +02:00
|
|
|
}
|
|
|
|
}
|
2018-05-04 20:30:30 +02:00
|
|
|
|
2019-06-20 18:08:59 +02:00
|
|
|
if (profile)
|
|
|
|
g_object_unref (profile);
|
|
|
|
|
2018-05-04 20:30:30 +02:00
|
|
|
heif_image_handle_release (handle);
|
|
|
|
heif_context_free (ctx);
|
|
|
|
heif_image_release (img);
|
|
|
|
|
2018-05-05 12:53:39 +02:00
|
|
|
gimp_progress_update (1.0);
|
|
|
|
|
2020-08-19 16:36:18 +02:00
|
|
|
if (image)
|
|
|
|
{
|
|
|
|
*status = GIMP_PDB_SUCCESS;
|
|
|
|
}
|
2019-08-24 10:23:01 +02:00
|
|
|
return image;
|
2018-05-04 20:30:30 +02:00
|
|
|
}
|
|
|
|
|
2020-09-17 13:48:03 +02:00
|
|
|
static void
|
|
|
|
heifplugin_image_metadata_copy_tag (GExiv2Metadata *src,
|
|
|
|
GExiv2Metadata *dest,
|
|
|
|
const gchar *tag)
|
|
|
|
{
|
|
|
|
gchar **values = gexiv2_metadata_get_tag_multiple (src, tag);
|
|
|
|
|
|
|
|
if (values)
|
|
|
|
{
|
|
|
|
gexiv2_metadata_set_tag_multiple (dest, tag, (const gchar **) values);
|
|
|
|
g_strfreev (values);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
gchar *value = gexiv2_metadata_get_tag_string (src, tag);
|
|
|
|
|
|
|
|
if (value)
|
|
|
|
{
|
|
|
|
gexiv2_metadata_set_tag_string (dest, tag, value);
|
|
|
|
g_free (value);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-05-05 12:53:39 +02:00
|
|
|
static struct heif_error
|
|
|
|
write_callback (struct heif_context *ctx,
|
|
|
|
const void *data,
|
|
|
|
size_t size,
|
|
|
|
void *userdata)
|
|
|
|
{
|
|
|
|
GOutputStream *output = userdata;
|
|
|
|
GError *error = NULL;
|
|
|
|
struct heif_error heif_error;
|
|
|
|
|
|
|
|
heif_error.code = heif_error_Ok;
|
2020-08-19 16:36:18 +02:00
|
|
|
heif_error.subcode = heif_suberror_Unspecified;
|
2018-05-05 12:53:39 +02:00
|
|
|
heif_error.message = "";
|
|
|
|
|
|
|
|
if (! g_output_stream_write_all (output, data, size, NULL, NULL, &error))
|
|
|
|
{
|
|
|
|
heif_error.code = 99; /* hmm */
|
|
|
|
heif_error.message = error->message;
|
|
|
|
}
|
|
|
|
|
|
|
|
return heif_error;
|
|
|
|
}
|
|
|
|
|
2018-05-04 20:30:30 +02:00
|
|
|
static gboolean
|
2020-08-19 16:36:18 +02:00
|
|
|
save_image (GFile *file,
|
|
|
|
GimpImage *image,
|
|
|
|
GimpDrawable *drawable,
|
|
|
|
GObject *config,
|
|
|
|
GError **error,
|
2020-09-17 13:48:03 +02:00
|
|
|
enum heif_compression_format compression,
|
|
|
|
GimpMetadata *metadata)
|
2018-05-04 20:30:30 +02:00
|
|
|
{
|
2019-08-24 10:23:01 +02:00
|
|
|
struct heif_image *h_image = NULL;
|
2018-05-04 20:30:30 +02:00
|
|
|
struct heif_context *context = heif_context_alloc ();
|
2019-07-01 19:14:28 +02:00
|
|
|
struct heif_encoder *encoder = NULL;
|
|
|
|
struct heif_image_handle *handle = NULL;
|
2018-05-05 12:53:39 +02:00
|
|
|
struct heif_writer writer;
|
2018-05-04 20:30:30 +02:00
|
|
|
struct heif_error err;
|
2018-05-05 12:53:39 +02:00
|
|
|
GOutputStream *output;
|
2018-05-04 21:14:23 +02:00
|
|
|
GeglBuffer *buffer;
|
2019-07-17 14:21:23 +02:00
|
|
|
const gchar *encoding;
|
2018-05-04 21:14:23 +02:00
|
|
|
const Babl *format;
|
2019-07-01 00:34:36 +02:00
|
|
|
const Babl *space = NULL;
|
2018-05-04 20:30:30 +02:00
|
|
|
guint8 *data;
|
|
|
|
gint stride;
|
|
|
|
gint width;
|
|
|
|
gint height;
|
|
|
|
gboolean has_alpha;
|
2019-07-01 15:36:59 +02:00
|
|
|
gboolean out_linear = FALSE;
|
2019-09-24 19:35:49 +02:00
|
|
|
gboolean lossless;
|
|
|
|
gint quality;
|
|
|
|
gboolean save_profile;
|
2020-08-19 16:36:18 +02:00
|
|
|
gint save_bit_depth = 8;
|
2020-10-15 17:38:24 +02:00
|
|
|
#if LIBHEIF_HAVE_VERSION(1,9,0)
|
|
|
|
HeifpluginExportFormat pixel_format = HEIFPLUGIN_EXPORT_FORMAT_YUV420;
|
|
|
|
#endif
|
|
|
|
#if LIBHEIF_HAVE_VERSION(1,8,0)
|
|
|
|
HeifpluginEncoderSpeed encoder_speed = HEIFPLUGIN_ENCODER_SPEED_BALANCED;
|
|
|
|
const char *encoder_name;
|
|
|
|
const char *parameter_value;
|
|
|
|
struct heif_color_profile_nclx nclx_profile;
|
|
|
|
#endif
|
2020-09-17 13:48:03 +02:00
|
|
|
#if GEXIV2_CHECK_VERSION(0, 12, 2)
|
|
|
|
gboolean save_exif = FALSE;
|
|
|
|
#endif
|
|
|
|
gboolean save_xmp = FALSE;
|
2020-08-19 16:36:18 +02:00
|
|
|
|
|
|
|
if (!context)
|
|
|
|
{
|
|
|
|
g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
|
|
|
|
"cannot allocate heif_context");
|
2020-08-31 12:52:23 +02:00
|
|
|
return FALSE;
|
2020-08-19 16:36:18 +02:00
|
|
|
}
|
2019-09-24 19:35:49 +02:00
|
|
|
|
|
|
|
g_object_get (config,
|
2019-09-25 12:50:29 +02:00
|
|
|
"lossless", &lossless,
|
|
|
|
"quality", &quality,
|
2020-10-15 17:38:24 +02:00
|
|
|
#if LIBHEIF_HAVE_VERSION(1,9,0)
|
|
|
|
"pixel-format", &pixel_format,
|
|
|
|
#endif
|
2020-08-19 16:36:18 +02:00
|
|
|
#if LIBHEIF_HAVE_VERSION(1,8,0)
|
|
|
|
"save-bit-depth", &save_bit_depth,
|
2020-10-15 17:38:24 +02:00
|
|
|
"encoder-speed", &encoder_speed,
|
2020-08-19 16:36:18 +02:00
|
|
|
#endif
|
2019-09-25 12:50:29 +02:00
|
|
|
"save-color-profile", &save_profile,
|
2020-09-17 13:48:03 +02:00
|
|
|
#if GEXIV2_CHECK_VERSION(0, 12, 2)
|
|
|
|
"save-exif", &save_exif,
|
|
|
|
#endif
|
|
|
|
"save-xmp", &save_xmp,
|
2019-09-24 19:35:49 +02:00
|
|
|
NULL);
|
2018-05-04 20:30:30 +02:00
|
|
|
|
2018-05-05 12:53:39 +02:00
|
|
|
gimp_progress_init_printf (_("Exporting '%s'"),
|
|
|
|
g_file_get_parse_name (file));
|
|
|
|
|
2019-08-24 10:23:01 +02:00
|
|
|
width = gimp_drawable_width (drawable);
|
|
|
|
height = gimp_drawable_height (drawable);
|
2018-05-04 20:30:30 +02:00
|
|
|
|
2019-08-24 10:23:01 +02:00
|
|
|
has_alpha = gimp_drawable_has_alpha (drawable);
|
2018-05-04 20:30:30 +02:00
|
|
|
|
2020-08-19 16:36:18 +02:00
|
|
|
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;
|
2020-09-08 12:00:32 +02:00
|
|
|
#if LIBHEIF_HAVE_VERSION(1,8,0)
|
2020-08-19 16:36:18 +02:00
|
|
|
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;
|
2020-09-08 12:00:32 +02:00
|
|
|
#endif
|
2020-08-19 16:36:18 +02:00
|
|
|
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;
|
|
|
|
}
|
2018-05-04 20:30:30 +02:00
|
|
|
|
2020-08-19 16:36:18 +02:00
|
|
|
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)
|
2019-09-24 19:35:49 +02:00
|
|
|
if (save_profile)
|
2019-07-01 15:36:59 +02:00
|
|
|
{
|
2019-07-01 16:39:13 +02:00
|
|
|
GimpColorProfile *profile = NULL;
|
|
|
|
const guint8 *icc_data;
|
|
|
|
gsize icc_length;
|
|
|
|
|
2019-08-24 10:23:01 +02:00
|
|
|
profile = gimp_image_get_color_profile (image);
|
2019-07-01 16:39:13 +02:00
|
|
|
if (profile && gimp_color_profile_is_linear (profile))
|
|
|
|
out_linear = TRUE;
|
2019-07-01 15:36:59 +02:00
|
|
|
|
2019-07-01 16:39:13 +02:00
|
|
|
if (! profile)
|
2019-07-01 00:34:36 +02:00
|
|
|
{
|
2019-08-24 10:23:01 +02:00
|
|
|
profile = gimp_image_get_effective_color_profile (image);
|
2019-07-01 15:36:59 +02:00
|
|
|
|
2019-07-01 16:39:13 +02:00
|
|
|
if (gimp_color_profile_is_linear (profile))
|
2019-07-01 15:36:59 +02:00
|
|
|
{
|
2019-08-24 10:23:01 +02:00
|
|
|
if (gimp_image_get_precision (image) != GIMP_PRECISION_U8_LINEAR)
|
2019-07-01 16:39:13 +02:00
|
|
|
{
|
|
|
|
/* If stored data was linear, let's convert the profile. */
|
|
|
|
GimpColorProfile *saved_profile;
|
|
|
|
|
|
|
|
saved_profile = gimp_color_profile_new_srgb_trc_from_color_profile (profile);
|
|
|
|
g_clear_object (&profile);
|
|
|
|
profile = saved_profile;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
/* Keep linear profile as-is for 8-bit linear image. */
|
|
|
|
out_linear = TRUE;
|
|
|
|
}
|
2019-07-01 15:36:59 +02:00
|
|
|
}
|
2019-07-01 00:34:36 +02:00
|
|
|
}
|
|
|
|
|
2020-10-15 17:38:24 +02:00
|
|
|
#if LIBHEIF_HAVE_VERSION(1,9,0)
|
|
|
|
if (pixel_format == HEIFPLUGIN_EXPORT_FORMAT_RGB && save_bit_depth == 8)
|
|
|
|
{
|
|
|
|
nclx_profile.version = 1;
|
|
|
|
nclx_profile.color_primaries = heif_color_primaries_unspecified;
|
|
|
|
|
|
|
|
if (out_linear)
|
|
|
|
{
|
|
|
|
nclx_profile.transfer_characteristics = heif_transfer_characteristic_linear;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
nclx_profile.transfer_characteristics = heif_transfer_characteristic_unspecified;
|
|
|
|
}
|
|
|
|
|
|
|
|
nclx_profile.matrix_coefficients = heif_matrix_coefficients_RGB_GBR;
|
|
|
|
nclx_profile.full_range_flag = 1;
|
|
|
|
|
|
|
|
heif_image_set_nclx_color_profile (h_image, &nclx_profile);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2019-07-01 16:39:13 +02:00
|
|
|
icc_data = gimp_color_profile_get_icc_profile (profile, &icc_length);
|
2019-08-24 10:23:01 +02:00
|
|
|
heif_image_set_raw_color_profile (h_image, "prof", icc_data, icc_length);
|
2019-07-01 16:39:13 +02:00
|
|
|
space = gimp_color_profile_get_space (profile,
|
|
|
|
GIMP_COLOR_RENDERING_INTENT_RELATIVE_COLORIMETRIC,
|
|
|
|
error);
|
|
|
|
if (error && *error)
|
|
|
|
{
|
|
|
|
/* Don't make this a hard failure yet output the error. */
|
|
|
|
g_printerr ("%s: error getting the profile space: %s",
|
|
|
|
G_STRFUNC, (*error)->message);
|
|
|
|
g_clear_error (error);
|
|
|
|
}
|
2019-07-01 15:36:59 +02:00
|
|
|
|
2019-07-01 16:39:13 +02:00
|
|
|
g_object_unref (profile);
|
|
|
|
}
|
2020-08-19 16:36:18 +02:00
|
|
|
else
|
|
|
|
{
|
|
|
|
#if LIBHEIF_HAVE_VERSION(1,8,0)
|
|
|
|
/* We save as sRGB */
|
|
|
|
|
2020-09-12 22:28:03 +02:00
|
|
|
nclx_profile.version = 1;
|
2020-08-19 16:36:18 +02:00
|
|
|
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;
|
|
|
|
|
2020-10-15 17:38:24 +02:00
|
|
|
#if LIBHEIF_HAVE_VERSION(1,9,0)
|
|
|
|
if (pixel_format == HEIFPLUGIN_EXPORT_FORMAT_RGB && save_bit_depth == 8)
|
|
|
|
{
|
|
|
|
nclx_profile.matrix_coefficients = heif_matrix_coefficients_RGB_GBR;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2020-08-19 16:36:18 +02:00
|
|
|
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) */
|
2019-07-01 15:36:59 +02:00
|
|
|
|
2019-07-01 00:34:36 +02:00
|
|
|
if (! space)
|
2019-08-24 10:23:01 +02:00
|
|
|
space = gimp_drawable_get_format (drawable);
|
2019-04-01 15:29:58 +02:00
|
|
|
|
2018-05-04 20:30:30 +02:00
|
|
|
|
2020-08-19 16:36:18 +02:00
|
|
|
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;
|
2018-05-04 20:30:30 +02:00
|
|
|
|
2020-08-19 16:36:18 +02:00
|
|
|
if (out_linear)
|
|
|
|
encoding = "RGBA u16";
|
|
|
|
else
|
|
|
|
encoding = "R'G'B'A u16";
|
|
|
|
}
|
|
|
|
else /* no alpha */
|
|
|
|
{
|
|
|
|
rowentries = width * 3;
|
2018-05-04 20:30:30 +02:00
|
|
|
|
2020-08-19 16:36:18 +02:00
|
|
|
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);
|
2019-07-01 15:36:59 +02:00
|
|
|
}
|
2020-08-19 16:36:18 +02:00
|
|
|
else /* save_bit_depth == 8 */
|
2019-07-01 15:36:59 +02:00
|
|
|
{
|
2020-08-19 16:36:18 +02:00
|
|
|
#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";
|
|
|
|
}
|
2019-07-01 15:36:59 +02:00
|
|
|
else
|
2020-08-19 16:36:18 +02:00
|
|
|
{
|
|
|
|
if (out_linear)
|
|
|
|
encoding = "RGB u8";
|
|
|
|
else
|
|
|
|
encoding = "R'G'B' u8";
|
|
|
|
}
|
|
|
|
format = babl_format_with_space (encoding, space);
|
2018-05-04 20:30:30 +02:00
|
|
|
|
2020-08-19 16:36:18 +02:00
|
|
|
gegl_buffer_get (buffer,
|
|
|
|
GEGL_RECTANGLE (0, 0, width, height),
|
|
|
|
1.0, format, data, stride, GEGL_ABYSS_NONE);
|
2018-05-04 20:30:30 +02:00
|
|
|
|
2020-08-19 16:36:18 +02:00
|
|
|
g_object_unref (buffer);
|
|
|
|
}
|
2018-05-04 20:30:30 +02:00
|
|
|
|
2018-05-05 12:53:39 +02:00
|
|
|
gimp_progress_update (0.33);
|
|
|
|
|
2018-05-04 20:30:30 +02:00
|
|
|
/* encode to HEIF file */
|
|
|
|
err = heif_context_get_encoder_for_format (context,
|
2020-08-19 16:36:18 +02:00
|
|
|
compression,
|
2018-05-04 20:30:30 +02:00
|
|
|
&encoder);
|
|
|
|
|
2020-08-19 16:36:18 +02:00
|
|
|
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;
|
2020-10-15 17:38:24 +02:00
|
|
|
}
|
2020-08-19 16:36:18 +02:00
|
|
|
|
2019-09-24 19:35:49 +02:00
|
|
|
heif_encoder_set_lossy_quality (encoder, quality);
|
|
|
|
heif_encoder_set_lossless (encoder, lossless);
|
2018-05-04 20:30:30 +02:00
|
|
|
/* heif_encoder_set_logging_level (encoder, logging_level); */
|
|
|
|
|
2020-10-15 17:38:24 +02:00
|
|
|
#if LIBHEIF_HAVE_VERSION(1,8,0)
|
|
|
|
encoder_name = heif_encoder_get_name (encoder);
|
|
|
|
#if LIBHEIF_HAVE_VERSION(1,9,0)
|
|
|
|
|
|
|
|
if (lossless && pixel_format != HEIFPLUGIN_EXPORT_FORMAT_RGB)
|
|
|
|
{
|
|
|
|
/* disable subsampling for lossless */
|
|
|
|
pixel_format = HEIFPLUGIN_EXPORT_FORMAT_YUV444;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (pixel_format)
|
|
|
|
{
|
|
|
|
case HEIFPLUGIN_EXPORT_FORMAT_RGB:
|
|
|
|
/* same as HEIFPLUGIN_EXPORT_FORMAT_YUV444 */
|
|
|
|
case HEIFPLUGIN_EXPORT_FORMAT_YUV444:
|
|
|
|
parameter_value = "444";
|
|
|
|
break;
|
|
|
|
case HEIFPLUGIN_EXPORT_FORMAT_YUV422:
|
|
|
|
parameter_value = "422";
|
|
|
|
break;
|
|
|
|
default: /* HEIFPLUGIN_EXPORT_FORMAT_YUV420 */
|
|
|
|
parameter_value = "420";
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
err = heif_encoder_set_parameter_string (encoder, "chroma", parameter_value);
|
|
|
|
if (err.code != 0)
|
|
|
|
{
|
|
|
|
g_printerr ("Failed to set chroma %s for %s encoder: %s", parameter_value, encoder_name, err.message);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
if (compression == heif_compression_HEVC)
|
|
|
|
{
|
|
|
|
switch (encoder_speed)
|
|
|
|
{
|
|
|
|
case HEIFPLUGIN_ENCODER_SPEED_SLOW:
|
|
|
|
parameter_value = "veryslow";
|
|
|
|
break;
|
|
|
|
case HEIFPLUGIN_ENCODER_SPEED_FASTER:
|
|
|
|
parameter_value = "faster";
|
|
|
|
break;
|
|
|
|
default: /* HEIFPLUGIN_ENCODER_SPEED_BALANCED */
|
|
|
|
parameter_value = "medium";
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
err = heif_encoder_set_parameter_string (encoder, "preset", parameter_value);
|
|
|
|
if (err.code != 0)
|
|
|
|
{
|
|
|
|
g_printerr ("Failed to set preset %s for %s encoder: %s", parameter_value, encoder_name, err.message);
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
else if (compression == heif_compression_AV1)
|
|
|
|
{
|
|
|
|
int parameter_number;
|
|
|
|
|
|
|
|
parameter_number = g_get_num_processors();
|
|
|
|
parameter_number = CLAMP(parameter_number, 1, 16);
|
|
|
|
|
|
|
|
err = heif_encoder_set_parameter_integer (encoder, "threads", parameter_number);
|
|
|
|
if (err.code != 0)
|
|
|
|
{
|
|
|
|
g_printerr ("Failed to set threads=%d for %s encoder: %s", parameter_number, encoder_name, err.message);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (g_ascii_strncasecmp (encoder_name, "AOM", 3) == 0) /* AOMedia AV1 encoder */
|
|
|
|
{
|
|
|
|
switch (encoder_speed)
|
|
|
|
{
|
|
|
|
case HEIFPLUGIN_ENCODER_SPEED_SLOW:
|
|
|
|
parameter_number = 1;
|
|
|
|
break;
|
|
|
|
case HEIFPLUGIN_ENCODER_SPEED_FASTER:
|
|
|
|
parameter_number = 6;
|
|
|
|
break;
|
|
|
|
default: /* HEIFPLUGIN_ENCODER_SPEED_BALANCED */
|
|
|
|
parameter_number = 4;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
err = heif_encoder_set_parameter_integer (encoder, "speed", parameter_number);
|
|
|
|
if (err.code != 0)
|
|
|
|
{
|
|
|
|
g_printerr ("Failed to set speed=%d for %s encoder: %s", parameter_number, encoder_name, err.message);
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
else if (g_ascii_strncasecmp (encoder_name, "Rav1e", 5) == 0) /* Rav1e encoder */
|
|
|
|
{
|
|
|
|
switch (encoder_speed)
|
|
|
|
{
|
|
|
|
case HEIFPLUGIN_ENCODER_SPEED_SLOW:
|
|
|
|
parameter_number = 6;
|
|
|
|
break;
|
|
|
|
case HEIFPLUGIN_ENCODER_SPEED_FASTER:
|
|
|
|
parameter_number = 10;
|
|
|
|
break;
|
|
|
|
default: /* HEIFPLUGIN_ENCODER_SPEED_BALANCED */
|
|
|
|
parameter_number = 8;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
err = heif_encoder_set_parameter_integer (encoder, "speed", parameter_number);
|
|
|
|
if (err.code != 0)
|
|
|
|
{
|
|
|
|
g_printerr ("Failed to set speed=%d for %s encoder: %s", parameter_number, encoder_name, err.message);
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
g_printerr ("Parameters not set, unsupported AV1 encoder: %s", encoder_name);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2018-05-04 20:30:30 +02:00
|
|
|
err = heif_context_encode_image (context,
|
2019-08-24 10:23:01 +02:00
|
|
|
h_image,
|
2018-05-04 20:30:30 +02:00
|
|
|
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);
|
2020-08-19 16:36:18 +02:00
|
|
|
heif_encoder_release (encoder);
|
|
|
|
heif_image_release (h_image);
|
|
|
|
heif_context_free (context);
|
2018-05-04 20:30:30 +02:00
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
2020-09-17 13:48:03 +02:00
|
|
|
/* EXIF metadata */
|
|
|
|
#if GEXIV2_CHECK_VERSION(0, 12, 2)
|
|
|
|
if (save_exif && metadata)
|
|
|
|
{
|
|
|
|
if (gexiv2_metadata_get_supports_exif (GEXIV2_METADATA (metadata)) &&
|
|
|
|
gexiv2_metadata_has_exif (GEXIV2_METADATA (metadata)))
|
|
|
|
{
|
|
|
|
GimpMetadata *new_exif_metadata = gimp_metadata_new ();
|
|
|
|
GExiv2Metadata *new_gexiv2metadata = GEXIV2_METADATA (new_exif_metadata);
|
|
|
|
GBytes *raw_exif_data;
|
|
|
|
gchar **exif_data = gexiv2_metadata_get_exif_tags (GEXIV2_METADATA (metadata));
|
|
|
|
guint i;
|
|
|
|
|
|
|
|
gexiv2_metadata_clear_exif (new_gexiv2metadata);
|
|
|
|
|
|
|
|
for (i = 0; exif_data[i] != NULL; i++)
|
|
|
|
{
|
|
|
|
if (! gexiv2_metadata_has_tag (new_gexiv2metadata, exif_data[i]) &&
|
|
|
|
gimp_metadata_is_tag_supported (exif_data[i], "image/heif"))
|
|
|
|
{
|
|
|
|
heifplugin_image_metadata_copy_tag (GEXIV2_METADATA (metadata),
|
|
|
|
new_gexiv2metadata,
|
|
|
|
exif_data[i]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
g_strfreev (exif_data);
|
|
|
|
|
|
|
|
raw_exif_data = gexiv2_metadata_get_exif_data (new_gexiv2metadata, GEXIV2_BYTE_ORDER_LITTLE, error);
|
|
|
|
if (raw_exif_data)
|
|
|
|
{
|
|
|
|
gsize exif_size = 0;
|
|
|
|
gconstpointer exif_buffer = g_bytes_get_data (raw_exif_data, &exif_size);
|
|
|
|
|
|
|
|
if (exif_size >= 4)
|
|
|
|
{
|
|
|
|
err = heif_context_add_exif_metadata (context, handle,
|
|
|
|
exif_buffer, exif_size);
|
|
|
|
if (err.code != 0)
|
|
|
|
{
|
|
|
|
g_printerr ("Failed to save EXIF metadata: %s", err.message);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
g_bytes_unref (raw_exif_data);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if (error && *error)
|
|
|
|
{
|
|
|
|
g_printerr ("%s: error preparing EXIF metadata: %s",
|
|
|
|
G_STRFUNC, (*error)->message);
|
|
|
|
g_clear_error (error);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
g_object_unref (new_exif_metadata);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
/* XMP metadata */
|
|
|
|
if (save_xmp && metadata)
|
|
|
|
{
|
|
|
|
if (gexiv2_metadata_get_supports_xmp (GEXIV2_METADATA (metadata)) &&
|
|
|
|
gexiv2_metadata_has_xmp (GEXIV2_METADATA (metadata)))
|
|
|
|
{
|
|
|
|
GimpMetadata *new_metadata = gimp_metadata_new ();
|
|
|
|
GExiv2Metadata *new_g2metadata = GEXIV2_METADATA (new_metadata);
|
|
|
|
guint i;
|
|
|
|
|
|
|
|
static const XmpStructs structlist[] =
|
|
|
|
{
|
|
|
|
{ "Xmp.iptcExt.LocationCreated", GEXIV2_STRUCTURE_XA_BAG },
|
|
|
|
{ "Xmp.iptcExt.LocationShown", GEXIV2_STRUCTURE_XA_BAG },
|
|
|
|
{ "Xmp.iptcExt.ArtworkOrObject", GEXIV2_STRUCTURE_XA_BAG },
|
|
|
|
{ "Xmp.iptcExt.RegistryId", GEXIV2_STRUCTURE_XA_BAG },
|
|
|
|
{ "Xmp.xmpMM.History", GEXIV2_STRUCTURE_XA_SEQ },
|
|
|
|
{ "Xmp.plus.ImageSupplier", GEXIV2_STRUCTURE_XA_SEQ },
|
|
|
|
{ "Xmp.plus.ImageCreator", GEXIV2_STRUCTURE_XA_SEQ },
|
|
|
|
{ "Xmp.plus.CopyrightOwner", GEXIV2_STRUCTURE_XA_SEQ },
|
|
|
|
{ "Xmp.plus.Licensor", GEXIV2_STRUCTURE_XA_SEQ }
|
|
|
|
};
|
|
|
|
|
|
|
|
gchar **xmp_data;
|
|
|
|
struct timeval timer_usec;
|
|
|
|
gint64 timestamp_usec;
|
|
|
|
gchar ts[128];
|
|
|
|
gchar *xmp_packet;
|
|
|
|
|
|
|
|
gexiv2_metadata_clear_xmp (new_g2metadata);
|
|
|
|
|
|
|
|
gettimeofday (&timer_usec, NULL);
|
|
|
|
timestamp_usec = ( (gint64) timer_usec.tv_sec) * 1000000ll +
|
|
|
|
(gint64) timer_usec.tv_usec;
|
|
|
|
g_snprintf (ts, sizeof (ts), "%" G_GINT64_FORMAT, timestamp_usec);
|
|
|
|
|
|
|
|
gimp_metadata_add_xmp_history (metadata, "");
|
|
|
|
|
|
|
|
gexiv2_metadata_set_tag_string (GEXIV2_METADATA (metadata),
|
|
|
|
"Xmp.GIMP.TimeStamp",
|
|
|
|
ts);
|
|
|
|
|
|
|
|
gexiv2_metadata_set_tag_string (GEXIV2_METADATA (metadata),
|
|
|
|
"Xmp.xmp.CreatorTool",
|
|
|
|
"GIMP");
|
|
|
|
|
|
|
|
gexiv2_metadata_set_tag_string (GEXIV2_METADATA (metadata),
|
|
|
|
"Xmp.GIMP.Version",
|
|
|
|
GIMP_VERSION);
|
|
|
|
|
|
|
|
gexiv2_metadata_set_tag_string (GEXIV2_METADATA (metadata),
|
|
|
|
"Xmp.GIMP.API",
|
|
|
|
GIMP_API_VERSION);
|
|
|
|
gexiv2_metadata_set_tag_string (GEXIV2_METADATA (metadata),
|
|
|
|
"Xmp.GIMP.Platform",
|
|
|
|
#if defined(_WIN32) || defined(__CYGWIN__) || defined(__MINGW32__)
|
|
|
|
"Windows"
|
|
|
|
#elif defined(__linux__)
|
|
|
|
"Linux"
|
|
|
|
#elif defined(__APPLE__) && defined(__MACH__)
|
|
|
|
"Mac OS"
|
|
|
|
#elif defined(unix) || defined(__unix__) || defined(__unix)
|
|
|
|
"Unix"
|
|
|
|
#else
|
|
|
|
"Unknown"
|
|
|
|
#endif
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
|
|
xmp_data = gexiv2_metadata_get_xmp_tags (GEXIV2_METADATA (metadata));
|
|
|
|
|
|
|
|
/* Patch necessary structures */
|
|
|
|
for (i = 0; i < (gint) G_N_ELEMENTS (structlist); i++)
|
|
|
|
{
|
|
|
|
gexiv2_metadata_set_xmp_tag_struct (GEXIV2_METADATA (new_g2metadata),
|
|
|
|
structlist[i].tag,
|
|
|
|
structlist[i].type);
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; xmp_data[i] != NULL; i++)
|
|
|
|
{
|
|
|
|
if (! gexiv2_metadata_has_tag (new_g2metadata, xmp_data[i]) &&
|
|
|
|
gimp_metadata_is_tag_supported (xmp_data[i], "image/heif"))
|
|
|
|
{
|
|
|
|
heifplugin_image_metadata_copy_tag (GEXIV2_METADATA (metadata),
|
|
|
|
new_g2metadata,
|
|
|
|
xmp_data[i]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
g_strfreev (xmp_data);
|
|
|
|
|
|
|
|
xmp_packet = gexiv2_metadata_generate_xmp_packet (new_g2metadata, GEXIV2_USE_COMPACT_FORMAT | GEXIV2_OMIT_ALL_FORMATTING, 0);
|
|
|
|
if (xmp_packet)
|
|
|
|
{
|
|
|
|
int xmp_size = strlen (xmp_packet);
|
|
|
|
if (xmp_size > 0)
|
|
|
|
{
|
|
|
|
heif_context_add_XMP_metadata (context, handle,
|
|
|
|
xmp_packet, xmp_size);
|
|
|
|
}
|
|
|
|
g_free (xmp_packet);
|
|
|
|
}
|
|
|
|
|
|
|
|
g_object_unref (new_metadata);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-05-04 20:30:30 +02:00
|
|
|
heif_image_handle_release (handle);
|
|
|
|
|
2018-05-05 12:53:39 +02:00
|
|
|
gimp_progress_update (0.66);
|
|
|
|
|
|
|
|
writer.writer_api_version = 1;
|
|
|
|
writer.write = write_callback;
|
|
|
|
|
|
|
|
output = G_OUTPUT_STREAM (g_file_replace (file,
|
|
|
|
NULL, FALSE, G_FILE_CREATE_NONE,
|
|
|
|
NULL, error));
|
|
|
|
if (! output)
|
2020-08-19 16:36:18 +02:00
|
|
|
{
|
|
|
|
heif_encoder_release (encoder);
|
|
|
|
heif_image_release (h_image);
|
|
|
|
heif_context_free (context);
|
|
|
|
return FALSE;
|
|
|
|
}
|
2018-05-05 12:53:39 +02:00
|
|
|
|
|
|
|
err = heif_context_write (context, &writer, output);
|
2018-05-04 20:30:30 +02:00
|
|
|
|
|
|
|
if (err.code != 0)
|
|
|
|
{
|
2018-11-27 12:27:20 +01:00
|
|
|
GCancellable *cancellable = g_cancellable_new ();
|
|
|
|
|
|
|
|
g_cancellable_cancel (cancellable);
|
|
|
|
g_output_stream_close (output, cancellable, NULL);
|
|
|
|
g_object_unref (cancellable);
|
|
|
|
|
2018-05-04 20:30:30 +02:00
|
|
|
g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
|
|
|
|
_("Writing HEIF image failed: %s"),
|
|
|
|
err.message);
|
2020-08-19 16:36:18 +02:00
|
|
|
|
|
|
|
heif_encoder_release (encoder);
|
|
|
|
heif_image_release (h_image);
|
|
|
|
heif_context_free (context);
|
2018-05-04 20:30:30 +02:00
|
|
|
return FALSE;
|
|
|
|
}
|
|
|
|
|
2018-05-05 12:53:39 +02:00
|
|
|
g_object_unref (output);
|
|
|
|
|
2018-05-04 20:30:30 +02:00
|
|
|
heif_encoder_release (encoder);
|
2020-08-19 16:36:18 +02:00
|
|
|
heif_image_release (h_image);
|
|
|
|
heif_context_free (context);
|
2018-05-04 20:30:30 +02:00
|
|
|
|
2018-05-05 12:53:39 +02:00
|
|
|
gimp_progress_update (1.0);
|
|
|
|
|
2018-05-04 20:30:30 +02:00
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2018-06-12 18:30:44 +02:00
|
|
|
/* the load dialog */
|
2018-05-04 20:30:30 +02:00
|
|
|
|
|
|
|
#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++)
|
|
|
|
{
|
2019-07-01 19:14:28 +02:00
|
|
|
struct heif_image_handle *handle = NULL;
|
2018-05-04 20:30:30 +02:00
|
|
|
struct heif_error err;
|
|
|
|
gint width;
|
|
|
|
gint height;
|
2019-07-01 19:14:28 +02:00
|
|
|
struct heif_image_handle *thumbnail_handle = NULL;
|
2018-05-04 20:30:30 +02:00
|
|
|
heif_item_id thumbnail_ID;
|
|
|
|
gint n_thumbnails;
|
2019-07-01 19:14:28 +02:00
|
|
|
struct heif_image *thumbnail_img = NULL;
|
2018-05-04 20:30:30 +02:00
|
|
|
gint thumbnail_width;
|
|
|
|
gint thumbnail_height;
|
|
|
|
|
2018-06-12 12:54:34 +02:00
|
|
|
images[i].ID = IDs[i];
|
2018-05-04 20:30:30 +02:00
|
|
|
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,
|
2019-07-01 19:14:28 +02:00
|
|
|
heif_chroma_interleaved_RGB,
|
2018-05-04 20:30:30 +02:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2018-06-12 18:30:44 +02:00
|
|
|
static void
|
|
|
|
load_dialog_item_activated (GtkIconView *icon_view,
|
|
|
|
GtkTreePath *path,
|
|
|
|
GtkDialog *dialog)
|
|
|
|
{
|
|
|
|
gtk_dialog_response (dialog, GTK_RESPONSE_OK);
|
|
|
|
}
|
2018-05-04 20:30:30 +02:00
|
|
|
|
|
|
|
static gboolean
|
|
|
|
load_dialog (struct heif_context *heif,
|
|
|
|
uint32_t *selected_image)
|
|
|
|
{
|
2018-06-12 18:30:44 +02:00
|
|
|
GtkWidget *dialog;
|
|
|
|
GtkWidget *main_vbox;
|
|
|
|
GtkWidget *frame;
|
|
|
|
HeifImage *heif_images;
|
|
|
|
GtkListStore *list_store;
|
|
|
|
GtkTreeIter iter;
|
|
|
|
GtkWidget *scrolled_window;
|
|
|
|
GtkWidget *icon_view;
|
|
|
|
GtkCellRenderer *renderer;
|
|
|
|
gint n_images;
|
|
|
|
gint i;
|
|
|
|
gint selected_idx = -1;
|
|
|
|
gboolean run = FALSE;
|
2018-05-04 20:30:30 +02:00
|
|
|
|
|
|
|
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,
|
|
|
|
|
2018-06-12 13:12:16 +02:00
|
|
|
_("_Cancel"), GTK_RESPONSE_CANCEL,
|
|
|
|
_("_OK"), GTK_RESPONSE_OK,
|
2018-05-04 20:30:30 +02:00
|
|
|
|
|
|
|
NULL);
|
|
|
|
|
2018-06-12 13:12:16 +02:00
|
|
|
main_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12);
|
2018-05-04 20:30:30 +02:00
|
|
|
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"));
|
2018-06-12 13:34:24 +02:00
|
|
|
gtk_box_pack_start (GTK_BOX (main_vbox), frame, TRUE, TRUE, 0);
|
2018-05-04 20:30:30 +02:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2018-06-12 13:34:24 +02:00
|
|
|
scrolled_window = gtk_scrolled_window_new (NULL, NULL);
|
|
|
|
gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled_window),
|
|
|
|
GTK_SHADOW_IN);
|
|
|
|
gtk_widget_set_size_request (scrolled_window,
|
|
|
|
2 * MAX_THUMBNAIL_SIZE,
|
|
|
|
1.5 * MAX_THUMBNAIL_SIZE);
|
|
|
|
gtk_container_add (GTK_CONTAINER (frame), scrolled_window);
|
|
|
|
gtk_widget_show (scrolled_window);
|
|
|
|
|
2018-06-12 18:30:44 +02:00
|
|
|
icon_view = gtk_icon_view_new_with_model (GTK_TREE_MODEL (list_store));
|
2018-06-12 13:34:24 +02:00
|
|
|
gtk_container_add (GTK_CONTAINER (scrolled_window), icon_view);
|
2018-05-04 20:30:30 +02:00
|
|
|
gtk_widget_show (icon_view);
|
|
|
|
|
2018-06-12 18:30:44 +02:00
|
|
|
renderer = gtk_cell_renderer_pixbuf_new ();
|
|
|
|
gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (icon_view), renderer, FALSE);
|
|
|
|
gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (icon_view), renderer,
|
|
|
|
"pixbuf", 1,
|
|
|
|
NULL);
|
|
|
|
|
|
|
|
renderer = gtk_cell_renderer_text_new ();
|
|
|
|
gtk_cell_layout_pack_start (GTK_CELL_LAYOUT (icon_view), renderer, FALSE);
|
|
|
|
gtk_cell_layout_set_attributes (GTK_CELL_LAYOUT (icon_view), renderer,
|
|
|
|
"text", 0,
|
|
|
|
NULL);
|
|
|
|
g_object_set (renderer,
|
|
|
|
"alignment", PANGO_ALIGN_CENTER,
|
|
|
|
"wrap-mode", PANGO_WRAP_WORD_CHAR,
|
|
|
|
"xalign", 0.5,
|
|
|
|
"yalign", 0.0,
|
|
|
|
NULL);
|
|
|
|
|
|
|
|
g_signal_connect (icon_view, "item-activated",
|
|
|
|
G_CALLBACK (load_dialog_item_activated),
|
|
|
|
dialog);
|
|
|
|
|
2018-05-04 20:30:30 +02:00
|
|
|
/* 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;
|
|
|
|
}
|
|
|
|
|
2018-06-12 18:30:44 +02:00
|
|
|
|
|
|
|
/* the save dialog */
|
|
|
|
|
2018-05-04 20:30:30 +02:00
|
|
|
gboolean
|
2019-09-24 19:35:49 +02:00
|
|
|
save_dialog (GimpProcedure *procedure,
|
2020-08-19 16:36:18 +02:00
|
|
|
GObject *config,
|
|
|
|
GimpImage *image)
|
2018-05-04 20:30:30 +02:00
|
|
|
{
|
|
|
|
GtkWidget *dialog;
|
|
|
|
GtkWidget *main_vbox;
|
2019-09-24 19:35:49 +02:00
|
|
|
GtkWidget *grid;
|
|
|
|
GtkWidget *button;
|
2019-07-01 16:50:51 +02:00
|
|
|
GtkWidget *frame;
|
2020-08-19 16:36:18 +02:00
|
|
|
#if LIBHEIF_HAVE_VERSION(1,8,0)
|
|
|
|
GtkWidget *grid2;
|
|
|
|
GtkListStore *store;
|
|
|
|
GtkWidget *combo;
|
|
|
|
gint save_bit_depth = 8;
|
|
|
|
#endif
|
2019-09-24 19:35:49 +02:00
|
|
|
gboolean run;
|
2018-05-04 20:30:30 +02:00
|
|
|
|
2019-09-24 19:35:49 +02:00
|
|
|
dialog = gimp_procedure_dialog_new (procedure,
|
|
|
|
GIMP_PROCEDURE_CONFIG (config),
|
2020-10-04 17:41:58 +02:00
|
|
|
g_strcmp0 (gimp_procedure_get_name (procedure), SAVE_PROC_AV1) == 0 ?
|
|
|
|
_("Export Image as AVIF") : _("Export Image as HEIF"));
|
2018-05-04 20:30:30 +02:00
|
|
|
|
2018-06-12 13:12:16 +02:00
|
|
|
main_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12);
|
2018-05-04 20:30:30 +02:00
|
|
|
gtk_container_set_border_width (GTK_CONTAINER (main_vbox), 12);
|
2019-09-24 19:35:49 +02:00
|
|
|
gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))),
|
|
|
|
main_vbox, FALSE, FALSE, 0);
|
|
|
|
gtk_widget_show (main_vbox);
|
2018-05-04 20:30:30 +02:00
|
|
|
|
2019-07-01 16:50:51 +02:00
|
|
|
frame = gimp_frame_new (NULL);
|
|
|
|
gtk_box_pack_start (GTK_BOX (main_vbox), frame, FALSE, FALSE, 0);
|
2019-09-24 19:35:49 +02:00
|
|
|
gtk_widget_show (frame);
|
2019-07-01 16:50:51 +02:00
|
|
|
|
2019-09-24 19:35:49 +02:00
|
|
|
button = gimp_prop_check_button_new (config, "lossless",
|
|
|
|
_("_Lossless"));
|
|
|
|
gtk_frame_set_label_widget (GTK_FRAME (frame), button);
|
2018-05-04 20:30:30 +02:00
|
|
|
|
2019-09-24 19:35:49 +02:00
|
|
|
grid = gtk_grid_new ();
|
|
|
|
gtk_grid_set_column_spacing (GTK_GRID (grid), 6);
|
2020-10-15 17:38:24 +02:00
|
|
|
gtk_grid_set_row_spacing (GTK_GRID (grid), 2);
|
2019-09-24 19:35:49 +02:00
|
|
|
gtk_container_add (GTK_CONTAINER (frame), grid);
|
|
|
|
gtk_widget_show (grid);
|
2018-05-04 20:30:30 +02:00
|
|
|
|
2019-09-24 19:35:49 +02:00
|
|
|
g_object_bind_property (config, "lossless",
|
|
|
|
grid, "sensitive",
|
|
|
|
G_BINDING_SYNC_CREATE |
|
|
|
|
G_BINDING_INVERT_BOOLEAN);
|
2018-05-04 20:30:30 +02:00
|
|
|
|
2019-09-24 19:35:49 +02:00
|
|
|
gimp_prop_scale_entry_new (config, "quality",
|
|
|
|
GTK_GRID (grid), 0, 1,
|
|
|
|
_("_Quality"),
|
|
|
|
1, 10, 0,
|
|
|
|
FALSE, 0, 0);
|
2018-05-04 20:30:30 +02:00
|
|
|
|
2020-10-15 17:38:24 +02:00
|
|
|
#if LIBHEIF_HAVE_VERSION(1,9,0)
|
|
|
|
store = gimp_int_store_new (_("RGB"), HEIFPLUGIN_EXPORT_FORMAT_RGB,
|
|
|
|
_("YUV444"), HEIFPLUGIN_EXPORT_FORMAT_YUV444,
|
|
|
|
_("YUV420"), HEIFPLUGIN_EXPORT_FORMAT_YUV420,
|
|
|
|
NULL);
|
|
|
|
|
|
|
|
combo = gimp_prop_int_combo_box_new (config, "pixel-format",
|
|
|
|
GIMP_INT_STORE (store));
|
|
|
|
g_object_unref (store);
|
|
|
|
gimp_grid_attach_aligned (GTK_GRID (grid), 0, 2,
|
|
|
|
_("Pixel format:"), 0.0, 0.5,
|
|
|
|
combo, 2);
|
|
|
|
#endif
|
|
|
|
|
2020-08-19 16:36:18 +02:00
|
|
|
#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 */
|
2020-09-18 15:31:05 +02:00
|
|
|
if (save_bit_depth < 12)
|
2020-08-19 16:36:18 +02:00
|
|
|
{
|
2020-09-18 15:31:05 +02:00
|
|
|
save_bit_depth = 12;
|
2020-08-19 16:36:18 +02:00
|
|
|
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);
|
2020-10-15 17:38:24 +02:00
|
|
|
gtk_grid_set_row_spacing (GTK_GRID (grid2), 2);
|
2020-08-19 16:36:18 +02:00
|
|
|
gtk_box_pack_start (GTK_BOX (main_vbox), grid2, FALSE, FALSE, 0);
|
|
|
|
gtk_widget_show (grid2);
|
|
|
|
|
2020-09-12 22:28:03 +02:00
|
|
|
store = gimp_int_store_new (_("8 bit/channel"), 8,
|
2020-10-19 20:47:07 +02:00
|
|
|
_("10 bit/channel"), 10,
|
|
|
|
_("12 bit/channel"), 12,
|
2020-08-19 16:36:18 +02:00
|
|
|
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,
|
2020-09-12 22:28:03 +02:00
|
|
|
_("Bit depth:"), 0.0, 0.5,
|
2020-08-19 16:36:18 +02:00
|
|
|
combo, 2);
|
2020-10-15 17:38:24 +02:00
|
|
|
|
|
|
|
store = gimp_int_store_new (_("Slow"), HEIFPLUGIN_ENCODER_SPEED_SLOW,
|
|
|
|
_("Balanced"), HEIFPLUGIN_ENCODER_SPEED_BALANCED,
|
|
|
|
_("Fast"), HEIFPLUGIN_ENCODER_SPEED_FASTER,
|
|
|
|
NULL);
|
|
|
|
|
|
|
|
combo = gimp_prop_int_combo_box_new (config, "encoder-speed",
|
|
|
|
GIMP_INT_STORE (store));
|
|
|
|
g_object_unref (store);
|
|
|
|
gimp_grid_attach_aligned (GTK_GRID (grid2), 0, 2,
|
|
|
|
_("Speed:"), 0.0, 0.5,
|
|
|
|
combo, 2);
|
2020-08-19 16:36:18 +02:00
|
|
|
#endif
|
|
|
|
|
|
|
|
#if LIBHEIF_HAVE_VERSION(1,4,0)
|
2019-09-25 12:50:29 +02:00
|
|
|
button = gimp_prop_check_button_new (config, "save-color-profile",
|
2019-09-24 19:35:49 +02:00
|
|
|
_("Save color _profile"));
|
|
|
|
gtk_box_pack_start (GTK_BOX (main_vbox), button, FALSE, FALSE, 0);
|
2019-07-01 16:39:13 +02:00
|
|
|
#endif
|
|
|
|
|
2020-09-17 13:48:03 +02:00
|
|
|
/* Save EXIF data */
|
|
|
|
#if GEXIV2_CHECK_VERSION(0, 12, 2)
|
|
|
|
button = gimp_prop_check_button_new (config, "save-exif",
|
|
|
|
_("_Save Exif data"));
|
|
|
|
gtk_box_pack_start (GTK_BOX (main_vbox), button, FALSE, FALSE, 0);
|
|
|
|
#endif
|
|
|
|
|
|
|
|
/* XMP metadata */
|
|
|
|
button = gimp_prop_check_button_new (config, "save-xmp",
|
|
|
|
_("Save _XMP data"));
|
|
|
|
gtk_box_pack_start (GTK_BOX (main_vbox), button, FALSE, FALSE, 0);
|
|
|
|
|
2019-09-24 19:35:49 +02:00
|
|
|
gtk_widget_show (dialog);
|
2018-05-04 20:30:30 +02:00
|
|
|
|
2019-09-24 19:35:49 +02:00
|
|
|
run = gimp_procedure_dialog_run (GIMP_PROCEDURE_DIALOG (dialog));
|
2018-05-04 20:30:30 +02:00
|
|
|
|
|
|
|
gtk_widget_destroy (dialog);
|
|
|
|
|
|
|
|
return run;
|
|
|
|
}
|