Commit 8219092a authored by Michael Natterer's avatar Michael Natterer 😴

libgimp: start integrating image export with GimpProcedureConfig

Add gimp_procedure_config_begin_export() and end_export() which
are wrappers around begin_run() and end_run() and additionally
pretty much completely manage GimpMetadata handling.

A GimpProcedureConfig can provide boolean properties "save-exif",
"save-xmp" etc. in order to have them automatically managed by
begin_export() and end_export(). This also restores the feature of
overriding the procedure's saved default values with export
preferences, but not the values from the last export, like it
used to be in 2.10.

Move gimp_image_metadata_save_prepare() and save_finish() (which are
now completely handled by GimpProcedureConfig) from libgimpui to
libgimp, but keep their declarations in the libgimpui header. Not
perfect, but not finished either.

Also fix gimp_image_metadata_save_prepare() to set the affected
GimpMetadataSaveFlags to 0 when the image has no metadata at all.
parent 9b328167
......@@ -152,6 +152,7 @@ libgimp_introspectable = \
../libgimp/gimpgradientselect.c \
../libgimp/gimpimage.c \
../libgimp/gimpimagecolorprofile.c \
../libgimp/gimpimagemetadata-save.c \
../libgimp/gimpimageprocedure.c \
../libgimp/gimpitem.c \
../libgimp/gimplayer.c \
......
......@@ -438,6 +438,8 @@ EXPORTS
gimp_image_merge_down
gimp_image_merge_layer_group
gimp_image_merge_visible_layers
gimp_image_metadata_save_finish
gimp_image_metadata_save_prepare
gimp_image_new
gimp_image_new_with_precision
gimp_image_pick_color
......@@ -682,7 +684,9 @@ EXPORTS
gimp_procedure_add_menu_path
gimp_procedure_add_return_value
gimp_procedure_add_return_value_from_property
gimp_procedure_config_begin_export
gimp_procedure_config_begin_run
gimp_procedure_config_end_export
gimp_procedure_config_end_run
gimp_procedure_config_get_procedure
gimp_procedure_config_get_type
......
/* LIBGIMP - The GIMP Library
* Copyright (C) 1995-2000 Peter Mattis and Spencer Kimball
*
* gimpimagemetadata-save.c
*
* This library is free software: you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library. If not, see
* <https://www.gnu.org/licenses/>.
*/
#include "config.h"
#include <string.h>
#include <sys/time.h>
#include <gexiv2/gexiv2.h>
#include "gimp.h"
#include "gimpimagemetadata.h"
#include "libgimp-intl.h"
typedef struct
{
gchar *tag;
gint type;
} XmpStructs;
/* public functions */
/**
* gimp_image_metadata_save_prepare:
* @image: The image
* @mime_type: The saved file's mime-type
* @suggested_flags: Suggested default values for the @flags passed to
* gimp_image_metadata_save_finish()
*
* Gets the image metadata for saving it using
* gimp_image_metadata_save_finish().
*
* The @suggested_flags are determined from what kind of metadata
* (Exif, XMP, ...) is actually present in the image and the preferences
* for metadata exporting.
* The calling application may still update @available_flags, for
* instance to follow the settings from a previous export in the same
* session, or a previous export of the same image. But it should not
* override the preferences without a good reason since it is a data
* leak.
*
* The suggested value for GIMP_METADATA_SAVE_THUMBNAIL is determined by
* whether there was a thumbnail in the previously imported image.
*
* Returns: (transfer full): The image's metadata, prepared for saving.
*
* Since: 2.10
*/
GimpMetadata *
gimp_image_metadata_save_prepare (GimpImage *image,
const gchar *mime_type,
GimpMetadataSaveFlags *suggested_flags)
{
GimpMetadata *metadata;
g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
g_return_val_if_fail (mime_type != NULL, NULL);
g_return_val_if_fail (suggested_flags != NULL, NULL);
*suggested_flags = GIMP_METADATA_SAVE_ALL;
metadata = gimp_image_get_metadata (image);
if (metadata)
{
GDateTime *datetime;
const GimpParasite *comment_parasite;
const gchar *comment = NULL;
gint image_width;
gint image_height;
gdouble xres;
gdouble yres;
gchar buffer[32];
GExiv2Metadata *g2metadata = GEXIV2_METADATA (metadata);
image_width = gimp_image_width (image);
image_height = gimp_image_height (image);
datetime = g_date_time_new_now_local ();
comment_parasite = gimp_image_get_parasite (image, "gimp-comment");
if (comment_parasite)
comment = gimp_parasite_data (comment_parasite);
/* Exif */
if (! gimp_export_exif () ||
! gexiv2_metadata_has_exif (g2metadata))
*suggested_flags &= ~GIMP_METADATA_SAVE_EXIF;
if (comment)
{
gexiv2_metadata_set_tag_string (g2metadata,
"Exif.Photo.UserComment",
comment);
gexiv2_metadata_set_tag_string (g2metadata,
"Exif.Image.ImageDescription",
comment);
}
g_snprintf (buffer, sizeof (buffer),
"%d:%02d:%02d %02d:%02d:%02d",
g_date_time_get_year (datetime),
g_date_time_get_month (datetime),
g_date_time_get_day_of_month (datetime),
g_date_time_get_hour (datetime),
g_date_time_get_minute (datetime),
g_date_time_get_second (datetime));
gexiv2_metadata_set_tag_string (g2metadata,
"Exif.Image.DateTime",
buffer);
gexiv2_metadata_set_tag_string (g2metadata,
"Exif.Image.Software",
PACKAGE_STRING);
gimp_metadata_set_pixel_size (metadata,
image_width, image_height);
gimp_image_get_resolution (image, &xres, &yres);
gimp_metadata_set_resolution (metadata, xres, yres,
gimp_image_get_unit (image));
/* XMP */
if (! gimp_export_xmp () ||
! gexiv2_metadata_has_xmp (g2metadata))
*suggested_flags &= ~GIMP_METADATA_SAVE_XMP;
gexiv2_metadata_set_tag_string (g2metadata,
"Xmp.dc.Format",
mime_type);
if (! g_strcmp0 (mime_type, "image/tiff"))
{
/* TIFF specific XMP data */
g_snprintf (buffer, sizeof (buffer), "%d", image_width);
gexiv2_metadata_set_tag_string (g2metadata,
"Xmp.tiff.ImageWidth",
buffer);
g_snprintf (buffer, sizeof (buffer), "%d", image_height);
gexiv2_metadata_set_tag_string (g2metadata,
"Xmp.tiff.ImageLength",
buffer);
g_snprintf (buffer, sizeof (buffer),
"%d:%02d:%02d %02d:%02d:%02d",
g_date_time_get_year (datetime),
g_date_time_get_month (datetime),
g_date_time_get_day_of_month (datetime),
g_date_time_get_hour (datetime),
g_date_time_get_minute (datetime),
g_date_time_get_second (datetime));
gexiv2_metadata_set_tag_string (g2metadata,
"Xmp.tiff.DateTime",
buffer);
}
/* IPTC */
if (! gimp_export_iptc () ||
! gexiv2_metadata_has_iptc (g2metadata))
*suggested_flags &= ~GIMP_METADATA_SAVE_IPTC;
g_snprintf (buffer, sizeof (buffer),
"%d-%d-%d",
g_date_time_get_year (datetime),
g_date_time_get_month (datetime),
g_date_time_get_day_of_month (datetime));
gexiv2_metadata_set_tag_string (g2metadata,
"Iptc.Application2.DateCreated",
buffer);
g_snprintf (buffer, sizeof (buffer),
"%02d:%02d:%02d-%02d:%02d",
g_date_time_get_hour (datetime),
g_date_time_get_minute (datetime),
g_date_time_get_second (datetime),
g_date_time_get_hour (datetime),
g_date_time_get_minute (datetime));
gexiv2_metadata_set_tag_string (g2metadata,
"Iptc.Application2.TimeCreated",
buffer);
g_date_time_unref (datetime);
}
else
{
/* At least initialize the returned flags with preferences defaults */
if (! gimp_export_exif ())
*suggested_flags &= ~GIMP_METADATA_SAVE_EXIF;
if (! gimp_export_xmp ())
*suggested_flags &= ~GIMP_METADATA_SAVE_XMP;
if (! gimp_export_iptc ())
*suggested_flags &= ~GIMP_METADATA_SAVE_IPTC;
}
/* Thumbnail */
if (FALSE /* FIXME if (original image had a thumbnail) */)
*suggested_flags &= ~GIMP_METADATA_SAVE_THUMBNAIL;
/* Color profile */
if (! gimp_export_color_profile ())
*suggested_flags &= ~GIMP_METADATA_SAVE_COLOR_PROFILE;
return metadata;
}
static void
gimp_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);
}
}
}
/**
* gimp_image_metadata_save_finish:
* @image: The image
* @mime_type: The saved file's mime-type
* @metadata: The metadata to set on the image
* @flags: Flags to specify what of the metadata to save
* @file: The file to load the metadata from
* @error: Return location for error message
*
* Saves the @metadata retrieved from the image with
* gimp_image_metadata_save_prepare() to @file, taking into account
* the passed @flags.
*
* Returns: Whether the save was successful.
*
* Since: 2.10
*/
gboolean
gimp_image_metadata_save_finish (GimpImage *image,
const gchar *mime_type,
GimpMetadata *metadata,
GimpMetadataSaveFlags flags,
GFile *file,
GError **error)
{
GimpMetadata *new_metadata;
GExiv2Metadata *new_g2metadata;
gboolean support_exif;
gboolean support_xmp;
gboolean support_iptc;
gboolean success = FALSE;
gint i;
g_return_val_if_fail (GIMP_IS_IMAGE (image), FALSE);
g_return_val_if_fail (mime_type != NULL, FALSE);
g_return_val_if_fail (GEXIV2_IS_METADATA (metadata), FALSE);
g_return_val_if_fail (G_IS_FILE (file), FALSE);
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
if (! (flags & (GIMP_METADATA_SAVE_EXIF |
GIMP_METADATA_SAVE_XMP |
GIMP_METADATA_SAVE_IPTC |
GIMP_METADATA_SAVE_THUMBNAIL)))
return TRUE;
/* read metadata from saved file */
new_metadata = gimp_metadata_load_from_file (file, error);
new_g2metadata = GEXIV2_METADATA (new_metadata);
if (! new_metadata)
return FALSE;
support_exif = gexiv2_metadata_get_supports_exif (new_g2metadata);
support_xmp = gexiv2_metadata_get_supports_xmp (new_g2metadata);
support_iptc = gexiv2_metadata_get_supports_iptc (new_g2metadata);
if ((flags & GIMP_METADATA_SAVE_EXIF) && support_exif)
{
gchar **exif_data = gexiv2_metadata_get_exif_tags (GEXIV2_METADATA (metadata));
for (i = 0; exif_data[i] != NULL; i++)
{
if (! gexiv2_metadata_has_tag (new_g2metadata, exif_data[i]) &&
gimp_metadata_is_tag_supported (exif_data[i], mime_type))
{
gimp_image_metadata_copy_tag (GEXIV2_METADATA (metadata),
new_g2metadata,
exif_data[i]);
}
}
g_strfreev (exif_data);
}
if ((flags & GIMP_METADATA_SAVE_XMP) && support_xmp)
{
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];
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",
N_("GIMP 2.10"));
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 < 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], mime_type))
{
gimp_image_metadata_copy_tag (GEXIV2_METADATA (metadata),
new_g2metadata,
xmp_data[i]);
}
}
g_strfreev (xmp_data);
}
if ((flags & GIMP_METADATA_SAVE_IPTC) && support_iptc)
{
gchar **iptc_data = gexiv2_metadata_get_iptc_tags (GEXIV2_METADATA (metadata));
for (i = 0; iptc_data[i] != NULL; i++)
{
if (! gexiv2_metadata_has_tag (new_g2metadata, iptc_data[i]) &&
gimp_metadata_is_tag_supported (iptc_data[i], mime_type))
{
gimp_image_metadata_copy_tag (GEXIV2_METADATA (metadata),
new_g2metadata,
iptc_data[i]);
}
}
g_strfreev (iptc_data);
}
if (flags & GIMP_METADATA_SAVE_THUMBNAIL)
{
GdkPixbuf *thumb_pixbuf;
gchar *thumb_buffer;
gint image_width;
gint image_height;
gsize count;
gint thumbw;
gint thumbh;
#define EXIF_THUMBNAIL_SIZE 256
image_width = gimp_image_width (image);
image_height = gimp_image_height (image);
if (image_width > image_height)
{
thumbw = EXIF_THUMBNAIL_SIZE;
thumbh = EXIF_THUMBNAIL_SIZE * image_height / image_width;
}
else
{
thumbh = EXIF_THUMBNAIL_SIZE;
thumbw = EXIF_THUMBNAIL_SIZE * image_width / image_height;
}
thumb_pixbuf = gimp_image_get_thumbnail (image, thumbw, thumbh,
GIMP_PIXBUF_KEEP_ALPHA);
if (gdk_pixbuf_save_to_buffer (thumb_pixbuf, &thumb_buffer, &count,
"jpeg", NULL,
"quality", "75",
NULL))
{
gchar buffer[32];
gexiv2_metadata_set_exif_thumbnail_from_buffer (new_g2metadata,
(guchar *) thumb_buffer,
count);
g_snprintf (buffer, sizeof (buffer), "%d", thumbw);
gexiv2_metadata_set_tag_string (new_g2metadata,
"Exif.Thumbnail.ImageWidth",
buffer);
g_snprintf (buffer, sizeof (buffer), "%d", thumbh);
gexiv2_metadata_set_tag_string (new_g2metadata,
"Exif.Thumbnail.ImageLength",
buffer);
gexiv2_metadata_set_tag_string (new_g2metadata,
"Exif.Thumbnail.BitsPerSample",
"8 8 8");
gexiv2_metadata_set_tag_string (new_g2metadata,
"Exif.Thumbnail.SamplesPerPixel",
"3");
gexiv2_metadata_set_tag_string (new_g2metadata,
"Exif.Thumbnail.PhotometricInterpretation",
"6");
g_free (thumb_buffer);
}
g_object_unref (thumb_pixbuf);
}
if (flags & GIMP_METADATA_SAVE_COLOR_PROFILE)
{
/* nothing to do, but if we ever need to modify metadata based
* on the exported color profile, this is probably the place to
* add it
*/
}
success = gimp_metadata_save_to_file (new_metadata, file, error);
g_object_unref (new_metadata);
return success;
}
......@@ -198,467 +198,6 @@ gimp_image_metadata_load_finish (GimpImage *image,
gimp_image_set_metadata (image, metadata);
}
/**
* gimp_image_metadata_save_prepare:
* @image: The image
* @mime_type: The saved file's mime-type
* @suggested_flags: Suggested default values for the @flags passed to
* gimp_image_metadata_save_finish()
*
* Gets the image metadata for saving it using
* gimp_image_metadata_save_finish().
*
* The @suggested_flags are determined from what kind of metadata
* (Exif, XMP, ...) is actually present in the image and the preferences
* for metadata exporting.
* The calling application may still update @available_flags, for
* instance to follow the settings from a previous export in the same
* session, or a previous export of the same image. But it should not
* override the preferences without a good reason since it is a data
* leak.
*
* The suggested value for GIMP_METADATA_SAVE_THUMBNAIL is determined by
* whether there was a thumbnail in the previously imported image.
*
* Returns: (transfer full): The image's metadata, prepared for saving.
*
* Since: 2.10
*/
GimpMetadata *
gimp_image_metadata_save_prepare (GimpImage *image,
const gchar *mime_type,
GimpMetadataSaveFlags *suggested_flags)
{
GimpMetadata *metadata;
g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
g_return_val_if_fail (mime_type != NULL, NULL);
g_return_val_if_fail (suggested_flags != NULL, NULL);
*suggested_flags = GIMP_METADATA_SAVE_ALL;
metadata = gimp_image_get_metadata (image);
if (metadata)
{
GDateTime *datetime;
const GimpParasite *comment_parasite;
const gchar *comment = NULL;
gint image_width;
gint image_height;
gdouble xres;
gdouble yres;
gchar buffer[32];
GExiv2Metadata *g2metadata = GEXIV2_METADATA (metadata);
image_width = gimp_image_width (image);
image_height = gimp_image_height (image);
datetime = g_date_time_new_now_local ();
comment_parasite = gimp_image_get_parasite (image, "gimp-comment");
if (comment_parasite)
comment = gimp_parasite_data (comment_parasite);
/* Exif */
if (! gimp_export_exif () ||
! gexiv2_metadata_has_exif (g2metadata))
*suggested_flags &= ~GIMP_METADATA_SAVE_EXIF;
if (comment)
{
gexiv2_metadata_set_tag_string (g2metadata,
"Exif.Photo.UserComment",
comment);
gexiv2_metadata_set_tag_string (g2metadata,
"Exif.Image.ImageDescription",
comment);
}
g_snprintf (buffer, sizeof (buffer),
"%d:%02d:%02d %02d:%02d:%02d",
g_date_time_get_year (datetime),
g_date_time_get_month (datetime),
g_date_time_get_day_of_month (datetime),
g_date_time_get_hour (datetime),
g_date_time_get_minute (datetime),
g_date_time_get_second (datetime));
gexiv2_metadata_set_tag_string (g2metadata,
"Exif.Image.DateTime",
buffer);
gexiv2_metadata_set_tag_string (g2metadata,
"Exif.Image.Software",
PACKAGE_STRING);
gimp_metadata_set_pixel_size (metadata,
image_width, image_height);
gimp_image_get_resolution (image, &xres, &yres);
gimp_metadata_set_resolution (metadata, xres, yres,
gimp_image_get_unit (image));
/* XMP */
if (! gimp_export_xmp () ||
! gexiv2_metadata_has_xmp (g2metadata))
*suggested_flags &= ~GIMP_METADATA_SAVE_XMP;
gexiv2_metadata_set_tag_string (g2metadata,
"Xmp.dc.Format",
mime_type);
if (! g_strcmp0 (mime_type, "image/tiff"))
{
/* TIFF specific XMP data */
g_snprintf (buffer, sizeof (buffer), "%d", image_width);
gexiv2_metadata_set_tag_string (g2metadata,
"Xmp.tiff.ImageWidth",
buffer);
g_snprintf (buffer, sizeof (buffer), "%d", image_height);
gexiv2_metadata_set_tag_string (g2metadata,
"Xmp.tiff.ImageLength",
buffer);
g_snprintf (buffer, sizeof (buffer),
"%d:%02d:%02d %02d:%02d:%02d",
g_date_time_get_year (datetime),
g_date_time_get_month (datetime),
g_date_time_get_day_of_month (datetime),
g_date_time_get_hour (datetime),
g_date_time_get_minute (datetime),
g_date_time_get_second (datetime));
gexiv2_metadata_set_tag_string (g2metadata,
"Xmp.tiff.DateTime",
buffer);
}
/* IPTC */
if (! gimp_export_iptc () ||
! gexiv2_metadata_has_iptc (g2metadata))
*suggested_flags &= ~GIMP_METADATA_SAVE_IPTC;
g_snprintf (buffer, sizeof (buffer),
"%d-%d-%d",
g_date_time_get_year (datetime),
g_date_time_get_month (datetime),
g_date_time_get_day_of_month (datetime));
gexiv2_metadata_set_tag_string (g2metadata,
"Iptc.Application2.DateCreated",
buffer);
g_snprintf (buffer, sizeof (buffer),
"%02d:%02d:%02d-%02d:%02d",
g_date_time_get_hour (datetime),
g_date_time_get_minute (datetime),
g_date_time_get_second (datetime),
g_date_time_get_hour (datetime),
g_date_time_get_minute (datetime));
gexiv2_metadata_set_tag_string (g2metadata,
"Iptc.Application2.TimeCreated",
buffer);
g_date_time_unref (datetime);
}
/* Thumbnail */
if (FALSE /* FIXME if (original image had a thumbnail) */)
*suggested_flags &= ~GIMP_METADATA_SAVE_THUMBNAIL;
/* Color profile */
if (! gimp_export_color_profile ())
*suggested_flags &= ~GIMP_METADATA_SAVE_COLOR_PROFILE;
return metadata;
}
static void
gimp_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);