mirror of
https://gitlab.gnome.org/GNOME/gimp.git
synced 2025-07-03 09:23:24 +00:00

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.
515 lines
18 KiB
C
515 lines
18 KiB
C
/* 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;
|
|
}
|