2590 lines
84 KiB
C
2590 lines
84 KiB
C
/*
|
|
* PIKA HEIF loader / write plugin.
|
|
* Copyright (c) 2018 struktur AG, Dirk Farin <farin@struktur.de>
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include "config.h"
|
|
|
|
#include <libheif/heif.h>
|
|
#include <lcms2.h>
|
|
#include <gexiv2/gexiv2.h>
|
|
#include <sys/time.h>
|
|
|
|
#include <libpika/pika.h>
|
|
#include <libpika/pikaui.h>
|
|
|
|
#include "libpika/stdplugins-intl.h"
|
|
|
|
|
|
#define LOAD_PROC "file-heif-load"
|
|
#define LOAD_PROC_AV1 "file-heif-av1-load"
|
|
#define LOAD_PROC_HEJ2 "file-heif-hej2-load"
|
|
#define SAVE_PROC "file-heif-save"
|
|
#define SAVE_PROC_AV1 "file-heif-av1-save"
|
|
#define PLUG_IN_BINARY "file-heif"
|
|
|
|
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;
|
|
|
|
typedef struct _PikaHeif PikaHeif;
|
|
typedef struct _PikaHeifClass PikaHeifClass;
|
|
|
|
struct _PikaHeif
|
|
{
|
|
PikaPlugIn parent_instance;
|
|
};
|
|
|
|
struct _PikaHeifClass
|
|
{
|
|
PikaPlugInClass parent_class;
|
|
};
|
|
|
|
|
|
#define PIKA_HEIF_TYPE (pika_heif_get_type ())
|
|
#define PIKA_HEIF(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), PIKA_HEIF_TYPE, PikaHeif))
|
|
|
|
GType pika_heif_get_type (void) G_GNUC_CONST;
|
|
|
|
static GList * heif_init_procedures (PikaPlugIn *plug_in);
|
|
static PikaProcedure * heif_create_procedure (PikaPlugIn *plug_in,
|
|
const gchar *name);
|
|
|
|
static PikaValueArray * heif_load (PikaProcedure *procedure,
|
|
PikaRunMode run_mode,
|
|
GFile *file,
|
|
PikaMetadata *metadata,
|
|
PikaMetadataLoadFlags *flags,
|
|
PikaProcedureConfig *config,
|
|
gpointer run_data);
|
|
static PikaValueArray * heif_save (PikaProcedure *procedure,
|
|
PikaRunMode run_mode,
|
|
PikaImage *image,
|
|
gint n_drawables,
|
|
PikaDrawable **drawables,
|
|
GFile *file,
|
|
PikaMetadata *metadata,
|
|
PikaProcedureConfig *config,
|
|
gpointer run_data);
|
|
|
|
#if LIBHEIF_HAVE_VERSION(1,8,0)
|
|
static PikaValueArray * heif_av1_save (PikaProcedure *procedure,
|
|
PikaRunMode run_mode,
|
|
PikaImage *image,
|
|
gint n_drawables,
|
|
PikaDrawable **drawables,
|
|
GFile *file,
|
|
PikaMetadata *metadata,
|
|
PikaProcedureConfig *config,
|
|
gpointer run_data);
|
|
#endif
|
|
|
|
static PikaImage * load_image (GFile *file,
|
|
PikaMetadata *metadata,
|
|
PikaMetadataLoadFlags *flags,
|
|
gboolean interactive,
|
|
PikaPDBStatusType *status,
|
|
GError **error);
|
|
static gboolean save_image (GFile *file,
|
|
PikaImage *image,
|
|
PikaDrawable *drawable,
|
|
GObject *config,
|
|
GError **error,
|
|
enum heif_compression_format compression,
|
|
PikaMetadata *metadata);
|
|
|
|
static gboolean load_dialog (struct heif_context *heif,
|
|
uint32_t *selected_image);
|
|
static gboolean save_dialog (PikaProcedure *procedure,
|
|
GObject *config,
|
|
PikaImage *image);
|
|
|
|
|
|
G_DEFINE_TYPE (PikaHeif, pika_heif, PIKA_TYPE_PLUG_IN)
|
|
|
|
PIKA_MAIN (PIKA_HEIF_TYPE)
|
|
DEFINE_STD_SET_I18N
|
|
|
|
|
|
static void
|
|
pika_heif_class_init (PikaHeifClass *klass)
|
|
{
|
|
PikaPlugInClass *plug_in_class = PIKA_PLUG_IN_CLASS (klass);
|
|
|
|
plug_in_class->init_procedures = heif_init_procedures;
|
|
plug_in_class->create_procedure = heif_create_procedure;
|
|
plug_in_class->set_i18n = STD_SET_I18N;
|
|
}
|
|
|
|
static void
|
|
pika_heif_init (PikaHeif *heif)
|
|
{
|
|
}
|
|
|
|
static GList *
|
|
heif_init_procedures (PikaPlugIn *plug_in)
|
|
{
|
|
GList *list = NULL;
|
|
|
|
#if LIBHEIF_HAVE_VERSION(1,13,0)
|
|
heif_init (NULL);
|
|
#endif
|
|
|
|
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));
|
|
}
|
|
#if LIBHEIF_HAVE_VERSION(1,8,0)
|
|
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));
|
|
}
|
|
#endif
|
|
|
|
#if LIBHEIF_HAVE_VERSION(1,17,0)
|
|
if (heif_have_decoder_for_format (heif_compression_JPEG2000))
|
|
{
|
|
list = g_list_append (list, g_strdup (LOAD_PROC_HEJ2));
|
|
}
|
|
#endif
|
|
|
|
#if LIBHEIF_HAVE_VERSION(1,13,0)
|
|
heif_deinit ();
|
|
#endif
|
|
|
|
return list;
|
|
}
|
|
|
|
static PikaProcedure *
|
|
heif_create_procedure (PikaPlugIn *plug_in,
|
|
const gchar *name)
|
|
{
|
|
PikaProcedure *procedure = NULL;
|
|
|
|
if (! strcmp (name, LOAD_PROC))
|
|
{
|
|
procedure = pika_load_procedure_new (plug_in, name,
|
|
PIKA_PDB_PROC_TYPE_PLUGIN,
|
|
heif_load, NULL, NULL);
|
|
|
|
pika_procedure_set_menu_label (procedure, _("HEIF/HEIC"));
|
|
|
|
pika_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);
|
|
pika_procedure_set_attribution (procedure,
|
|
"Dirk Farin <farin@struktur.de>",
|
|
"Dirk Farin <farin@struktur.de>",
|
|
"2018");
|
|
|
|
pika_file_procedure_set_handles_remote (PIKA_FILE_PROCEDURE (procedure),
|
|
TRUE);
|
|
pika_file_procedure_set_mime_types (PIKA_FILE_PROCEDURE (procedure),
|
|
"image/heif");
|
|
pika_file_procedure_set_extensions (PIKA_FILE_PROCEDURE (procedure),
|
|
"heif,heic");
|
|
|
|
/* HEIF is an ISOBMFF format whose "brand" (the value after "ftyp")
|
|
* can be of various values.
|
|
* See also: https://gitlab.gnome.org/GNOME/pika/issues/2209
|
|
*/
|
|
pika_file_procedure_set_magics (PIKA_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,"
|
|
"4,string,ftypmsf1");
|
|
}
|
|
else if (! strcmp (name, SAVE_PROC))
|
|
{
|
|
procedure = pika_save_procedure_new (plug_in, name,
|
|
PIKA_PDB_PROC_TYPE_PLUGIN,
|
|
FALSE, heif_save, NULL, NULL);
|
|
|
|
pika_procedure_set_image_types (procedure, "RGB*");
|
|
|
|
pika_procedure_set_menu_label (procedure, _("HEIF/HEIC"));
|
|
pika_file_procedure_set_format_name (PIKA_FILE_PROCEDURE (procedure),
|
|
"HEIF");
|
|
|
|
pika_procedure_set_documentation (procedure,
|
|
_("Exports HEIF images"),
|
|
_("Save image in HEIF format (High "
|
|
"Efficiency Image File Format)."),
|
|
name);
|
|
pika_procedure_set_attribution (procedure,
|
|
"Dirk Farin <farin@struktur.de>",
|
|
"Dirk Farin <farin@struktur.de>",
|
|
"2018");
|
|
|
|
pika_file_procedure_set_handles_remote (PIKA_FILE_PROCEDURE (procedure),
|
|
TRUE);
|
|
pika_file_procedure_set_mime_types (PIKA_FILE_PROCEDURE (procedure),
|
|
"image/heif");
|
|
pika_file_procedure_set_extensions (PIKA_FILE_PROCEDURE (procedure),
|
|
"heif,heic");
|
|
|
|
PIKA_PROC_ARG_INT (procedure, "quality",
|
|
_("_Quality"),
|
|
_("Quality factor (0 = worst, 100 = best)"),
|
|
0, 100, 50,
|
|
G_PARAM_READWRITE);
|
|
|
|
PIKA_PROC_ARG_BOOLEAN (procedure, "lossless",
|
|
_("L_ossless"),
|
|
_("Use lossless compression"),
|
|
FALSE,
|
|
G_PARAM_READWRITE);
|
|
|
|
PIKA_PROC_AUX_ARG_BOOLEAN (procedure, "save-color-profile",
|
|
_("Save color prof_ile"),
|
|
_("Save the image's color profile"),
|
|
pika_export_color_profile (),
|
|
G_PARAM_READWRITE);
|
|
|
|
PIKA_PROC_ARG_INT (procedure, "save-bit-depth",
|
|
_("_Bit depth"),
|
|
_("Bit depth of exported image"),
|
|
8, 12, 8,
|
|
G_PARAM_READWRITE);
|
|
|
|
PIKA_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);
|
|
|
|
PIKA_PROC_ARG_INT (procedure, "encoder-speed",
|
|
_("Enco_der speed"),
|
|
_("Tradeoff between speed and compression"),
|
|
HEIFPLUGIN_ENCODER_SPEED_SLOW, HEIFPLUGIN_ENCODER_SPEED_FASTER,
|
|
HEIFPLUGIN_ENCODER_SPEED_BALANCED,
|
|
G_PARAM_READWRITE);
|
|
|
|
PIKA_PROC_ARG_BOOLEAN (procedure, "save-exif",
|
|
_("Save Exi_f"),
|
|
_("Toggle saving Exif data"),
|
|
pika_export_exif (),
|
|
G_PARAM_READWRITE);
|
|
|
|
PIKA_PROC_ARG_BOOLEAN (procedure, "save-xmp",
|
|
_("Save _XMP"),
|
|
_("Toggle saving XMP data"),
|
|
pika_export_xmp (),
|
|
G_PARAM_READWRITE);
|
|
}
|
|
#if LIBHEIF_HAVE_VERSION(1,8,0)
|
|
else if (! strcmp (name, LOAD_PROC_AV1))
|
|
{
|
|
procedure = pika_load_procedure_new (plug_in, name,
|
|
PIKA_PDB_PROC_TYPE_PLUGIN,
|
|
heif_load, NULL, NULL);
|
|
|
|
pika_procedure_set_menu_label (procedure, "HEIF/AVIF");
|
|
|
|
pika_procedure_set_documentation (procedure,
|
|
_("Loads AVIF images"),
|
|
_("Load image stored in AV1 Image File Format (AVIF)"),
|
|
name);
|
|
pika_procedure_set_attribution (procedure,
|
|
"Daniel Novomesky <dnovomesky@gmail.com>",
|
|
"Daniel Novomesky <dnovomesky@gmail.com>",
|
|
"2020");
|
|
|
|
pika_file_procedure_set_handles_remote (PIKA_FILE_PROCEDURE (procedure),
|
|
TRUE);
|
|
pika_file_procedure_set_mime_types (PIKA_FILE_PROCEDURE (procedure),
|
|
"image/avif");
|
|
pika_file_procedure_set_extensions (PIKA_FILE_PROCEDURE (procedure),
|
|
"avif");
|
|
|
|
pika_file_procedure_set_magics (PIKA_FILE_PROCEDURE (procedure),
|
|
"4,string,ftypmif1,4,string,ftypavif");
|
|
|
|
pika_file_procedure_set_priority (PIKA_FILE_PROCEDURE (procedure), 100);
|
|
}
|
|
else if (! strcmp (name, SAVE_PROC_AV1))
|
|
{
|
|
procedure = pika_save_procedure_new (plug_in, name,
|
|
PIKA_PDB_PROC_TYPE_PLUGIN,
|
|
FALSE, heif_av1_save, NULL, NULL);
|
|
|
|
pika_procedure_set_image_types (procedure, "RGB*");
|
|
|
|
pika_procedure_set_menu_label (procedure, "HEIF/AVIF");
|
|
pika_file_procedure_set_format_name (PIKA_FILE_PROCEDURE (procedure),
|
|
"AVIF");
|
|
|
|
pika_procedure_set_documentation (procedure,
|
|
_("Exports AVIF images"),
|
|
_("Save image in AV1 Image File Format (AVIF)"),
|
|
name);
|
|
pika_procedure_set_attribution (procedure,
|
|
"Daniel Novomesky <dnovomesky@gmail.com>",
|
|
"Daniel Novomesky <dnovomesky@gmail.com>",
|
|
"2020");
|
|
|
|
pika_file_procedure_set_handles_remote (PIKA_FILE_PROCEDURE (procedure),
|
|
TRUE);
|
|
pika_file_procedure_set_mime_types (PIKA_FILE_PROCEDURE (procedure),
|
|
"image/avif");
|
|
pika_file_procedure_set_extensions (PIKA_FILE_PROCEDURE (procedure),
|
|
"avif");
|
|
|
|
pika_file_procedure_set_priority (PIKA_FILE_PROCEDURE (procedure), 100);
|
|
|
|
PIKA_PROC_ARG_INT (procedure, "quality",
|
|
_("_Quality"),
|
|
_("Quality factor (0 = worst, 100 = best)"),
|
|
0, 100, 50,
|
|
G_PARAM_READWRITE);
|
|
|
|
PIKA_PROC_ARG_BOOLEAN (procedure, "lossless",
|
|
_("L_ossless"),
|
|
_("Use lossless compression"),
|
|
FALSE,
|
|
G_PARAM_READWRITE);
|
|
|
|
PIKA_PROC_AUX_ARG_BOOLEAN (procedure, "save-color-profile",
|
|
_("Save color prof_ile"),
|
|
_("Save the image's color profile"),
|
|
pika_export_color_profile (),
|
|
G_PARAM_READWRITE);
|
|
|
|
PIKA_PROC_ARG_INT (procedure, "save-bit-depth",
|
|
_("_Bit depth"),
|
|
_("Bit depth of exported image"),
|
|
8, 12, 8,
|
|
G_PARAM_READWRITE);
|
|
|
|
PIKA_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);
|
|
|
|
PIKA_PROC_ARG_INT (procedure, "encoder-speed",
|
|
_("Enco_der speed"),
|
|
_("Tradeoff between speed and compression"),
|
|
HEIFPLUGIN_ENCODER_SPEED_SLOW, HEIFPLUGIN_ENCODER_SPEED_FASTER,
|
|
HEIFPLUGIN_ENCODER_SPEED_BALANCED,
|
|
G_PARAM_READWRITE);
|
|
|
|
PIKA_PROC_ARG_BOOLEAN (procedure, "save-exif",
|
|
_("Save Exi_f"),
|
|
_("Toggle saving Exif data"),
|
|
pika_export_exif (),
|
|
G_PARAM_READWRITE);
|
|
|
|
PIKA_PROC_ARG_BOOLEAN (procedure, "save-xmp",
|
|
_("Save _XMP"),
|
|
_("Toggle saving XMP data"),
|
|
pika_export_xmp (),
|
|
G_PARAM_READWRITE);
|
|
}
|
|
#endif
|
|
#if LIBHEIF_HAVE_VERSION(1,17,0)
|
|
else if (! strcmp (name, LOAD_PROC_HEJ2))
|
|
{
|
|
procedure = pika_load_procedure_new (plug_in, name, PIKA_PDB_PROC_TYPE_PLUGIN,
|
|
heif_load, NULL, NULL);
|
|
|
|
pika_procedure_set_menu_label (procedure, _("JPEG 2000 encapsulated in HEIF"));
|
|
|
|
pika_procedure_set_documentation (procedure,
|
|
_("Loads HEJ2 images"),
|
|
_("Load JPEG 2000 image encapsulated in HEIF (HEJ2)"),
|
|
name);
|
|
pika_procedure_set_attribution (procedure,
|
|
"Daniel Novomesky <dnovomesky@gmail.com>",
|
|
"Daniel Novomesky <dnovomesky@gmail.com>",
|
|
"2023");
|
|
|
|
pika_file_procedure_set_handles_remote (PIKA_FILE_PROCEDURE (procedure), TRUE);
|
|
pika_file_procedure_set_mime_types (PIKA_FILE_PROCEDURE (procedure),
|
|
"image/hej2k");
|
|
pika_file_procedure_set_extensions (PIKA_FILE_PROCEDURE (procedure),
|
|
"hej2");
|
|
|
|
pika_file_procedure_set_magics (PIKA_FILE_PROCEDURE (procedure),
|
|
"4,string,ftypj2ki");
|
|
}
|
|
#endif
|
|
return procedure;
|
|
}
|
|
|
|
static PikaValueArray *
|
|
heif_load (PikaProcedure *procedure,
|
|
PikaRunMode run_mode,
|
|
GFile *file,
|
|
PikaMetadata *metadata,
|
|
PikaMetadataLoadFlags *flags,
|
|
PikaProcedureConfig *config,
|
|
gpointer run_data)
|
|
{
|
|
PikaValueArray *return_vals;
|
|
PikaPDBStatusType status = PIKA_PDB_SUCCESS;
|
|
PikaImage *image;
|
|
gboolean interactive;
|
|
GError *error = NULL;
|
|
|
|
gegl_init (NULL, NULL);
|
|
|
|
interactive = (run_mode == PIKA_RUN_INTERACTIVE);
|
|
|
|
if (interactive)
|
|
pika_ui_init (PLUG_IN_BINARY);
|
|
|
|
#if LIBHEIF_HAVE_VERSION(1,13,0)
|
|
heif_init (NULL);
|
|
#endif
|
|
|
|
image = load_image (file, metadata, flags, interactive, &status, &error);
|
|
|
|
#if LIBHEIF_HAVE_VERSION(1,13,0)
|
|
heif_deinit ();
|
|
#endif
|
|
|
|
if (! image)
|
|
return pika_procedure_new_return_values (procedure, status, error);
|
|
|
|
return_vals = pika_procedure_new_return_values (procedure,
|
|
PIKA_PDB_SUCCESS,
|
|
NULL);
|
|
|
|
PIKA_VALUES_SET_IMAGE (return_vals, 1, image);
|
|
|
|
return return_vals;
|
|
}
|
|
|
|
static PikaValueArray *
|
|
heif_save (PikaProcedure *procedure,
|
|
PikaRunMode run_mode,
|
|
PikaImage *image,
|
|
gint n_drawables,
|
|
PikaDrawable **drawables,
|
|
GFile *file,
|
|
PikaMetadata *metadata_unused,
|
|
PikaProcedureConfig *config,
|
|
gpointer run_data)
|
|
{
|
|
PikaPDBStatusType status = PIKA_PDB_SUCCESS;
|
|
PikaExportReturn export = PIKA_EXPORT_CANCEL;
|
|
PikaMetadata *metadata;
|
|
GError *error = NULL;
|
|
|
|
gegl_init (NULL, NULL);
|
|
|
|
switch (run_mode)
|
|
{
|
|
case PIKA_RUN_INTERACTIVE:
|
|
case PIKA_RUN_WITH_LAST_VALS:
|
|
pika_ui_init (PLUG_IN_BINARY);
|
|
|
|
export = pika_export_image (&image, &n_drawables, &drawables, "HEIF",
|
|
PIKA_EXPORT_CAN_HANDLE_RGB |
|
|
PIKA_EXPORT_CAN_HANDLE_ALPHA);
|
|
|
|
if (export == PIKA_EXPORT_CANCEL)
|
|
return pika_procedure_new_return_values (procedure,
|
|
PIKA_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 pika_procedure_new_return_values (procedure,
|
|
PIKA_PDB_CALLING_ERROR,
|
|
error);
|
|
}
|
|
|
|
if (run_mode == PIKA_RUN_INTERACTIVE)
|
|
{
|
|
if (! save_dialog (procedure, G_OBJECT (config), image))
|
|
status = PIKA_PDB_CANCEL;
|
|
}
|
|
|
|
if (status == PIKA_PDB_SUCCESS)
|
|
{
|
|
PikaMetadataSaveFlags metadata_flags;
|
|
|
|
metadata = pika_image_metadata_save_prepare (image, "image/heif", &metadata_flags);
|
|
|
|
#if LIBHEIF_HAVE_VERSION(1,13,0)
|
|
heif_init (NULL);
|
|
#endif
|
|
|
|
if (! save_image (file, image, drawables[0], G_OBJECT (config),
|
|
&error, heif_compression_HEVC, metadata))
|
|
{
|
|
status = PIKA_PDB_EXECUTION_ERROR;
|
|
}
|
|
|
|
#if LIBHEIF_HAVE_VERSION(1,13,0)
|
|
heif_deinit ();
|
|
#endif
|
|
|
|
if (metadata)
|
|
{
|
|
g_object_unref (metadata);
|
|
}
|
|
}
|
|
|
|
if (export == PIKA_EXPORT_EXPORT)
|
|
{
|
|
pika_image_delete (image);
|
|
g_free (drawables);
|
|
}
|
|
|
|
return pika_procedure_new_return_values (procedure, status, error);
|
|
}
|
|
|
|
#if LIBHEIF_HAVE_VERSION(1,8,0)
|
|
static PikaValueArray *
|
|
heif_av1_save (PikaProcedure *procedure,
|
|
PikaRunMode run_mode,
|
|
PikaImage *image,
|
|
gint n_drawables,
|
|
PikaDrawable **drawables,
|
|
GFile *file,
|
|
PikaMetadata *metadata_unused,
|
|
PikaProcedureConfig *config,
|
|
gpointer run_data)
|
|
{
|
|
PikaPDBStatusType status = PIKA_PDB_SUCCESS;
|
|
PikaExportReturn export = PIKA_EXPORT_CANCEL;
|
|
PikaMetadata *metadata;
|
|
GError *error = NULL;
|
|
|
|
gegl_init (NULL, NULL);
|
|
|
|
switch (run_mode)
|
|
{
|
|
case PIKA_RUN_INTERACTIVE:
|
|
case PIKA_RUN_WITH_LAST_VALS:
|
|
pika_ui_init (PLUG_IN_BINARY);
|
|
|
|
export = pika_export_image (&image, &n_drawables, &drawables, "AVIF",
|
|
PIKA_EXPORT_CAN_HANDLE_RGB |
|
|
PIKA_EXPORT_CAN_HANDLE_ALPHA);
|
|
|
|
if (export == PIKA_EXPORT_CANCEL)
|
|
return pika_procedure_new_return_values (procedure,
|
|
PIKA_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 pika_procedure_new_return_values (procedure,
|
|
PIKA_PDB_CALLING_ERROR,
|
|
error);
|
|
}
|
|
|
|
if (run_mode == PIKA_RUN_INTERACTIVE)
|
|
{
|
|
if (! save_dialog (procedure, G_OBJECT (config), image))
|
|
status = PIKA_PDB_CANCEL;
|
|
}
|
|
|
|
if (status == PIKA_PDB_SUCCESS)
|
|
{
|
|
PikaMetadataSaveFlags metadata_flags;
|
|
|
|
metadata = pika_image_metadata_save_prepare (image, "image/avif", &metadata_flags);
|
|
|
|
#if LIBHEIF_HAVE_VERSION(1,13,0)
|
|
heif_init (NULL);
|
|
#endif
|
|
|
|
if (! save_image (file, image, drawables[0], G_OBJECT (config),
|
|
&error, heif_compression_AV1, metadata))
|
|
{
|
|
status = PIKA_PDB_EXECUTION_ERROR;
|
|
}
|
|
|
|
#if LIBHEIF_HAVE_VERSION(1,13,0)
|
|
heif_deinit ();
|
|
#endif
|
|
|
|
if (metadata)
|
|
{
|
|
g_object_unref (metadata);
|
|
}
|
|
}
|
|
|
|
if (export == PIKA_EXPORT_EXPORT)
|
|
{
|
|
pika_image_delete (image);
|
|
g_free (drawables);
|
|
}
|
|
|
|
return pika_procedure_new_return_values (procedure, status, error);
|
|
}
|
|
#endif
|
|
|
|
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_attribute_uint64 (info, G_FILE_ATTRIBUTE_STANDARD_SIZE);
|
|
|
|
g_object_unref (info);
|
|
}
|
|
|
|
return size;
|
|
}
|
|
|
|
#if LIBHEIF_HAVE_VERSION(1,8,0)
|
|
static void
|
|
heifplugin_color_profile_set_tag (cmsHPROFILE profile,
|
|
cmsTagSignature sig,
|
|
const gchar *tag)
|
|
{
|
|
cmsMLU *mlu;
|
|
|
|
mlu = cmsMLUalloc (NULL, 1);
|
|
cmsMLUsetASCII (mlu, "en", "US", tag);
|
|
cmsWriteTag (profile, sig, mlu);
|
|
cmsMLUfree (mlu);
|
|
}
|
|
|
|
static PikaColorProfile *
|
|
nclx_to_pika_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 pika_color_profile_new_rgb_srgb();
|
|
}
|
|
|
|
if (nclx->transfer_characteristics == heif_transfer_characteristic_linear)
|
|
{
|
|
return pika_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)
|
|
{
|
|
PikaColorProfile *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,
|
|
"PIKA");
|
|
heifplugin_color_profile_set_tag (profile, cmsSigDeviceModelDescTag,
|
|
description);
|
|
heifplugin_color_profile_set_tag (profile, cmsSigCopyrightTag,
|
|
"Public Domain");
|
|
|
|
new_profile = pika_color_profile_new_from_lcms_profile (profile, NULL);
|
|
|
|
cmsCloseProfile (profile);
|
|
g_free (description);
|
|
return new_profile;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
#endif
|
|
|
|
PikaImage *
|
|
load_image (GFile *file,
|
|
PikaMetadata *metadata,
|
|
PikaMetadataLoadFlags *flags,
|
|
gboolean interactive,
|
|
PikaPDBStatusType *status,
|
|
GError **error)
|
|
{
|
|
GInputStream *input;
|
|
goffset file_size;
|
|
guchar *file_buffer;
|
|
gsize bytes_read;
|
|
struct heif_context *ctx;
|
|
struct heif_error err;
|
|
struct heif_image_handle *handle = NULL;
|
|
struct heif_image *img = NULL;
|
|
PikaColorProfile *profile = NULL;
|
|
gint n_images;
|
|
heif_item_id primary;
|
|
heif_item_id selected_image;
|
|
gboolean has_alpha;
|
|
gint width;
|
|
gint height;
|
|
PikaImage *image;
|
|
PikaLayer *layer;
|
|
GeglBuffer *buffer;
|
|
const Babl *format;
|
|
const guint8 *data;
|
|
gint stride;
|
|
gint bit_depth = 8;
|
|
enum heif_chroma chroma = heif_chroma_interleaved_RGB;
|
|
PikaPrecision precision;
|
|
gboolean load_linear;
|
|
const char *encoding;
|
|
|
|
pika_progress_init_printf (_("Opening '%s'"),
|
|
pika_file_get_utf8_name (file));
|
|
|
|
*status = PIKA_PDB_EXECUTION_ERROR;
|
|
|
|
file_size = get_file_size (file, error);
|
|
if (file_size <= 0)
|
|
return NULL;
|
|
|
|
input = G_INPUT_STREAM (g_file_read (file, NULL, error));
|
|
if (! input)
|
|
return NULL;
|
|
|
|
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);
|
|
return NULL;
|
|
}
|
|
|
|
pika_progress_update (0.25);
|
|
|
|
ctx = heif_context_alloc ();
|
|
if (!ctx)
|
|
{
|
|
g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
|
|
"cannot allocate heif_context");
|
|
g_free (file_buffer);
|
|
g_object_unref (input);
|
|
return NULL;
|
|
}
|
|
|
|
err = heif_context_read_from_memory (ctx, file_buffer, file_size, NULL);
|
|
if (err.code)
|
|
{
|
|
g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
|
|
_("Loading HEIF image failed: %s"),
|
|
err.message);
|
|
heif_context_free (ctx);
|
|
g_free (file_buffer);
|
|
g_object_unref (input);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
g_free (file_buffer);
|
|
g_object_unref (input);
|
|
|
|
pika_progress_update (0.5);
|
|
|
|
/* analyze image content
|
|
* Is there more than one image? Which image is the primary image?
|
|
*/
|
|
|
|
n_images = heif_context_get_number_of_top_level_images (ctx);
|
|
if (n_images == 0)
|
|
{
|
|
g_set_error_literal (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
|
|
_("Loading HEIF image failed: "
|
|
"Input file contains no readable images"));
|
|
heif_context_free (ctx);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
err = heif_context_get_primary_image_ID (ctx, &primary);
|
|
if (err.code)
|
|
{
|
|
g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
|
|
_("Loading HEIF image failed: %s"),
|
|
err.message);
|
|
heif_context_free (ctx);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
/* 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);
|
|
|
|
*status = PIKA_PDB_CANCEL;
|
|
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
/* load the picture */
|
|
|
|
err = heif_context_get_image_handle (ctx, selected_image, &handle);
|
|
if (err.code)
|
|
{
|
|
g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
|
|
_("Loading HEIF image failed: %s"),
|
|
err.message);
|
|
heif_context_free (ctx);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
has_alpha = heif_image_handle_has_alpha_channel (handle);
|
|
|
|
#if LIBHEIF_HAVE_VERSION(1,8,0)
|
|
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;
|
|
}
|
|
#endif
|
|
|
|
if (bit_depth == 8)
|
|
{
|
|
if (has_alpha)
|
|
{
|
|
chroma = heif_chroma_interleaved_RGBA;
|
|
}
|
|
else
|
|
{
|
|
chroma = heif_chroma_interleaved_RGB;
|
|
}
|
|
}
|
|
else /* high bit depth */
|
|
{
|
|
#if LIBHEIF_HAVE_VERSION(1,8,0)
|
|
#if ( G_BYTE_ORDER == G_LITTLE_ENDIAN )
|
|
if (has_alpha)
|
|
{
|
|
chroma = heif_chroma_interleaved_RRGGBBAA_LE;
|
|
}
|
|
else
|
|
{
|
|
chroma = heif_chroma_interleaved_RRGGBB_LE;
|
|
}
|
|
#else
|
|
if (has_alpha)
|
|
{
|
|
chroma = heif_chroma_interleaved_RRGGBBAA_BE;
|
|
}
|
|
else
|
|
{
|
|
chroma = heif_chroma_interleaved_RRGGBB_BE;
|
|
}
|
|
#endif
|
|
#endif
|
|
}
|
|
|
|
err = heif_decode_image (handle,
|
|
&img,
|
|
heif_colorspace_RGB,
|
|
chroma,
|
|
NULL);
|
|
if (err.code)
|
|
{
|
|
g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
|
|
_("Loading HEIF image failed: %s"),
|
|
err.message);
|
|
heif_image_handle_release (handle);
|
|
heif_context_free (ctx);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
#if LIBHEIF_HAVE_VERSION(1,4,0)
|
|
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
|
|
*/
|
|
{
|
|
void *profile_data;
|
|
size_t profile_size;
|
|
|
|
profile_size = heif_image_handle_get_raw_color_profile_size (handle);
|
|
profile_data = g_malloc0 (profile_size);
|
|
err = heif_image_handle_get_raw_color_profile (handle, profile_data);
|
|
|
|
if (err.code)
|
|
g_warning ("%s: ICC profile loading failed and discarded.",
|
|
G_STRFUNC);
|
|
else
|
|
profile = pika_color_profile_new_from_icc_profile ((guint8 *) profile_data,
|
|
profile_size, NULL);
|
|
|
|
g_free (profile_data);
|
|
}
|
|
break;
|
|
#if LIBHEIF_HAVE_VERSION(1,8,0)
|
|
case heif_color_profile_type_nclx:
|
|
{
|
|
struct heif_color_profile_nclx *nclx = NULL;
|
|
|
|
err = heif_image_handle_get_nclx_color_profile (handle, &nclx);
|
|
if (err.code)
|
|
{
|
|
g_warning ("%s: NCLX profile loading failed and discarded.",
|
|
G_STRFUNC);
|
|
}
|
|
else
|
|
{
|
|
profile = nclx_to_pika_profile (nclx);
|
|
heif_nclx_color_profile_free (nclx);
|
|
}
|
|
}
|
|
break;
|
|
#endif
|
|
default:
|
|
g_warning ("%s: unknown color profile type has been discarded.",
|
|
G_STRFUNC);
|
|
break;
|
|
}
|
|
#endif /* LIBHEIF_HAVE_VERSION(1,4,0) */
|
|
|
|
pika_progress_update (0.75);
|
|
|
|
width = heif_image_get_width (img, heif_channel_interleaved);
|
|
height = heif_image_get_height (img, heif_channel_interleaved);
|
|
|
|
/* create PIKA image and copy HEIF image into the PIKA image
|
|
* (converting it to RGB)
|
|
*/
|
|
|
|
if (profile)
|
|
{
|
|
load_linear = pika_color_profile_is_linear (profile);
|
|
}
|
|
else
|
|
{
|
|
load_linear = FALSE;
|
|
}
|
|
|
|
if (load_linear)
|
|
{
|
|
if (bit_depth == 8)
|
|
{
|
|
precision = PIKA_PRECISION_U8_LINEAR;
|
|
encoding = has_alpha ? "RGBA u8" : "RGB u8";
|
|
}
|
|
else
|
|
{
|
|
precision = PIKA_PRECISION_U16_LINEAR;
|
|
encoding = has_alpha ? "RGBA u16" : "RGB u16";
|
|
}
|
|
}
|
|
else /* non-linear profiles */
|
|
{
|
|
if (bit_depth == 8)
|
|
{
|
|
precision = PIKA_PRECISION_U8_NON_LINEAR;
|
|
encoding = has_alpha ? "R'G'B'A u8" : "R'G'B' u8";
|
|
}
|
|
else
|
|
{
|
|
precision = PIKA_PRECISION_U16_NON_LINEAR;
|
|
encoding = has_alpha ? "R'G'B'A u16" : "R'G'B' u16";
|
|
}
|
|
}
|
|
|
|
image = pika_image_new_with_precision (width, height, PIKA_RGB, precision);
|
|
|
|
if (profile)
|
|
{
|
|
if (pika_color_profile_is_rgb (profile))
|
|
{
|
|
pika_image_set_color_profile (image, profile);
|
|
}
|
|
else if (pika_color_profile_is_gray (profile))
|
|
{
|
|
g_warning ("Gray ICC profile was not applied to the imported image.");
|
|
}
|
|
else
|
|
{
|
|
g_warning ("ICC profile was not applied to the imported image.");
|
|
}
|
|
}
|
|
|
|
layer = pika_layer_new (image,
|
|
_("image content"),
|
|
width, height,
|
|
has_alpha ? PIKA_RGBA_IMAGE : PIKA_RGB_IMAGE,
|
|
100.0,
|
|
pika_image_get_default_new_layer_mode (image));
|
|
|
|
pika_image_insert_layer (image, layer, NULL, 0);
|
|
|
|
buffer = pika_drawable_get_buffer (PIKA_DRAWABLE (layer));
|
|
|
|
data = heif_image_get_plane_readonly (img, heif_channel_interleaved,
|
|
&stride);
|
|
|
|
format = babl_format_with_space (encoding,
|
|
gegl_buffer_get_format (buffer));
|
|
|
|
if (bit_depth == 8)
|
|
{
|
|
gegl_buffer_set (buffer,
|
|
GEGL_RECTANGLE (0, 0, width, height),
|
|
0, format, data, stride);
|
|
}
|
|
else /* high bit depth */
|
|
{
|
|
uint16_t *data16;
|
|
const uint16_t *src16;
|
|
uint16_t *dest16;
|
|
gint x, y, rowentries;
|
|
int tmp_pixelval;
|
|
|
|
if (has_alpha)
|
|
{
|
|
rowentries = width * 4;
|
|
}
|
|
else /* no alpha */
|
|
{
|
|
rowentries = width * 3;
|
|
}
|
|
|
|
data16 = g_malloc_n (height, rowentries * 2);
|
|
dest16 = data16;
|
|
|
|
switch (bit_depth)
|
|
{
|
|
case 10:
|
|
for (y = 0; y < height; y++)
|
|
{
|
|
src16 = (const uint16_t *) (y * stride + data);
|
|
for (x = 0; x < rowentries; x++)
|
|
{
|
|
tmp_pixelval = (int) ( ( (float) (0x03ff & (*src16)) / 1023.0f) * 65535.0f + 0.5f);
|
|
*dest16 = CLAMP (tmp_pixelval, 0, 65535);
|
|
dest16++;
|
|
src16++;
|
|
}
|
|
}
|
|
break;
|
|
case 12:
|
|
for (y = 0; y < height; y++)
|
|
{
|
|
src16 = (const uint16_t *) (y * stride + data);
|
|
for (x = 0; x < rowentries; x++)
|
|
{
|
|
tmp_pixelval = (int) ( ( (float) (0x0fff & (*src16)) / 4095.0f) * 65535.0f + 0.5f);
|
|
*dest16 = CLAMP (tmp_pixelval, 0, 65535);
|
|
dest16++;
|
|
src16++;
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
for (y = 0; y < height; y++)
|
|
{
|
|
src16 = (const uint16_t *) (y * stride + data);
|
|
for (x = 0; x < rowentries; x++)
|
|
{
|
|
*dest16 = *src16;
|
|
dest16++;
|
|
src16++;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
|
|
gegl_buffer_set (buffer,
|
|
GEGL_RECTANGLE (0, 0, width, height),
|
|
0, format, data16, GEGL_AUTO_ROWSTRIDE);
|
|
|
|
g_free (data16);
|
|
}
|
|
|
|
g_object_unref (buffer);
|
|
|
|
if (metadata)
|
|
{
|
|
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, "mime",
|
|
&metadata_id, 1);
|
|
if (n_metadata > 0)
|
|
{
|
|
if (g_strcmp0 (heif_image_handle_get_metadata_content_type (handle, metadata_id), "application/rdf+xml")
|
|
== 0)
|
|
{
|
|
xmp_data_size = heif_image_handle_get_metadata_size (handle, metadata_id);
|
|
|
|
xmp_data = g_alloca (xmp_data_size);
|
|
|
|
err = heif_image_handle_get_metadata (handle, metadata_id, xmp_data);
|
|
if (err.code != 0)
|
|
{
|
|
xmp_data = NULL;
|
|
xmp_data_size = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (exif_data || xmp_data)
|
|
{
|
|
gexiv2_metadata_clear (GEXIV2_METADATA (metadata));
|
|
|
|
if (exif_data)
|
|
{
|
|
const guint8 tiffHeaderBE[4] = { 'M', 'M', 0, 42 };
|
|
const guint8 tiffHeaderLE[4] = { 'I', 'I', 42, 0 };
|
|
GExiv2Metadata *exif_metadata = GEXIV2_METADATA (metadata);
|
|
const guint8 *tiffheader = exif_data;
|
|
glong new_exif_size = exif_data_size;
|
|
|
|
while (new_exif_size >= 4) /*Searching for TIFF Header*/
|
|
{
|
|
if (tiffheader[0] == tiffHeaderBE[0] && tiffheader[1] == tiffHeaderBE[1] &&
|
|
tiffheader[2] == tiffHeaderBE[2] && tiffheader[3] == tiffHeaderBE[3])
|
|
{
|
|
break;
|
|
}
|
|
if (tiffheader[0] == tiffHeaderLE[0] && tiffheader[1] == tiffHeaderLE[1] &&
|
|
tiffheader[2] == tiffHeaderLE[2] && tiffheader[3] == tiffHeaderLE[3])
|
|
{
|
|
break;
|
|
}
|
|
new_exif_size--;
|
|
tiffheader++;
|
|
}
|
|
|
|
if (new_exif_size > 4) /* TIFF header + some data found*/
|
|
{
|
|
if (! gexiv2_metadata_open_buf (exif_metadata, tiffheader, new_exif_size, error))
|
|
{
|
|
g_printerr ("%s: Failed to set EXIF metadata: %s\n", G_STRFUNC, (*error)->message);
|
|
g_clear_error (error);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
g_printerr ("%s: EXIF metadata not set\n", G_STRFUNC);
|
|
}
|
|
}
|
|
|
|
if (xmp_data)
|
|
{
|
|
if (! pika_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);
|
|
}
|
|
}
|
|
}
|
|
|
|
gexiv2_metadata_try_set_orientation (GEXIV2_METADATA (metadata),
|
|
GEXIV2_ORIENTATION_NORMAL, NULL);
|
|
gexiv2_metadata_try_set_metadata_pixel_width (GEXIV2_METADATA (metadata), width, NULL);
|
|
gexiv2_metadata_try_set_metadata_pixel_height (GEXIV2_METADATA (metadata),
|
|
height, NULL);
|
|
|
|
*flags = PIKA_METADATA_LOAD_COMMENT | PIKA_METADATA_LOAD_RESOLUTION;
|
|
}
|
|
|
|
if (profile)
|
|
g_object_unref (profile);
|
|
|
|
heif_image_handle_release (handle);
|
|
heif_context_free (ctx);
|
|
heif_image_release (img);
|
|
|
|
pika_progress_update (1.0);
|
|
|
|
if (image)
|
|
{
|
|
*status = PIKA_PDB_SUCCESS;
|
|
}
|
|
return image;
|
|
}
|
|
|
|
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;
|
|
heif_error.subcode = heif_suberror_Unspecified;
|
|
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;
|
|
}
|
|
|
|
static gboolean
|
|
save_image (GFile *file,
|
|
PikaImage *image,
|
|
PikaDrawable *drawable,
|
|
GObject *config,
|
|
GError **error,
|
|
enum heif_compression_format compression,
|
|
PikaMetadata *metadata)
|
|
{
|
|
struct heif_image *h_image = NULL;
|
|
struct heif_context *context = heif_context_alloc ();
|
|
struct heif_encoder *encoder = NULL;
|
|
struct heif_encoding_options *encoder_options = NULL;
|
|
const struct heif_encoder_descriptor *encoder_descriptor;
|
|
const char *encoder_name;
|
|
struct heif_image_handle *handle = NULL;
|
|
struct heif_writer writer;
|
|
struct heif_error err;
|
|
GOutputStream *output;
|
|
GeglBuffer *buffer;
|
|
const gchar *encoding;
|
|
const Babl *format;
|
|
const Babl *space = NULL;
|
|
guint8 *data;
|
|
gint stride;
|
|
gint width;
|
|
gint height;
|
|
gboolean has_alpha;
|
|
gboolean out_linear = FALSE;
|
|
gboolean lossless;
|
|
gint quality;
|
|
gboolean save_profile;
|
|
gint save_bit_depth = 8;
|
|
#if LIBHEIF_HAVE_VERSION(1,10,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 *parameter_value;
|
|
struct heif_color_profile_nclx nclx_profile;
|
|
#endif
|
|
gboolean save_exif = FALSE;
|
|
gboolean save_xmp = FALSE;
|
|
|
|
if (!context)
|
|
{
|
|
g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
|
|
"cannot allocate heif_context");
|
|
return FALSE;
|
|
}
|
|
|
|
g_object_get (config,
|
|
"lossless", &lossless,
|
|
"quality", &quality,
|
|
#if LIBHEIF_HAVE_VERSION(1,10,0)
|
|
"pixel-format", &pixel_format,
|
|
#endif
|
|
#if LIBHEIF_HAVE_VERSION(1,8,0)
|
|
"save-bit-depth", &save_bit_depth,
|
|
"encoder-speed", &encoder_speed,
|
|
#endif
|
|
"save-color-profile", &save_profile,
|
|
"save-exif", &save_exif,
|
|
"save-xmp", &save_xmp,
|
|
NULL);
|
|
|
|
if (compression == heif_compression_HEVC)
|
|
{
|
|
if (heif_context_get_encoder_descriptors (context,
|
|
heif_compression_HEVC,
|
|
NULL,
|
|
&encoder_descriptor, 1) == 1)
|
|
{
|
|
encoder_name = heif_encoder_descriptor_get_id_name (encoder_descriptor);
|
|
}
|
|
else
|
|
{
|
|
g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
|
|
"Unable to find suitable HEIF encoder");
|
|
heif_context_free (context);
|
|
return FALSE;
|
|
}
|
|
}
|
|
else /* AV1 compression */
|
|
{
|
|
if (heif_context_get_encoder_descriptors (context,
|
|
compression,
|
|
"aom", /* we prefer aom rather than rav1e */
|
|
&encoder_descriptor, 1) == 1)
|
|
{
|
|
encoder_name = heif_encoder_descriptor_get_id_name (encoder_descriptor);
|
|
}
|
|
else if (heif_context_get_encoder_descriptors (context,
|
|
compression,
|
|
NULL,
|
|
&encoder_descriptor, 1) == 1)
|
|
{
|
|
encoder_name = heif_encoder_descriptor_get_id_name (encoder_descriptor);
|
|
}
|
|
else
|
|
{
|
|
g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
|
|
"Unable to find suitable AVIF encoder");
|
|
heif_context_free (context);
|
|
return FALSE;
|
|
}
|
|
}
|
|
|
|
pika_progress_init_printf (_("Exporting '%s' using %s encoder"),
|
|
pika_file_get_utf8_name (file), encoder_name);
|
|
|
|
width = pika_drawable_get_width (drawable);
|
|
height = pika_drawable_get_height (drawable);
|
|
|
|
has_alpha = pika_drawable_has_alpha (drawable);
|
|
|
|
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;
|
|
#if LIBHEIF_HAVE_VERSION(1,8,0)
|
|
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;
|
|
#endif
|
|
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;
|
|
}
|
|
|
|
if (err.code != 0)
|
|
{
|
|
g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
|
|
_("Encoding HEIF image failed: %s"),
|
|
err.message);
|
|
heif_context_free (context);
|
|
return FALSE;
|
|
}
|
|
|
|
#if LIBHEIF_HAVE_VERSION(1,4,0)
|
|
if (save_profile)
|
|
{
|
|
PikaColorProfile *profile = NULL;
|
|
const guint8 *icc_data;
|
|
gsize icc_length;
|
|
|
|
profile = pika_image_get_color_profile (image);
|
|
if (profile && pika_color_profile_is_linear (profile))
|
|
out_linear = TRUE;
|
|
|
|
if (! profile)
|
|
{
|
|
profile = pika_image_get_effective_color_profile (image);
|
|
|
|
if (pika_color_profile_is_linear (profile))
|
|
{
|
|
if (pika_image_get_precision (image) != PIKA_PRECISION_U8_LINEAR)
|
|
{
|
|
/* If stored data was linear, let's convert the profile. */
|
|
PikaColorProfile *saved_profile;
|
|
|
|
saved_profile = pika_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;
|
|
}
|
|
}
|
|
}
|
|
|
|
#if LIBHEIF_HAVE_VERSION(1,10,0)
|
|
if (pixel_format == HEIFPLUGIN_EXPORT_FORMAT_RGB)
|
|
{
|
|
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
|
|
|
|
icc_data = pika_color_profile_get_icc_profile (profile, &icc_length);
|
|
heif_image_set_raw_color_profile (h_image, "prof", icc_data, icc_length);
|
|
space = pika_color_profile_get_space (profile,
|
|
PIKA_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);
|
|
}
|
|
|
|
g_object_unref (profile);
|
|
}
|
|
else
|
|
{
|
|
#if LIBHEIF_HAVE_VERSION(1,8,0)
|
|
/* We save as sRGB */
|
|
|
|
nclx_profile.version = 1;
|
|
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;
|
|
|
|
#if LIBHEIF_HAVE_VERSION(1,10,0)
|
|
if (pixel_format == HEIFPLUGIN_EXPORT_FORMAT_RGB)
|
|
{
|
|
nclx_profile.matrix_coefficients = heif_matrix_coefficients_RGB_GBR;
|
|
}
|
|
#endif
|
|
|
|
heif_image_set_nclx_color_profile (h_image, &nclx_profile);
|
|
|
|
space = babl_space ("sRGB");
|
|
out_linear = FALSE;
|
|
#endif
|
|
}
|
|
#endif /* LIBHEIF_HAVE_VERSION(1,4,0) */
|
|
|
|
if (! space)
|
|
space = pika_drawable_get_format (drawable);
|
|
|
|
|
|
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;
|
|
|
|
if (out_linear)
|
|
encoding = "RGBA u16";
|
|
else
|
|
encoding = "R'G'B'A u16";
|
|
}
|
|
else /* no alpha */
|
|
{
|
|
rowentries = width * 3;
|
|
|
|
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 = pika_drawable_get_buffer (drawable);
|
|
|
|
gegl_buffer_get (buffer,
|
|
GEGL_RECTANGLE (0, 0, width, height),
|
|
1.0, format, data16, GEGL_AUTO_ROWSTRIDE, GEGL_ABYSS_NONE);
|
|
|
|
g_object_unref (buffer);
|
|
|
|
heif_image_add_plane (h_image, heif_channel_interleaved,
|
|
width, height, save_bit_depth);
|
|
|
|
data = heif_image_get_plane (h_image, heif_channel_interleaved, &stride);
|
|
|
|
switch (save_bit_depth)
|
|
{
|
|
case 10:
|
|
for (y = 0; y < height; y++)
|
|
{
|
|
dest16 = (uint16_t *) (y * stride + data);
|
|
for (x = 0; x < rowentries; x++)
|
|
{
|
|
tmp_pixelval = (int) ( ( (float) (*src16) / 65535.0f) * 1023.0f + 0.5f);
|
|
*dest16 = CLAMP (tmp_pixelval, 0, 1023);
|
|
dest16++;
|
|
src16++;
|
|
}
|
|
}
|
|
break;
|
|
case 12:
|
|
for (y = 0; y < height; y++)
|
|
{
|
|
dest16 = (uint16_t *) (y * stride + data);
|
|
for (x = 0; x < rowentries; x++)
|
|
{
|
|
tmp_pixelval = (int) ( ( (float) (*src16) / 65535.0f) * 4095.0f + 0.5f);
|
|
*dest16 = CLAMP (tmp_pixelval, 0, 4095);
|
|
dest16++;
|
|
src16++;
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
for (y = 0; y < height; y++)
|
|
{
|
|
dest16 = (uint16_t *) (y * stride + data);
|
|
for (x = 0; x < rowentries; x++)
|
|
{
|
|
*dest16 = *src16;
|
|
dest16++;
|
|
src16++;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
g_free (data16);
|
|
}
|
|
else /* save_bit_depth == 8 */
|
|
{
|
|
#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 = pika_drawable_get_buffer (drawable);
|
|
|
|
if (has_alpha)
|
|
{
|
|
if (out_linear)
|
|
encoding = "RGBA u8";
|
|
else
|
|
encoding = "R'G'B'A u8";
|
|
}
|
|
else
|
|
{
|
|
if (out_linear)
|
|
encoding = "RGB u8";
|
|
else
|
|
encoding = "R'G'B' u8";
|
|
}
|
|
format = babl_format_with_space (encoding, space);
|
|
|
|
gegl_buffer_get (buffer,
|
|
GEGL_RECTANGLE (0, 0, width, height),
|
|
1.0, format, data, stride, GEGL_ABYSS_NONE);
|
|
|
|
g_object_unref (buffer);
|
|
}
|
|
|
|
pika_progress_update (0.33);
|
|
|
|
/* encode to HEIF file */
|
|
err = heif_context_get_encoder (context,
|
|
encoder_descriptor,
|
|
&encoder);
|
|
|
|
if (err.code != 0)
|
|
{
|
|
g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
|
|
"Unable to get an encoder instance");
|
|
heif_image_release (h_image);
|
|
heif_context_free (context);
|
|
return FALSE;
|
|
}
|
|
|
|
/* workaround for a bug in libheif when heif_encoder_set_lossless is not working
|
|
(known problem with encoding via rav1e) */
|
|
if (lossless)
|
|
{
|
|
quality = 100;
|
|
}
|
|
|
|
heif_encoder_set_lossy_quality (encoder, quality);
|
|
heif_encoder_set_lossless (encoder, lossless);
|
|
/* heif_encoder_set_logging_level (encoder, logging_level); */
|
|
|
|
#if LIBHEIF_HAVE_VERSION(1,8,0)
|
|
#if LIBHEIF_HAVE_VERSION(1,10,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 = pika_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_strcmp0 (encoder_name, "aom") == 0) /* AOMedia AV1 encoder */
|
|
{
|
|
switch (encoder_speed)
|
|
{
|
|
case HEIFPLUGIN_ENCODER_SPEED_SLOW:
|
|
parameter_number = 1;
|
|
break;
|
|
case HEIFPLUGIN_ENCODER_SPEED_FASTER:
|
|
parameter_number = 6;
|
|
#if LIBHEIF_HAVE_VERSION(1,10,0)
|
|
err = heif_encoder_set_parameter_boolean (encoder, "realtime", 1);
|
|
if (err.code != 0)
|
|
{
|
|
g_printerr ("Failed to set realtime=1 for %s encoder: %s", encoder_name, err.message);
|
|
}
|
|
#endif
|
|
break;
|
|
default: /* HEIFPLUGIN_ENCODER_SPEED_BALANCED */
|
|
parameter_number = 5;
|
|
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_strcmp0 (encoder_name, "rav1e") == 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
|
|
|
|
#if LIBHEIF_HAVE_VERSION(1,10,0)
|
|
if (pixel_format == HEIFPLUGIN_EXPORT_FORMAT_RGB)
|
|
{
|
|
encoder_options = heif_encoding_options_alloc ();
|
|
encoder_options->save_two_colr_boxes_when_ICC_and_nclx_available = 1;
|
|
}
|
|
#endif
|
|
|
|
err = heif_context_encode_image (context,
|
|
h_image,
|
|
encoder,
|
|
encoder_options,
|
|
&handle);
|
|
|
|
if (encoder_options)
|
|
{
|
|
heif_encoding_options_free (encoder_options);
|
|
}
|
|
|
|
if (err.code != 0)
|
|
{
|
|
g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
|
|
_("Encoding HEIF image failed: %s"),
|
|
err.message);
|
|
heif_encoder_release (encoder);
|
|
heif_image_release (h_image);
|
|
heif_context_free (context);
|
|
return FALSE;
|
|
}
|
|
|
|
if (metadata && (save_exif || save_xmp))
|
|
{
|
|
PikaMetadata *filtered_metadata;
|
|
PikaMetadataSaveFlags metadata_flags = 0;
|
|
|
|
if (save_exif)
|
|
{
|
|
metadata_flags |= PIKA_METADATA_SAVE_EXIF;
|
|
}
|
|
|
|
if (save_xmp)
|
|
{
|
|
metadata_flags |= PIKA_METADATA_SAVE_XMP;
|
|
}
|
|
|
|
filtered_metadata = pika_image_metadata_save_filter (image, "image/heif", metadata, metadata_flags, NULL, error);
|
|
if(! filtered_metadata)
|
|
{
|
|
if (error && *error)
|
|
{
|
|
g_printerr ("%s: error filtering metadata: %s",
|
|
G_STRFUNC, (*error)->message);
|
|
g_clear_error (error);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
GExiv2Metadata *filtered_g2metadata = GEXIV2_METADATA (filtered_metadata);
|
|
|
|
/* EXIF metadata */
|
|
if (save_exif && gexiv2_metadata_has_exif (filtered_g2metadata))
|
|
{
|
|
GBytes *raw_exif_data;
|
|
|
|
raw_exif_data = gexiv2_metadata_get_exif_data (filtered_g2metadata, 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);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* XMP metadata */
|
|
if (save_xmp && gexiv2_metadata_has_xmp (filtered_g2metadata))
|
|
{
|
|
gchar *xmp_packet;
|
|
|
|
xmp_packet = gexiv2_metadata_try_generate_xmp_packet (filtered_g2metadata, GEXIV2_USE_COMPACT_FORMAT | GEXIV2_OMIT_ALL_FORMATTING, 0, NULL);
|
|
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 (filtered_metadata);
|
|
}
|
|
}
|
|
|
|
heif_image_handle_release (handle);
|
|
|
|
pika_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)
|
|
{
|
|
heif_encoder_release (encoder);
|
|
heif_image_release (h_image);
|
|
heif_context_free (context);
|
|
return FALSE;
|
|
}
|
|
|
|
err = heif_context_write (context, &writer, output);
|
|
|
|
if (err.code != 0)
|
|
{
|
|
GCancellable *cancellable = g_cancellable_new ();
|
|
|
|
g_cancellable_cancel (cancellable);
|
|
g_output_stream_close (output, cancellable, NULL);
|
|
g_object_unref (cancellable);
|
|
|
|
g_set_error (error, G_FILE_ERROR, G_FILE_ERROR_FAILED,
|
|
_("Writing HEIF image failed: %s"),
|
|
err.message);
|
|
|
|
heif_encoder_release (encoder);
|
|
heif_image_release (h_image);
|
|
heif_context_free (context);
|
|
return FALSE;
|
|
}
|
|
|
|
g_object_unref (output);
|
|
|
|
heif_encoder_release (encoder);
|
|
heif_image_release (h_image);
|
|
heif_context_free (context);
|
|
|
|
pika_progress_update (1.0);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
/* the load dialog */
|
|
|
|
#define MAX_THUMBNAIL_SIZE 320
|
|
|
|
typedef struct _HeifImage HeifImage;
|
|
|
|
struct _HeifImage
|
|
{
|
|
uint32_t ID;
|
|
gchar caption[100];
|
|
struct heif_image *thumbnail;
|
|
gint width;
|
|
gint height;
|
|
};
|
|
|
|
static gboolean
|
|
load_thumbnails (struct heif_context *heif,
|
|
HeifImage *images)
|
|
{
|
|
guint32 *IDs;
|
|
gint n_images;
|
|
gint i;
|
|
|
|
n_images = heif_context_get_number_of_top_level_images (heif);
|
|
|
|
/* get list of all (top level) image IDs */
|
|
|
|
IDs = g_alloca (n_images * sizeof (guint32));
|
|
|
|
heif_context_get_list_of_top_level_image_IDs (heif, IDs, n_images);
|
|
|
|
|
|
/* Load a thumbnail for each image. */
|
|
|
|
for (i = 0; i < n_images; i++)
|
|
{
|
|
struct heif_image_handle *handle = NULL;
|
|
struct heif_error err;
|
|
gint width;
|
|
gint height;
|
|
struct heif_image_handle *thumbnail_handle = NULL;
|
|
heif_item_id thumbnail_ID;
|
|
gint n_thumbnails;
|
|
struct heif_image *thumbnail_img = NULL;
|
|
gint thumbnail_width;
|
|
gint thumbnail_height;
|
|
|
|
images[i].ID = IDs[i];
|
|
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)
|
|
{
|
|
pika_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)
|
|
{
|
|
pika_message (err.message);
|
|
continue;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
err = heif_context_get_image_handle (heif, IDs[i], &thumbnail_handle);
|
|
if (err.code)
|
|
{
|
|
pika_message (err.message);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
/* decode the thumbnail image */
|
|
|
|
err = heif_decode_image (thumbnail_handle,
|
|
&thumbnail_img,
|
|
heif_colorspace_RGB,
|
|
heif_chroma_interleaved_RGB,
|
|
NULL);
|
|
if (err.code)
|
|
{
|
|
pika_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)
|
|
{
|
|
pika_message (err.message);
|
|
continue;
|
|
}
|
|
|
|
/* release the old image and only keep the scaled down version */
|
|
|
|
heif_image_release (thumbnail_img);
|
|
thumbnail_img = scaled_img;
|
|
|
|
thumbnail_width = new_width;
|
|
thumbnail_height = new_height;
|
|
}
|
|
|
|
heif_image_handle_release (thumbnail_handle);
|
|
heif_image_handle_release (handle);
|
|
|
|
/* remember the HEIF thumbnail image (we need it for the GdkPixbuf) */
|
|
|
|
images[i].thumbnail = thumbnail_img;
|
|
|
|
images[i].width = thumbnail_width;
|
|
images[i].height = thumbnail_height;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
static void
|
|
load_dialog_item_activated (GtkIconView *icon_view,
|
|
GtkTreePath *path,
|
|
GtkDialog *dialog)
|
|
{
|
|
gtk_dialog_response (dialog, GTK_RESPONSE_OK);
|
|
}
|
|
|
|
static gboolean
|
|
load_dialog (struct heif_context *heif,
|
|
uint32_t *selected_image)
|
|
{
|
|
GtkWidget *dialog;
|
|
GtkWidget *main_vbox;
|
|
GtkWidget *frame;
|
|
HeifImage *heif_images;
|
|
GtkListStore *list_store;
|
|
GtkTreeIter iter;
|
|
GtkWidget *scrolled_window;
|
|
GtkWidget *icon_view;
|
|
GtkCellRenderer *renderer;
|
|
gint n_images;
|
|
gint i;
|
|
gint selected_idx = -1;
|
|
gboolean run = FALSE;
|
|
|
|
n_images = heif_context_get_number_of_top_level_images (heif);
|
|
|
|
heif_images = g_alloca (n_images * sizeof (HeifImage));
|
|
|
|
if (! load_thumbnails (heif, heif_images))
|
|
return FALSE;
|
|
|
|
dialog = pika_dialog_new (_("Load HEIF Image"), PLUG_IN_BINARY,
|
|
NULL, 0,
|
|
pika_standard_help_func, LOAD_PROC,
|
|
|
|
_("_Cancel"), GTK_RESPONSE_CANCEL,
|
|
_("_OK"), GTK_RESPONSE_OK,
|
|
|
|
NULL);
|
|
|
|
main_vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12);
|
|
gtk_container_set_border_width (GTK_CONTAINER (main_vbox), 12);
|
|
gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))),
|
|
main_vbox, TRUE, TRUE, 0);
|
|
|
|
frame = pika_frame_new (_("Select Image"));
|
|
gtk_box_pack_start (GTK_BOX (main_vbox), frame, TRUE, TRUE, 0);
|
|
gtk_widget_show (frame);
|
|
|
|
/* prepare list store with all thumbnails and caption */
|
|
|
|
list_store = gtk_list_store_new (2, G_TYPE_STRING, GDK_TYPE_PIXBUF);
|
|
|
|
for (i = 0; i < n_images; i++)
|
|
{
|
|
GdkPixbuf *pixbuf;
|
|
const guint8 *data;
|
|
gint stride;
|
|
|
|
gtk_list_store_append (list_store, &iter);
|
|
gtk_list_store_set (list_store, &iter, 0, heif_images[i].caption, -1);
|
|
|
|
data = heif_image_get_plane_readonly (heif_images[i].thumbnail,
|
|
heif_channel_interleaved,
|
|
&stride);
|
|
|
|
pixbuf = gdk_pixbuf_new_from_data (data,
|
|
GDK_COLORSPACE_RGB,
|
|
FALSE,
|
|
8,
|
|
heif_images[i].width,
|
|
heif_images[i].height,
|
|
stride,
|
|
NULL,
|
|
NULL);
|
|
|
|
gtk_list_store_set (list_store, &iter, 1, pixbuf, -1);
|
|
}
|
|
|
|
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);
|
|
|
|
icon_view = gtk_icon_view_new_with_model (GTK_TREE_MODEL (list_store));
|
|
gtk_container_add (GTK_CONTAINER (scrolled_window), icon_view);
|
|
gtk_widget_show (icon_view);
|
|
|
|
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);
|
|
|
|
/* 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 = (pika_dialog_run (PIKA_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;
|
|
}
|
|
|
|
|
|
/* the save dialog */
|
|
|
|
gboolean
|
|
save_dialog (PikaProcedure *procedure,
|
|
GObject *config,
|
|
PikaImage *image)
|
|
{
|
|
GtkWidget *dialog;
|
|
GtkWidget *quality_scale;
|
|
#if LIBHEIF_HAVE_VERSION(1, 10, 0)
|
|
GtkListStore *store_pixelformats;
|
|
#endif
|
|
#if LIBHEIF_HAVE_VERSION(1, 8, 0)
|
|
GtkListStore *store_bitdepths;
|
|
GtkListStore *store_speeds;
|
|
#endif
|
|
gboolean run;
|
|
|
|
dialog = pika_save_procedure_dialog_new (PIKA_SAVE_PROCEDURE (procedure),
|
|
PIKA_PROCEDURE_CONFIG (config),
|
|
image);
|
|
|
|
pika_procedure_dialog_get_widget (PIKA_PROCEDURE_DIALOG (dialog),
|
|
"lossless", GTK_TYPE_CHECK_BUTTON);
|
|
|
|
quality_scale = pika_procedure_dialog_get_widget (PIKA_PROCEDURE_DIALOG (dialog),
|
|
"quality",
|
|
PIKA_TYPE_SCALE_ENTRY);
|
|
|
|
g_object_bind_property (config, "lossless",
|
|
quality_scale, "sensitive",
|
|
G_BINDING_SYNC_CREATE |
|
|
G_BINDING_INVERT_BOOLEAN);
|
|
|
|
#if LIBHEIF_HAVE_VERSION(1, 10, 0)
|
|
store_pixelformats = pika_int_store_new (_("RGB"), HEIFPLUGIN_EXPORT_FORMAT_RGB,
|
|
_("YUV444"), HEIFPLUGIN_EXPORT_FORMAT_YUV444,
|
|
_("YUV420"), HEIFPLUGIN_EXPORT_FORMAT_YUV420,
|
|
NULL);
|
|
|
|
pika_procedure_dialog_get_int_combo (PIKA_PROCEDURE_DIALOG (dialog),
|
|
"pixel-format", PIKA_INT_STORE (store_pixelformats));
|
|
#endif
|
|
|
|
#if LIBHEIF_HAVE_VERSION(1, 8, 0)
|
|
store_bitdepths = pika_int_store_new (_("8 bit/channel"), 8,
|
|
_("10 bit/channel"), 10,
|
|
_("12 bit/channel"), 12,
|
|
NULL);
|
|
|
|
pika_procedure_dialog_get_int_combo (PIKA_PROCEDURE_DIALOG (dialog),
|
|
"save-bit-depth", PIKA_INT_STORE (store_bitdepths));
|
|
|
|
store_speeds = pika_int_store_new (_("Slow"), HEIFPLUGIN_ENCODER_SPEED_SLOW,
|
|
_("Balanced"), HEIFPLUGIN_ENCODER_SPEED_BALANCED,
|
|
_("Fast"), HEIFPLUGIN_ENCODER_SPEED_FASTER,
|
|
NULL);
|
|
|
|
pika_procedure_dialog_get_int_combo (PIKA_PROCEDURE_DIALOG (dialog),
|
|
"encoder-speed", PIKA_INT_STORE (store_speeds));
|
|
#endif
|
|
|
|
pika_procedure_dialog_fill (PIKA_PROCEDURE_DIALOG (dialog),
|
|
"lossless", "quality",
|
|
#if LIBHEIF_HAVE_VERSION(1, 10, 0)
|
|
"pixel-format",
|
|
#endif
|
|
#if LIBHEIF_HAVE_VERSION(1, 8, 0)
|
|
"save-bit-depth", "encoder-speed",
|
|
#endif
|
|
#if LIBHEIF_HAVE_VERSION(1, 4, 0)
|
|
"save-color-profile",
|
|
#endif
|
|
"save-exif", "save-xmp", NULL);
|
|
|
|
run = pika_procedure_dialog_run (PIKA_PROCEDURE_DIALOG (dialog));
|
|
|
|
gtk_widget_destroy (dialog);
|
|
|
|
return run;
|
|
}
|