diff --git a/app/Makefile.am b/app/Makefile.am index e559433333..80eaac07b5 100644 --- a/app/Makefile.am +++ b/app/Makefile.am @@ -177,6 +177,7 @@ gimpconsoleldadd = \ $(CAIRO_LIBS) \ $(GEGL_LIBS) \ $(GLIB_LIBS) \ + $(GEXIV2_LIBS) \ $(INTLLIBS) \ $(RT_LIBS) diff --git a/app/config/Makefile.am b/app/config/Makefile.am index cc3fe41794..132fb26614 100644 --- a/app/config/Makefile.am +++ b/app/config/Makefile.am @@ -110,7 +110,8 @@ test_config_LDADD = \ $(PANGOCAIRO_LIBS) \ $(GDK_PIXBUF_LIBS) \ $(GEGL_LIBS) \ - $(GLIB_LIBS) + $(GIO_LIBS) \ + $(GEXIV2_LIBS) CLEANFILES = $(EXTRA_PROGRAMS) foorc diff --git a/app/config/test-config.c b/app/config/test-config.c index 4f06e96db5..da0aa9b709 100644 --- a/app/config/test-config.c +++ b/app/config/test-config.c @@ -23,7 +23,7 @@ #include "stdlib.h" #include "string.h" -#include +#include #include "libgimpbase/gimpbase.h" #include "libgimpbase/gimpbase-private.h" diff --git a/app/core/Makefile.am b/app/core/Makefile.am index 764a798642..cd94de7398 100644 --- a/app/core/Makefile.am +++ b/app/core/Makefile.am @@ -10,6 +10,7 @@ AM_CPPFLAGS = \ $(CAIRO_CFLAGS) \ $(GEGL_CFLAGS) \ $(GDK_PIXBUF_CFLAGS) \ + $(GEXIV2_CFLAGS) \ -I$(includedir) noinst_LIBRARIES = libappcore.a @@ -219,6 +220,8 @@ libappcore_a_sources = \ gimpimage-item-list.h \ gimpimage-merge.c \ gimpimage-merge.h \ + gimpimage-metadata.c \ + gimpimage-metadata.h \ gimpimage-new.c \ gimpimage-new.h \ gimpimage-pick-color.c \ diff --git a/app/core/core-enums.c b/app/core/core-enums.c index 4dadaa560a..b47176be9e 100644 --- a/app/core/core-enums.c +++ b/app/core/core-enums.c @@ -1115,6 +1115,7 @@ gimp_undo_type_get_type (void) { GIMP_UNDO_IMAGE_SIZE, "GIMP_UNDO_IMAGE_SIZE", "image-size" }, { GIMP_UNDO_IMAGE_RESOLUTION, "GIMP_UNDO_IMAGE_RESOLUTION", "image-resolution" }, { GIMP_UNDO_IMAGE_GRID, "GIMP_UNDO_IMAGE_GRID", "image-grid" }, + { GIMP_UNDO_IMAGE_METADATA, "GIMP_UNDO_IMAGE_METADATA", "image-metadata" }, { GIMP_UNDO_IMAGE_COLORMAP, "GIMP_UNDO_IMAGE_COLORMAP", "image-colormap" }, { GIMP_UNDO_GUIDE, "GIMP_UNDO_GUIDE", "guide" }, { GIMP_UNDO_SAMPLE_POINT, "GIMP_UNDO_SAMPLE_POINT", "sample-point" }, @@ -1205,6 +1206,7 @@ gimp_undo_type_get_type (void) { GIMP_UNDO_IMAGE_SIZE, NC_("undo-type", "Image size"), NULL }, { GIMP_UNDO_IMAGE_RESOLUTION, NC_("undo-type", "Image resolution change"), NULL }, { GIMP_UNDO_IMAGE_GRID, NC_("undo-type", "Grid"), NULL }, + { GIMP_UNDO_IMAGE_METADATA, NC_("undo-type", "Change metadata"), NULL }, { GIMP_UNDO_IMAGE_COLORMAP, NC_("undo-type", "Change indexed palette"), NULL }, { GIMP_UNDO_GUIDE, NC_("undo-type", "Guide"), NULL }, { GIMP_UNDO_SAMPLE_POINT, NC_("undo-type", "Sample Point"), NULL }, diff --git a/app/core/core-enums.h b/app/core/core-enums.h index 4ee7713bfb..b8339df5d3 100644 --- a/app/core/core-enums.h +++ b/app/core/core-enums.h @@ -530,6 +530,7 @@ typedef enum /*< pdb-skip >*/ GIMP_UNDO_IMAGE_SIZE, /*< desc="Image size" >*/ GIMP_UNDO_IMAGE_RESOLUTION, /*< desc="Image resolution change" >*/ GIMP_UNDO_IMAGE_GRID, /*< desc="Grid" >*/ + GIMP_UNDO_IMAGE_METADATA, /*< desc="Change metadata" >*/ GIMP_UNDO_IMAGE_COLORMAP, /*< desc="Change indexed palette" >*/ GIMP_UNDO_GUIDE, /*< desc="Guide" >*/ GIMP_UNDO_SAMPLE_POINT, /*< desc="Sample Point" >*/ diff --git a/app/core/gimpimage-metadata.c b/app/core/gimpimage-metadata.c new file mode 100644 index 0000000000..fb947ecd4f --- /dev/null +++ b/app/core/gimpimage-metadata.c @@ -0,0 +1,103 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * 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 . + */ + +#include "config.h" + +#include +#include + +#include "libgimpbase/gimpbase.h" + +#include "core-types.h" + +#include "gimpimage.h" +#include "gimpimage-metadata.h" +#include "gimpimage-private.h" +#include "gimpimage-undo-push.h" + + +/* public functions */ + + +GimpMetadata * +gimp_image_get_metadata (GimpImage *image) +{ + GimpImagePrivate *private; + + g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL); + + private = GIMP_IMAGE_GET_PRIVATE (image); + + return private->metadata; +} + +void +gimp_image_set_metadata (GimpImage *image, + GimpMetadata *metadata, + gboolean push_undo) +{ + GimpImagePrivate *private; + + g_return_if_fail (GIMP_IS_IMAGE (image)); + + private = GIMP_IMAGE_GET_PRIVATE (image); + + if (metadata != private->metadata) + { + if (push_undo) + gimp_image_undo_push_image_metadata (image, NULL); + + if (private->metadata) + g_object_unref (private->metadata); + + private->metadata = metadata; + + if (private->metadata) + { + gdouble xres, yres; + + g_object_ref (private->metadata); + + gimp_metadata_set_pixel_size (metadata, + gimp_image_get_width (image), + gimp_image_get_height (image)); + + switch (gimp_image_get_component_type (image)) + { + case GIMP_COMPONENT_TYPE_U8: + gimp_metadata_set_bits_per_sample (metadata, 8); + break; + + case GIMP_COMPONENT_TYPE_U16: + case GIMP_COMPONENT_TYPE_HALF: + gimp_metadata_set_bits_per_sample (metadata, 16); + break; + + case GIMP_COMPONENT_TYPE_U32: + case GIMP_COMPONENT_TYPE_FLOAT: + gimp_metadata_set_bits_per_sample (metadata, 32); + break; + } + + gimp_image_get_resolution (image, &xres, &yres); + gimp_metadata_set_resolution (metadata, xres, yres, + gimp_image_get_unit (image)); + } + + g_object_notify (G_OBJECT (image), "metadata"); + } +} diff --git a/app/core/gimpimage-metadata.h b/app/core/gimpimage-metadata.h new file mode 100644 index 0000000000..1f90d34400 --- /dev/null +++ b/app/core/gimpimage-metadata.h @@ -0,0 +1,28 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * 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 . + */ + +#ifndef __GIMP_IMAGE_METADATA_H__ +#define __GIMP_IMAGE_METADATA_H__ + + +GimpMetadata * gimp_image_get_metadata (GimpImage *image); +void gimp_image_set_metadata (GimpImage *image, + GimpMetadata *metadata, + gboolean push_undo); + + +#endif /* __GIMP_IMAGE_METADATA_H__ */ diff --git a/app/core/gimpimage-private.h b/app/core/gimpimage-private.h index 5dee180461..a4ae2f1855 100644 --- a/app/core/gimpimage-private.h +++ b/app/core/gimpimage-private.h @@ -55,6 +55,8 @@ struct _GimpImagePrivate const Babl *babl_palette_rgb; /* palette's RGB Babl format */ const Babl *babl_palette_rgba; /* palette's RGBA Babl format */ + GimpMetadata *metadata; /* image's metadata */ + gint dirty; /* dirty flag -- # of ops */ guint dirty_time; /* time when image became dirty */ gint export_dirty; /* 'dirty' but for export */ diff --git a/app/core/gimpimage-undo-push.c b/app/core/gimpimage-undo-push.c index 31970d2003..066f35af49 100644 --- a/app/core/gimpimage-undo-push.c +++ b/app/core/gimpimage-undo-push.c @@ -148,6 +148,18 @@ gimp_image_undo_push_image_colormap (GimpImage *image, NULL); } +GimpUndo * +gimp_image_undo_push_image_metadata (GimpImage *image, + const gchar *undo_desc) +{ + g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL); + + return gimp_image_undo_push (image, GIMP_TYPE_IMAGE_UNDO, + GIMP_UNDO_IMAGE_METADATA, undo_desc, + GIMP_DIRTY_IMAGE_META, + NULL); +} + GimpUndo * gimp_image_undo_push_image_parasite (GimpImage *image, const gchar *undo_desc, diff --git a/app/core/gimpimage-undo-push.h b/app/core/gimpimage-undo-push.h index 6680c6885c..28d463d54b 100644 --- a/app/core/gimpimage-undo-push.h +++ b/app/core/gimpimage-undo-push.h @@ -38,6 +38,8 @@ GimpUndo * gimp_image_undo_push_image_grid (GimpImage *image, GimpGrid *grid); GimpUndo * gimp_image_undo_push_image_colormap (GimpImage *image, const gchar *undo_desc); +GimpUndo * gimp_image_undo_push_image_metadata (GimpImage *image, + const gchar *undo_desc); GimpUndo * gimp_image_undo_push_image_parasite (GimpImage *image, const gchar *undo_desc, const GimpParasite *parasite); diff --git a/app/core/gimpimage.c b/app/core/gimpimage.c index 191239c2fd..226b872b20 100644 --- a/app/core/gimpimage.c +++ b/app/core/gimpimage.c @@ -23,6 +23,7 @@ #include #include #include +#include #include "libgimpcolor/gimpcolor.h" #include "libgimpmath/gimpmath.h" @@ -47,6 +48,7 @@ #include "gimpimage.h" #include "gimpimage-colormap.h" #include "gimpimage-guides.h" +#include "gimpimage-metadata.h" #include "gimpimage-sample-points.h" #include "gimpimage-preview.h" #include "gimpimage-private.h" @@ -130,6 +132,7 @@ enum PROP_HEIGHT, PROP_BASE_TYPE, PROP_PRECISION, + PROP_METADATA, PROP_BUFFER }; @@ -165,12 +168,14 @@ static gchar * gimp_image_get_description (GimpViewable *viewable, static void gimp_image_real_mode_changed (GimpImage *image); static void gimp_image_real_precision_changed(GimpImage *image); +static void gimp_image_real_resolution_changed(GimpImage *image); static void gimp_image_real_size_changed_detailed (GimpImage *image, gint previous_origin_x, gint previous_origin_y, gint previous_width, gint previous_height); +static void gimp_image_real_unit_changed (GimpImage *image); static void gimp_image_real_colormap_changed (GimpImage *image, gint color_index); @@ -552,9 +557,9 @@ gimp_image_class_init (GimpImageClass *klass) klass->component_visibility_changed = NULL; klass->component_active_changed = NULL; klass->mask_changed = NULL; - klass->resolution_changed = NULL; + klass->resolution_changed = gimp_image_real_resolution_changed; klass->size_changed_detailed = gimp_image_real_size_changed_detailed; - klass->unit_changed = NULL; + klass->unit_changed = gimp_image_real_unit_changed; klass->quick_mask_changed = NULL; klass->selection_invalidate = NULL; @@ -610,6 +615,11 @@ gimp_image_class_init (GimpImageClass *klass) GIMP_PARAM_READWRITE | G_PARAM_CONSTRUCT)); + g_object_class_install_property (object_class, PROP_METADATA, + g_param_spec_object ("metadata", NULL, NULL, + GEXIV2_TYPE_METADATA, + GIMP_PARAM_READABLE)); + g_object_class_override_property (object_class, PROP_BUFFER, "buffer"); g_type_class_add_private (klass, sizeof (GimpImagePrivate)); @@ -667,6 +677,8 @@ gimp_image_init (GimpImage *image) private->n_colors = 0; private->palette = NULL; + private->metadata = NULL; + private->dirty = 1; private->dirty_time = 0; private->undo_freeze_count = 0; @@ -837,6 +849,7 @@ gimp_image_set_property (GObject *object, case PROP_PRECISION: private->precision = g_value_get_enum (value); break; + case PROP_METADATA: case PROP_BUFFER: default: G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec); @@ -873,6 +886,9 @@ gimp_image_get_property (GObject *object, case PROP_PRECISION: g_value_set_enum (value, private->precision); break; + case PROP_METADATA: + g_value_set_object (value, gimp_image_get_metadata (image)); + break; case PROP_BUFFER: g_value_set_object (value, gimp_image_get_buffer (GIMP_PICKABLE (image))); break; @@ -948,6 +964,12 @@ gimp_image_finalize (GObject *object) if (private->colormap) gimp_image_colormap_free (image); + if (private->metadata) + { + g_object_unref (private->metadata); + private->metadata = NULL; + } + if (private->layers) { g_object_unref (private->layers); @@ -1128,9 +1150,10 @@ gimp_image_get_size (GimpViewable *viewable, static void gimp_image_size_changed (GimpViewable *viewable) { - GimpImage *image = GIMP_IMAGE (viewable); - GList *all_items; - GList *list; + GimpImage *image = GIMP_IMAGE (viewable); + GimpMetadata *metadata; + GList *all_items; + GList *list; if (GIMP_VIEWABLE_CLASS (parent_class)->size_changed) GIMP_VIEWABLE_CLASS (parent_class)->size_changed (viewable); @@ -1155,6 +1178,12 @@ gimp_image_size_changed (GimpViewable *viewable) gimp_viewable_size_changed (GIMP_VIEWABLE (gimp_image_get_mask (image))); + metadata = gimp_image_get_metadata (image); + if (metadata) + gimp_metadata_set_pixel_size (metadata, + gimp_image_get_width (image), + gimp_image_get_height (image)); + gimp_projectable_structure_changed (GIMP_PROJECTABLE (image)); } @@ -1181,9 +1210,48 @@ gimp_image_real_mode_changed (GimpImage *image) static void gimp_image_real_precision_changed (GimpImage *image) { + GimpMetadata *metadata; + + metadata = gimp_image_get_metadata (image); + if (metadata) + { + switch (gimp_image_get_component_type (image)) + { + case GIMP_COMPONENT_TYPE_U8: + gimp_metadata_set_bits_per_sample (metadata, 8); + break; + + case GIMP_COMPONENT_TYPE_U16: + case GIMP_COMPONENT_TYPE_HALF: + gimp_metadata_set_bits_per_sample (metadata, 16); + break; + + case GIMP_COMPONENT_TYPE_U32: + case GIMP_COMPONENT_TYPE_FLOAT: + gimp_metadata_set_bits_per_sample (metadata, 32); + break; + } + } + gimp_projectable_structure_changed (GIMP_PROJECTABLE (image)); } +static void +gimp_image_real_resolution_changed (GimpImage *image) +{ + GimpMetadata *metadata; + + metadata = gimp_image_get_metadata (image); + if (metadata) + { + gdouble xres, yres; + + gimp_image_get_resolution (image, &xres, &yres); + gimp_metadata_set_resolution (metadata, xres, yres, + gimp_image_get_unit (image)); + } +} + static void gimp_image_real_size_changed_detailed (GimpImage *image, gint previous_origin_x, @@ -1198,6 +1266,22 @@ gimp_image_real_size_changed_detailed (GimpImage *image, gimp_viewable_size_changed (GIMP_VIEWABLE (image)); } +static void +gimp_image_real_unit_changed (GimpImage *image) +{ + GimpMetadata *metadata; + + metadata = gimp_image_get_metadata (image); + if (metadata) + { + gdouble xres, yres; + + gimp_image_get_resolution (image, &xres, &yres); + gimp_metadata_set_resolution (metadata, xres, yres, + gimp_image_get_unit (image)); + } +} + static void gimp_image_real_colormap_changed (GimpImage *image, gint color_index) diff --git a/app/core/gimpimageundo.c b/app/core/gimpimageundo.c index a6f0877433..2faafa6f8b 100644 --- a/app/core/gimpimageundo.c +++ b/app/core/gimpimageundo.c @@ -35,6 +35,7 @@ #include "gimpimage.h" #include "gimpimage-colormap.h" #include "gimpimage-grid.h" +#include "gimpimage-metadata.h" #include "gimpimage-private.h" #include "gimpimageundo.h" #include "gimpparasitelist.h" @@ -186,6 +187,11 @@ gimp_image_undo_constructed (GObject *object) GIMP_IMAGE_COLORMAP_SIZE); break; + case GIMP_UNDO_IMAGE_METADATA: + image_undo->metadata = + gimp_metadata_duplicate (gimp_image_get_metadata (image)); + break; + case GIMP_UNDO_PARASITE_ATTACH: case GIMP_UNDO_PARASITE_REMOVE: g_assert (image_undo->parasite_name != NULL); @@ -284,6 +290,9 @@ gimp_image_undo_get_memsize (GimpObject *object, if (image_undo->colormap) memsize += GIMP_IMAGE_COLORMAP_SIZE; + if (image_undo->metadata) + memsize += gimp_g_object_get_memsize (G_OBJECT (image_undo->metadata)); + memsize += gimp_object_get_memsize (GIMP_OBJECT (image_undo->grid), gui_size); memsize += gimp_string_get_memsize (image_undo->parasite_name); @@ -447,6 +456,20 @@ gimp_image_undo_pop (GimpUndo *undo, } break; + case GIMP_UNDO_IMAGE_METADATA: + { + GimpMetadata *metadata; + + metadata = gimp_metadata_duplicate (gimp_image_get_metadata (image)); + + gimp_image_set_metadata (image, image_undo->metadata, FALSE); + + if (image_undo->metadata) + g_object_unref (image_undo->metadata); + image_undo->metadata = metadata; + } + break; + case GIMP_UNDO_PARASITE_ATTACH: case GIMP_UNDO_PARASITE_REMOVE: { @@ -495,6 +518,12 @@ gimp_image_undo_free (GimpUndo *undo, image_undo->colormap = NULL; } + if (image_undo->metadata) + { + g_free (image_undo->metadata); + image_undo->metadata = NULL; + } + if (image_undo->parasite_name) { g_free (image_undo->parasite_name); diff --git a/app/core/gimpimageundo.h b/app/core/gimpimageundo.h index 33b2c5e79c..b6a9b01f0a 100644 --- a/app/core/gimpimageundo.h +++ b/app/core/gimpimageundo.h @@ -50,6 +50,7 @@ struct _GimpImageUndo GimpGrid *grid; gint num_colors; guchar *colormap; + GimpMetadata *metadata; gchar *parasite_name; GimpParasite *parasite; }; diff --git a/app/pdb/image-cmds.c b/app/pdb/image-cmds.c index 78c9a3e770..f171fce441 100644 --- a/app/pdb/image-cmds.c +++ b/app/pdb/image-cmds.c @@ -42,6 +42,7 @@ #include "core/gimpimage-duplicate.h" #include "core/gimpimage-flip.h" #include "core/gimpimage-merge.h" +#include "core/gimpimage-metadata.h" #include "core/gimpimage-pick-color.h" #include "core/gimpimage-pick-layer.h" #include "core/gimpimage-resize.h" @@ -1706,6 +1707,67 @@ image_set_colormap_invoker (GimpProcedure *procedure, error ? *error : NULL); } +static GimpValueArray * +image_get_metadata_invoker (GimpProcedure *procedure, + Gimp *gimp, + GimpContext *context, + GimpProgress *progress, + const GimpValueArray *args, + GError **error) +{ + gboolean success = TRUE; + GimpValueArray *return_vals; + GimpImage *image; + gchar *metadata_string = NULL; + + image = gimp_value_get_image (gimp_value_array_index (args, 0), gimp); + + if (success) + { + GimpMetadata *metadata = gimp_image_get_metadata (image); + + if (metadata) + metadata_string = gimp_metadata_serialize (metadata); + } + + return_vals = gimp_procedure_get_return_values (procedure, success, + error ? *error : NULL); + + if (success) + g_value_take_string (gimp_value_array_index (return_vals, 1), metadata_string); + + return return_vals; +} + +static GimpValueArray * +image_set_metadata_invoker (GimpProcedure *procedure, + Gimp *gimp, + GimpContext *context, + GimpProgress *progress, + const GimpValueArray *args, + GError **error) +{ + gboolean success = TRUE; + GimpImage *image; + const gchar *metadata_string; + + image = gimp_value_get_image (gimp_value_array_index (args, 0), gimp); + metadata_string = g_value_get_string (gimp_value_array_index (args, 1)); + + if (success) + { + GimpMetadata *metadata = gimp_metadata_deserialize (metadata_string); + + gimp_image_set_metadata (image, metadata, TRUE); + + if (metadata) + g_object_unref (metadata); + } + + return gimp_procedure_get_return_values (procedure, success, + error ? *error : NULL); +} + static GimpValueArray * image_clean_all_invoker (GimpProcedure *procedure, Gimp *gimp, @@ -4553,6 +4615,66 @@ register_image_procs (GimpPDB *pdb) gimp_pdb_register_procedure (pdb, procedure); g_object_unref (procedure); + /* + * gimp-image-get-metadata + */ + procedure = gimp_procedure_new (image_get_metadata_invoker); + gimp_object_set_static_name (GIMP_OBJECT (procedure), + "gimp-image-get-metadata"); + gimp_procedure_set_static_strings (procedure, + "gimp-image-get-metadata", + "Returns the image's metadata.", + "Returns exif/iptc/xmp metadata from the image.", + "Spencer Kimball & Peter Mattis", + "Spencer Kimball & Peter Mattis", + "1995-1996", + NULL); + gimp_procedure_add_argument (procedure, + gimp_param_spec_image_id ("image", + "image", + "The image", + pdb->gimp, FALSE, + GIMP_PARAM_READWRITE)); + gimp_procedure_add_return_value (procedure, + gimp_param_spec_string ("metadata-string", + "metadata string", + "The exif/ptc/xmp metadata as a string", + FALSE, FALSE, FALSE, + NULL, + GIMP_PARAM_READWRITE)); + gimp_pdb_register_procedure (pdb, procedure); + g_object_unref (procedure); + + /* + * gimp-image-set-metadata + */ + procedure = gimp_procedure_new (image_set_metadata_invoker); + gimp_object_set_static_name (GIMP_OBJECT (procedure), + "gimp-image-set-metadata"); + gimp_procedure_set_static_strings (procedure, + "gimp-image-set-metadata", + "Set the image's metadata.", + "Sets exif/iptc/xmp metadata on the image.", + "Spencer Kimball & Peter Mattis", + "Spencer Kimball & Peter Mattis", + "1995-1996", + NULL); + gimp_procedure_add_argument (procedure, + gimp_param_spec_image_id ("image", + "image", + "The image", + pdb->gimp, FALSE, + GIMP_PARAM_READWRITE)); + gimp_procedure_add_argument (procedure, + gimp_param_spec_string ("metadata-string", + "metadata string", + "The exif/ptc/xmp metadata as a string", + FALSE, FALSE, FALSE, + NULL, + GIMP_PARAM_READWRITE)); + gimp_pdb_register_procedure (pdb, procedure); + g_object_unref (procedure); + /* * gimp-image-clean-all */ diff --git a/app/pdb/internal-procs.c b/app/pdb/internal-procs.c index 790cb86f3c..5dada82d66 100644 --- a/app/pdb/internal-procs.c +++ b/app/pdb/internal-procs.c @@ -28,7 +28,7 @@ #include "internal-procs.h" -/* 701 procedures registered total */ +/* 703 procedures registered total */ void internal_procs_init (GimpPDB *pdb) diff --git a/app/sanity.c b/app/sanity.c index 557fddb645..4337a55bc2 100644 --- a/app/sanity.c +++ b/app/sanity.c @@ -22,6 +22,7 @@ #include #include #include +#include #include #include "libgimpbase/gimpbase.h" @@ -38,6 +39,7 @@ static gchar * sanity_check_pango (void); static gchar * sanity_check_fontconfig (void); static gchar * sanity_check_freetype (void); static gchar * sanity_check_gdk_pixbuf (void); +static gchar * sanity_check_gexiv2 (void); static gchar * sanity_check_babl (void); static gchar * sanity_check_gegl (void); static gchar * sanity_check_gegl_ops (void); @@ -69,6 +71,9 @@ sanity_check (void) if (! abort_message) abort_message = sanity_check_gdk_pixbuf (); + if (! abort_message) + abort_message = sanity_check_gexiv2 (); + if (! abort_message) abort_message = sanity_check_babl (); @@ -346,6 +351,46 @@ sanity_check_gdk_pixbuf (void) return NULL; } +static gchar * +sanity_check_gexiv2 (void) +{ +#ifdef GEXIV2_MAJOR_VERSION + +#define GEXIV2_REQUIRED_MAJOR 0 +#define GEXIV2_REQUIRED_MINOR 7 +#define GEXIV2_REQUIRED_MICRO 0 + + gint gexiv2_version = gexiv2_get_version (); + + if (gexiv2_version < (GEXIV2_REQUIRED_MAJOR * 100 * 100 + + GEXIV2_REQUIRED_MINOR * 100 + + GEXIV2_REQUIRED_MICRO)) + { + const gint gexiv2_major_version = gexiv2_version / 100 / 100; + const gint gexiv2_minor_version = gexiv2_version / 100 % 100; + const gint gexiv2_micro_version = gexiv2_version % 100; + + return g_strdup_printf + ("gexiv2 version too old!\n\n" + "GIMP requires gexiv2 version %d.%d.%d or later.\n" + "Installed gexiv2 version is %d.%d.%d.\n\n" + "Somehow you or your software packager managed\n" + "to install GIMP with an older gexiv2 version.\n\n" + "Please upgrade to gexiv2 version %d.%d.%d or later.", + GEXIV2_REQUIRED_MAJOR, GEXIV2_REQUIRED_MINOR, GEXIV2_REQUIRED_MICRO, + gexiv2_major_version, gexiv2_minor_version, gexiv2_micro_version, + GEXIV2_REQUIRED_MAJOR, GEXIV2_REQUIRED_MINOR, GEXIV2_REQUIRED_MICRO); + } + +#undef GEXIV2_REQUIRED_MAJOR +#undef GEXIV2_REQUIRED_MINOR +#undef GEXIV2_REQUIRED_MICRO + +#endif + + return NULL; +} + static gchar * sanity_check_babl (void) { diff --git a/app/tests/Makefile.am b/app/tests/Makefile.am index 45a36e4c54..d488305d0c 100644 --- a/app/tests/Makefile.am +++ b/app/tests/Makefile.am @@ -124,7 +124,8 @@ LDADD = \ $(PANGOCAIRO_LIBS) \ $(CAIRO_LIBS) \ $(GEGL_LIBS) \ - $(GLIB_LIBS) \ + $(GIO_LIBS) \ + $(GEXIV2_LIBS) \ $(INTLLIBS) \ $(RT_LIBS) diff --git a/app/xcf/xcf-load.c b/app/xcf/xcf-load.c index f3c89abc94..ae3dad0814 100644 --- a/app/xcf/xcf-load.c +++ b/app/xcf/xcf-load.c @@ -41,6 +41,7 @@ #include "core/gimpimage-colormap.h" #include "core/gimpimage-grid.h" #include "core/gimpimage-guides.h" +#include "core/gimpimage-metadata.h" #include "core/gimpimage-private.h" #include "core/gimpimage-sample-points.h" #include "core/gimpimage-undo.h" @@ -140,6 +141,7 @@ xcf_load_image (Gimp *gimp, { GimpImage *image = NULL; const GimpParasite *parasite; + gboolean has_metadata = FALSE; guint32 saved_pos; guint32 offset; gint width; @@ -208,6 +210,124 @@ xcf_load_image (Gimp *gimp, } } + /* check for a metadata parasite */ + parasite = gimp_image_parasite_find (GIMP_IMAGE (image), + "gimp-image-metadata"); + if (parasite) + { + GimpImagePrivate *private = GIMP_IMAGE_GET_PRIVATE (image); + GimpMetadata *metadata; + const gchar *meta_string; + + meta_string = (gchar *) gimp_parasite_data (parasite); + metadata = gimp_metadata_deserialize (meta_string); + + if (metadata) + { + has_metadata = TRUE; + + gimp_image_set_metadata (image, metadata, FALSE); + g_object_unref (metadata); + } + + gimp_parasite_list_remove (private->parasites, + gimp_parasite_name (parasite)); + } + + /* migrate the old "exif-data" parasite */ + parasite = gimp_image_parasite_find (GIMP_IMAGE (image), + "exif-data"); + if (parasite) + { + GimpImagePrivate *private = GIMP_IMAGE_GET_PRIVATE (image); + + if (has_metadata) + { + g_printerr ("xcf-load: inconsistent metadata discovered: XCF file " + "has both 'gimp-image-metadata' and 'exif-data' " + "parasites, dropping old 'exif-data'\n"); + } + else + { + GimpMetadata *metadata = gimp_image_get_metadata (image); + GError *my_error = NULL; + + if (metadata) + g_object_ref (metadata); + else + metadata = gimp_metadata_new (); + + if (! gimp_metadata_set_from_exif (metadata, + gimp_parasite_data (parasite), + gimp_parasite_data_size (parasite), + &my_error)) + { + gimp_message (gimp, G_OBJECT (info->progress), + GIMP_MESSAGE_WARNING, + _("Corrupt 'exif-data' parasite discovered.\n" + "EXIF data could not be migrated: %s"), + my_error->message); + g_clear_error (&my_error); + } + else + { + gimp_image_set_metadata (image, metadata, FALSE); + } + + g_object_unref (metadata); + } + + gimp_parasite_list_remove (private->parasites, + gimp_parasite_name (parasite)); + } + + /* migrate the old "gimp-metadata" parasite */ + parasite = gimp_image_parasite_find (GIMP_IMAGE (image), + "gimp-metadata"); + if (parasite) + { + GimpImagePrivate *private = GIMP_IMAGE_GET_PRIVATE (image); + + if (has_metadata) + { + g_printerr ("xcf-load: inconsistent metadata discovered: XCF file " + "has both 'gimp-image-metadata' and 'gimp-metadata' " + "parasites, dropping old 'gimp-metadata'\n"); + } + else + { + GimpMetadata *metadata = gimp_image_get_metadata (image); + GError *my_error = NULL; + + if (metadata) + g_object_ref (metadata); + else + metadata = gimp_metadata_new (); + + if (! gimp_metadata_set_from_xmp (metadata, + gimp_parasite_data (parasite), + gimp_parasite_data_size (parasite), + &my_error)) + { + gimp_message (gimp, G_OBJECT (info->progress), + GIMP_MESSAGE_WARNING, + _("Corrupt 'gimp-metadata' parasite discovered.\n" + "XMP data could not be migrated: %s"), + my_error->message); + g_clear_error (&my_error); + } + else + { + gimp_image_set_metadata (image, metadata, FALSE); + } + + g_object_unref (metadata); + } + + gimp_parasite_list_remove (private->parasites, + gimp_parasite_name (parasite)); + } + xcf_progress_update (info); while (TRUE) diff --git a/app/xcf/xcf-save.c b/app/xcf/xcf-save.c index d931f8175d..ff28aa46c1 100644 --- a/app/xcf/xcf-save.c +++ b/app/xcf/xcf-save.c @@ -41,6 +41,7 @@ #include "core/gimpimage-colormap.h" #include "core/gimpimage-grid.h" #include "core/gimpimage-guides.h" +#include "core/gimpimage-metadata.h" #include "core/gimpimage-private.h" #include "core/gimpimage-sample-points.h" #include "core/gimplayer.h" @@ -390,9 +391,10 @@ xcf_save_image_props (XcfInfo *info, GimpImage *image, GError **error) { - GimpImagePrivate *private = GIMP_IMAGE_GET_PRIVATE (image); - GimpParasite *parasite = NULL; - GimpUnit unit = gimp_image_get_unit (image); + GimpImagePrivate *private = GIMP_IMAGE_GET_PRIVATE (image); + GimpParasite *grid_parasite = NULL; + GimpParasite *meta_parasite = NULL; + GimpUnit unit = gimp_image_get_unit (image); gdouble xres; gdouble yres; @@ -440,8 +442,26 @@ xcf_save_image_props (XcfInfo *info, { GimpGrid *grid = gimp_image_get_grid (image); - parasite = gimp_grid_to_parasite (grid); - gimp_parasite_list_add (private->parasites, parasite); + grid_parasite = gimp_grid_to_parasite (grid); + gimp_parasite_list_add (private->parasites, grid_parasite); + } + + if (gimp_image_get_metadata (image)) + { + GimpMetadata *metadata = gimp_image_get_metadata (image); + gchar *meta_string; + + meta_string = gimp_metadata_serialize (metadata); + + if (meta_string) + { + meta_parasite = gimp_parasite_new ("gimp-image-metadata", + GIMP_PARASITE_PERSISTENT, + strlen (meta_string) + 1, + meta_string); + gimp_parasite_list_add (private->parasites, meta_parasite); + g_free (meta_string); + } } if (gimp_parasite_list_length (private->parasites) > 0) @@ -450,11 +470,18 @@ xcf_save_image_props (XcfInfo *info, private->parasites)); } - if (parasite) + if (grid_parasite) { gimp_parasite_list_remove (private->parasites, - gimp_parasite_name (parasite)); - gimp_parasite_free (parasite); + gimp_parasite_name (grid_parasite)); + gimp_parasite_free (grid_parasite); + } + + if (meta_parasite) + { + gimp_parasite_list_remove (private->parasites, + gimp_parasite_name (meta_parasite)); + gimp_parasite_free (meta_parasite); } xcf_check_error (xcf_save_prop (info, image, PROP_END, error)); diff --git a/configure.ac b/configure.ac index a56aa9f762..d6d2e792b6 100644 --- a/configure.ac +++ b/configure.ac @@ -59,7 +59,7 @@ m4_define([pygtk_required_version], [2.10.4]) m4_define([poppler_required_version], [0.12.4]) m4_define([libcurl_required_version], [7.15.1]) m4_define([libgudev_required_version], [167]) -m4_define([exif_required_version], [0.6.15]) +m4_define([gexiv2_required_version], [0.6.1]) m4_define([lcms_required_version], [2.2]) m4_define([libpng_required_version], [1.2.37]) m4_define([liblzma_required_version], [5.0.0]) @@ -123,11 +123,13 @@ GDK_PIXBUF_REQUIRED_VERSION=gdk_pixbuf_required_version GTK_REQUIRED_VERSION=gtk_required_version CAIRO_REQUIRED_VERSION=cairo_required_version GEGL_REQUIRED_VERSION=gegl_required_version +GEXIV2_REQUIRED_VERSION=gexiv2_required_version AC_SUBST(GLIB_REQUIRED_VERSION) AC_SUBST(GDK_PIXBUF_REQUIRED_VERSION) AC_SUBST(GTK_REQUIRED_VERSION) AC_SUBST(CAIRO_REQUIRED_VERSION) AC_SUBST(GEGL_REQUIRED_VERSION) +AC_SUBST(GEXIV2_REQUIRED_VERSION) # The symbol GIMP_UNSTABLE is defined above for substitution in # Makefiles and conditionally defined here as a preprocessor symbol @@ -607,6 +609,7 @@ if test "x$FREETYPE_CONFIG" != "xno" ; then fi AC_SUBST(FREETYPE_LIBS) +PKG_CHECK_MODULES(GEXIV2, gexiv2 >= gexiv2_required_version) ########################################## # Check for some special functions we need @@ -1327,28 +1330,6 @@ AC_SUBST(MNG_LIBS) AC_SUBST(MNG_CFLAGS) -############################################################ -# libexif: Library to allow exif tags to be read from, and -# saved to, jpeg files. Currently, this permits exif data to -# avoid destruction, but no data modification is performed. -############################################################ - -AC_ARG_WITH(libexif, [ --without-libexif build without EXIF support]) - -have_libexif=no -if test "x$with_libexif" != xno && test -z "$EXIF_LIBS" && test -n "$JPEG_LIBS"; then - have_libexif=yes - PKG_CHECK_MODULES(EXIF, libexif >= exif_required_version, - AC_DEFINE(HAVE_LIBEXIF, 1, [Define to 1 if libexif is available]), - have_libexif="no (libexif not found or too old)") -fi - -AC_SUBST(EXIF_CFLAGS) -AC_SUBST(EXIF_LIBS) - -AM_CONDITIONAL(HAVE_LIBEXIF, test "x$have_libexif" = xyes) - - ################# # Check for libaa ################# @@ -2406,9 +2387,6 @@ Optional Plug-Ins: X11 Mouse Cursor: $have_xmc XPM: $have_libxpm -Plug-In Features: - EXIF support: $have_libexif - Optional Modules: ALSA (MIDI Input): $have_alsa Linux Input: $have_linux_input (GUdev support: $have_libgudev) diff --git a/libgimp/Makefile.am b/libgimp/Makefile.am index bff3883a78..7de00fbaac 100644 --- a/libgimp/Makefile.am +++ b/libgimp/Makefile.am @@ -68,6 +68,7 @@ AM_CPPFLAGS = \ -I$(top_srcdir) \ $(GTK_CFLAGS) \ $(GEGL_CFLAGS) \ + $(GEXIV2_CFLAGS) \ -I$(includedir) \ $(xobjective_c) @@ -280,6 +281,8 @@ libgimpui_sources = \ gimpitemcombobox.h \ gimpmenu.c \ gimpmenu.h \ + gimpmetadata.c \ + gimpmetadats.h \ gimppalettemenu.c \ gimppalettemenu.h \ gimppaletteselectbutton.c \ @@ -354,6 +357,7 @@ gimpinclude_HEADERS = \ gimpimagecombobox.h \ gimpitemcombobox.h \ gimpmenu.h \ + gimpmetadata.h \ gimppalettemenu.h \ gimppaletteselectbutton.h \ gimppatternmenu.h \ @@ -399,6 +403,7 @@ libgimpui_@GIMP_API_VERSION@_la_LIBADD = \ $(libgimpbase) \ $(libgimpmodule) \ $(GTK_LIBS) \ + $(GEXIV2_LIBS) \ $(RT_LIBS) libgimpui_@GIMP_API_VERSION@_la_DEPENDENCIES = \ diff --git a/libgimp/gimp.def b/libgimp/gimp.def index fcf3fa8e7f..8c1fd0cd41 100644 --- a/libgimp/gimp.def +++ b/libgimp/gimp.def @@ -403,6 +403,7 @@ EXPORTS gimp_image_get_layer_by_tattoo gimp_image_get_layer_position gimp_image_get_layers + gimp_image_get_metadata gimp_image_get_name gimp_image_get_parasite gimp_image_get_parasite_list @@ -484,6 +485,7 @@ EXPORTS gimp_image_set_component_active gimp_image_set_component_visible gimp_image_set_filename + gimp_image_set_metadata gimp_image_set_resolution gimp_image_set_tattoo_state gimp_image_set_unit diff --git a/libgimp/gimpimage.c b/libgimp/gimpimage.c index f6ef79d5e7..2c324ada97 100644 --- a/libgimp/gimpimage.c +++ b/libgimp/gimpimage.c @@ -100,6 +100,66 @@ gimp_image_get_thumbnail_data (gint32 image_ID, return image_data; } +/** + * gimp_image_get_metadata: + * @image_ID: The image. + * + * Returns the image's metadata. + * + * Returns exif/iptc/xmp metadata from the image. + * + * Returns: The exif/ptc/xmp metadata, or %NULL if there is none. + * + * Since: GIMP 2.10 + **/ +GimpMetadata * +gimp_image_get_metadata (gint32 image_ID) +{ + GimpMetadata *metadata = NULL; + gchar *metadata_string; + + metadata_string = _gimp_image_get_metadata (image_ID); + if (metadata_string) + { + metadata = gimp_metadata_deserialize (metadata_string); + g_free (metadata_string); + } + + return metadata; +} + +/** + * gimp_image_set_metadata: + * @image_ID: The image. + * @metadata: The exif/ptc/xmp metadata. + * + * Set the image's metadata. + * + * Sets exif/iptc/xmp metadata on the image, or deletes it if + * @metadata is %NULL. + * + * Returns: TRUE on success. + * + * Since: GIMP 2.10 + **/ +gboolean +gimp_image_set_metadata (gint32 image_ID, + GimpMetadata *metadata) +{ + gchar *metadata_string = NULL; + gboolean success; + + if (metadata) + metadata_string = gimp_metadata_serialize (metadata); + + success = _gimp_image_set_metadata (image_ID, metadata_string); + + if (metadata_string) + g_free (metadata_string); + + return success; +} + /** * gimp_image_get_cmap: * @image_ID: The image. diff --git a/libgimp/gimpimage.h b/libgimp/gimpimage.h index f25b9d739a..376b033f07 100644 --- a/libgimp/gimpimage.h +++ b/libgimp/gimpimage.h @@ -41,6 +41,10 @@ guchar * gimp_image_get_thumbnail_data (gint32 image_ID, gint *height, gint *bpp); +GimpMetadata * gimp_image_get_metadata (gint32 image_ID); +gboolean gimp_image_set_metadata (gint32 image_ID, + GimpMetadata *metadata); + GIMP_DEPRECATED_FOR(gimp_image_get_colormap) guchar * gimp_image_get_cmap (gint32 image_ID, gint *num_colors); diff --git a/libgimp/gimpimage_pdb.c b/libgimp/gimpimage_pdb.c index ace2f69c00..76d9c15b7b 100644 --- a/libgimp/gimpimage_pdb.c +++ b/libgimp/gimpimage_pdb.c @@ -1799,6 +1799,68 @@ _gimp_image_set_colormap (gint32 image_ID, return success; } +/** + * _gimp_image_get_metadata: + * @image_ID: The image. + * + * Returns the image's metadata. + * + * Returns exif/iptc/xmp metadata from the image. + * + * Returns: The exif/ptc/xmp metadata as a string. + **/ +gchar * +_gimp_image_get_metadata (gint32 image_ID) +{ + GimpParam *return_vals; + gint nreturn_vals; + gchar *metadata_string = NULL; + + return_vals = gimp_run_procedure ("gimp-image-get-metadata", + &nreturn_vals, + GIMP_PDB_IMAGE, image_ID, + GIMP_PDB_END); + + if (return_vals[0].data.d_status == GIMP_PDB_SUCCESS) + metadata_string = g_strdup (return_vals[1].data.d_string); + + gimp_destroy_params (return_vals, nreturn_vals); + + return metadata_string; +} + +/** + * _gimp_image_set_metadata: + * @image_ID: The image. + * @metadata_string: The exif/ptc/xmp metadata as a string. + * + * Set the image's metadata. + * + * Sets exif/iptc/xmp metadata on the image. + * + * Returns: TRUE on success. + **/ +gboolean +_gimp_image_set_metadata (gint32 image_ID, + const gchar *metadata_string) +{ + GimpParam *return_vals; + gint nreturn_vals; + gboolean success = TRUE; + + return_vals = gimp_run_procedure ("gimp-image-set-metadata", + &nreturn_vals, + GIMP_PDB_IMAGE, image_ID, + GIMP_PDB_STRING, metadata_string, + GIMP_PDB_END); + + success = return_vals[0].data.d_status == GIMP_PDB_SUCCESS; + + gimp_destroy_params (return_vals, nreturn_vals); + + return success; +} + /** * gimp_image_clean_all: * @image_ID: The image. diff --git a/libgimp/gimpimage_pdb.h b/libgimp/gimpimage_pdb.h index ae5015ef6e..e5a246678c 100644 --- a/libgimp/gimpimage_pdb.h +++ b/libgimp/gimpimage_pdb.h @@ -148,6 +148,9 @@ G_GNUC_INTERNAL guint8* _gimp_image_get_colormap (gint32 G_GNUC_INTERNAL gboolean _gimp_image_set_colormap (gint32 image_ID, gint num_bytes, const guint8 *colormap); +G_GNUC_INTERNAL gchar* _gimp_image_get_metadata (gint32 image_ID); +G_GNUC_INTERNAL gboolean _gimp_image_set_metadata (gint32 image_ID, + const gchar *metadata_string); gboolean gimp_image_clean_all (gint32 image_ID); gboolean gimp_image_is_dirty (gint32 image_ID); G_GNUC_INTERNAL gboolean _gimp_image_thumbnail (gint32 image_ID, diff --git a/libgimp/gimpmetadata.c b/libgimp/gimpmetadata.c new file mode 100644 index 0000000000..63aec25936 --- /dev/null +++ b/libgimp/gimpmetadata.c @@ -0,0 +1,640 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-2000 Peter Mattis and Spencer Kimball + * + * gimpmetadata.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 + * . + */ + +#include "config.h" + +#include + +#include +#include + +#include "gimp.h" +#include "gimpui.h" +#include "gimpmetadata.h" + +#include "libgimp-intl.h" + + +static void gimp_image_metadata_rotate (gint32 image_ID, + GExiv2Orientation orientation); +static void gimp_image_metadata_rotate_query (gint32 image_ID, + const gchar *mime_type, + GimpMetadata *metadata, + gboolean interactive); +static gboolean gimp_image_metadata_rotate_dialog (gint32 image_ID, + const gchar *parasite_name); + + +/* public functions */ + +void +gimp_image_metadata_load (gint32 image_ID, + const gchar *mime_type, + GFile *file, + gboolean interactive) +{ + GimpMetadata *metadata; + + g_return_if_fail (image_ID > 0); + g_return_if_fail (mime_type != NULL); + g_return_if_fail (G_IS_FILE (file)); + + metadata = gimp_metadata_load_from_file (file, NULL); + + if (metadata) + { + gchar *comment; + gdouble xres; + gdouble yres; + GimpUnit unit; + +#if 0 + { + gchar *xml = gimp_metadata_serialize (metadata); + GimpMetadata *new = gimp_metadata_deserialize (xml); + gchar *xml2 = gimp_metadata_serialize (new); + + FILE *f = fopen ("/tmp/gimp-test-xml1", "w"); + fprintf (f, "%s", xml); + fclose (f); + + f = fopen ("/tmp/gimp-test-xml2", "w"); + fprintf (f, "%s", xml2); + fclose (f); + + system ("diff -u /tmp/gimp-test-xml1 /tmp/gimp-test-xml2"); + + g_free (xml); + g_free (xml2); + g_object_unref (new); + } +#endif + + comment = gexiv2_metadata_get_tag_string (metadata, + "Exif.Photo.UserComment"); + if (! comment) + comment = gexiv2_metadata_get_tag_string (metadata, + "Exif.Image.ImageDescription"); + + if (comment) + { + GimpParasite *parasite; + + parasite = gimp_parasite_new ("gimp-comment", + GIMP_PARASITE_PERSISTENT, + strlen (comment) + 1, + comment); + g_free (comment); + + gimp_image_attach_parasite (image_ID, parasite); + gimp_parasite_free (parasite); + } + + if (gimp_metadata_get_resolution (metadata, &xres, &yres, &unit)) + { + gimp_image_set_resolution (image_ID, xres, yres); + gimp_image_set_unit (image_ID, unit); + } + + gimp_image_metadata_rotate_query (image_ID, mime_type, + metadata, interactive); + + gexiv2_metadata_erase_exif_thumbnail (metadata); + + gimp_image_set_metadata (image_ID, metadata); + + g_object_unref (metadata); + } +} + +GimpMetadata * +gimp_image_metadata_save_prepare (gint32 image_ID, + const gchar *mime_type) +{ + GimpMetadata *metadata; + + g_return_val_if_fail (image_ID > 0, NULL); + g_return_val_if_fail (mime_type != NULL, NULL); + + metadata = gimp_image_get_metadata (image_ID); + + 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]; + + image_width = gimp_image_width (image_ID); + image_height = gimp_image_height (image_ID); + + datetime = g_date_time_new_now_local (); + + comment_parasite = gimp_image_get_parasite (image_ID, "gimp-comment"); + if (comment_parasite) + comment = gimp_parasite_data (comment_parasite); + + /* EXIF */ + + if (comment) + { + gexiv2_metadata_set_tag_string (metadata, + "Exif.Photo.UserComment", + comment); + gexiv2_metadata_set_tag_string (metadata, + "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 (metadata, + "Exif.Image.DateTime", + buffer); + + gexiv2_metadata_set_tag_string (metadata, + "Exif.Image.Software", + PACKAGE_STRING); + + gimp_metadata_set_pixel_size (metadata, + image_width, image_height); + + gimp_image_get_resolution (image_ID, &xres, &yres); + gimp_metadata_set_resolution (metadata, xres, yres, + gimp_image_get_unit (image_ID)); + + /* XMP */ + + gexiv2_metadata_set_tag_string (metadata, + "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 (metadata, + "Xmp.tiff.ImageWidth", + buffer); + + g_snprintf (buffer, sizeof (buffer), "%d", image_height); + gexiv2_metadata_set_tag_string (metadata, + "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 (metadata, + "Xmp.tiff.DateTime", + buffer); + } + + /* 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 (metadata, + "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 (metadata, + "Iptc.Application2.TimeCreated", + buffer); + + g_date_time_unref (datetime); + } + + return metadata; +} + +gboolean +gimp_image_metadata_save_finish (gint32 image_ID, + const gchar *mime_type, + GimpMetadata *metadata, + GFile *file, + GimpMetadataSaveFlags flags, + GError **error) +{ + GExiv2Metadata *new_metadata; + gboolean support_exif; + gboolean support_xmp; + gboolean support_iptc; + gchar *value; + gboolean success = FALSE; + gint i; + + g_return_val_if_fail (image_ID > 0, 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); + + /* read metadata from saved file */ + new_metadata = gimp_metadata_load_from_file (file, error); + + if (! new_metadata) + return FALSE; + + support_exif = gexiv2_metadata_get_supports_exif (new_metadata); + support_xmp = gexiv2_metadata_get_supports_xmp (new_metadata); + support_iptc = gexiv2_metadata_get_supports_iptc (new_metadata); + + if ((flags & GIMP_METADATA_SAVE_EXIF) && support_exif) + { + gchar **exif_data = gexiv2_metadata_get_exif_tags (metadata); + + for (i = 0; exif_data[i] != NULL; i++) + { + if (! gexiv2_metadata_has_tag (new_metadata, exif_data[i]) && + gimp_metadata_is_tag_supported (exif_data[i], mime_type)) + { + value = gexiv2_metadata_get_tag_string (metadata, exif_data[i]); + gexiv2_metadata_set_tag_string (new_metadata, exif_data[i], + value); + g_free (value); + } + } + + g_strfreev (exif_data); + } + + if ((flags & GIMP_METADATA_SAVE_XMP) && support_xmp) + { + gchar **xmp_data = gexiv2_metadata_get_xmp_tags (metadata); + + for (i = 0; xmp_data[i] != NULL; i++) + { + if (! gexiv2_metadata_has_tag (new_metadata, xmp_data[i]) && + gimp_metadata_is_tag_supported (xmp_data[i], mime_type)) + { + value = gexiv2_metadata_get_tag_string (metadata, xmp_data[i]); + gexiv2_metadata_set_tag_string (new_metadata, xmp_data[i], + value); + g_free (value); + } + } + + g_strfreev (xmp_data); + } + + if ((flags & GIMP_METADATA_SAVE_IPTC) && support_iptc) + { + gchar **iptc_data = gexiv2_metadata_get_iptc_tags (metadata); + + for (i = 0; iptc_data[i] != NULL; i++) + { + if (! gexiv2_metadata_has_tag (new_metadata, iptc_data[i]) && + gimp_metadata_is_tag_supported (iptc_data[i], mime_type)) + { + value = gexiv2_metadata_get_tag_string (metadata, iptc_data[i]); + gexiv2_metadata_set_tag_string (new_metadata, iptc_data[i], + value); + g_free (value); + } + } + + 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_ID); + image_height = gimp_image_height (image_ID); + + 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_height / image_width; + } + + thumb_pixbuf = gimp_image_get_thumbnail (image_ID, 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_metadata, + (guchar *) thumb_buffer, + count); + + g_snprintf (buffer, sizeof (buffer), "%d", thumbw); + gexiv2_metadata_set_tag_string (new_metadata, + "Exif.Thumbnail.ImageWidth", + buffer); + + g_snprintf (buffer, sizeof (buffer), "%d", thumbh); + gexiv2_metadata_set_tag_string (new_metadata, + "Exif.Thumbnail.ImageLength", + buffer); + + gexiv2_metadata_set_tag_string (new_metadata, + "Exif.Thumbnail.BitsPerSample", + "8 8 8"); + gexiv2_metadata_set_tag_string (new_metadata, + "Exif.Thumbnail.SamplesPerPixel", + "3"); + gexiv2_metadata_set_tag_string (new_metadata, + "Exif.Thumbnail.PhotometricInterpretation", + "6"); + + g_free (thumb_buffer); + } + + g_object_unref (thumb_pixbuf); + } + + success = gimp_metadata_save_to_file (new_metadata, file, error); + + g_object_unref (new_metadata); + + return success; +} + + +/* private functions */ + +static void +gimp_image_metadata_rotate (gint32 image_ID, + GExiv2Orientation orientation) +{ + switch (orientation) + { + case GEXIV2_ORIENTATION_UNSPECIFIED: + case GEXIV2_ORIENTATION_NORMAL: /* standard orientation, do nothing */ + break; + + case GEXIV2_ORIENTATION_HFLIP: + gimp_image_flip (image_ID, GIMP_ORIENTATION_HORIZONTAL); + break; + + case GEXIV2_ORIENTATION_ROT_180: + gimp_image_rotate (image_ID, GIMP_ROTATE_180); + break; + + case GEXIV2_ORIENTATION_VFLIP: + gimp_image_flip (image_ID, GIMP_ORIENTATION_VERTICAL); + break; + + case GEXIV2_ORIENTATION_ROT_90_HFLIP: /* flipped diagonally around '\' */ + gimp_image_rotate (image_ID, GIMP_ROTATE_90); + gimp_image_flip (image_ID, GIMP_ORIENTATION_HORIZONTAL); + break; + + case GEXIV2_ORIENTATION_ROT_90: /* 90 CW */ + gimp_image_rotate (image_ID, GIMP_ROTATE_90); + break; + + case GEXIV2_ORIENTATION_ROT_90_VFLIP: /* flipped diagonally around '/' */ + gimp_image_rotate (image_ID, GIMP_ROTATE_90); + gimp_image_flip (image_ID, GIMP_ORIENTATION_VERTICAL); + break; + + case GEXIV2_ORIENTATION_ROT_270: /* 90 CCW */ + gimp_image_rotate (image_ID, GIMP_ROTATE_270); + break; + + default: /* shouldn't happen */ + break; + } +} + +static void +gimp_image_metadata_rotate_query (gint32 image_ID, + const gchar *mime_type, + GimpMetadata *metadata, + gboolean interactive) +{ + GimpParasite *parasite; + gchar *parasite_name; + GExiv2Orientation orientation; + gboolean query = interactive; + + orientation = gexiv2_metadata_get_orientation (metadata); + + if (orientation <= GEXIV2_ORIENTATION_NORMAL || + orientation > GEXIV2_ORIENTATION_MAX) + return; + + parasite_name = g_strdup_printf ("gimp-metadata-exif-rotate(%s)", mime_type); + + parasite = gimp_get_parasite (parasite_name); + + if (parasite) + { + if (strncmp (gimp_parasite_data (parasite), "yes", + gimp_parasite_data_size (parasite)) == 0) + { + query = FALSE; + } + else if (strncmp (gimp_parasite_data (parasite), "no", + gimp_parasite_data_size (parasite)) == 0) + { + gimp_parasite_free (parasite); + g_free (parasite_name); + return; + } + + gimp_parasite_free (parasite); + } + + if (query && ! gimp_image_metadata_rotate_dialog (image_ID, + parasite_name)) + { + g_free (parasite_name); + return; + } + + g_free (parasite_name); + + gimp_image_metadata_rotate (image_ID, orientation); + + gexiv2_metadata_set_tag_string (metadata, + "Exif.Image.Orientation", "1"); + gexiv2_metadata_set_tag_string (metadata, + "Xmp.tiff.Orientation", "1"); +} + +static gboolean +gimp_image_metadata_rotate_dialog (gint32 image_ID, + const gchar *parasite_name) +{ + GtkWidget *dialog; + GtkWidget *hbox; + GtkWidget *vbox; + GtkWidget *label; + GtkWidget *toggle; + GdkPixbuf *pixbuf; + gint response; + + dialog = gimp_dialog_new (_("Rotate Image?"), "gimp-metadata-rotate-dialog", + NULL, 0, NULL, NULL, + + _("_Keep Orientation"), GTK_RESPONSE_CANCEL, + GIMP_STOCK_TOOL_ROTATE, GTK_RESPONSE_OK, + + NULL); + + gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog), + GTK_RESPONSE_OK, + GTK_RESPONSE_CANCEL, + -1); + + gtk_window_set_resizable (GTK_WINDOW (dialog), FALSE); + gimp_window_set_transient (GTK_WINDOW (dialog)); + + hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12); + gtk_container_set_border_width (GTK_CONTAINER (hbox), 12); + gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), + hbox, FALSE, FALSE, 0); + gtk_widget_show (hbox); + + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6); + gtk_box_pack_start (GTK_BOX (hbox), vbox, FALSE, FALSE, 0); + gtk_widget_show (vbox); + +#define THUMBNAIL_SIZE 128 + + pixbuf = gimp_image_get_thumbnail (image_ID, + THUMBNAIL_SIZE, THUMBNAIL_SIZE, + GIMP_PIXBUF_SMALL_CHECKS); + + if (pixbuf) + { + GtkWidget *image; + gchar *name; + + image = gtk_image_new_from_pixbuf (pixbuf); + g_object_unref (pixbuf); + + gtk_box_pack_start (GTK_BOX (vbox), image, FALSE, FALSE, 0); + gtk_widget_show (image); + + name = gimp_image_get_name (image_ID); + + label = gtk_label_new (name); + gtk_label_set_ellipsize (GTK_LABEL (label), PANGO_ELLIPSIZE_MIDDLE); + gimp_label_set_attributes (GTK_LABEL (label), + PANGO_ATTR_STYLE, PANGO_STYLE_ITALIC, + -1); + gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0); + gtk_widget_show (label); + + g_free (name); + } + + vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12); + gtk_box_pack_start (GTK_BOX (hbox), vbox, TRUE, TRUE, 0); + gtk_widget_show (vbox); + + label = g_object_new (GTK_TYPE_LABEL, + "label", _("According to the EXIF data, " + "this image is rotated."), + "wrap", TRUE, + "justify", GTK_JUSTIFY_LEFT, + "xalign", 0.0, + "yalign", 0.5, + NULL); + gimp_label_set_attributes (GTK_LABEL (label), + PANGO_ATTR_SCALE, PANGO_SCALE_LARGE, + PANGO_ATTR_WEIGHT, PANGO_WEIGHT_BOLD, + -1); + gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0); + gtk_widget_show (label); + + label = g_object_new (GTK_TYPE_LABEL, + "label", _("Would you like GIMP to rotate it " + "into the standard orientation?"), + "wrap", TRUE, + "justify", GTK_JUSTIFY_LEFT, + "xalign", 0.0, + "yalign", 0.5, + NULL); + gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0); + gtk_widget_show (label); + + toggle = gtk_check_button_new_with_mnemonic (_("_Don't ask me again")); + gtk_box_pack_end (GTK_BOX (vbox), toggle, FALSE, FALSE, 0); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), FALSE); + gtk_widget_show (toggle); + + response = gimp_dialog_run (GIMP_DIALOG (dialog)); + + if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (toggle))) + { + GimpParasite *parasite; + const gchar *str = (response == GTK_RESPONSE_OK) ? "yes" : "no"; + + parasite = gimp_parasite_new (parasite_name, + GIMP_PARASITE_PERSISTENT, + strlen (str), str); + gimp_attach_parasite (parasite); + gimp_parasite_free (parasite); + } + + gtk_widget_destroy (dialog); + + return (response == GTK_RESPONSE_OK); +} diff --git a/libgimp/gimpmetadata.h b/libgimp/gimpmetadata.h new file mode 100644 index 0000000000..a2f9574e62 --- /dev/null +++ b/libgimp/gimpmetadata.h @@ -0,0 +1,48 @@ +/* LIBGIMP - The GIMP Library + * Copyright (C) 1995-2000 Peter Mattis and Spencer Kimball + * + * gimpmetadata.h + * + * 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 + * . + */ + +#if !defined (__GIMP_UI_H_INSIDE__) && !defined (GIMP_COMPILATION) +#error "Only can be included directly." +#endif + +#ifndef __LIBGIMP_GIMP_METADATA_H__ +#define __LIBGIMP_GIMP_METADATA_H__ + +G_BEGIN_DECLS + +/* For information look into the C source or the html documentation */ + + +void gimp_image_metadata_load (gint32 image_ID, + const gchar *mime_type, + GFile *file, + gboolean interactive); +GimpMetadata * gimp_image_metadata_save_prepare (gint32 image_ID, + const gchar *mime_type); +gboolean gimp_image_metadata_save_finish (gint32 image_ID, + const gchar *mime_type, + GimpMetadata *metadata, + GFile *file, + GimpMetadataSaveFlags flags, + GError **error); + +G_END_DECLS + +#endif /* ___LIBGIMP_GIMP_METADATA_H__ */ diff --git a/libgimp/gimpui.def b/libgimp/gimpui.def index f9ad3af85d..d50f081946 100644 --- a/libgimp/gimpui.def +++ b/libgimp/gimpui.def @@ -38,6 +38,9 @@ EXPORTS gimp_image_combo_box_get_type gimp_image_combo_box_new gimp_image_menu_new + gimp_image_metadata_load + gimp_image_metadata_save_prepare + gimp_image_metadata_save_finish gimp_layer_combo_box_get_type gimp_layer_combo_box_new gimp_layer_menu_new diff --git a/libgimp/gimpui.h b/libgimp/gimpui.h index 84fdea95b1..85c5ea5766 100644 --- a/libgimp/gimpui.h +++ b/libgimp/gimpui.h @@ -29,6 +29,7 @@ #include #include +#include #include #include #include diff --git a/libgimpbase/Makefile.am b/libgimpbase/Makefile.am index 59a258bd6e..ffc8b3efa1 100644 --- a/libgimpbase/Makefile.am +++ b/libgimpbase/Makefile.am @@ -61,6 +61,7 @@ AM_CPPFLAGS = \ -DGIMP_BASE_COMPILATION \ -I$(top_srcdir) \ $(GIO_CFLAGS) \ + $(GEXIV2_CFLAGS) \ $(BINRELOC_CFLAGS) \ -I$(includedir) \ $(xobjective_c) @@ -101,6 +102,8 @@ libgimpbase_sources = \ gimpenv.h \ gimpmemsize.c \ gimpmemsize.h \ + gimpmetadata.c \ + gimpmetadata.h \ gimpparasite.c \ gimpparasite.h \ gimpparasiteio.c \ @@ -144,6 +147,7 @@ libgimpbaseinclude_HEADERS = \ gimpdatafiles.h \ gimpenv.h \ gimpmemsize.h \ + gimpmetadata.h \ gimpparasite.h \ gimpparasiteio.h \ gimprectangle.h \ @@ -152,7 +156,6 @@ libgimpbaseinclude_HEADERS = \ gimputils.h \ gimpvaluearray.h - libgimpbase_@GIMP_API_VERSION@_la_LDFLAGS = \ -version-info $(LT_VERSION_INFO) \ $(no_undefined) \ @@ -163,9 +166,9 @@ libgimpbase_@GIMP_API_VERSION@_la_DEPENDENCIES = $(gimpbase_def) libgimpbase_@GIMP_API_VERSION@_la_LIBADD = \ $(GIO_LIBS) \ + $(GEXIV2_LIBS) \ $(ole32_lib) - install-data-local: install-ms-lib install-libtool-import-lib uninstall-local: uninstall-ms-lib uninstall-libtool-import-lib diff --git a/libgimpbase/gimpbase.def b/libgimpbase/gimpbase.def index a6330e0939..d2bfa5a9a2 100644 --- a/libgimpbase/gimpbase.def +++ b/libgimpbase/gimpbase.def @@ -50,6 +50,19 @@ EXPORTS gimp_memsize_serialize gimp_memsize_to_string gimp_message_handler_type_get_type + gimp_metadata_deserialize + gimp_metadata_duplicate + gimp_metadata_get_resolution + gimp_metadata_is_tag_supported + gimp_metadata_load_from_file + gimp_metadata_new + gimp_metadata_save_to_file + gimp_metadata_serialize + gimp_metadata_set_bits_per_sample + gimp_metadata_set_from_exif + gimp_metadata_set_from_xmp + gimp_metadata_set_pixel_size + gimp_metadata_set_resolution gimp_micro_version gimp_minor_version gimp_paint_application_mode_get_type @@ -100,7 +113,7 @@ EXPORTS gimp_sysconf_directory gimp_text_direction_get_type gimp_text_hint_style_get_type - gimp_text_justification_get_type + gimp_text_justification_get_type gimp_transfer_mode_get_type gimp_transform_direction_get_type gimp_transform_resize_get_type diff --git a/libgimpbase/gimpbase.h b/libgimpbase/gimpbase.h index 08930dc76d..a5682e7e98 100644 --- a/libgimpbase/gimpbase.h +++ b/libgimpbase/gimpbase.h @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include diff --git a/libgimpbase/gimpbasetypes.h b/libgimpbase/gimpbasetypes.h index 481c1cdbb9..bef9904842 100644 --- a/libgimpbase/gimpbasetypes.h +++ b/libgimpbase/gimpbasetypes.h @@ -55,6 +55,8 @@ typedef struct _GimpValueArray GimpValueArray; typedef void (* GimpDatafileLoaderFunc) (const GimpDatafileData *file_data, gpointer user_data); +typedef struct _GExiv2Metadata GimpMetadata; + /** * GimpEnumDesc: diff --git a/libgimpbase/gimpmetadata.c b/libgimpbase/gimpmetadata.c new file mode 100644 index 0000000000..c2b8f3e2df --- /dev/null +++ b/libgimpbase/gimpmetadata.c @@ -0,0 +1,945 @@ +/* LIBGIMPBASE - The GIMP Basic Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimpmetadata.c + * Copyright (C) 2013 Hartmut Kuhse + * + * 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 + * . + */ + +#include "config.h" + +#include +#include + +#include +#include + +#include "gimpbasetypes.h" + +#include "gimpmetadata.h" +#include "gimpunit.h" + +#include "libgimp/libgimp-intl.h" + + +#define TAG_LINE_DELIMITER "\v" +#define TAG_TAG_DELIMITER "#" + + +static GQuark gimp_metadata_error_quark (void); +static gint gimp_metadata_length (const gchar *testline, + const gchar *delim); +static gboolean gimp_metadata_get_rational (const gchar *value, + gint sections, + gchar ***numerator, + gchar ***denominator); +static void gimp_metadata_add (GimpMetadata *src, + GimpMetadata *dest); + + +static const gchar *tiff_tags[] = +{ + "Xmp.tiff", + "Exif.Image.ImageWidth", + "Exif.Image.ImageLength", + "Exif.Image.BitsPerSample", + "Exif.Image.Compression", + "Exif.Image.PhotometricInterpretation", + "Exif.Image.FillOrder", + "Exif.Image.SamplesPerPixel", + "Exif.Image.StripOffsets", + "Exif.Image.RowsPerStrip", + "Exif.Image.StripByteCounts", + "Exif.Image.PlanarConfiguration" +}; + +static const gchar *jpeg_tags[] = +{ + "Exif.Image.JPEGProc", + "Exif.Image.JPEGInterchangeFormat", + "Exif.Image.JPEGInterchangeFormatLength", + "Exif.Image.JPEGRestartInterval", + "Exif.Image.JPEGLosslessPredictors", + "Exif.Image.JPEGPointTransforms", + "Exif.Image.JPEGQTables", + "Exif.Image.JPEGDCTables", + "Exif.Image.JPEGACTables" +}; + +static const gchar *unsupported_tags[] = +{ + "Exif.Image.SubIFDs", + "Exif.Image.ClipPath", + "Exif.Image.XClipPathUnits", + "Exif.Image.YClipPathUnits", + "Xmp.xmpMM.History", + "Exif.Image.XPTitle", + "Exif.Image.XPComment", + "Exif.Image.XPAuthor", + "Exif.Image.XPKeywords", + "Exif.Image.XPSubject", + "Exif.Image.DNGVersion", + "Exif.Image.DNGBackwardVersion", + "Exif.Iop" +}; + +static const guint8 minimal_exif[] = +{ + 0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10, 0x4a, 0x46, 0x49, 0x46, 0x00, 0x01, + 0x01, 0x01, 0x00, 0x5a, 0x00, 0x5a, 0x00, 0x00, 0xff, 0xe1 +}; + +static const guint8 wilber_jpg[] = +{ + 0xff, 0xd8, 0xff, 0xe0, 0x00, 0x10, 0x4a, 0x46, 0x49, 0x46, 0x00, 0x01, + 0x01, 0x01, 0x00, 0x5a, 0x00, 0x5a, 0x00, 0x00, 0xff, 0xdb, 0x00, 0x43, + 0x00, 0x50, 0x37, 0x3c, 0x46, 0x3c, 0x32, 0x50, 0x46, 0x41, 0x46, 0x5a, + 0x55, 0x50, 0x5f, 0x78, 0xc8, 0x82, 0x78, 0x6e, 0x6e, 0x78, 0xf5, 0xaf, + 0xb9, 0x91, 0xc8, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xdb, 0x00, 0x43, 0x01, 0x55, 0x5a, + 0x5a, 0x78, 0x69, 0x78, 0xeb, 0x82, 0x82, 0xeb, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xc0, 0x00, 0x11, 0x08, 0x00, 0x10, 0x00, 0x10, 0x03, + 0x01, 0x22, 0x00, 0x02, 0x11, 0x01, 0x03, 0x11, 0x01, 0xff, 0xc4, 0x00, + 0x16, 0x00, 0x01, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x01, 0x02, 0xff, 0xc4, 0x00, + 0x1e, 0x10, 0x00, 0x01, 0x05, 0x00, 0x02, 0x03, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x02, 0x03, 0x11, 0x31, + 0x04, 0x12, 0x51, 0x61, 0x71, 0xff, 0xc4, 0x00, 0x14, 0x01, 0x01, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0xff, 0xc4, 0x00, 0x14, 0x11, 0x01, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0xff, 0xda, 0x00, 0x0c, 0x03, 0x01, 0x00, 0x02, 0x11, 0x03, 0x11, + 0x00, 0x3f, 0x00, 0x18, 0xa0, 0x0e, 0x6d, 0xbc, 0xf5, 0xca, 0xf7, 0x78, + 0xb6, 0xfe, 0x3b, 0x23, 0xb2, 0x1d, 0x64, 0x68, 0xf0, 0x8a, 0x39, 0x4b, + 0x74, 0x9c, 0xa5, 0x5f, 0x35, 0x8a, 0xb2, 0x7e, 0xa0, 0xff, 0xd9, 0x00 +}; + +static const guint wilber_jpg_len = G_N_ELEMENTS (wilber_jpg); + + +GimpMetadata * +gimp_metadata_new (void) +{ + GExiv2Metadata *metadata = NULL; + + if (gexiv2_initialize ()) + { + metadata = gexiv2_metadata_new (); + + if (! gexiv2_metadata_open_buf (metadata, wilber_jpg, wilber_jpg_len, + NULL)) + { + g_object_unref (metadata); + + return NULL; + } + } + + return metadata; +} + +GimpMetadata * +gimp_metadata_duplicate (GimpMetadata *metadata) +{ + GimpMetadata *new_metadata = NULL; + + g_return_val_if_fail (metadata == NULL || GEXIV2_IS_METADATA (metadata), NULL); + + if (metadata) + { + gchar *xml; + + xml = gimp_metadata_serialize (metadata); + new_metadata = gimp_metadata_deserialize (xml); + g_free (xml); + } + + return new_metadata; +} + +typedef struct +{ + gchar name[1024]; + GimpMetadata *metadata; +} GimpMetadataParseData; + +static const gchar* +gimp_metadata_attribute_name_to_value (const gchar **attribute_names, + const gchar **attribute_values, + const gchar *name) +{ + while (*attribute_names) + { + if (! strcmp (*attribute_names, name)) + { + return *attribute_values; + } + + attribute_names++; + attribute_values++; + } + + return NULL; +} + +static void +gimp_metadata_deserialize_start_element (GMarkupParseContext *context, + const gchar *element_name, + const gchar **attribute_names, + const gchar **attribute_values, + gpointer user_data, + GError **error) +{ + GimpMetadataParseData *parse_data = user_data; + + if (! strcmp (element_name, "tag")) + { + const gchar *name; + + name = gimp_metadata_attribute_name_to_value (attribute_names, + attribute_values, + "name"); + + if (! name) + { + g_set_error (error, gimp_metadata_error_quark (), 1001, + "Element 'tag' not contain required attribute 'name'."); + return; + } + + strncpy (parse_data->name, name, sizeof (parse_data->name)); + parse_data->name[sizeof (parse_data->name) - 1] = 0; + } +} + +static void +gimp_metadata_deserialize_end_element (GMarkupParseContext *context, + const gchar *element_name, + gpointer user_data, + GError **error) +{ +} + +static void +gimp_metadata_deserialize_text (GMarkupParseContext *context, + const gchar *text, + gsize text_len, + gpointer user_data, + GError **error) +{ + GimpMetadataParseData *parse_data = user_data; + const gchar *current_element; + + current_element = g_markup_parse_context_get_element (context); + + if (! g_strcmp0 (current_element, "tag")) + { + gchar *value = g_strndup (text, text_len); + + gexiv2_metadata_set_tag_string (parse_data->metadata, + parse_data->name, + value); + + g_free (value); + } +} + +static void +gimp_metadata_deserialize_error (GMarkupParseContext *context, + GError *error, + gpointer user_data) +{ + g_printerr ("Metadata parse error: %s\n", error->message); +} + +/** + * Deserializes metadata from a string + **/ +GExiv2Metadata * +gimp_metadata_deserialize (const gchar *metadata_xml) +{ + GimpMetadata *metadata; + GMarkupParser markup_parser; + GimpMetadataParseData parse_data; + GMarkupParseContext *context; + + g_return_val_if_fail (metadata_xml != NULL, NULL); + + metadata = gimp_metadata_new (); + + parse_data.metadata = metadata; + + markup_parser.start_element = gimp_metadata_deserialize_start_element; + markup_parser.end_element = gimp_metadata_deserialize_end_element; + markup_parser.text = gimp_metadata_deserialize_text; + markup_parser.passthrough = NULL; + markup_parser.error = gimp_metadata_deserialize_error; + + context = g_markup_parse_context_new (&markup_parser, 0, &parse_data, NULL); + + g_markup_parse_context_parse (context, + metadata_xml, strlen (metadata_xml), + NULL); + + g_markup_parse_context_unref (context); + + return metadata; +} + +/** + * Serializing metadata as a string + */ +gchar * +gimp_metadata_serialize (GimpMetadata *metadata) +{ + GString *string; + gchar **exif_data = NULL; + gchar **iptc_data = NULL; + gchar **xmp_data = NULL; + gchar *value; + gchar *escaped; + gint i; + + g_return_val_if_fail (GEXIV2_IS_METADATA (metadata), NULL); + + string = g_string_new (NULL); + + g_string_append (string, "\n"); + g_string_append (string, "\n"); + + exif_data = gexiv2_metadata_get_exif_tags (metadata); + + if (exif_data) + { + for (i = 0; exif_data[i] != NULL; i++) + { + value = gexiv2_metadata_get_tag_string (metadata, exif_data[i]); + escaped = g_markup_escape_text (value, -1); + + g_string_append_printf (string, " %s\n", + exif_data[i], escaped); + + g_free (escaped); + g_free (value); + } + + g_strfreev (exif_data); + } + + xmp_data = gexiv2_metadata_get_xmp_tags (metadata); + + if (xmp_data) + { + for (i = 0; xmp_data[i] != NULL; i++) + { + value = gexiv2_metadata_get_tag_string (metadata, xmp_data[i]); + escaped = g_markup_escape_text (value, -1); + + g_string_append_printf (string, " %s\n", + xmp_data[i], escaped); + + g_free (escaped); + g_free (value); + } + + g_strfreev (xmp_data); + } + + iptc_data = gexiv2_metadata_get_iptc_tags (metadata); + + if (iptc_data) + { + for (i = 0; iptc_data[i] != NULL; i++) + { + value = gexiv2_metadata_get_tag_string (metadata, iptc_data[i]); + escaped = g_markup_escape_text (value, -1); + + g_string_append_printf (string, " %s\n", + iptc_data[i], escaped); + + g_free (escaped); + g_free (value); + } + + g_strfreev (iptc_data); + } + + g_string_append (string, "\n"); + + return g_string_free (string, FALSE); +} + +/* + * reads metadata from a physical file + */ +GimpMetadata * +gimp_metadata_load_from_file (GFile *file, + GError **error) +{ + GExiv2Metadata *meta = NULL; + gchar *path; + gchar *filename; + + g_return_val_if_fail (G_IS_FILE (file), NULL); + g_return_val_if_fail (error == NULL || *error == NULL, NULL); + + path = g_file_get_path (file); + + if (! path) + { + g_set_error (error, gimp_metadata_error_quark (), 0, + _("Can load metadata only from local files")); + return NULL; + } + +#ifdef G_OS_WIN32 + filename = g_win32_locale_filename_from_utf8 (path); +#else + filename = g_strdup (path); +#endif + + g_free (path); + + if (gexiv2_initialize ()) + { + meta = gexiv2_metadata_new (); + + if (! gexiv2_metadata_open_path (meta, filename, error)) + { + g_object_unref (meta); + g_free (filename); + + return NULL; + } + } + + g_free (filename); + + return meta; +} + +/* + * saves metadata to a physical file + */ +gboolean +gimp_metadata_save_to_file (GimpMetadata *metadata, + GFile *file, + GError **error) +{ + gchar *path; + gchar *filename; + gboolean success; + + 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); + + path = g_file_get_path (file); + + if (! path) + { + g_set_error (error, gimp_metadata_error_quark (), 0, + _("Can save metadata only from to files")); + return FALSE; + } + +#ifdef G_OS_WIN32 + filename = g_win32_locale_filename_from_utf8 (path); +#else + filename = g_strdup (path); +#endif + + g_free (path); + + success = gexiv2_metadata_save_file (metadata, filename, error); + + g_free (filename); + + return success; +} + +gboolean +gimp_metadata_set_from_exif (GimpMetadata *metadata, + const guchar *exif_data, + gint exif_data_length, + GError **error) +{ + + GByteArray *exif_bytes; + GimpMetadata *exif_metadata; + guint8 data_size[2] = { 0, }; + + g_return_val_if_fail (GEXIV2_IS_METADATA (metadata), FALSE); + g_return_val_if_fail (exif_data != NULL, FALSE); + g_return_val_if_fail (exif_data_length > 0, FALSE); + g_return_val_if_fail (exif_data_length < 65536, FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + data_size[0] = (exif_data_length & 0xFF00) >> 8; + data_size[1] = (exif_data_length & 0x00FF); + + exif_bytes = g_byte_array_new (); + exif_bytes = g_byte_array_append (exif_bytes, + minimal_exif, G_N_ELEMENTS (minimal_exif)); + exif_bytes = g_byte_array_append (exif_bytes, + data_size, 2); + exif_bytes = g_byte_array_append (exif_bytes, + (guint8 *) exif_data, exif_data_length); + + exif_metadata = gimp_metadata_new (); + + if (! gexiv2_metadata_open_buf (exif_metadata, + exif_bytes->data, exif_bytes->len, error)) + { + g_object_unref (exif_metadata); + g_byte_array_free (exif_bytes, TRUE); + return FALSE; + } + + if (! gexiv2_metadata_has_exif (exif_metadata)) + { + g_set_error (error, gimp_metadata_error_quark (), 0, + _("Parsing EXIF data failed.")); + g_object_unref (exif_metadata); + g_byte_array_free (exif_bytes, TRUE); + return FALSE; + } + + gimp_metadata_add (exif_metadata, metadata); + g_object_unref (exif_metadata); + g_byte_array_free (exif_bytes, TRUE); + + return TRUE; +} + +gboolean +gimp_metadata_set_from_xmp (GimpMetadata *metadata, + const guchar *xmp_data, + gint xmp_data_length, + GError **error) +{ + GimpMetadata *xmp_metadata; + + g_return_val_if_fail (GEXIV2_IS_METADATA (metadata), FALSE); + g_return_val_if_fail (xmp_data != NULL, FALSE); + g_return_val_if_fail (xmp_data_length > 0, FALSE); + g_return_val_if_fail (error == NULL || *error == NULL, FALSE); + + xmp_data += 10; + xmp_data_length -= 10; + + xmp_metadata = gimp_metadata_new (); + + if (! gexiv2_metadata_open_buf (xmp_metadata, + xmp_data, xmp_data_length, error)) + { + g_object_unref (xmp_metadata); + return FALSE; + } + + if (! gexiv2_metadata_has_xmp (xmp_metadata)) + { + g_set_error (error, gimp_metadata_error_quark (), 0, + _("Parsing XMP data failed.")); + g_object_unref (xmp_metadata); + return FALSE; + } + + gimp_metadata_add (xmp_metadata, metadata); + g_object_unref (xmp_metadata); + + return TRUE; +} + +void +gimp_metadata_set_pixel_size (GimpMetadata *metadata, + gint width, + gint height) +{ + gchar buffer[32]; + + g_return_if_fail (GEXIV2_IS_METADATA (metadata)); + + g_snprintf (buffer, sizeof (buffer), "%d", width); + gexiv2_metadata_set_tag_string (metadata, "Exif.Image.ImageWidth", buffer); + + g_snprintf (buffer, sizeof (buffer), "%d", height); + gexiv2_metadata_set_tag_string (metadata, "Exif.Image.ImageLength", buffer); +} + +void +gimp_metadata_set_bits_per_sample (GimpMetadata *metadata, + gint bps) +{ + gchar buffer[32]; + + g_return_if_fail (GEXIV2_IS_METADATA (metadata)); + + g_snprintf (buffer, sizeof (buffer), "%d %d %d", bps, bps, bps); + gexiv2_metadata_set_tag_string (metadata, "Exif.Image.BitsPerSample", buffer); +} + +/** + * gets exif resolution + */ +gboolean +gimp_metadata_get_resolution (GimpMetadata *metadata, + gdouble *xres, + gdouble *yres, + GimpUnit *unit) +{ + gchar *xr; + gchar *yr; + gchar *un; + gint exif_unit = 2; + gchar **xnom = NULL; + gchar **xdenom = NULL; + gchar **ynom = NULL; + gchar **ydenom = NULL; + + g_return_val_if_fail (GEXIV2_IS_METADATA (metadata), FALSE); + + xr = gexiv2_metadata_get_tag_string (metadata, "Exif.Image.XResolution"); + yr = gexiv2_metadata_get_tag_string (metadata, "Exif.Image.YResolution"); + + if (! (xr && yr)) + { + g_free (xr); + g_free (yr); + return FALSE; + } + + un = gexiv2_metadata_get_tag_string (metadata, "Exif.Image.ResolutionUnit"); + + if (un) + { + exif_unit = atoi (un); + g_free (un); + } + + if (exif_unit == 3) + *unit = GIMP_UNIT_MM; + else + *unit = GIMP_UNIT_INCH; + + if (gimp_metadata_get_rational (xr, 1, &xnom, &xdenom)) + { + gdouble x1 = g_ascii_strtod (xnom[0], NULL); + gdouble x2 = g_ascii_strtod (xdenom[0], NULL); + gdouble xrd; + + if (x2 == 0.0) + return FALSE; + + xrd = x1 / x2; + + if (exif_unit == 3) + xrd *= 2.54; + + *xres = xrd; + } + + if (gimp_metadata_get_rational (yr, 1, &ynom, &ydenom)) + { + gdouble y1 = g_ascii_strtod (ynom[0], NULL); + gdouble y2 = g_ascii_strtod (ydenom[0], NULL); + gdouble yrd; + + if (y2 == 0.0) + return FALSE; + + yrd = y1 / y2; + + if (exif_unit == 3) + yrd *= 2.54; + + *yres = yrd; + } + + g_free (xr); + g_free (yr); + + g_strfreev (xnom); + g_strfreev (xdenom); + g_strfreev (ynom); + g_strfreev (ydenom); + + return TRUE; +} + +/** + * sets exif resolution + */ +void +gimp_metadata_set_resolution (GimpMetadata *metadata, + gdouble xres, + gdouble yres, + GimpUnit unit) +{ + gchar buffer[32]; + gint exif_unit; + + g_return_if_fail (GEXIV2_IS_METADATA (metadata)); + + if (gimp_unit_is_metric (unit)) + { + xres /= 2.54; + yres /= 2.54; + + exif_unit = 3; + } + else + { + exif_unit = 2; + } + + g_ascii_formatd (buffer, sizeof (buffer), "%.0f/1", xres); + gexiv2_metadata_set_tag_string (metadata, "Exif.Image.XResolution", buffer); + + g_ascii_formatd (buffer, sizeof (buffer), "%.0f/1", yres); + gexiv2_metadata_set_tag_string (metadata, "Exif.Image.YResolution", buffer); + + g_snprintf (buffer, sizeof (buffer), "%d", exif_unit); + gexiv2_metadata_set_tag_string (metadata, "Exif.Image.ResolutionUnit", buffer); +} + +/** + * checks for supported tags + */ +gboolean +gimp_metadata_is_tag_supported (const gchar *tag, + const gchar *mime_type) +{ + gint j; + + g_return_val_if_fail (tag != NULL, FALSE); + g_return_val_if_fail (mime_type != NULL, FALSE); + + for (j = 0; j < G_N_ELEMENTS (unsupported_tags); j++) + { + if (g_str_has_prefix (tag, unsupported_tags[j])) + { + return FALSE; + } + } + + if (! strcmp (mime_type, "image/jpeg")) + { + for (j = 0; j < G_N_ELEMENTS (tiff_tags); j++) + { + if (g_str_has_prefix (tag, tiff_tags[j])) + { + return FALSE; + } + } + } + else if (! strcmp (mime_type, "image/tiff")) + { + for (j = 0; j < G_N_ELEMENTS (jpeg_tags); j++) + { + if (g_str_has_prefix (tag, jpeg_tags[j])) + { + return FALSE; + } + } + } + + return TRUE; +} + + +/* private functions */ + +static GQuark +gimp_metadata_error_quark (void) +{ + static GQuark quark = 0; + + if (G_UNLIKELY (quark == 0)) + quark = g_quark_from_static_string ("gimp-metadata-error-quark"); + + return quark; +} + +/** + * determines the amount of delimiters in serialized + * metadata string + */ +static gint +gimp_metadata_length (const gchar *testline, + const gchar *delim) +{ + gchar *delim_test; + gint i; + gint sum; + + delim_test = g_strdup (testline); + + sum =0; + + for (i=0; i < strlen (delim_test); i++) + { + if (delim_test[i] == delim[0]) + sum++; + } + + g_free (delim_test); + + return sum; +} + +/** + * gets rational values from string + */ +static gboolean +gimp_metadata_get_rational (const gchar *value, + gint sections, + gchar ***numerator, + gchar ***denominator) +{ + GSList *nomlist = NULL; + GSList *denomlist = NULL; + + GSList *nlist, *dlist; + + gchar sect[] = " "; + gchar rdel[] = "/"; + gchar **sects; + gchar **nom = NULL; + gint i; + gint n; + + gchar **num; + gchar **den; + + if (! value) + return FALSE; + + if (gimp_metadata_length (value, sect) == (sections -1)) + { + i = 0; + sects = g_strsplit (value, sect, -1); + while (sects[i] != NULL) + { + if(gimp_metadata_length (sects[i], rdel) == 1) + { + nom = g_strsplit (sects[i], rdel, -1); + nomlist = g_slist_prepend (nomlist, g_strdup (nom[0])); + denomlist = g_slist_prepend (denomlist, g_strdup (nom[1])); + } + else + { + return FALSE; + } + i++; + } + } + else + { + return FALSE; + } + + n = i; + + num = g_new0 (gchar*, i + 1); + den = g_new0 (gchar*, n + 1); + + for (nlist = nomlist; nlist; nlist = nlist->next) + num[--i] = nlist->data; + + for (dlist = denomlist; dlist; dlist = dlist->next) + den[--n] = dlist->data; + + *numerator = num; + *denominator = den; + + g_slist_free (nomlist); + g_slist_free (denomlist); + + g_strfreev (sects); + g_strfreev (nom); + + return TRUE; +} + +static void +gimp_metadata_add (GimpMetadata *src, + GimpMetadata *dest) +{ + gchar *value; + gint i; + + if (gexiv2_metadata_get_supports_exif (src) && + gexiv2_metadata_get_supports_exif (dest)) + { + gchar **exif_data = gexiv2_metadata_get_exif_tags (src); + + if (exif_data) + { + for (i = 0; exif_data[i] != NULL; i++) + { + value = gexiv2_metadata_get_tag_string (src, exif_data[i]); + gexiv2_metadata_set_tag_string (dest, exif_data[i], value); + g_free (value); + } + + g_strfreev (exif_data); + } + } + + + if (gexiv2_metadata_get_supports_xmp (src) && + gexiv2_metadata_get_supports_xmp (dest)) + { + gchar **xmp_data = gexiv2_metadata_get_xmp_tags (src); + + if (xmp_data) + { + for (i = 0; xmp_data[i] != NULL; i++) + { + value = gexiv2_metadata_get_tag_string (src, xmp_data[i]); + gexiv2_metadata_set_tag_string (dest, xmp_data[i], value); + g_free (value); + } + + g_strfreev (xmp_data); + } + } + + if (gexiv2_metadata_get_supports_iptc (src) && + gexiv2_metadata_get_supports_iptc (dest)) + { + gchar **iptc_data = gexiv2_metadata_get_iptc_tags (src); + + if (iptc_data) + { + for (i = 0; iptc_data[i] != NULL; i++) + { + value = gexiv2_metadata_get_tag_string (src, iptc_data[i]); + gexiv2_metadata_set_tag_string (dest, iptc_data[i], value); + g_free (value); + } + + g_strfreev (iptc_data); + } + } +} diff --git a/libgimpbase/gimpmetadata.h b/libgimpbase/gimpmetadata.h new file mode 100644 index 0000000000..aefab9f470 --- /dev/null +++ b/libgimpbase/gimpmetadata.h @@ -0,0 +1,82 @@ +/* LIBGIMPBASE - The GIMP Basic Library + * Copyright (C) 1995-1997 Peter Mattis and Spencer Kimball + * + * gimpmetadata.h + * Copyright (C) 2013 Hartmut Kuhse + * + * 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 + * . + */ + +#ifndef __GIMP_METADATA_H__ +#define __GIMP_METADATA_H__ + +G_BEGIN_DECLS + + +typedef enum +{ + GIMP_METADATA_SAVE_EXIF = 1 << 0, + GIMP_METADATA_SAVE_XMP = 1 << 1, + GIMP_METADATA_SAVE_IPTC = 1 << 2, + GIMP_METADATA_SAVE_THUMBNAIL = 1 << 3, + + GIMP_METADATA_SAVE_ALL = (GIMP_METADATA_SAVE_EXIF | + GIMP_METADATA_SAVE_XMP | + GIMP_METADATA_SAVE_IPTC | + GIMP_METADATA_SAVE_THUMBNAIL) +} GimpMetadataSaveFlags; + +GimpMetadata * gimp_metadata_new (void); +GimpMetadata * gimp_metadata_duplicate (GimpMetadata *metadata); + +GimpMetadata * gimp_metadata_deserialize (const gchar *metadata_xml); +gchar * gimp_metadata_serialize (GimpMetadata *metadata); + +GimpMetadata * gimp_metadata_load_from_file (GFile *file, + GError **error); +gboolean gimp_metadata_save_to_file (GimpMetadata *metadata, + GFile *file, + GError **error); + +gboolean gimp_metadata_set_from_exif (GimpMetadata *metadata, + const guchar *exif_data, + gint exif_data_length, + GError **error); +gboolean gimp_metadata_set_from_xmp (GimpMetadata *metadata, + const guchar *xmp_data, + gint xmp_data_length, + GError **error); + +void gimp_metadata_set_pixel_size (GimpMetadata *metadata, + gint width, + gint height); +void gimp_metadata_set_bits_per_sample (GimpMetadata *metadata, + gint bits_per_sample); + +gboolean gimp_metadata_get_resolution (GimpMetadata *metadata, + gdouble *xres, + gdouble *yres, + GimpUnit *unit); +void gimp_metadata_set_resolution (GimpMetadata *metadata, + gdouble xres, + gdouble yres, + GimpUnit unit); + +gboolean gimp_metadata_is_tag_supported (const gchar *tag, + const gchar *mime_type); + +G_END_DECLS + +#endif /* __GIMP_METADATA_H__ */ diff --git a/plug-ins/Makefile.am b/plug-ins/Makefile.am index 50eb64507d..38cc20f24d 100644 --- a/plug-ins/Makefile.am +++ b/plug-ins/Makefile.am @@ -51,11 +51,6 @@ else file_uri = file-uri endif -if HAVE_LIBEXIF -metadata = metadata -endif - - SUBDIRS = \ $(script_fu) \ $(pygimp) \ @@ -83,7 +78,6 @@ SUBDIRS = \ lighting \ map-object \ maze \ - $(metadata) \ pagecurl \ $(print) \ selection-to-path \ diff --git a/plug-ins/common/.gitignore b/plug-ins/common/.gitignore index 6cb37a869d..917b863d8a 100644 --- a/plug-ins/common/.gitignore +++ b/plug-ins/common/.gitignore @@ -182,6 +182,8 @@ /mail.exe /max-rgb /max-rgb.exe +/metadata +/metadata.exe /newsprint /newsprint.exe /nl-filter diff --git a/plug-ins/common/Makefile.am b/plug-ins/common/Makefile.am index 441aae78ba..06db0d2083 100644 --- a/plug-ins/common/Makefile.am +++ b/plug-ins/common/Makefile.am @@ -134,6 +134,7 @@ libexec_PROGRAMS = \ lens-flare \ $(MAIL) \ max-rgb \ + metadata \ newsprint \ nl-filter \ noise-rgb \ @@ -1090,13 +1091,15 @@ file_jp2_load_SOURCES = \ file-jp2-load.c file_jp2_load_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ $(libgimp) \ $(libgimpmath) \ $(libgimpconfig) \ $(libgimpcolor) \ $(libgimpbase) \ - $(CAIRO_LIBS) \ - $(GDK_PIXBUF_LIBS) \ + $(GTK_LIBS) \ $(GEGL_LIBS) \ $(JP2_LIBS) \ $(RT_LIBS) \ @@ -1773,6 +1776,26 @@ max_rgb_LDADD = \ $(INTLLIBS) \ $(max_rgb_RC) +metadata_CFLAGS = $(GEXIV2_CFLAGS) + +metadata_SOURCES = \ + metadata.c + +metadata_LDADD = \ + $(libgimpui) \ + $(libgimpwidgets) \ + $(libgimpmodule) \ + $(libgimp) \ + $(libgimpmath) \ + $(libgimpconfig) \ + $(libgimpcolor) \ + $(libgimpbase) \ + $(GTK_LIBS) \ + $(GEXIV2_LIBS) \ + $(RT_LIBS) \ + $(INTLLIBS) \ + $(metadata_RC) + newsprint_SOURCES = \ newsprint.c diff --git a/plug-ins/common/file-jp2-load.c b/plug-ins/common/file-jp2-load.c index e89b86096d..f346c29995 100644 --- a/plug-ins/common/file-jp2-load.c +++ b/plug-ins/common/file-jp2-load.c @@ -46,7 +46,8 @@ #include -#define LOAD_PROC "file-jp2-load" +#define LOAD_PROC "file-jp2-load" +#define PLUG_IN_BINARY "file-jp2-load" static void query (void); @@ -120,10 +121,13 @@ run (const gchar *name, GimpParam **return_vals) { static GimpParam values[2]; + GimpRunMode run_mode; GimpPDBStatusType status = GIMP_PDB_SUCCESS; gint image_ID; GError *error = NULL; + run_mode = param[0].data.d_int32; + INIT_I18N (); gegl_init (NULL, NULL); @@ -135,10 +139,31 @@ run (const gchar *name, if (strcmp (name, LOAD_PROC) == 0) { + gboolean interactive; + + switch (run_mode) + { + case GIMP_RUN_INTERACTIVE: + case GIMP_RUN_WITH_LAST_VALS: + gimp_ui_init (PLUG_IN_BINARY, FALSE); + interactive = TRUE; + break; + default: + interactive = FALSE; + break; + } + image_ID = load_image (param[1].data.d_string, &error); if (image_ID != -1) { + GFile *file = g_file_new_for_path (param[1].data.d_string); + + gimp_image_metadata_load (image_ID, "image/jp2", file, + interactive); + + g_object_unref (file); + *nreturn_vals = 2; values[1].type = GIMP_PDB_IMAGE; values[1].data.d_image = image_ID; diff --git a/plug-ins/common/file-png.c b/plug-ins/common/file-png.c index f6f8a602ca..2bbd7cdc98 100644 --- a/plug-ins/common/file-png.c +++ b/plug-ins/common/file-png.c @@ -88,6 +88,10 @@ typedef struct gboolean comment; gboolean save_transp_pixels; gint compression_level; + gboolean save_exif; + gboolean save_xmp; + gboolean save_iptc; + gboolean save_thumbnail; } PngSaveVals; @@ -104,6 +108,10 @@ typedef struct GtkWidget *comment; GtkWidget *save_transp_pixels; GtkAdjustment *compression_level; + GtkWidget *save_exif; + GtkWidget *save_xmp; + GtkWidget *save_iptc; + GtkWidget *save_thumbnail; } PngSaveGui; @@ -119,6 +127,7 @@ typedef struct } PngGlobals; + /* * Local functions... */ @@ -164,6 +173,7 @@ static void load_defaults (void); static void save_defaults (void); static void load_gui_defaults (PngSaveGui *pg); + /* * Globals... */ @@ -186,11 +196,15 @@ static const PngSaveVals defaults = TRUE, TRUE, TRUE, - 9 + 9, + TRUE, /* save exif */ + TRUE, /* save xmp */ + TRUE, /* save iptc */ + TRUE /* save thumbnail */ }; static PngSaveVals pngvals; -static PngGlobals pngg; +static PngGlobals pngg; /* @@ -406,6 +420,8 @@ run (const gchar *name, GimpExportReturn export = GIMP_EXPORT_CANCEL; GError *error = NULL; + run_mode = param[0].data.d_int32; + INIT_I18N (); gegl_init (NULL, NULL); @@ -417,13 +433,32 @@ run (const gchar *name, if (strcmp (name, LOAD_PROC) == 0) { - run_mode = param[0].data.d_int32; + gboolean interactive; + + switch (run_mode) + { + case GIMP_RUN_INTERACTIVE: + case GIMP_RUN_WITH_LAST_VALS: + gimp_ui_init (PLUG_IN_BINARY, FALSE); + interactive = TRUE; + break; + default: + interactive = FALSE; + break; + } image_ID = load_image (param[1].data.d_string, - run_mode == GIMP_RUN_INTERACTIVE, &error); + interactive, &error); if (image_ID != -1) { + GFile *file = g_file_new_for_path (param[1].data.d_string); + + gimp_image_metadata_load (image_ID, "image/png", file, + interactive); + + g_object_unref (file); + *nreturn_vals = 2; values[1].type = GIMP_PDB_IMAGE; values[1].data.d_image = image_ID; @@ -439,7 +474,6 @@ run (const gchar *name, { gboolean alpha; - run_mode = param[0].data.d_int32; image_ID = orig_image_ID = param[1].data.d_int32; drawable_ID = param[2].data.d_int32; @@ -549,6 +583,33 @@ run (const gchar *name, if (save_image (param[3].data.d_string, image_ID, drawable_ID, orig_image_ID, &error)) { + GimpMetadata *metadata; + + metadata = gimp_image_metadata_save_prepare (image_ID, + "image/png"); + + if (metadata) + { + GFile *file; + GimpMetadataSaveFlags flags = 0; + + gimp_metadata_set_bits_per_sample (metadata, 8); + + if (pngvals.save_exif) flags |= GIMP_METADATA_SAVE_EXIF; + if (pngvals.save_xmp) flags |= GIMP_METADATA_SAVE_XMP; + if (pngvals.save_iptc) flags |= GIMP_METADATA_SAVE_IPTC; + if (pngvals.save_thumbnail) flags |= GIMP_METADATA_SAVE_THUMBNAIL; + + file = g_file_new_for_path (param[3].data.d_string); + gimp_image_metadata_save_finish (image_ID, + "image/png", + metadata, file, flags, + NULL); + g_object_unref (file); + + g_object_unref (metadata); + } + gimp_set_data (SAVE_PROC, &pngvals, sizeof (pngvals)); } else @@ -587,6 +648,8 @@ run (const gchar *name, { if (nparams == 9) { + load_defaults (); + pngvals.interlaced = param[0].data.d_int32; pngvals.compression_level = param[1].data.d_int32; pngvals.bkgd = param[2].data.d_int32; @@ -2059,7 +2122,7 @@ save_dialog (gint32 image_ID, /* Table */ gtk_box_pack_start (GTK_BOX (gimp_export_dialog_get_content_area (dialog)), GTK_WIDGET (gtk_builder_get_object (builder, "table")), - TRUE, TRUE, 0); + FALSE, FALSE, 0); /* Toggles */ pg.interlaced = toggle_button_init (builder, "interlace", @@ -2080,6 +2143,18 @@ save_dialog (gint32 image_ID, pg.time = toggle_button_init (builder, "save-creation-time", pngvals.time, &pngvals.time); + pg.save_exif = toggle_button_init (builder, "sv_exif", + pngvals.save_exif, + &pngvals.save_exif); + pg.save_xmp = toggle_button_init (builder, "sv_xmp", + pngvals.save_xmp, + &pngvals.save_xmp); + pg.save_iptc = toggle_button_init (builder, "sv_iptc", + pngvals.save_iptc, + &pngvals.save_iptc); + pg.save_thumbnail = toggle_button_init (builder, "sv_thumbnail", + pngvals.save_thumbnail, + &pngvals.save_thumbnail); /* Comment toggle */ parasite = gimp_image_get_parasite (image_ID, "gimp-comment"); @@ -2150,12 +2225,15 @@ load_defaults (void) { GimpParasite *parasite; + /* initialize with hardcoded defaults */ + pngvals = defaults; + parasite = gimp_get_parasite (PNG_DEFAULTS_PARASITE); if (parasite) { gchar *def_str; - PngSaveVals tmpvals; + PngSaveVals tmpvals = defaults; gint num_fields; def_str = g_strndup (gimp_parasite_data (parasite), @@ -2163,7 +2241,7 @@ load_defaults (void) gimp_parasite_free (parasite); - num_fields = sscanf (def_str, "%d %d %d %d %d %d %d %d %d", + num_fields = sscanf (def_str, "%d %d %d %d %d %d %d %d %d %d %d %d %d", &tmpvals.interlaced, &tmpvals.bkgd, &tmpvals.gama, @@ -2172,18 +2250,17 @@ load_defaults (void) &tmpvals.time, &tmpvals.comment, &tmpvals.save_transp_pixels, - &tmpvals.compression_level); + &tmpvals.compression_level, + &tmpvals.save_exif, + &tmpvals.save_xmp, + &tmpvals.save_iptc, + &tmpvals.save_thumbnail); g_free (def_str); - if (num_fields == 9) - { - memcpy (&pngvals, &tmpvals, sizeof (tmpvals)); - return; - } + if (num_fields == 9 || num_fields == 13) + pngvals = tmpvals; } - - memcpy (&pngvals, &defaults, sizeof (defaults)); } static void @@ -2192,7 +2269,7 @@ save_defaults (void) GimpParasite *parasite; gchar *def_str; - def_str = g_strdup_printf ("%d %d %d %d %d %d %d %d %d", + def_str = g_strdup_printf ("%d %d %d %d %d %d %d %d %d %d %d %d %d", pngvals.interlaced, pngvals.bkgd, pngvals.gama, @@ -2201,7 +2278,11 @@ save_defaults (void) pngvals.time, pngvals.comment, pngvals.save_transp_pixels, - pngvals.compression_level); + pngvals.compression_level, + pngvals.save_exif, + pngvals.save_xmp, + pngvals.save_iptc, + pngvals.save_thumbnail); parasite = gimp_parasite_new (PNG_DEFAULTS_PARASITE, GIMP_PARASITE_PERSISTENT, @@ -2230,6 +2311,10 @@ load_gui_defaults (PngSaveGui *pg) SET_ACTIVE (time); SET_ACTIVE (comment); SET_ACTIVE (save_transp_pixels); + SET_ACTIVE (save_exif); + SET_ACTIVE (save_xmp); + SET_ACTIVE (save_iptc); + SET_ACTIVE (save_thumbnail); #undef SET_ACTIVE diff --git a/plug-ins/common/file-tiff-load.c b/plug-ins/common/file-tiff-load.c index b5d966c669..f0eb87512c 100644 --- a/plug-ins/common/file-tiff-load.c +++ b/plug-ins/common/file-tiff-load.c @@ -280,6 +280,13 @@ run (const gchar *name, if (image != -1) { + GFile *file = g_file_new_for_path (param[1].data.d_string); + + gimp_image_metadata_load (image, "image/tiff", file, + run_mode == GIMP_RUN_INTERACTIVE); + + g_object_unref (file); + *nreturn_vals = 2; values[1].type = GIMP_PDB_IMAGE; values[1].data.d_image = image; diff --git a/plug-ins/common/file-tiff-save.c b/plug-ins/common/file-tiff-save.c index fe5ba4626d..7e5e029e21 100644 --- a/plug-ins/common/file-tiff-save.c +++ b/plug-ins/common/file-tiff-save.c @@ -82,6 +82,10 @@ typedef struct gint compression; gint fillorder; gboolean save_transp_pixels; + gboolean save_exif; + gboolean save_xmp; + gboolean save_iptc; + gboolean save_thumbnail; } TiffSaveVals; typedef struct @@ -110,6 +114,7 @@ static gboolean save_image (const gchar *filename, gint32 image, gint32 drawable, gint32 orig_image, + gint *saved_bpp, GError **error); static gboolean save_dialog (gboolean has_alpha, @@ -143,8 +148,13 @@ const GimpPlugInInfo PLUG_IN_INFO = static TiffSaveVals tsvals = { - COMPRESSION_NONE, /* compression */ - TRUE, /* alpha handling */ + COMPRESSION_NONE, /* compression */ + TRUE, /* alpha handling */ + TRUE, /* save transp. pixels */ + TRUE, /* save exif */ + TRUE, /* save xmp */ + TRUE, /* save iptc */ + TRUE /* save thumbnail */ }; static gchar *image_comment = NULL; @@ -346,9 +356,38 @@ run (const gchar *name, if (status == GIMP_PDB_SUCCESS) { + gint saved_bpp; + if (save_image (param[3].data.d_string, image, drawable, orig_image, - &error)) + &saved_bpp, &error)) { + GimpMetadata *metadata; + + metadata = gimp_image_metadata_save_prepare (image, + "image/tiff"); + + if (metadata) + { + GFile *file; + GimpMetadataSaveFlags flags = 0; + + gimp_metadata_set_bits_per_sample (metadata, saved_bpp); + + if (tsvals.save_exif) flags |= GIMP_METADATA_SAVE_EXIF; + if (tsvals.save_xmp) flags |= GIMP_METADATA_SAVE_XMP; + if (tsvals.save_iptc) flags |= GIMP_METADATA_SAVE_IPTC; + if (tsvals.save_thumbnail) flags |= GIMP_METADATA_SAVE_THUMBNAIL; + + file = g_file_new_for_path (param[3].data.d_string); + gimp_image_metadata_save_finish (image, + "image/tiff", + metadata, file, flags, + NULL); + g_object_unref (file); + + g_object_unref (metadata); + } + /* Store mvals data */ gimp_set_data (SAVE_PROC, &tsvals, sizeof (TiffSaveVals)); } @@ -657,6 +696,7 @@ save_image (const gchar *filename, gint32 image, gint32 layer, gint32 orig_image, /* the export function might have */ + gint *saved_bpp, GError **error) /* created a duplicate */ { gboolean status = FALSE; @@ -725,6 +765,8 @@ save_image (const gchar *filename, else bitspersample = 16; + *saved_bpp = bitspersample; + drawable_type = gimp_drawable_type (layer); buffer = gimp_drawable_get_buffer (layer); @@ -1075,25 +1117,44 @@ static gboolean save_dialog (gboolean has_alpha, gboolean is_monochrome) { - GtkWidget *dialog; - GtkWidget *vbox; - GtkWidget *frame; - GtkWidget *hbox; - GtkWidget *label; - GtkWidget *entry; - GtkWidget *toggle; - GtkWidget *g3; - GtkWidget *g4; - gboolean run; + GError *error = NULL; + GtkWidget *dialog; + GtkWidget *vbox; + GtkWidget *frame; + GtkWidget *entry; + GtkWidget *toggle; + GtkWidget *cmp_g3; + GtkWidget *cmp_g4; + GtkBuilder *builder; + gchar *ui_file; + gboolean run; dialog = gimp_export_dialog_new (_("TIFF"), PLUG_IN_BINARY, SAVE_PROC); - vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12); - gtk_container_set_border_width (GTK_CONTAINER (vbox), 12); - gtk_box_pack_start (GTK_BOX (gimp_export_dialog_get_content_area (dialog)), - vbox, FALSE, TRUE, 0); + builder = gtk_builder_new (); + ui_file = g_build_filename (gimp_data_directory (), + "ui", "plug-ins", "plug-in-file-tiff.ui", + NULL); + if (! gtk_builder_add_from_file (builder, ui_file, &error)) + { + gchar *display_name = g_filename_display_name (ui_file); + + g_printerr (_("Error loading UI file '%s': %s"), + display_name, error ? error->message : _("Unknown error")); + + g_free (display_name); + } + + g_free (ui_file); + + vbox = GTK_WIDGET (gtk_builder_get_object (builder, "tiff_export_vbox")); + + gtk_box_pack_start (GTK_BOX (gimp_export_dialog_get_content_area (dialog)), + vbox, FALSE, FALSE, 0); + gtk_widget_show (vbox); + + vbox = GTK_WIDGET (gtk_builder_get_object (builder, "radio_button_box")); - /* compression */ frame = gimp_int_radio_group_new (TRUE, _("Compression"), G_CALLBACK (gimp_radio_button_update), &tsvals.compression, tsvals.compression, @@ -1103,20 +1164,20 @@ save_dialog (gboolean has_alpha, _("_Pack Bits"), COMPRESSION_PACKBITS, NULL, _("_Deflate"), COMPRESSION_ADOBE_DEFLATE, NULL, _("_JPEG"), COMPRESSION_JPEG, NULL, - _("CCITT Group _3 fax"), COMPRESSION_CCITTFAX3, &g3, - _("CCITT Group _4 fax"), COMPRESSION_CCITTFAX4, &g4, + _("CCITT Group _3 fax"), COMPRESSION_CCITTFAX3, &cmp_g3, + _("CCITT Group _4 fax"), COMPRESSION_CCITTFAX4, &cmp_g4, NULL); - gtk_widget_set_sensitive (g3, is_monochrome); - gtk_widget_set_sensitive (g4, is_monochrome); + gtk_widget_set_sensitive (cmp_g3, is_monochrome); + gtk_widget_set_sensitive (cmp_g4, is_monochrome); if (! is_monochrome) { if (tsvals.compression == COMPRESSION_CCITTFAX3 || tsvals.compression == COMPRESSION_CCITTFAX4) { - gimp_int_radio_group_set_active (GTK_RADIO_BUTTON (g3), + gimp_int_radio_group_set_active (GTK_RADIO_BUTTON (cmp_g3), COMPRESSION_NONE); } } @@ -1124,40 +1185,49 @@ save_dialog (gboolean has_alpha, gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0); gtk_widget_show (frame); - /* Keep colors behind alpha mask */ - toggle = gtk_check_button_new_with_mnemonic - ( _("Save _color values from transparent pixels")); + toggle = GTK_WIDGET (gtk_builder_get_object (builder, "sv_alpha")); gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), has_alpha && tsvals.save_transp_pixels); gtk_widget_set_sensitive (toggle, has_alpha); - gtk_box_pack_start (GTK_BOX (vbox), toggle, FALSE, FALSE, 0); - gtk_widget_show (toggle); - g_signal_connect (toggle, "toggled", G_CALLBACK (gimp_toggle_button_update), &tsvals.save_transp_pixels); - /* comment entry */ - hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 6); - gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0); - gtk_widget_show (hbox); - - label = gtk_label_new ( _("Comment:")); - gtk_box_pack_start (GTK_BOX (hbox), label, FALSE, FALSE, 0); - gtk_widget_show (label); - - entry = gtk_entry_new (); - gtk_widget_show (entry); - gtk_box_pack_start (GTK_BOX (hbox), entry, TRUE, TRUE, 0); + entry = GTK_WIDGET (gtk_builder_get_object (builder, "commentfield")); gtk_entry_set_text (GTK_ENTRY (entry), image_comment ? image_comment : ""); g_signal_connect (entry, "changed", G_CALLBACK (comment_entry_callback), NULL); - gtk_widget_show (frame); + toggle = GTK_WIDGET (gtk_builder_get_object (builder, "sv_exif")); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), + tsvals.save_exif); + g_signal_connect (toggle, "toggled", + G_CALLBACK (gimp_toggle_button_update), + &tsvals.save_exif); + + toggle = GTK_WIDGET (gtk_builder_get_object (builder, "sv_xmp")); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), + tsvals.save_xmp); + g_signal_connect (toggle, "toggled", + G_CALLBACK (gimp_toggle_button_update), + &tsvals.save_xmp); + + toggle = GTK_WIDGET (gtk_builder_get_object (builder, "sv_iptc")); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), + tsvals.save_iptc); + g_signal_connect (toggle, "toggled", + G_CALLBACK (gimp_toggle_button_update), + &tsvals.save_iptc); + + toggle = GTK_WIDGET (gtk_builder_get_object (builder, "sv_thumbnail")); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), + tsvals.save_thumbnail); + g_signal_connect (toggle, "toggled", + G_CALLBACK (gimp_toggle_button_update), + &tsvals.save_thumbnail); - gtk_widget_show (vbox); gtk_widget_show (dialog); run = (gimp_dialog_run (GIMP_DIALOG (dialog)) == GTK_RESPONSE_OK); diff --git a/plug-ins/common/gimprc.common b/plug-ins/common/gimprc.common index 3a4bae5488..59c84547af 100644 --- a/plug-ins/common/gimprc.common +++ b/plug-ins/common/gimprc.common @@ -88,6 +88,7 @@ lens_apply_RC = lens-apply.rc.o lens_flare_RC = lens-flare.rc.o mail_RC = mail.rc.o max_rgb_RC = max-rgb.rc.o +metadata_RC = metadata.rc.o newsprint_RC = newsprint.rc.o nl_filter_RC = nl-filter.rc.o noise_rgb_RC = noise-rgb.rc.o diff --git a/plug-ins/common/metadata.c b/plug-ins/common/metadata.c new file mode 100644 index 0000000000..e94cbaedc5 --- /dev/null +++ b/plug-ins/common/metadata.c @@ -0,0 +1,382 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * metadata.c + * Copyright (C) 2013 Hartmut Kuhse + * + * 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 . + */ + +#include "config.h" + +#include +#include +#include + +#include +#include + +#include "libgimp/stdplugins-intl.h" + + +#define PLUG_IN_PROC "plug-in-metadata-editor" +#define PLUG_IN_BINARY "metadata" +#define PLUG_IN_ROLE "gimp-metadata" + +#define EXIF_PREFIX "Exif." +#define IPTC_PREFIX "Iptc." +#define XMP_PREFIX "Xmp." + +enum +{ + C_XMP_TAG = 0, + C_XMP_VALUE, + NUM_XMP_COLS +}; + +enum +{ + C_EXIF_TAG = 0, + C_EXIF_VALUE, + NUM_EXIF_COLS +}; + + +typedef struct +{ + gchar *tag; + gchar *mode; +} iptc_tag; + + +/* local function prototypes */ + +static void query (void); +static void run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals); + +static gboolean metadata_dialog (gint32 image_id, + GExiv2Metadata *metadata); + +static void metadata_dialog_set_metadata (GExiv2Metadata *metadata, + GtkBuilder *builder); + +static void metadata_dialog_iptc_callback (GtkWidget *dialog, + GtkBuilder *builder); + + +/* local variables */ + +const GimpPlugInInfo PLUG_IN_INFO = +{ + NULL, /* init_proc */ + NULL, /* quit_proc */ + query, /* query_proc */ + run, /* run_proc */ +}; + +static const iptc_tag const iptc_tags[] = +{ + { "Iptc.Application2.Byline", "single" }, + { "Iptc.Application2.BylineTitle", "single" }, + { "Iptc.Application2.Caption", "multi" }, + { "Iptc.Application2.Category", "single" }, + { "Iptc.Application2.City", "single" }, + { "Iptc.Application2.Copyright", "single" }, + { "Iptc.Application2.CountryName", "single" }, + { "Iptc.Application2.Credit", "single" }, + { "Iptc.Application2.Headline", "multi" }, + { "Iptc.Application2.Keywords", "multi" }, + { "Iptc.Application2.ObjectName", "single" }, + { "Iptc.Application2.ProvinceState", "single" }, + { "Iptc.Application2.Source", "single" }, + { "Iptc.Application2.SpecialInstructions", "multi" }, + { "Iptc.Application2.SubLocation", "single" }, + { "Iptc.Application2.SuppCategory", "multi" }, + { "Iptc.Application2.TransmissionReference", "single" }, + { "Iptc.Application2.Urgency", "single" }, + { "Iptc.Application2.Writer", "single" } +}; + + +/* functions */ + +MAIN () + +static void +query (void) +{ + static const GimpParamDef metadata_args[] = + { + { GIMP_PDB_INT32, "run-mode", "Run mode { RUN-INTERACTIVE (0) }" }, + { GIMP_PDB_IMAGE, "image", "Input image" } + }; + + gimp_install_procedure (PLUG_IN_PROC, + N_("View and edit metadata (EXIF, IPTC, XMP)"), + "View and edit metadata information attached to the " + "current image. This can include EXIF, IPTC and/or " + "XMP information. Some or all of this metadata " + "will be saved in the file, depending on the output " + "file format.", + "Hartmut Kuhse, Michael Natterer", + "Hartmut Kuhse, Michael Natterer", + "2013", + N_("Show Metadata"), + "*", + GIMP_PLUGIN, + G_N_ELEMENTS (metadata_args), 0, + metadata_args, NULL); + + gimp_plugin_menu_register (PLUG_IN_PROC, "/File/Info"); +} + +static void +run (const gchar *name, + gint nparams, + const GimpParam *param, + gint *nreturn_vals, + GimpParam **return_vals) +{ + static GimpParam values[1]; + GimpPDBStatusType status = GIMP_PDB_SUCCESS; + + *nreturn_vals = 1; + *return_vals = values; + + values[0].type = GIMP_PDB_STATUS; + values[0].data.d_status = GIMP_PDB_EXECUTION_ERROR; + + INIT_I18N(); + gimp_ui_init (PLUG_IN_BINARY, TRUE); + + if (! strcmp (name, PLUG_IN_PROC)) + { + GimpMetadata *metadata; + gint32 image_ID = param[1].data.d_image; + + metadata = gimp_image_get_metadata (image_ID); + + if (metadata) + { + metadata_dialog (image_ID, metadata); + } + else + { + g_message (_("This image has no metadata attached to it.")); + } + + status = GIMP_PDB_SUCCESS; + } + else + { + status = GIMP_PDB_CALLING_ERROR; + } + + values[0].data.d_status = status; +} + +static gboolean +metadata_dialog (gint32 image_id, + GExiv2Metadata *metadata) +{ + GtkBuilder *builder; + GtkWidget *dialog; + GtkWidget *metadata_vbox; + GtkWidget *content_area; + GObject *write_button; + gchar *ui_file; + gchar *title; + gchar *fname; + GError *error = NULL; + + builder = gtk_builder_new (); + + ui_file = g_build_filename (gimp_data_directory (), + "ui", "plug-ins", "plug-in-metadata.ui", NULL); + + if (! gtk_builder_add_from_file (builder, ui_file, &error)) + { + g_printerr ("Error occured while loading UI file!\n"); + g_printerr ("Message: %s\n", error->message); + g_clear_error (&error); + g_free (ui_file); + g_object_unref (builder); + return FALSE; + } + + g_free (ui_file); + fname = g_filename_display_basename (gimp_image_get_uri (image_id)); + title = g_strdup_printf ("Metadata: %s", fname); + g_free (fname); + + dialog = gimp_dialog_new (title, + "gimp-metadata-dialog", + NULL, 0, + gimp_standard_help_func, PLUG_IN_PROC, + + GTK_STOCK_CLOSE, GTK_RESPONSE_CLOSE, + + NULL); + + g_free (title); + + gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog), + GTK_RESPONSE_CLOSE, + -1); + + content_area = gtk_dialog_get_content_area (GTK_DIALOG (dialog)); + + metadata_vbox = GTK_WIDGET (gtk_builder_get_object (builder, + "metadata-vbox")); + gtk_container_set_border_width (GTK_CONTAINER (metadata_vbox), 12); + gtk_box_pack_start (GTK_BOX (content_area), metadata_vbox, TRUE, TRUE, 0); + + write_button = gtk_builder_get_object (builder, "iptc-write-button"); + + g_signal_connect (write_button, "clicked", + G_CALLBACK (metadata_dialog_iptc_callback), + builder); + + metadata_dialog_set_metadata (metadata, builder); + + gtk_dialog_run (GTK_DIALOG (dialog)); + + return TRUE; +} + + +/* private functions */ + +static void +metadata_dialog_set_metadata (GExiv2Metadata *metadata, + GtkBuilder *builder) +{ + GtkListStore *exif_store; + GtkListStore *xmp_store; + gchar **exif_data; + gchar **iptc_data; + gchar **xmp_data; + gchar *value; + gchar *value_utf; + GtkTreeIter iter; + gint i; + + exif_store = GTK_LIST_STORE (gtk_builder_get_object (builder, + "exif-liststore")); + xmp_store = GTK_LIST_STORE (gtk_builder_get_object (builder, + "xmp-liststore")); + + exif_data = gexiv2_metadata_get_exif_tags (metadata); + + for (i = 0; exif_data[i] != NULL; i++) + { + gtk_list_store_append (exif_store, &iter); + value = gexiv2_metadata_get_tag_interpreted_string (metadata, + exif_data[i]); + value_utf = g_locale_to_utf8 (value, -1, NULL, NULL, NULL); + + gtk_list_store_set (exif_store, &iter, + C_EXIF_TAG, exif_data[i], + C_EXIF_VALUE, value_utf, + -1); + } + + xmp_data = gexiv2_metadata_get_xmp_tags (metadata); + + for (i = 0; xmp_data[i] != NULL; i++) + { + gtk_list_store_append (xmp_store, &iter); + value = gexiv2_metadata_get_tag_interpreted_string (metadata, + xmp_data[i]); + value_utf = g_locale_to_utf8 (value, -1, NULL, NULL, NULL); + + gtk_list_store_set (xmp_store, &iter, + C_XMP_TAG, xmp_data[i], + C_XMP_VALUE, value_utf, + -1); + } + + iptc_data = gexiv2_metadata_get_iptc_tags (metadata); + + for (i = 0; iptc_data[i] != NULL; i++) + { + GtkWidget *widget; + + widget = GTK_WIDGET (gtk_builder_get_object (builder, iptc_data[i])); + value = gexiv2_metadata_get_tag_interpreted_string (metadata, + iptc_data[i]); + value_utf = g_locale_to_utf8 (value, -1, NULL, NULL, NULL); + + if (GTK_IS_ENTRY (widget)) + { + GtkEntry *entry_widget = GTK_ENTRY (widget); + + gtk_entry_set_text (entry_widget, value_utf); + } + else if (GTK_IS_TEXT_VIEW (widget)) + { + GtkTextView *text_view = GTK_TEXT_VIEW (widget); + GtkTextBuffer *buffer; + + buffer = gtk_text_view_get_buffer (text_view); + gtk_text_buffer_set_text (buffer, value_utf, -1); + } + } +} + +static void +metadata_dialog_iptc_callback (GtkWidget *dialog, + GtkBuilder *builder) +{ +#if 0 + GExiv2Metadata *metadata; + gint i; + + metadata = gimp_image_get_metadata (handler->image); + + for (i = 0; i < G_N_ELEMENTS (iptc_tags); i++) + { + GObject *object = gtk_builder_get_object (handler->builder, + iptc_tags[i].tag); + + if (! strcmp ("single", iptc_tags[i].mode)) + { + GtkEntry *entry = GTK_ENTRY (object); + + gexiv2_metadata_set_tag_string (metadata, iptc_tags[i].tag, + gtk_entry_get_text (entry)); + } + else if (!strcmp ("multi", iptc_tags[i].mode)) + { + GtkTextView *text_view = GTK_TEXT_VIEW (object); + GtkTextBuffer *buffer; + GtkTextIter start; + GtkTextIter end; + gchar *text; + + buffer = gtk_text_view_get_buffer (text_view); + gtk_text_buffer_get_start_iter (buffer, &start); + gtk_text_buffer_get_end_iter (buffer, &end); + + text = gtk_text_buffer_get_text (buffer, &start, &end, TRUE); + gexiv2_metadata_set_tag_string (metadata, iptc_tags[i].tag, text); + g_free (text); + } + } +#endif +} diff --git a/plug-ins/common/plugin-defs.pl b/plug-ins/common/plugin-defs.pl index 5722c4e374..4f363bef32 100644 --- a/plug-ins/common/plugin-defs.pl +++ b/plug-ins/common/plugin-defs.pl @@ -51,7 +51,7 @@ 'file-glob' => {}, 'file-header' => { ui => 1, gegl => 1 }, 'file-html-table' => { ui => 1, gegl => 1 }, - 'file-jp2-load' => { optional => 1, gegl => 1, libs => 'JP2_LIBS' }, + 'file-jp2-load' => { ui => 1, optional => 1, gegl => 1, libs => 'JP2_LIBS' }, 'file-mng' => { ui => 1, gegl => 1, optional => 1, libs => 'MNG_LIBS', cflags => 'MNG_CFLAGS' }, 'file-pat' => { ui => 1, gegl => 1 }, 'file-pcx' => { ui => 1, gegl => 1 }, @@ -89,6 +89,7 @@ 'lens-flare' => { ui => 1 }, 'mail' => { ui => 1, optional => 1 }, 'max-rgb' => { ui => 1 }, + 'metadata' => { ui => 1, libs => 'GEXIV2_LIBS', cflags => 'GEXIV2_CFLAGS' }, 'newsprint' => { ui => 1 }, 'nl-filter' => { ui => 1 }, 'noise-rgb' => { ui => 1 }, diff --git a/plug-ins/file-jpeg/Makefile.am b/plug-ins/file-jpeg/Makefile.am index 5b3f2ae1dd..c53d1af00a 100644 --- a/plug-ins/file-jpeg/Makefile.am +++ b/plug-ins/file-jpeg/Makefile.am @@ -22,11 +22,12 @@ AM_LDFLAGS = $(mwindows) libexecdir = $(gimpplugindir)/plug-ins AM_CPPFLAGS = \ - -I$(top_srcdir) \ - $(GTK_CFLAGS) \ - $(EXIF_CFLAGS) \ - $(LCMS_CFLAGS) \ - $(GEGL_CFLAGS) \ + -I$(top_srcdir) \ + $(GTK_CFLAGS) \ + $(EXIF_CFLAGS) \ + $(LCMS_CFLAGS) \ + $(GEGL_CFLAGS) \ + $(GEXIV2_CFLAGS) \ -I$(includedir) libexec_PROGRAMS = file-jpeg @@ -45,14 +46,6 @@ file_jpeg_SOURCES = \ jpeg-settings.c \ jpeg-settings.h -if HAVE_LIBEXIF -file_jpeg_SOURCES += \ - jpeg-exif.c \ - jpeg-exif.h \ - gimpexif.c \ - gimpexif.h -endif - file_jpeg_LDADD = \ $(libgimpui) \ $(libgimpwidgets) \ @@ -65,15 +58,11 @@ file_jpeg_LDADD = \ $(LCMS_LIBS) \ $(GTK_LIBS) \ $(GEGL_LIBS) \ + $(GEXIV2_LIBS) \ $(RT_LIBS) \ $(INTLLIBS) \ $(file_jpeg_RC) -if HAVE_LIBEXIF -file_jpeg_LDADD += \ - $(EXIF_LIBS) -endif - noinst_PROGRAMS = jpegqual jpegqual_SOURCES = \ diff --git a/plug-ins/file-jpeg/gimpexif.c b/plug-ins/file-jpeg/gimpexif.c deleted file mode 100644 index ab14a99607..0000000000 --- a/plug-ins/file-jpeg/gimpexif.c +++ /dev/null @@ -1,160 +0,0 @@ -/* GIMP - The GNU Image Manipulation Program - * Copyright (C) 1995 Spencer Kimball and Peter Mattis - * - * 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 . - */ - -/* - * EXIF-handling code for the metadata library. - */ - -#include "config.h" - -#include - -#include - -#include -#include -#include - -#include "gimpexif.h" - - -#define EXIF_HEADER_SIZE 8 - -/* - * gimp_metadata_store_exif: - * @image_ID: the GIMP image to work on. - * @exif_data: the Exif data that is to be stored. - * - * This function is supposed to load the "gimp-metadata" parasite - * (which is in XMP format), parse it, add the exif information, - * reformat it into XMP, and store the result as the new parasite. - * The infrastructure to do this is not yet available, so for the - * moment it does something much simpler -- it calls upon libexif - * to serialize the exif data, and stores the result in a parasite - * called "exif-data". - */ -void gimp_metadata_store_exif (gint32 image_ID, - ExifData *exif_data) -{ - GimpParam *return_vals; - gint nreturn_vals; - GimpParasite *parasite = NULL; - guchar *exif_buf = NULL; - guint exif_buf_len = 0; - - exif_data_save_data (exif_data, &exif_buf, &exif_buf_len); - - if (exif_buf_len > EXIF_HEADER_SIZE) - { - parasite = gimp_parasite_new ("exif-data", - GIMP_PARASITE_PERSISTENT, - exif_buf_len, exif_buf); - gimp_image_attach_parasite (image_ID, parasite); - gimp_parasite_free (parasite); - } - return_vals = gimp_run_procedure ("plug-in-metadata-decode-exif", - &nreturn_vals, - GIMP_PDB_IMAGE, image_ID, - GIMP_PDB_INT32, 7, - GIMP_PDB_INT8ARRAY, "unused", - GIMP_PDB_END); - if (return_vals[0].data.d_status != GIMP_PDB_SUCCESS) - g_warning ("JPEG Exif -> XMP Merge failed"); - - free (exif_buf); -} - -/* - * gimp_metadata_generate_exif: - * @image_ID: the ID of the GIMP image to work on. - * - * This function is supposed to load the "gimp-metadata" parasite - * (which is a block of XMP info), parse it, extract the exif - * values, and build an ExifData structure from them. - * The infrastructure to do this is not yet available, so for the - * moment it does something much simpler -- it loads the "exif-data" - * parasite (which is a serialized representation of the exif data), - * and calls upon libexif to parse it into an ExifData struct. - * - * returns: The reconstructed exif data, or NULL if none exists. - */ -ExifData * -gimp_metadata_generate_exif (gint32 image_ID) -{ - ExifData *exif_data; - GimpParasite *parasite = gimp_image_get_parasite (image_ID, "exif-data"); - - if (parasite) - { - exif_data = exif_data_new_from_data (gimp_parasite_data (parasite), - gimp_parasite_data_size (parasite)); - - gimp_parasite_free (parasite); - return exif_data; - } - - return NULL; -} - -/* - * gimp_exif_content_get_value: - * @content: ExifContent block from which to get value - * @tag: Tag whose value to get - * @value: Place to put the result - * @maxlen: Maximum size of returned string - * - * This function is a wrapper around the libexif function - * exif_content_get_value(), necessary to deal with an incompatible - * API change. It looks up the value of the specified tag, - * returning the result as a human-readable string. Note that - * @value must be pre-allocated. - */ -const gchar * -gimp_exif_content_get_value (ExifContent *content, - ExifTag tag, - gchar *value, - gint maxlen) -{ - ExifEntry *entry = exif_content_get_entry (content, tag); - - if (entry) - return exif_entry_get_value (entry, value, maxlen); - else - return NULL; -} - -/* - * gimp_exif_data_remove_entry: - * @data: ExifData pointer - * @ifd: Index of the ifd holding the entry to be removed - * @tag: Tag of the entry to be removed - * - * This is a convenience function for removing a specified - * entry from an exif data structure. If no such entry - * exists, the function returns without doing anything. - */ -void -gimp_exif_data_remove_entry (ExifData *exif_data, - ExifIfd ifd, - ExifTag tag) -{ - ExifEntry *entry = exif_content_get_entry (exif_data->ifd[ifd], - tag); - - if (entry) - exif_content_remove_entry (exif_data->ifd[ifd], entry); -} diff --git a/plug-ins/file-jpeg/gimpexif.h b/plug-ins/file-jpeg/gimpexif.h deleted file mode 100644 index 5ceaaef1e2..0000000000 --- a/plug-ins/file-jpeg/gimpexif.h +++ /dev/null @@ -1,39 +0,0 @@ -/* GIMP - The GNU Image Manipulation Program - * Copyright (C) 1995 Spencer Kimball and Peter Mattis - * - * 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 . - */ - -/* - * EXIF-handling code for the metadata library. - */ - -#ifndef __GIMP_EXIF_H__ -#define __GIMP_EXIF_H__ - -void gimp_metadata_store_exif (gint32 image_ID, - ExifData *exif_data); - -ExifData * gimp_metadata_generate_exif (gint32 image_ID); - -const gchar * gimp_exif_content_get_value (ExifContent *content, - ExifTag tag, - gchar *value, - gint maxlen); - -void gimp_exif_data_remove_entry (ExifData *exif_data, - ExifIfd ifd, - ExifTag tag); - -#endif /* __GIMP_EXIF_H__ */ diff --git a/plug-ins/file-jpeg/jpeg-exif.c b/plug-ins/file-jpeg/jpeg-exif.c deleted file mode 100644 index bc6787d721..0000000000 --- a/plug-ins/file-jpeg/jpeg-exif.c +++ /dev/null @@ -1,458 +0,0 @@ -/* GIMP - The GNU Image Manipulation Program - * Copyright (C) 1995 Spencer Kimball and Peter Mattis - * - * 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 . - */ - -/* - * EXIF-handling code for the jpeg plugin. May eventually be better - * to move this stuff into libgimpbase or a new libgimpmetadata and - * make it available for other plugins. - */ - -#include "config.h" - -#include -#include -#include - -#include -#include - -#include -#include -#include - -#include -#include - -#include "jpeg-exif.h" -#include "gimpexif.h" -#include "jpeg.h" -#include "jpeg-settings.h" - -#include "libgimp/stdplugins-intl.h" - - -#define THUMBNAIL_SIZE 128 -#define JPEG_EXIF_ROTATE_PARASITE "exif-orientation-rotate" - - -static gboolean jpeg_exif_rotate_query_dialog (gint32 image_ID); - - -/* Replacement for exif_data_new_from_file() to work around - * filename encoding problems (see bug #335391). - */ -ExifData * -jpeg_exif_data_new_from_file (const gchar *filename, - GError **error) -{ - ExifData *data; - GMappedFile *file; - - g_return_val_if_fail (filename != NULL, NULL); - g_return_val_if_fail (error == NULL || *error == NULL, NULL); - - file = g_mapped_file_new (filename, FALSE, error); - if (! file) - return NULL; - - data = exif_data_new_from_data ((guchar *) g_mapped_file_get_contents (file), - g_mapped_file_get_length (file)); - - g_mapped_file_unref (file); - - return data; -} - - -gint -jpeg_exif_get_orientation (ExifData *exif_data) -{ - ExifEntry *entry; - gint byte_order = exif_data_get_byte_order (exif_data); - - /* get orientation and rotate image accordingly if necessary */ - if ((entry = exif_content_get_entry (exif_data->ifd[EXIF_IFD_0], - EXIF_TAG_ORIENTATION))) - { - return exif_get_short (entry->data, byte_order); - } - - return 0; -} - - -gboolean -jpeg_exif_get_resolution (ExifData *exif_data, - gdouble *xresolution, - gdouble *yresolution, - gint *unit) -{ - gboolean success; - ExifEntry *entry; - gint byte_order; - gdouble xres; - gdouble yres; - gint ruint; - ExifRational r; - - success = FALSE; - byte_order = exif_data_get_byte_order (exif_data); - - do - { - entry = exif_content_get_entry (exif_data->ifd[EXIF_IFD_0], - EXIF_TAG_X_RESOLUTION); - if (!entry) - break; - - r = exif_get_rational (entry->data, byte_order); - if (r.denominator == 0.0) - break; - xres = r.numerator / r.denominator; - - entry = exif_content_get_entry (exif_data->ifd[EXIF_IFD_0], - EXIF_TAG_Y_RESOLUTION); - if (!entry) - break; - - r = exif_get_rational (entry->data, byte_order); - if (r.denominator == 0.0) - break; - yres = r.numerator / r.denominator; - - entry = exif_content_get_entry (exif_data->ifd[EXIF_IFD_0], - EXIF_TAG_RESOLUTION_UNIT); - if (!entry) - break; - - ruint = exif_get_short (entry->data, byte_order); - if ((ruint != 2) && /* inches */ - (ruint != 3)) /* centimetres */ - break; - - success = TRUE; - } - while (0); - - if (success) - { - *xresolution = xres; - *yresolution = yres; - *unit = ruint; - } - - return success; -} - - -void -jpeg_setup_exif_for_save (ExifData *exif_data, - const gint32 image_ID) -{ - ExifRational r; - gdouble xres, yres; - ExifEntry *entry; - gint byte_order = exif_data_get_byte_order (exif_data); - - /* set orientation to top - left */ - if ((entry = exif_content_get_entry (exif_data->ifd[EXIF_IFD_0], - EXIF_TAG_ORIENTATION))) - { - exif_set_short (entry->data, byte_order, (ExifShort) 1); - } - - /* set x and y resolution */ - gimp_image_get_resolution (image_ID, &xres, &yres); - r.numerator = xres; - r.denominator = 1; - if ((entry = exif_content_get_entry (exif_data->ifd[EXIF_IFD_0], - EXIF_TAG_X_RESOLUTION))) - { - exif_set_rational (entry->data, byte_order, r); - } - r.numerator = yres; - if ((entry = exif_content_get_entry (exif_data->ifd[EXIF_IFD_0], - EXIF_TAG_Y_RESOLUTION))) - { - exif_set_rational (entry->data, byte_order, r); - } - - /* set resolution unit, always inches */ - if ((entry = exif_content_get_entry (exif_data->ifd[EXIF_IFD_0], - EXIF_TAG_RESOLUTION_UNIT))) - { - exif_set_short (entry->data, byte_order, (ExifShort) 2); - } - - /* set software to "GIMP" and include the version number */ - if ((entry = exif_content_get_entry (exif_data->ifd[EXIF_IFD_0], - EXIF_TAG_SOFTWARE))) - { - const gchar *name = "GIMP " GIMP_VERSION; - - entry->data = (guchar *) g_strdup (name); - entry->size = strlen (name) + 1; - entry->components = entry->size; - } - - /* set the width and height */ - if ((entry = exif_content_get_entry (exif_data->ifd[EXIF_IFD_EXIF], - EXIF_TAG_PIXEL_X_DIMENSION))) - { - exif_set_long (entry->data, byte_order, - (ExifLong) gimp_image_width (image_ID)); - } - if ((entry = exif_content_get_entry (exif_data->ifd[EXIF_IFD_EXIF], - EXIF_TAG_PIXEL_Y_DIMENSION))) - { - exif_set_long (entry->data, byte_order, - (ExifLong) gimp_image_height (image_ID)); - } - - /* - * set the date & time image was saved - * note, date & time of original photo is stored elsewwhere, we - * aren't losing it. - */ - if ((entry = exif_content_get_entry (exif_data->ifd[EXIF_IFD_0], - EXIF_TAG_DATE_TIME))) - { - /* small memory leak here */ - entry->data = NULL; - exif_entry_initialize (entry, EXIF_TAG_DATE_TIME); - } - - /* should set components configuration, don't know how */ - - /* - * remove entries that don't apply to jpeg - * (may have come from tiff or raw) - */ - gimp_exif_data_remove_entry (exif_data, EXIF_IFD_0, EXIF_TAG_COMPRESSION); - gimp_exif_data_remove_entry (exif_data, EXIF_IFD_0, EXIF_TAG_IMAGE_WIDTH); - gimp_exif_data_remove_entry (exif_data, EXIF_IFD_0, EXIF_TAG_IMAGE_LENGTH); - gimp_exif_data_remove_entry (exif_data, EXIF_IFD_0, EXIF_TAG_BITS_PER_SAMPLE); - gimp_exif_data_remove_entry (exif_data, EXIF_IFD_0, EXIF_TAG_SAMPLES_PER_PIXEL); - gimp_exif_data_remove_entry (exif_data, EXIF_IFD_0, EXIF_TAG_PHOTOMETRIC_INTERPRETATION); - gimp_exif_data_remove_entry (exif_data, EXIF_IFD_0, EXIF_TAG_STRIP_OFFSETS); - gimp_exif_data_remove_entry (exif_data, EXIF_IFD_0, EXIF_TAG_PLANAR_CONFIGURATION); - gimp_exif_data_remove_entry (exif_data, EXIF_IFD_0, EXIF_TAG_YCBCR_SUB_SAMPLING); - - /* should set thumbnail attributes */ -} - -void -jpeg_exif_rotate (gint32 image_ID, - gint orientation) -{ - switch (orientation) - { - case 1: /* standard orientation, do nothing */ - break; - - case 2: /* flipped right-left */ - gimp_image_flip (image_ID, GIMP_ORIENTATION_HORIZONTAL); - break; - - case 3: /* rotated 180 */ - gimp_image_rotate (image_ID, GIMP_ROTATE_180); - break; - - case 4: /* flipped top-bottom */ - gimp_image_flip (image_ID, GIMP_ORIENTATION_VERTICAL); - break; - - case 5: /* flipped diagonally around '\' */ - gimp_image_rotate (image_ID, GIMP_ROTATE_90); - jpeg_swap_original_settings (image_ID); - gimp_image_flip (image_ID, GIMP_ORIENTATION_HORIZONTAL); - break; - - case 6: /* 90 CW */ - gimp_image_rotate (image_ID, GIMP_ROTATE_90); - jpeg_swap_original_settings (image_ID); - break; - - case 7: /* flipped diagonally around '/' */ - gimp_image_rotate (image_ID, GIMP_ROTATE_90); - jpeg_swap_original_settings (image_ID); - gimp_image_flip (image_ID, GIMP_ORIENTATION_VERTICAL); - break; - - case 8: /* 90 CCW */ - gimp_image_rotate (image_ID, GIMP_ROTATE_270); - jpeg_swap_original_settings (image_ID); - break; - - default: /* shouldn't happen */ - break; - } -} - -void -jpeg_exif_rotate_query (gint32 image_ID, - gint orientation) -{ - GimpParasite *parasite; - gboolean query = load_interactive; - - if (orientation < 2 || orientation > 8) - return; - - parasite = gimp_get_parasite (JPEG_EXIF_ROTATE_PARASITE); - - if (parasite) - { - if (strncmp (gimp_parasite_data (parasite), "yes", - gimp_parasite_data_size (parasite)) == 0) - { - query = FALSE; - } - else if (strncmp (gimp_parasite_data (parasite), "no", - gimp_parasite_data_size (parasite)) == 0) - { - gimp_parasite_free (parasite); - return; - } - - gimp_parasite_free (parasite); - } - - if (query && ! jpeg_exif_rotate_query_dialog (image_ID)) - return; - - jpeg_exif_rotate (image_ID, orientation); -} - -static gboolean -jpeg_exif_rotate_query_dialog (gint32 image_ID) -{ - GtkWidget *dialog; - GtkWidget *hbox; - GtkWidget *vbox; - GtkWidget *label; - GtkWidget *toggle; - GdkPixbuf *pixbuf; - gint response; - - dialog = gimp_dialog_new (_("Rotate Image?"), PLUG_IN_ROLE, - NULL, 0, NULL, NULL, - - _("_Keep Orientation"), GTK_RESPONSE_CANCEL, - GIMP_STOCK_TOOL_ROTATE, GTK_RESPONSE_OK, - - NULL); - - gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog), - GTK_RESPONSE_OK, - GTK_RESPONSE_CANCEL, - -1); - - gtk_window_set_resizable (GTK_WINDOW (dialog), FALSE); - gimp_window_set_transient (GTK_WINDOW (dialog)); - - hbox = gtk_box_new (GTK_ORIENTATION_HORIZONTAL, 12); - gtk_container_set_border_width (GTK_CONTAINER (hbox), 12); - gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (dialog))), - hbox, FALSE, FALSE, 0); - gtk_widget_show (hbox); - - vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 6); - gtk_box_pack_start (GTK_BOX (hbox), vbox, FALSE, FALSE, 0); - gtk_widget_show (vbox); - - pixbuf = gimp_image_get_thumbnail (image_ID, - THUMBNAIL_SIZE, THUMBNAIL_SIZE, - GIMP_PIXBUF_SMALL_CHECKS); - - if (pixbuf) - { - GtkWidget *image; - gchar *name; - - image = gtk_image_new_from_pixbuf (pixbuf); - g_object_unref (pixbuf); - - gtk_box_pack_start (GTK_BOX (vbox), image, FALSE, FALSE, 0); - gtk_widget_show (image); - - name = gimp_image_get_name (image_ID); - - label = gtk_label_new (name); - gtk_label_set_ellipsize (GTK_LABEL (label), PANGO_ELLIPSIZE_MIDDLE); - gimp_label_set_attributes (GTK_LABEL (label), - PANGO_ATTR_STYLE, PANGO_STYLE_ITALIC, - -1); - gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0); - gtk_widget_show (label); - - g_free (name); - } - - vbox = gtk_box_new (GTK_ORIENTATION_VERTICAL, 12); - gtk_box_pack_start (GTK_BOX (hbox), vbox, TRUE, TRUE, 0); - gtk_widget_show (vbox); - - label = g_object_new (GTK_TYPE_LABEL, - "label", _("According to the EXIF data, " - "this image is rotated."), - "wrap", TRUE, - "justify", GTK_JUSTIFY_LEFT, - "xalign", 0.0, - "yalign", 0.5, - NULL); - gimp_label_set_attributes (GTK_LABEL (label), - PANGO_ATTR_SCALE, PANGO_SCALE_LARGE, - PANGO_ATTR_WEIGHT, PANGO_WEIGHT_BOLD, - -1); - gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0); - gtk_widget_show (label); - - label = g_object_new (GTK_TYPE_LABEL, - "label", _("Would you like GIMP to rotate it " - "into the standard orientation?"), - "wrap", TRUE, - "justify", GTK_JUSTIFY_LEFT, - "xalign", 0.0, - "yalign", 0.5, - NULL); - gtk_box_pack_start (GTK_BOX (vbox), label, FALSE, FALSE, 0); - gtk_widget_show (label); - - toggle = gtk_check_button_new_with_mnemonic (_("_Don't ask me again")); - gtk_box_pack_end (GTK_BOX (vbox), toggle, FALSE, FALSE, 0); - gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), FALSE); - gtk_widget_show (toggle); - - response = gimp_dialog_run (GIMP_DIALOG (dialog)); - - if (gtk_toggle_button_get_active (GTK_TOGGLE_BUTTON (toggle))) - { - GimpParasite *parasite; - const gchar *str = (response == GTK_RESPONSE_OK) ? "yes" : "no"; - - parasite = gimp_parasite_new (JPEG_EXIF_ROTATE_PARASITE, - GIMP_PARASITE_PERSISTENT, - strlen (str), str); - gimp_attach_parasite (parasite); - gimp_parasite_free (parasite); - } - - gtk_widget_destroy (dialog); - - return (response == GTK_RESPONSE_OK); -} diff --git a/plug-ins/file-jpeg/jpeg-exif.h b/plug-ins/file-jpeg/jpeg-exif.h deleted file mode 100644 index 3e9a29f234..0000000000 --- a/plug-ins/file-jpeg/jpeg-exif.h +++ /dev/null @@ -1,41 +0,0 @@ -/* GIMP - The GNU Image Manipulation Program - * Copyright (C) 1995 Spencer Kimball and Peter Mattis - * - * 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 . - */ - -#ifndef __JPEG_EXIF_H__ -#define __JPEG_EXIF_H__ - -extern ExifData *exif_data; - -ExifData * jpeg_exif_data_new_from_file (const gchar *filename, - GError **error); - -gint jpeg_exif_get_orientation (ExifData *exif_data); - -gboolean jpeg_exif_get_resolution (ExifData *exif_data, - gdouble *xresolution, - gdouble *yresolution, - gint *unit); - -void jpeg_setup_exif_for_save (ExifData *exif_data, - const gint32 image_ID); - -void jpeg_exif_rotate (gint32 image_ID, - gint orientation); -void jpeg_exif_rotate_query (gint32 image_ID, - gint orientation); - -#endif /* __JPEG_EXIF_H__ */ diff --git a/plug-ins/file-jpeg/jpeg-load.c b/plug-ins/file-jpeg/jpeg-load.c index 41cd2e49fd..6a7ace6483 100644 --- a/plug-ins/file-jpeg/jpeg-load.c +++ b/plug-ins/file-jpeg/jpeg-load.c @@ -21,15 +21,13 @@ #include #include +#include #include +#include #include #include -#ifdef HAVE_LIBEXIF -#include -#endif /* HAVE_LIBEXIF */ - #ifdef HAVE_LCMS #include #endif @@ -43,21 +41,11 @@ #include "jpeg-icc.h" #include "jpeg-settings.h" #include "jpeg-load.h" -#ifdef HAVE_LIBEXIF -#include "jpeg-exif.h" -#include "gimpexif.h" -#endif - static void jpeg_load_resolution (gint32 image_ID, struct jpeg_decompress_struct *cinfo); -#ifdef HAVE_LIBEXIF -static gboolean jpeg_load_exif_resolution (gint32 image_ID, - ExifData *exif_data); -#endif - static void jpeg_load_sanitize_comment (gchar *comment); static gpointer jpeg_load_cmyk_transform (guint8 *profile_data, @@ -90,9 +78,6 @@ load_image (const gchar *filename, gint tile_height; gint scanlines; gint i, start, end; -#ifdef HAVE_LIBEXIF - gint orientation = 0; -#endif #ifdef HAVE_LCMS cmsHTRANSFORM cmyk_transform = NULL; #else @@ -265,9 +250,6 @@ load_image (const gchar *filename, GString *comment_buffer = NULL; guint8 *profile = NULL; guint profile_size = 0; -#ifdef HAVE_LIBEXIF - ExifData *exif_data = NULL; -#endif /* Step 5.0: save the original JPEG settings in a parasite */ jpeg_detect_original_settings (&cinfo, image_ID); @@ -303,19 +285,10 @@ load_image (const gchar *filename, #ifdef GIMP_UNSTABLE g_print ("jpeg-load: found EXIF block (%d bytes)\n", (gint) (len - sizeof (JPEG_APP_HEADER_EXIF))); -#endif -#ifdef HAVE_LIBEXIF - if (! exif_data) - exif_data = exif_data_new (); - /* if there are multiple blocks, their data will be merged */ - exif_data_load_data (exif_data, (unsigned char *) data, len); #endif } } -#ifdef HAVE_LIBEXIF - if (!jpeg_load_exif_resolution (image_ID, exif_data)) -#endif jpeg_load_resolution (image_ID, &cinfo); /* if we found any comments, then make a parasite for them */ @@ -334,55 +307,6 @@ load_image (const gchar *filename, g_string_free (comment_buffer, TRUE); } -#ifdef HAVE_LIBEXIF - /* if we found any EXIF block, then attach the metadata to the image */ - if (exif_data) - { - gimp_metadata_store_exif (image_ID, exif_data); - orientation = jpeg_exif_get_orientation (exif_data); - exif_data_unref (exif_data); - exif_data = NULL; - } -#endif - - /* Step 5.2: check for XMP metadata in APP1 markers (after EXIF) */ - for (marker = cinfo.marker_list; marker; marker = marker->next) - { - const gchar *data = (const gchar *) marker->data; - gsize len = marker->data_length; - - if ((marker->marker == JPEG_APP0 + 1) - && (len > sizeof (JPEG_APP_HEADER_XMP) + 20) - && ! strcmp (JPEG_APP_HEADER_XMP, data)) - { - GimpParam *return_vals; - gint nreturn_vals; - gchar *xmp_packet; - -#ifdef GIMP_UNSTABLE - g_print ("jpeg-load: found XMP packet (%d bytes)\n", - (gint) (len - sizeof (JPEG_APP_HEADER_XMP))); -#endif - xmp_packet = g_strndup (data + sizeof (JPEG_APP_HEADER_XMP), - len - sizeof (JPEG_APP_HEADER_XMP)); - - /* FIXME: running this through the PDB is not very efficient */ - return_vals = gimp_run_procedure ("plug-in-metadata-decode-xmp", - &nreturn_vals, - GIMP_PDB_IMAGE, image_ID, - GIMP_PDB_STRING, xmp_packet, - GIMP_PDB_END); - - if (return_vals[0].data.d_status != GIMP_PDB_SUCCESS) - { - g_warning ("JPEG - unable to decode XMP metadata packet"); - } - - gimp_destroy_params (return_vals, nreturn_vals); - g_free (xmp_packet); - } - } - /* Step 5.3: check for an embedded ICC profile in APP2 markers */ jpeg_icc_read_profile (&cinfo, &profile, &profile_size); @@ -489,10 +413,6 @@ load_image (const gchar *filename, gimp_image_insert_layer (image_ID, layer_ID, -1, 0); -#ifdef HAVE_LIBEXIF - jpeg_exif_rotate_query (image_ID, orientation); -#endif - return image_ID; } @@ -537,53 +457,6 @@ jpeg_load_resolution (gint32 image_ID, } } -#ifdef HAVE_LIBEXIF - -static gboolean -jpeg_load_exif_resolution (gint32 image_ID, - ExifData *exif_data) -{ - gboolean success; - gdouble xresolution; - gdouble yresolution; - gint unit; - - if (!exif_data) - return FALSE; - - if (!jpeg_exif_get_resolution (exif_data, - &xresolution, - &yresolution, - &unit)) - return FALSE; - - switch (unit) - { - case 2: - success = TRUE; - break; - case 3: /* dots per cm */ - xresolution *= 2.54; - yresolution *= 2.54; - gimp_image_set_unit (image_ID, GIMP_UNIT_MM); - success = TRUE; - break; - default: - g_warning ("Unknown EXIF resolution unit %d; skipping EXIF resolution.", - unit); - success = FALSE; - } - - if (success) - { - gimp_image_set_resolution (image_ID, xresolution, yresolution); - } - - return success; -} - -#endif /* HAVE_LIBEXIF */ - /* * A number of JPEG files have comments written in a local character set * instead of UTF-8. Some of these files may have been saved by older @@ -608,9 +481,6 @@ jpeg_load_sanitize_comment (gchar *comment) } } - -#ifdef HAVE_LIBEXIF - typedef struct { struct jpeg_source_mgr pub; /* public fields */ @@ -627,7 +497,6 @@ init_source (j_decompress_ptr cinfo) { } - static boolean fill_input_buffer (j_decompress_ptr cinfo) { @@ -660,14 +529,16 @@ term_source (j_decompress_ptr cinfo) } gint32 -load_thumbnail_image (const gchar *filename, +load_thumbnail_image (GFile *file, gint *width, gint *height, GimpImageType *type, GError **error) { - gint32 volatile image_ID; - ExifData *exif_data; + gint32 volatile image_ID = -1; + GimpMetadata *metadata = NULL; + guint8 *thumbnail_buffer = NULL; + gint thumbnail_size; gint32 layer_ID; struct jpeg_decompress_struct cinfo; struct my_error_mgr jerr; @@ -683,20 +554,25 @@ load_thumbnail_image (const gchar *filename, my_src_ptr src; FILE *infile; - image_ID = -1; - exif_data = jpeg_exif_data_new_from_file (filename, NULL); - - if (! ((exif_data) && (exif_data->data) && (exif_data->size > 0))) + metadata = gimp_metadata_load_from_file (file, error); + if (! metadata) return -1; - orientation = jpeg_exif_get_orientation (exif_data); + orientation = gexiv2_metadata_get_orientation (metadata); + + if (! gexiv2_metadata_get_exif_thumbnail (metadata, + &thumbnail_buffer, &thumbnail_size)) + { + g_object_unref (metadata); + return -1; + } cinfo.err = jpeg_std_error (&jerr.pub); jerr.pub.error_exit = my_error_exit; jerr.pub.output_message = my_output_message; gimp_progress_init_printf (_("Opening thumbnail for '%s'"), - gimp_filename_to_utf8 (filename)); + g_file_get_parse_name (file)); /* Establish the setjmp return context for my_error_exit to use. */ if (setjmp (jerr.setjmp_buffer)) @@ -710,15 +586,15 @@ load_thumbnail_image (const gchar *filename, if (image_ID != -1) gimp_image_delete (image_ID); - if (exif_data) - { - exif_data_unref (exif_data); - exif_data = NULL; - } + if (metadata) + g_object_unref (metadata); if (buffer) g_object_unref (buffer); + if (thumbnail_buffer) + g_free (thumbnail_buffer); + return -1; } @@ -740,11 +616,11 @@ load_thumbnail_image (const gchar *filename, src->pub.resync_to_restart = jpeg_resync_to_restart; src->pub.term_source = term_source; - src->pub.bytes_in_buffer = exif_data->size; - src->pub.next_input_byte = exif_data->data; + src->pub.bytes_in_buffer = thumbnail_size; + src->pub.next_input_byte = thumbnail_buffer; - src->buffer = exif_data->data; - src->size = exif_data->size; + src->buffer = thumbnail_buffer; + src->size = thumbnail_size; /* Step 3: read file parameters with jpeg_read_header() */ @@ -808,11 +684,11 @@ load_thumbnail_image (const gchar *filename, cinfo.output_components, cinfo.out_color_space, cinfo.jpeg_color_space); - if (exif_data) - { - exif_data_unref (exif_data); - exif_data = NULL; - } + if (metadata) + g_object_unref (metadata); + + if (thumbnail_buffer) + g_free (thumbnail_buffer); return -1; break; @@ -822,7 +698,6 @@ load_thumbnail_image (const gchar *filename, image_type); gimp_image_undo_disable (image_ID); - gimp_image_set_filename (image_ID, filename); jpeg_load_resolution (image_ID, &cinfo); @@ -877,7 +752,9 @@ load_thumbnail_image (const gchar *filename, */ jpeg_destroy_decompress (&cinfo); + g_object_unref (metadata); g_object_unref (buffer); + g_free (thumbnail_buffer); /* free up the temporary buffers */ g_free (rowbuf); @@ -897,17 +774,14 @@ load_thumbnail_image (const gchar *filename, jerr.pub.error_exit = my_error_exit; jerr.pub.output_message = my_output_message; - if ((infile = g_fopen (filename, "rb")) == NULL) + if ((infile = g_fopen (g_file_get_path (file), "rb")) == NULL) { g_set_error (error, G_FILE_ERROR, g_file_error_from_errno (errno), _("Could not open '%s' for reading: %s"), - gimp_filename_to_utf8 (filename), g_strerror (errno)); + g_file_get_parse_name (file), g_strerror (errno)); - if (exif_data) - { - exif_data_unref (exif_data); - exif_data = NULL; - } + if (image_ID != -1) + gimp_image_delete (image_ID); return -1; } @@ -924,12 +798,6 @@ load_thumbnail_image (const gchar *filename, if (image_ID != -1) gimp_image_delete (image_ID); - if (exif_data) - { - exif_data_unref (exif_data); - exif_data = NULL; - } - return -1; } @@ -958,22 +826,15 @@ load_thumbnail_image (const gchar *filename, fclose (infile); - if (exif_data) - { - exif_data_unref (exif_data); - exif_data = NULL; - } - +#if 0 jpeg_exif_rotate (image_ID, orientation); +#endif *type = layer_type; return image_ID; } -#endif /* HAVE_LIBEXIF */ - - static gpointer jpeg_load_cmyk_transform (guint8 *profile_data, gsize profile_len) diff --git a/plug-ins/file-jpeg/jpeg-load.h b/plug-ins/file-jpeg/jpeg-load.h index fe38c41c24..0ecdc8cbc4 100644 --- a/plug-ins/file-jpeg/jpeg-load.h +++ b/plug-ins/file-jpeg/jpeg-load.h @@ -23,15 +23,10 @@ gint32 load_image (const gchar *filename, gboolean preview, GError **error); - -#ifdef HAVE_LIBEXIF - -gint32 load_thumbnail_image (const gchar *filename, +gint32 load_thumbnail_image (GFile *file, gint *width, gint *height, GimpImageType *type, GError **error); -#endif /* HAVE_LIBEXIF */ - #endif /* __JPEG_LOAD_H__ */ diff --git a/plug-ins/file-jpeg/jpeg-save.c b/plug-ins/file-jpeg/jpeg-save.c index 9aa0c81113..d4058ca0da 100644 --- a/plug-ins/file-jpeg/jpeg-save.c +++ b/plug-ins/file-jpeg/jpeg-save.c @@ -33,10 +33,6 @@ #include #include -#ifdef HAVE_LIBEXIF -#include -#endif /* HAVE_LIBEXIF */ - #include #include @@ -47,10 +43,6 @@ #include "jpeg-load.h" #include "jpeg-save.h" #include "jpeg-settings.h" -#ifdef HAVE_LIBEXIF -#include "jpeg-exif.h" -#endif - #define SCALE_WIDTH 125 @@ -69,6 +61,7 @@ #define DEFAULT_EXIF TRUE #define DEFAULT_THUMBNAIL FALSE #define DEFAULT_XMP TRUE +#define DEFAULT_IPTC TRUE #define DEFAULT_USE_ORIG_QUALITY FALSE #define JPEG_DEFAULTS_PARASITE "jpeg-save-defaults" @@ -111,6 +104,7 @@ typedef struct GtkWidget *save_exif; GtkWidget *save_thumbnail; GtkWidget *save_xmp; + GtkWidget *save_iptc; GtkWidget *use_orig_quality; /*quant tables toggle*/ } JpegSaveGui; @@ -127,15 +121,6 @@ static void use_orig_qual_changed (GtkWidget *toggle, static void use_orig_qual_changed2 (GtkWidget *toggle, GtkWidget *combo); -#ifdef HAVE_LIBEXIF - -static gint create_thumbnail (gint32 image_ID, - gint32 drawable_ID, - gdouble quality, - guchar **thumbnail_buffer); - -#endif /* HAVE_LIBEXIF */ - static GtkWidget *restart_markers_scale = NULL; static GtkWidget *restart_markers_label = NULL; @@ -527,82 +512,6 @@ save_image (const gchar *filename, */ jpeg_start_compress (&cinfo, TRUE); -#ifdef HAVE_LIBEXIF - - /* Create the thumbnail JPEG in a buffer */ - if ((jsvals.save_exif && exif_data) || jsvals.save_thumbnail) - { - ExifData *exif_data_tmp = NULL; - guchar *exif_buf = NULL; - guchar *thumbnail_buffer = NULL; - gint thumbnail_buffer_length = 0; - guint exif_buf_len; - gdouble quality = MIN (75.0, jsvals.quality); - - if ( (! jsvals.save_exif) || (! exif_data)) - exif_data_tmp = exif_data_new (); - else - exif_data_tmp = exif_data; - - /* avoid saving markers longer than 65533, gradually decrease - * quality in steps of 5 until exif_buf_len is lower than that. - */ - for (exif_buf_len = 65535; - exif_buf_len > 65533 && quality > 0.0; - quality -= 5.0) - { - if (jsvals.save_thumbnail) - thumbnail_buffer_length = create_thumbnail (image_ID, drawable_ID, - quality, - &thumbnail_buffer); - - exif_data_tmp->data = thumbnail_buffer; - exif_data_tmp->size = thumbnail_buffer_length; - - if (exif_buf) - free (exif_buf); - - exif_data_save_data (exif_data_tmp, &exif_buf, &exif_buf_len); - } - - if (exif_buf_len > 65533) - { - /* last attempt with quality 0.0 */ - if (jsvals.save_thumbnail) - thumbnail_buffer_length = create_thumbnail (image_ID, drawable_ID, - 0.0, - &thumbnail_buffer); - exif_data_tmp->data = thumbnail_buffer; - exif_data_tmp->size = thumbnail_buffer_length; - - if (exif_buf) - free (exif_buf); - - exif_data_save_data (exif_data_tmp, &exif_buf, &exif_buf_len); - } - - if (exif_buf_len > 65533) - { - /* still no go? save without thumbnail */ - exif_data_tmp->data = NULL; - exif_data_tmp->size = 0; - - if (exif_buf) - free (exif_buf); - - exif_data_save_data (exif_data_tmp, &exif_buf, &exif_buf_len); - } - -#ifdef GIMP_UNSTABLE - g_print ("jpeg-save: saving EXIF block (%d bytes)\n", exif_buf_len); -#endif - jpeg_write_marker (&cinfo, JPEG_APP0 + 1, exif_buf, exif_buf_len); - - if (exif_buf) - free (exif_buf); - } -#endif /* HAVE_LIBEXIF */ - /* Step 4.1: Write the comment out - pw */ if (image_comment && *image_comment) { @@ -614,36 +523,7 @@ save_image (const gchar *filename, (guchar *) image_comment, strlen (image_comment)); } - /* Step 4.2: Write the XMP packet in an APP1 marker */ - if (jsvals.save_xmp) - { - /* FIXME: temporary hack until the right thing is done by a library */ - parasite = gimp_image_get_parasite (orig_image_ID, "gimp-metadata"); - if (parasite) - { - const gchar *xmp_data; - glong xmp_data_size; - guchar *app_block; - - xmp_data = ((const gchar *) gimp_parasite_data (parasite)) + 10; - xmp_data_size = gimp_parasite_data_size (parasite) - 10; -#ifdef GIMP_UNSTABLE - g_print ("jpeg-save: saving XMP packet (%d bytes)\n", - (int) xmp_data_size); -#endif - app_block = g_malloc (sizeof (JPEG_APP_HEADER_XMP) + xmp_data_size); - memcpy (app_block, JPEG_APP_HEADER_XMP, - sizeof (JPEG_APP_HEADER_XMP)); - memcpy (app_block + sizeof (JPEG_APP_HEADER_XMP), xmp_data, - xmp_data_size); - jpeg_write_marker (&cinfo, JPEG_APP0 + 1, app_block, - sizeof (JPEG_APP_HEADER_XMP) + xmp_data_size); - g_free (app_block); - gimp_parasite_free (parasite); - } - } - - /* Step 4.3: store the color profile if there is one */ + /* Step 4.2: store the color profile if there is one */ parasite = gimp_image_get_parasite (orig_image_ID, "icc-profile"); if (parasite) { @@ -913,7 +793,7 @@ save_dialog (void) gtk_box_pack_start (GTK_BOX (vbox), frame, FALSE, FALSE, 0); gtk_widget_show (frame); - table = gtk_table_new (4, 7, FALSE); + table = gtk_table_new (4, 8, FALSE); gtk_table_set_col_spacings (GTK_TABLE (table), 6); gtk_table_set_row_spacings (GTK_TABLE (table), 6); gtk_table_set_col_spacing (GTK_TABLE (table), 1, 12); @@ -1000,9 +880,9 @@ save_dialog (void) gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), jsvals.progressive); -#ifdef HAVE_LIBEXIF pg.save_exif = toggle = gtk_check_button_new_with_mnemonic (_("Save _EXIF data")); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), jsvals.save_exif); gtk_table_attach (GTK_TABLE (table), toggle, 0, 1, 2, 3, GTK_FILL, 0, 0, 0); gtk_widget_show (toggle); @@ -1013,13 +893,11 @@ save_dialog (void) G_CALLBACK (make_preview), NULL); - gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), - jsvals.save_exif && exif_data); - - gtk_widget_set_sensitive (toggle, exif_data != NULL); + gtk_widget_set_sensitive (toggle, TRUE); pg.save_thumbnail = toggle = gtk_check_button_new_with_mnemonic (_("Save _thumbnail")); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), jsvals.save_thumbnail); gtk_table_attach (GTK_TABLE (table), toggle, 0, 1, 3, 4, GTK_FILL, 0, 0, 0); gtk_widget_show (toggle); @@ -1030,13 +908,10 @@ save_dialog (void) G_CALLBACK (make_preview), NULL); - gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), - jsvals.save_thumbnail); -#endif /* HAVE_LIBEXIF */ - /* XMP metadata */ pg.save_xmp = toggle = gtk_check_button_new_with_mnemonic (_("Save _XMP data")); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), jsvals.save_xmp); gtk_table_attach (GTK_TABLE (table), toggle, 0, 1, 4, 5, GTK_FILL, 0, 0, 0); gtk_widget_show (toggle); @@ -1047,16 +922,29 @@ save_dialog (void) G_CALLBACK (make_preview), NULL); - gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), - jsvals.save_xmp && has_metadata); + gtk_widget_set_sensitive (toggle, TRUE); - gtk_widget_set_sensitive (toggle, has_metadata); + /* IPTC metadata */ + pg.save_iptc = toggle = + gtk_check_button_new_with_mnemonic (_("Save _IPTC data")); + gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (toggle), jsvals.save_iptc); + gtk_table_attach (GTK_TABLE (table), toggle, 0, 1, 5, 6, GTK_FILL, 0, 0, 0); + gtk_widget_show (toggle); + + g_signal_connect (toggle, "toggled", + G_CALLBACK (gimp_toggle_button_update), + &jsvals.save_iptc); + g_signal_connect (toggle, "toggled", + G_CALLBACK (make_preview), + NULL); + + gtk_widget_set_sensitive (toggle, TRUE); /* custom quantization tables - now used also for original quality */ pg.use_orig_quality = toggle = gtk_check_button_new_with_mnemonic (_("_Use quality settings from original " "image")); - gtk_table_attach (GTK_TABLE (table), toggle, 0, 4, 5, 6, GTK_FILL, 0, 0, 0); + gtk_table_attach (GTK_TABLE (table), toggle, 0, 4, 6, 7, GTK_FILL, 0, 0, 0); gtk_widget_show (toggle); gimp_help_set_help_data (toggle, @@ -1266,13 +1154,9 @@ load_defaults (void) jsvals.save_exif = DEFAULT_EXIF; jsvals.save_thumbnail = DEFAULT_THUMBNAIL; jsvals.save_xmp = DEFAULT_XMP; + jsvals.save_iptc = DEFAULT_IPTC; jsvals.use_orig_quality = DEFAULT_USE_ORIG_QUALITY; -#ifdef HAVE_LIBEXIF - if (exif_data && (exif_data->data)) - jsvals.save_thumbnail = TRUE; -#endif /* HAVE_LIBEXIF */ - parasite = gimp_get_parasite (JPEG_DEFAULTS_PARASITE); if (! parasite) @@ -1283,7 +1167,7 @@ load_defaults (void) gimp_parasite_free (parasite); - num_fields = sscanf (def_str, "%lf %lf %d %d %d %d %d %d %d %d %d %d %d", + num_fields = sscanf (def_str, "%lf %lf %d %d %d %d %d %d %d %d %d %d %d %d", &tmpvals.quality, &tmpvals.smoothing, &tmpvals.optimize, @@ -1296,12 +1180,15 @@ load_defaults (void) &tmpvals.save_exif, &tmpvals.save_thumbnail, &tmpvals.save_xmp, - &tmpvals.use_orig_quality); + &tmpvals.use_orig_quality, + &tmpvals.save_iptc); tmpvals.subsmp = subsampling; - if (num_fields == 13) - memcpy (&jsvals, &tmpvals, sizeof (tmpvals)); + if (num_fields == 13 || num_fields == 14) + { + memcpy (&jsvals, &tmpvals, sizeof (tmpvals)); + } g_free (def_str); } @@ -1312,7 +1199,7 @@ save_defaults (void) GimpParasite *parasite; gchar *def_str; - def_str = g_strdup_printf ("%lf %lf %d %d %d %d %d %d %d %d %d %d %d", + def_str = g_strdup_printf ("%lf %lf %d %d %d %d %d %d %d %d %d %d %d %d", jsvals.quality, jsvals.smoothing, jsvals.optimize, @@ -1325,7 +1212,8 @@ save_defaults (void) jsvals.save_exif, jsvals.save_thumbnail, jsvals.save_xmp, - jsvals.use_orig_quality); + jsvals.use_orig_quality, + jsvals.save_iptc); parasite = gimp_parasite_new (JPEG_DEFAULTS_PARASITE, GIMP_PARASITE_PERSISTENT, strlen (def_str), def_str); @@ -1350,11 +1238,10 @@ load_gui_defaults (JpegSaveGui *pg) SET_ACTIVE_BTTN (progressive); SET_ACTIVE_BTTN (use_orig_quality); SET_ACTIVE_BTTN (preview); -#ifdef HAVE_LIBEXIF SET_ACTIVE_BTTN (save_exif); SET_ACTIVE_BTTN (save_thumbnail); -#endif SET_ACTIVE_BTTN (save_xmp); + SET_ACTIVE_BTTN (save_iptc); #undef SET_ACTIVE_BTTN @@ -1447,218 +1334,3 @@ use_orig_qual_changed2 (GtkWidget *toggle, gimp_int_combo_box_set_active (GIMP_INT_COMBO_BOX (combo), orig_subsmp); } } - -#ifdef HAVE_LIBEXIF - -static guchar *tbuffer = NULL; -static guchar *tbuffer2 = NULL; - -static gint tbuffer_count = 0; - -typedef struct -{ - struct jpeg_destination_mgr pub; /* public fields */ - guchar *buffer; - gint size; -} my_destination_mgr; - -typedef my_destination_mgr *my_dest_ptr; - -static void -init_destination (j_compress_ptr cinfo) -{ -} - -static gboolean -empty_output_buffer (j_compress_ptr cinfo) -{ - my_dest_ptr dest = (my_dest_ptr) cinfo->dest; - - tbuffer_count = tbuffer_count + 16384; - tbuffer = g_renew (guchar, tbuffer, tbuffer_count); - g_memmove (tbuffer + tbuffer_count - 16384, tbuffer2, 16384); - - dest->pub.next_output_byte = tbuffer2; - dest->pub.free_in_buffer = 16384; - - return TRUE; -} - -static void -term_destination (j_compress_ptr cinfo) -{ - my_dest_ptr dest = (my_dest_ptr) cinfo->dest; - - tbuffer_count = (tbuffer_count + 16384) - (dest->pub.free_in_buffer); - - tbuffer = g_renew (guchar, tbuffer, tbuffer_count); - g_memmove (tbuffer + tbuffer_count - (16384 - dest->pub.free_in_buffer), - tbuffer2, 16384 - dest->pub.free_in_buffer); -} - -static gint -create_thumbnail (gint32 image_ID, - gint32 drawable_ID, - gdouble quality, - guchar **thumbnail_buffer) -{ - int width, height; - gint req_width, req_height, bpp, rbpp; - guchar *thumbnail_data = NULL; - struct jpeg_compress_struct cinfo; - struct my_error_mgr jerr; - my_dest_ptr dest; - JSAMPROW scanline[1]; - guchar *buf = NULL; - gint i; - - { - GeglBuffer *buffer = gimp_drawable_get_buffer (drawable_ID); - width = gegl_buffer_get_width (buffer); - height = gegl_buffer_get_height (buffer); - g_object_unref (buffer); - } - - req_width = 196; - req_height = 196; - - if (MIN (width, height) < 196) - req_width = req_height = MIN(width, height); - - thumbnail_data = gimp_drawable_get_thumbnail_data (drawable_ID, - &req_width, &req_height, - &bpp); - - if (! thumbnail_data) - return 0; - - rbpp = bpp; - - if ((bpp == 2) || (bpp == 4)) - { - rbpp = bpp - 1; - } - - buf = g_new (guchar, req_width * bpp); - tbuffer2 = g_new (guchar, 16384); - - tbuffer_count = 0; - - cinfo.err = jpeg_std_error (&jerr.pub); - jerr.pub.error_exit = my_error_exit; - - /* Establish the setjmp return context for my_error_exit to use. */ - if (setjmp (jerr.setjmp_buffer)) - { - /* If we get here, the JPEG code has signaled an error. - * We need to clean up the JPEG object, free memory, and return. - */ - jpeg_destroy_compress (&cinfo); - - if (thumbnail_data) - { - g_free (thumbnail_data); - thumbnail_data = NULL; - } - - if (buf) - { - g_free (buf); - buf = NULL; - } - - if (tbuffer2) - { - g_free (tbuffer2); - tbuffer2 = NULL; - } - - return 0; - } - - /* Now we can initialize the JPEG compression object. */ - jpeg_create_compress (&cinfo); - - if (cinfo.dest == NULL) - cinfo.dest = (struct jpeg_destination_mgr *) - (*cinfo.mem->alloc_small) ((j_common_ptr) &cinfo, JPOOL_PERMANENT, - sizeof(my_destination_mgr)); - - dest = (my_dest_ptr) cinfo.dest; - dest->pub.init_destination = init_destination; - dest->pub.empty_output_buffer = empty_output_buffer; - dest->pub.term_destination = term_destination; - - dest->pub.next_output_byte = tbuffer2; - dest->pub.free_in_buffer = 16384; - - dest->buffer = tbuffer2; - dest->size = 16384; - - cinfo.input_components = rbpp; - cinfo.image_width = req_width; - cinfo.image_height = req_height; - - /* colorspace of input image */ - cinfo.in_color_space = (rbpp == 3) ? JCS_RGB : JCS_GRAYSCALE; - - /* Now use the library's routine to set default compression parameters. - * (You must set at least cinfo.in_color_space before calling this, - * since the defaults depend on the source color space.) - */ - jpeg_set_defaults (&cinfo); - - jpeg_set_quality (&cinfo, (gint) (quality + 0.5), jsvals.baseline); - - /* Step 4: Start compressor */ - - /* TRUE ensures that we will write a complete interchange-JPEG file. - * Pass TRUE unless you are very sure of what you're doing. - */ - jpeg_start_compress (&cinfo, TRUE); - - while (cinfo.next_scanline < (unsigned int) req_height) - { - for (i = 0; i < req_width; i++) - { - buf[(i * rbpp) + 0] = thumbnail_data[(cinfo.next_scanline * req_width * bpp) + (i * bpp) + 0]; - - if (rbpp == 3) - { - buf[(i * rbpp) + 1] = thumbnail_data[(cinfo.next_scanline * req_width * bpp) + (i * bpp) + 1]; - buf[(i * rbpp) + 2] = thumbnail_data[(cinfo.next_scanline * req_width * bpp) + (i * bpp) + 2]; - } - } - - scanline[0] = buf; - jpeg_write_scanlines (&cinfo, scanline, 1); - } - - /* Step 6: Finish compression */ - jpeg_finish_compress (&cinfo); - - /* Step 7: release JPEG compression object */ - - /* This is an important step since it will release a good deal of memory. */ - jpeg_destroy_compress (&cinfo); - - /* And we're done! */ - - if (thumbnail_data) - { - g_free (thumbnail_data); - thumbnail_data = NULL; - } - - if (buf) - { - g_free (buf); - buf = NULL; - } - - *thumbnail_buffer = tbuffer; - - return tbuffer_count; -} - -#endif /* HAVE_LIBEXIF */ diff --git a/plug-ins/file-jpeg/jpeg-save.h b/plug-ins/file-jpeg/jpeg-save.h index 4465991694..2e5ad80dd2 100644 --- a/plug-ins/file-jpeg/jpeg-save.h +++ b/plug-ins/file-jpeg/jpeg-save.h @@ -32,6 +32,7 @@ typedef struct gboolean save_exif; gboolean save_thumbnail; gboolean save_xmp; + gboolean save_iptc; gboolean use_orig_quality; } JpegSaveVals; diff --git a/plug-ins/file-jpeg/jpeg-settings.c b/plug-ins/file-jpeg/jpeg-settings.c index e4112b9328..b86e2bc0a8 100644 --- a/plug-ins/file-jpeg/jpeg-settings.c +++ b/plug-ins/file-jpeg/jpeg-settings.c @@ -50,10 +50,6 @@ #include -#ifdef HAVE_LIBEXIF -#include -#endif /* HAVE_LIBEXIF */ - #include #include "libgimp/stdplugins-intl.h" diff --git a/plug-ins/file-jpeg/jpeg.c b/plug-ins/file-jpeg/jpeg.c index bdfb70a7de..94e8a1efda 100644 --- a/plug-ins/file-jpeg/jpeg.c +++ b/plug-ins/file-jpeg/jpeg.c @@ -24,10 +24,6 @@ #include #include -#ifdef HAVE_LIBEXIF -#include -#endif /* HAVE_LIBEXIF */ - #include #include @@ -37,11 +33,6 @@ #include "jpeg-settings.h" #include "jpeg-load.h" #include "jpeg-save.h" -#ifdef HAVE_LIBEXIF -#include "jpeg-exif.h" -#include "gimpexif.h" -#endif - /* Declare local functions. */ @@ -66,10 +57,6 @@ JpegSubsampling orig_subsmp; gint num_quant_tables; -#ifdef HAVE_LIBEXIF -ExifData *exif_data = NULL; -#endif - const GimpPlugInInfo PLUG_IN_INFO = { NULL, /* init_proc */ @@ -96,8 +83,6 @@ query (void) { GIMP_PDB_IMAGE, "image", "Output image" } }; -#ifdef HAVE_LIBEXIF - static const GimpParamDef thumb_args[] = { { GIMP_PDB_STRING, "filename", "The name of the file to load" }, @@ -110,8 +95,6 @@ query (void) { GIMP_PDB_INT32, "image-height", "Height of full-sized image" } }; -#endif /* HAVE_LIBEXIF */ - static const GimpParamDef save_args[] = { { GIMP_PDB_INT32, "run-mode", "The run mode { RUN-INTERACTIVE (0), RUN-NONINTERACTIVE (1) }" }, @@ -149,8 +132,6 @@ query (void) "", "6,string,JFIF,6,string,Exif"); -#ifdef HAVE_LIBEXIF - gimp_install_procedure (LOAD_THUMB_PROC, "Loads a thumbnail from a JPEG image", "Loads a thumbnail from a JPEG image (only if it exists)", @@ -166,8 +147,6 @@ query (void) gimp_register_thumbnail_loader (LOAD_PROC, LOAD_THUMB_PROC); -#endif /* HAVE_LIBEXIF */ - gimp_install_procedure (SAVE_PROC, "saves files in the JPEG file format", "saves files in the lossy, widely supported JPEG format", @@ -237,6 +216,13 @@ run (const gchar *name, if (image_ID != -1) { + GFile *file = g_file_new_for_path (param[1].data.d_string); + + gimp_image_metadata_load (image_ID, "image/jpeg", file, + load_interactive); + + g_object_unref (file); + *nreturn_vals = 2; values[1].type = GIMP_PDB_IMAGE; values[1].data.d_image = image_ID; @@ -247,9 +233,6 @@ run (const gchar *name, } } - -#ifdef HAVE_LIBEXIF - else if (strcmp (name, LOAD_THUMB_PROC) == 0) { if (nparams < 2) @@ -258,14 +241,16 @@ run (const gchar *name, } else { - const gchar *filename = param[0].data.d_string; + GFile *file = g_file_new_for_path (param[0].data.d_string); gint width = 0; gint height = 0; GimpImageType type = -1; - image_ID = load_thumbnail_image (filename, &width, &height, &type, + image_ID = load_thumbnail_image (file, &width, &height, &type, &error); + g_object_unref (file); + if (image_ID != -1) { *nreturn_vals = 6; @@ -286,9 +271,6 @@ run (const gchar *name, } } } - -#endif /* HAVE_LIBEXIF */ - else if (strcmp (name, SAVE_PROC) == 0) { image_ID = orig_image_ID = param[1].data.d_int32; @@ -348,14 +330,7 @@ run (const gchar *name, gimp_parasite_free (parasite); } -#ifdef HAVE_LIBEXIF - - exif_data = gimp_metadata_generate_exif (orig_image_ID); - if (exif_data) - jpeg_setup_exif_for_save (exif_data, orig_image_ID); - -#endif /* HAVE_LIBEXIF */ - + /* load defaults from gimp parasite */ load_defaults (); switch (run_mode) @@ -427,6 +402,7 @@ run (const gchar *name, jsvals.save_exif = save_vals->save_exif; jsvals.save_thumbnail = save_vals->save_thumbnail; jsvals.save_xmp = save_vals->save_xmp; + jsvals.save_iptc = save_vals->save_iptc; jsvals.use_orig_quality = save_vals->use_orig_quality; gimp_parasite_free (parasite); @@ -511,9 +487,12 @@ run (const gchar *name, if (status == GIMP_PDB_SUCCESS) { + GimpMetadata *metadata; + /* pw - now we need to change the defaults to be whatever * was used to save this image. Dump the old parasites - * and add new ones. */ + * and add new ones. + */ gimp_image_detach_parasite (orig_image_ID, "gimp-comment"); if (image_comment && strlen (image_comment)) @@ -526,11 +505,36 @@ run (const gchar *name, gimp_parasite_free (parasite); } - gimp_image_detach_parasite (orig_image_ID, "jpeg-save-options"); parasite = gimp_parasite_new ("jpeg-save-options", 0, sizeof (jsvals), &jsvals); gimp_image_attach_parasite (orig_image_ID, parasite); gimp_parasite_free (parasite); + + /* write metadata */ + metadata = gimp_image_metadata_save_prepare (image_ID, + "image/jpeg"); + + if (metadata) + { + GFile *file; + GimpMetadataSaveFlags flags = 0; + + gimp_metadata_set_bits_per_sample (metadata, 8); + + if (jsvals.save_exif) flags |= GIMP_METADATA_SAVE_EXIF; + if (jsvals.save_xmp) flags |= GIMP_METADATA_SAVE_XMP; + if (jsvals.save_iptc) flags |= GIMP_METADATA_SAVE_IPTC; + if (jsvals.save_thumbnail) flags |= GIMP_METADATA_SAVE_THUMBNAIL; + + file = g_file_new_for_path (param[3].data.d_string); + gimp_image_metadata_save_finish (image_ID, + "image/jpeg", + metadata, file, flags, + NULL); + g_object_unref (file); + + g_object_unref (metadata); + } } } else diff --git a/plug-ins/file-psd/psd-image-res-load.c b/plug-ins/file-psd/psd-image-res-load.c index bf2c343216..178b9dc101 100644 --- a/plug-ins/file-psd/psd-image-res-load.c +++ b/plug-ins/file-psd/psd-image-res-load.c @@ -94,9 +94,6 @@ #include #include -#ifdef HAVE_LIBEXIF -#include -#endif /* HAVE_LIBEXIF */ #ifdef HAVE_IPTCDATA #include #endif /* HAVE_IPTCDATA */ @@ -196,11 +193,6 @@ static gint load_resource_1058 (const PSDimageres *res_a, FILE *f, GError **error); -static gint load_resource_1060 (const PSDimageres *res_a, - const gint32 image_id, - FILE *f, - GError **error); - static gint load_resource_2000 (const PSDimageres *res_a, const gint32 image_id, FILE *f, @@ -353,7 +345,6 @@ load_image_resource (PSDimageres *res_a, break; case PSD_XMP_DATA: - load_resource_1060 (res_a, image_id, f, error); break; default: @@ -1209,21 +1200,7 @@ load_resource_1058 (const PSDimageres *res_a, FILE *f, GError **error) { - /* Load EXIF data block */ - -#ifdef HAVE_LIBEXIF - ExifData *exif_data; - ExifEntry *exif_entry; - guchar *exif_buf; - guchar *tmp_data; - guint exif_buf_len; - gint16 jpeg_len; - gint16 jpeg_fill = 0; - GimpParam *return_vals; - gint nreturn_vals; -#else gchar *name; -#endif /* HAVE_LIBEXIF */ GimpParasite *parasite; gchar *res_data; @@ -1238,79 +1215,6 @@ load_resource_1058 (const PSDimageres *res_a, return -1; } -#ifdef HAVE_LIBEXIF - /* Add JPEG header & trailer to the TIFF Exif data held in PSD - resource to allow us to use libexif to serialize the data - in the same manner as the JPEG load. - */ - jpeg_len = res_a->data_len + 8; - tmp_data = g_malloc (res_a->data_len + 12); - /* SOI & APP1 markers */ - memcpy (tmp_data, "\xFF\xD8\xFF\xE1", 4); - /* APP1 block len */ - memcpy (tmp_data + 4, &jpeg_len, 2); - /* Exif marker */ - memcpy (tmp_data + 6, "Exif", 4); - /* Filler */ - memcpy (tmp_data + 10, &jpeg_fill, 2); - /* Exif data */ - memcpy (tmp_data + 12, res_data, res_a->data_len); - - /* Create Exif data structure */ - exif_data = exif_data_new_from_data (tmp_data, res_a->data_len + 12); - g_free (tmp_data); - IFDBG (3) exif_data_dump (exif_data); - - /* Check for XMP data block in Exif data - PS7 */ - if ((exif_entry = exif_content_get_entry (exif_data->ifd[EXIF_IFD_0], - EXIF_TAG_XML_PACKET))) - { - IFDBG(3) g_debug ("Processing Exif XMP data block"); - /*Create NULL terminated EXIF data block */ - tmp_data = g_malloc (exif_entry->size + 1); - memcpy (tmp_data, exif_entry->data, exif_entry->size); - tmp_data[exif_entry->size] = 0; - /* Merge with existing XMP data block */ - return_vals = gimp_run_procedure (DECODE_XMP_PROC, - &nreturn_vals, - GIMP_PDB_IMAGE, image_id, - GIMP_PDB_STRING, tmp_data, - GIMP_PDB_END); - g_free (tmp_data); - gimp_destroy_params (return_vals, nreturn_vals); - IFDBG(3) g_debug ("Deleting XMP block from Exif data"); - /* Delete XMP data from Exif block */ - exif_content_remove_entry (exif_data->ifd[EXIF_IFD_0], - exif_entry); - } - - /* Check for Photoshp Image Resource data block in Exif data - PS7 */ - if ((exif_entry = exif_content_get_entry (exif_data->ifd[EXIF_IFD_0], - EXIF_TAG_IMAGE_RESOURCES))) - { - IFDBG(3) g_debug ("Deleting PS Image Resource block from Exif data"); - /* Delete PS Image Resource data from Exif block */ - exif_content_remove_entry (exif_data->ifd[EXIF_IFD_0], - exif_entry); - } - - IFDBG (3) exif_data_dump (exif_data); - /* Store resource data as a GIMP Exif parasite */ - IFDBG (2) g_debug ("Processing exif data as GIMP Exif parasite"); - /* Serialize exif data */ - exif_data_save_data (exif_data, &exif_buf, &exif_buf_len); - if (exif_buf_len > EXIF_HEADER_SIZE) - { - parasite = gimp_parasite_new (GIMP_PARASITE_EXIF, - GIMP_PARASITE_PERSISTENT, - exif_buf_len, exif_buf); - gimp_image_attach_parasite (image_id, parasite); - gimp_parasite_free (parasite); - } - exif_data_unref (exif_data); - g_free (exif_buf); - -#else /* Store resource data as a standard psd parasite */ IFDBG (2) g_debug ("Processing exif data as psd parasite"); name = g_strdup_printf ("psd-image-resource-%.4s-%.4x", @@ -1322,45 +1226,10 @@ load_resource_1058 (const PSDimageres *res_a, gimp_parasite_free (parasite); g_free (name); -#endif /* HAVE_LIBEXIF */ - g_free (res_data); return 0; } -static gint -load_resource_1060 (const PSDimageres *res_a, - const gint32 image_id, - FILE *f, - GError **error) -{ - /* Load XMP Metadata block */ - GimpParam *return_vals; - gint nreturn_vals; - gchar *res_data; - - IFDBG(2) g_debug ("Process image resource block: 1060: XMP Data"); - - res_data = g_malloc (res_a->data_len + 1); - if (fread (res_data, res_a->data_len, 1, f) < 1) - { - psd_set_error (feof (f), errno, error); - g_free (res_data); - return -1; - } - /* Null terminate metadata block for decode procedure */ - res_data[res_a->data_len] = 0; - - return_vals = gimp_run_procedure (DECODE_XMP_PROC, - &nreturn_vals, - GIMP_PDB_IMAGE, image_id, - GIMP_PDB_STRING, res_data, - GIMP_PDB_END); - g_free (res_data); - gimp_destroy_params (return_vals, nreturn_vals); - return 0; -} - static gint load_resource_2000 (const PSDimageres *res_a, const gint32 image_id, diff --git a/plug-ins/file-psd/psd-save.c b/plug-ins/file-psd/psd-save.c index 9b30ce1e3d..ce7ae576de 100644 --- a/plug-ins/file-psd/psd-save.c +++ b/plug-ins/file-psd/psd-save.c @@ -293,6 +293,33 @@ run (const gchar *name, if (save_image (param[3].data.d_string, image_id, &error)) { + GimpMetadata *metadata; + + metadata = gimp_image_metadata_save_prepare (image_id, + "image/x-psd"); + + if (metadata) + { + GFile *file; + GimpMetadataSaveFlags flags = 0; + + gimp_metadata_set_bits_per_sample (metadata, 8); + + if (TRUE) flags |= GIMP_METADATA_SAVE_EXIF; + if (TRUE) flags |= GIMP_METADATA_SAVE_XMP; + if (TRUE) flags |= GIMP_METADATA_SAVE_IPTC; + if (TRUE) flags |= GIMP_METADATA_SAVE_THUMBNAIL; + + file = g_file_new_for_path (param[3].data.d_string); + gimp_image_metadata_save_finish (image_id, + "image/x-psd", + metadata, file, flags, + NULL); + g_object_unref (file); + + g_object_unref (metadata); + } + values[0].data.d_status = GIMP_PDB_SUCCESS; } else diff --git a/plug-ins/file-psd/psd.c b/plug-ins/file-psd/psd.c index 97668e121c..b2bfc3ebff 100644 --- a/plug-ins/file-psd/psd.c +++ b/plug-ins/file-psd/psd.c @@ -23,6 +23,7 @@ #include #include +#include #include "psd.h" #include "psd-load.h" @@ -34,6 +35,7 @@ #include "libgimp/stdplugins-intl.h" + /* Local function prototypes */ static void query (void); @@ -168,6 +170,7 @@ run (const gchar *name, GimpParam **return_vals) { static GimpParam values[4]; + GimpRunMode run_mode; GimpPDBStatusType status = GIMP_PDB_SUCCESS; gint32 image_ID; GError *error = NULL; @@ -176,6 +179,8 @@ run (const gchar *name, GimpExportReturn export = GIMP_EXPORT_CANCEL; #endif /* PSD_SAVE */ + run_mode = param[0].data.d_int32; + INIT_I18N (); *nreturn_vals = 1; @@ -187,10 +192,31 @@ run (const gchar *name, /* File load */ if (strcmp (name, LOAD_PROC) == 0) { + gboolean interactive; + + switch (run_mode) + { + case GIMP_RUN_INTERACTIVE: + case GIMP_RUN_WITH_LAST_VALS: + gimp_ui_init (PLUG_IN_BINARY, FALSE); + interactive = TRUE; + break; + default: + interactive = FALSE; + break; + } + image_ID = load_image (param[1].data.d_string, &error); if (image_ID != -1) { + GFile *file = g_file_new_for_path (param[1].data.d_string); + + gimp_image_metadata_load (image_ID, "image/x-psd", file, + interactive); + + g_object_unref (file); + *nreturn_vals = 2; values[1].type = GIMP_PDB_IMAGE; values[1].data.d_image = image_ID; @@ -237,9 +263,6 @@ run (const gchar *name, /* File save */ else if (strcmp (name, SAVE_PROC) == 0) { - GimpRunMode run_mode; - - run_mode = param[0].data.d_int32; image_ID = param[1].data.d_int32; drawable_ID = param[2].data.d_int32; diff --git a/plug-ins/ui/Makefile.am b/plug-ins/ui/Makefile.am index 0f52918642..aec82b9158 100644 --- a/plug-ins/ui/Makefile.am +++ b/plug-ins/ui/Makefile.am @@ -2,6 +2,8 @@ uidatadir = $(gimpdatadir)/ui/plug-ins uidata_DATA = \ plug-in-file-gif.ui \ - plug-in-file-png.ui + plug-in-file-png.ui \ + plug-in-file-tiff.ui \ + plug-in-metadata.ui EXTRA_DIST = $(uidata_DATA) diff --git a/plug-ins/ui/plug-in-file-png.ui b/plug-ins/ui/plug-in-file-png.ui index 0005595631..2e967cb14e 100644 --- a/plug-ins/ui/plug-in-file-png.ui +++ b/plug-ins/ui/plug-in-file-png.ui @@ -1,20 +1,18 @@ - + - + 9 - - 9 - 1 1 True + False 12 - 10 + 11 3 6 6 @@ -25,6 +23,7 @@ True False True + 0 True @@ -38,6 +37,7 @@ True False True + 0 True @@ -52,6 +52,7 @@ True True False + 0 True @@ -67,6 +68,7 @@ True False True + 0 True @@ -82,6 +84,7 @@ True False True + 0 True @@ -97,6 +100,7 @@ True False True + 0 True @@ -112,6 +116,7 @@ True False True + 0 True @@ -128,6 +133,7 @@ True False True + 0 True @@ -139,6 +145,7 @@ True + False 0 Co_mpression level: True @@ -147,7 +154,7 @@ 8 9 - + @@ -170,7 +177,9 @@ True True Choose a high compression level for small file size - + + False + False compression-level True @@ -179,12 +188,13 @@ 3 8 9 - + True + False 6 start @@ -216,6 +226,94 @@ + + 3 + 10 + 11 + + + + + True + True + + + True + False + + + Save EXIF data + True + True + False + 0 + True + True + + + True + True + 0 + + + + + Save XMP data + True + True + False + 0 + True + True + + + True + True + 1 + + + + + Save IPTC data + True + True + False + 0 + True + True + + + True + True + 2 + + + + + Save thumbnail + True + True + False + 0 + True + True + + + True + True + 3 + + + + + + + True + False + Advanced + + + 3 9 diff --git a/plug-ins/ui/plug-in-file-tiff.ui b/plug-ins/ui/plug-in-file-tiff.ui new file mode 100644 index 0000000000..ace9c2c881 --- /dev/null +++ b/plug-ins/ui/plug-in-file-tiff.ui @@ -0,0 +1,404 @@ + + + + + + True + False + + + True + False + 0 + none + + + True + False + 12 + + + True + False + + + True + False + 3 + + + no compression + True + True + False + True + True + + + True + True + 0 + + + + + LZW + True + True + False + True + True + cmp_none + + + True + True + 1 + + + + + PackBits + True + True + False + True + True + cmp_none + + + True + True + 2 + + + + + Deflate + True + True + False + True + True + cmp_none + + + True + True + 3 + + + + + JPEG + True + True + False + True + True + cmp_none + + + True + True + 4 + + + + + True + True + 3 + 0 + + + + + True + False + + + CCITT Group 3 fax + True + True + False + True + True + cmp_none + + + True + True + 0 + + + + + CCITT Group _4 fax + True + True + False + True + True + cmp_none + + + True + True + 1 + + + + + + + + + + + + + + True + True + 3 + 1 + + + + + + + + + True + False + <b>Compression</b> + True + + + + + True + True + 3 + 0 + + + + + True + False + + + False + True + 1 + + + + + True + False + + + Save color values from transparent pixels + True + True + False + True + + + True + True + 0 + + + + + True + True + 6 + 2 + + + + + True + False + + + False + True + 3 + + + + + True + False + 0 + none + + + True + False + 12 + + + True + False + + + True + True + + True + False + False + True + True + + + True + True + 3 + 0 + + + + + + + + + True + False + <b>Comment</b> + True + + + + + True + True + 3 + 4 + + + + + True + False + + + False + True + 5 + + + + + True + True + + + True + False + + + True + False + + + save EXIF data + True + True + False + True + + + True + True + 0 + + + + + save XMP data + True + True + False + True + + + True + True + 1 + + + + + True + True + 0 + + + + + True + False + + + save IPTC data + True + True + False + True + + + True + True + 0 + + + + + save thumbnail + True + True + False + True + + + True + True + 1 + + + + + True + True + 1 + + + + + + + True + False + Advanced + + + + + True + True + 3 + 6 + + + + diff --git a/plug-ins/ui/plug-in-metadata.ui b/plug-ins/ui/plug-in-metadata.ui new file mode 100644 index 0000000000..1d076bc36b --- /dev/null +++ b/plug-ins/ui/plug-in-metadata.ui @@ -0,0 +1,908 @@ + + + + + + + + + + + + + + + + + + + + + True + False + + + True + True + True + + + True + True + 6 + in + + + True + True + exif-liststore + False + 0 + + + + + + True + 3 + EXIF Tag + + + + 0 + + + + + + + True + 3 + Value + + + + 1 + + + + + + + + + + + True + False + 4 + 4 + EXIF + + + True + False + + + + + True + True + 6 + in + + + True + True + xmp-liststore + False + 0 + + + + + + True + 3 + XMP Tag + + + + 0 + + + + + + + True + 3 + Value + + + + 1 + + + + + + + + + 1 + + + + + True + False + 4 + 4 + XMP + + + 1 + False + + + + + True + False + 6 + 6 + + + True + True + + + True + False + + + True + False + 6 + 8 + 2 + 3 + 3 + + + True + False + 0 + 3 + 3 + Title + True + + + GTK_SHRINK | GTK_FILL + + + + + True + False + 0 + 3 + 3 + Author + + + 1 + 2 + GTK_SHRINK | GTK_FILL + + + + + True + False + 0 + 3 + 3 + Authortitle + + + 2 + 3 + GTK_SHRINK | GTK_FILL + + + + + True + False + 0 + 3 + 3 + Copyright + + + 3 + 4 + GTK_SHRINK | GTK_FILL + + + + + True + False + 0 + 0 + 3 + 3 + Caption + + + 4 + 5 + GTK_SHRINK | GTK_FILL + + + + + True + False + 0 + 3 + 3 + Captionwriter + + + 5 + 6 + GTK_SHRINK | GTK_FILL + + + + + True + False + 0 + 0 + 3 + 3 + Headline + + + 6 + 7 + GTK_SHRINK | GTK_FILL + + + + + True + False + 0 + 0 + 3 + 3 + Special +Instructions + + + 7 + 8 + GTK_SHRINK | GTK_FILL + + + + + True + True + + True + + + 1 + 2 + + + + + True + True + + True + + + 1 + 2 + 1 + 2 + + + + + True + True + + True + + + 1 + 2 + 2 + 3 + + + + + True + True + + True + + + 1 + 2 + 3 + 4 + + + + + True + True + + True + + + 1 + 2 + 5 + 6 + + + + + True + True + in + + + True + True + + + + + 1 + 2 + 4 + 5 + + + + + True + True + in + + + True + True + + + + + 1 + 2 + 6 + 7 + + + + + True + True + in + + + True + True + + + + + 1 + 2 + 7 + 8 + + + + + False + True + 0 + + + + + + + True + False + 2 + 2 + Description + + + False + + + + + True + False + + + True + False + 6 + 4 + 2 + 3 + 3 + + + True + False + 0 + 0 + 3 + 3 + Keywords + + + GTK_SHRINK | GTK_FILL + + + + + True + False + 0 + 3 + 3 + Category + + + 1 + 2 + GTK_SHRINK | GTK_FILL + + + + + True + False + 0 + 0 + 3 + 3 + Supplemental +Category + + + 2 + 3 + GTK_SHRINK | GTK_FILL + + + + + True + False + 0 + 3 + 3 + Urgency + + + 3 + 4 + GTK_SHRINK | GTK_FILL + + + + + True + True + in + + + True + True + + + + + 1 + 2 + + + + + True + True + in + + + True + True + + + + + 1 + 2 + 2 + 3 + + + + + True + True + + True + + + 1 + 2 + 3 + 4 + + + + + True + True + + True + + + 1 + 2 + 1 + 2 + + + + + False + True + 0 + + + + + 1 + + + + + True + False + 2 + 2 + Keywords/Categories + + + 1 + False + + + + + True + False + + + True + False + 6 + 9 + 2 + 3 + 3 + + + True + False + + + 2 + 2 + 3 + + + + + True + False + 0 + 3 + 3 + Credit + + + GTK_SHRINK | GTK_FILL + + + + + True + False + 0 + 3 + 3 + Source + + + 1 + 2 + GTK_SHRINK | GTK_FILL + + + + + True + True + + True + + + 1 + 2 + + + + + True + True + + True + + + 1 + 2 + 1 + 2 + + + + + True + False + 0 + 3 + 3 + Transmission +reference + + + 8 + 9 + GTK_SHRINK | GTK_FILL + + + + + True + True + + True + + + 1 + 2 + 8 + 9 + + + + + True + False + 0 + 3 + 3 + City + + + 3 + 4 + GTK_SHRINK | GTK_FILL + + + + + True + True + + True + + + 1 + 2 + 3 + 4 + + + + + True + False + 0 + 3 + 3 + Sublocation + + + 4 + 5 + GTK_SHRINK | GTK_FILL + + + + + True + True + + True + + + 1 + 2 + 4 + 5 + + + + + True + False + 0 + 3 + 3 + Province/State + + + 5 + 6 + GTK_SHRINK | GTK_FILL + + + + + True + True + + True + + + 1 + 2 + 5 + 6 + + + + + True + False + 0 + Country + + + 6 + 7 + GTK_SHRINK | GTK_FILL + + + + + True + True + + True + + + 1 + 2 + 6 + 7 + + + + + True + False + + + 2 + 7 + 8 + + + + + False + True + 0 + + + + + 2 + + + + + True + False + 2 + 2 + Credits/Origin + + + 2 + False + + + + + False + True + 0 + + + + + Write IPTC Data + True + True + True + + + False + True + 1 + + + + + 2 + + + + + True + False + 4 + 4 + IPTC + + + 2 + False + + + + + True + True + 0 + + + + diff --git a/po-libgimp/POTFILES.in b/po-libgimp/POTFILES.in index 9c9a88a3c4..f93b15be06 100644 --- a/po-libgimp/POTFILES.in +++ b/po-libgimp/POTFILES.in @@ -11,6 +11,7 @@ libgimp/gimpfontselectbutton.c libgimp/gimpgradientmenu.c libgimp/gimpgradientselectbutton.c libgimp/gimpmenu.c +libgimp/gimpmetadata.c libgimp/gimppalettemenu.c libgimp/gimppaletteselectbutton.c libgimp/gimppatternmenu.c @@ -22,6 +23,7 @@ libgimp/gimpunitcache.c libgimpbase/gimpbaseenums.c libgimpbase/gimpcpuaccel.c libgimpbase/gimpmemsize.c +libgimpbase/gimpmetadata.c libgimpbase/gimputils.c libgimpconfig/gimpcolorconfig.c diff --git a/po-plug-ins/POTFILES.in b/po-plug-ins/POTFILES.in index d9c95024ba..70d52210f9 100644 --- a/po-plug-ins/POTFILES.in +++ b/po-plug-ins/POTFILES.in @@ -96,6 +96,7 @@ plug-ins/common/lens-apply.c plug-ins/common/lens-flare.c plug-ins/common/mail.c plug-ins/common/max-rgb.c +plug-ins/common/metadata.c plug-ins/common/newsprint.c plug-ins/common/nl-filter.c plug-ins/common/noise-rgb.c @@ -141,7 +142,6 @@ plug-ins/file-ico/ico-dialog.c plug-ins/file-ico/ico-load.c plug-ins/file-ico/ico-save.c plug-ins/file-ico/ico.c -plug-ins/file-jpeg/jpeg-exif.c plug-ins/file-jpeg/jpeg-load.c plug-ins/file-jpeg/jpeg-save.c plug-ins/file-jpeg/jpeg.c @@ -187,6 +187,8 @@ plug-ins/gimpressionist/sizemap.c plug-ins/gimpressionist/utils.c [type: gettext/glade]plug-ins/ui/plug-in-file-gif.ui [type: gettext/glade]plug-ins/ui/plug-in-file-png.ui +[type: gettext/glade]plug-ins/ui/plug-in-file-tiff.ui +[type: gettext/glade]plug-ins/ui/plug-in-metadata.ui plug-ins/gradient-flare/gradient-flare.c plug-ins/help-browser/dialog.c plug-ins/help-browser/help-browser.c diff --git a/tools/pdbgen/pdb/image.pdb b/tools/pdbgen/pdb/image.pdb index 50a934221c..a34b484a94 100644 --- a/tools/pdbgen/pdb/image.pdb +++ b/tools/pdbgen/pdb/image.pdb @@ -1625,6 +1625,61 @@ CODE ); } +sub image_get_metadata { + $blurb = "Returns the image's metadata."; + $help = 'Returns exif/iptc/xmp metadata from the image.'; + + &std_pdb_misc('2013', '2.10'); + + @inargs = ( + { name => 'image', type => 'image', + desc => 'The image' } + ); + + @outargs = ( + { name => 'metadata_string', type => 'string', wrap => 1, + desc => 'The exif/ptc/xmp metadata as a string'} + ); + + %invoke = ( + code => <<'CODE' +{ + GimpMetadata *metadata = gimp_image_get_metadata (image); + + if (metadata) + metadata_string = gimp_metadata_serialize (metadata); +} +CODE + ); +} + +sub image_set_metadata { + $blurb = "Set the image's metadata."; + $help = 'Sets exif/iptc/xmp metadata on the image.'; + + &std_pdb_misc('2013', '2.10'); + + @inargs = ( + { name => 'image', type => 'image', + desc => 'The image' }, + { name => 'metadata_string', type => 'string', wrap => 1, + desc => 'The exif/ptc/xmp metadata as a string' } + ); + + %invoke = ( + code => <<'CODE' +{ + GimpMetadata *metadata = gimp_metadata_deserialize (metadata_string); + + gimp_image_set_metadata (image, metadata, TRUE); + + if (metadata) + g_object_unref (metadata); +} +CODE + ); +} + sub image_clean_all { $blurb = 'Set the image dirty count to 0.'; @@ -2988,6 +3043,7 @@ CODE "libgimpbase/gimpbase.h" "core/gimp.h" "core/gimpcontainer.h" + "core/gimpimage-metadata.h" "core/gimpprogress.h" "core/gimptempbuf.h" "core/gimpunit.h" @@ -3029,6 +3085,7 @@ CODE image_flatten image_merge_visible_layers image_merge_down image_add_layer_mask image_remove_layer_mask image_get_colormap image_set_colormap + image_get_metadata image_set_metadata image_clean_all image_is_dirty image_thumbnail image_get_active_layer image_set_active_layer @@ -3060,7 +3117,7 @@ CODE # image_add_layer_mask and image_remove_layer_mask. # If adding or removing functions, make sure the range below is # updated correctly! -%exports = (app => [@procs], lib => [@procs[0..44,47..85]]); +%exports = (app => [@procs], lib => [@procs[0..44,47..87]]); $desc = 'Image'; $doc_title = 'gimpimage';