Reorganized the way JPEG comments and metadata are loaded.
2007-07-17 Raphael Quinet <raphael@gimp.org> Reorganized the way JPEG comments and metadata are loaded. * plug-ins/jpeg/jpeg-load.c (load_image): sanitize comments containing invalid UTF-8 instead of discarding them. If there is more than one EXIF block in a file, merge all of them instead of keeping only the block that contains a thumbnail image (see also bug #446809 and bug #358117). Process XMP after EXIF. * plug-ins/jpeg/jpeg.h: include two null characters as part of the EXIF header. * plug-ins/jpeg/jpeg-exif.c: added jpeg_exif_get_orientation(), removed jpeg_apply_exif_data_to_image() because this is now done directly in jpeg-load.c. * plug-ins/jpeg/jpeg-icc.c * plug-ins/jpeg/jpeg-icc.h: removed jpeg_icc_setup_read_profile() svn path=/trunk/; revision=22949
This commit is contained in:

committed by
Raphaël Quinet

parent
b47a3e52e5
commit
1065f0eb85
21
ChangeLog
21
ChangeLog
@ -1,3 +1,24 @@
|
||||
2007-07-17 Raphaël Quinet <raphael@gimp.org>
|
||||
|
||||
Reorganized the way JPEG comments and metadata are loaded.
|
||||
|
||||
* plug-ins/jpeg/jpeg-load.c (load_image): sanitize comments
|
||||
containing invalid UTF-8 instead of discarding them. If there is
|
||||
more than one EXIF block in a file, merge all of them instead of
|
||||
keeping only the block that contains a thumbnail image (see also
|
||||
bug #446809 and bug #358117). Process XMP after EXIF.
|
||||
|
||||
* plug-ins/jpeg/jpeg.h: include two null characters as part of the
|
||||
EXIF header.
|
||||
|
||||
* plug-ins/jpeg/jpeg-exif.c: added jpeg_exif_get_orientation(),
|
||||
removed jpeg_apply_exif_data_to_image() because this is now done
|
||||
directly in jpeg-load.c.
|
||||
|
||||
* plug-ins/jpeg/jpeg-icc.c
|
||||
* plug-ins/jpeg/jpeg-icc.h: removed jpeg_icc_setup_read_profile()
|
||||
because jpeg_save_markers() is used directly in jpeg-load.c.
|
||||
|
||||
2007-07-17 Sven Neumann <sven@gimp.org>
|
||||
|
||||
* app/base/Makefile.am
|
||||
|
@ -53,8 +53,6 @@
|
||||
#define JPEG_EXIF_ROTATE_PARASITE "exif-orientation-rotate"
|
||||
|
||||
|
||||
static void jpeg_exif_rotate (gint32 image_ID,
|
||||
gint orientation);
|
||||
static gboolean jpeg_exif_rotate_query (gint32 image_ID);
|
||||
|
||||
|
||||
@ -83,43 +81,22 @@ jpeg_exif_data_new_from_file (const gchar *filename,
|
||||
return data;
|
||||
}
|
||||
|
||||
void
|
||||
jpeg_apply_exif_data_to_image (const gchar *filename,
|
||||
const gint32 image_ID)
|
||||
|
||||
gint
|
||||
jpeg_exif_get_orientation (ExifData *exif_data)
|
||||
{
|
||||
ExifData *exif_data = NULL;
|
||||
ExifEntry *entry;
|
||||
gint byte_order;
|
||||
|
||||
exif_data = jpeg_exif_data_new_from_file (filename, NULL);
|
||||
if (!exif_data)
|
||||
return;
|
||||
|
||||
/* return if there is no thumbnail, to work around bug #358117 */
|
||||
if (!exif_data->data || exif_data->size == 0)
|
||||
return;
|
||||
|
||||
/* Previous versions of this code were checking for the presence of
|
||||
* some well-known tags in the EXIF data before proceeding because
|
||||
* libexif could return a non-null exif_data even if the file
|
||||
* contained no EXIF data. Now that the calling code in jpeg-load.c
|
||||
* checks for a JPEG APP1 marker containing the EXIF header before
|
||||
* calling this function, these tests are not necessary anymore.
|
||||
* See also bug #446809.
|
||||
*/
|
||||
|
||||
gimp_metadata_store_exif (image_ID, exif_data);
|
||||
|
||||
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)))
|
||||
{
|
||||
jpeg_exif_rotate (image_ID, exif_get_short (entry->data, byte_order));
|
||||
return exif_get_short (entry->data, byte_order);
|
||||
}
|
||||
|
||||
exif_data_unref (exif_data);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@ -220,7 +197,7 @@ jpeg_setup_exif_for_save (ExifData *exif_data,
|
||||
}
|
||||
|
||||
|
||||
static void
|
||||
void
|
||||
jpeg_exif_rotate (gint32 image_ID,
|
||||
gint orientation)
|
||||
{
|
||||
|
@ -138,18 +138,6 @@ jpeg_icc_write_profile (j_compress_ptr cinfo,
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Prepare for reading an ICC profile
|
||||
*/
|
||||
|
||||
void
|
||||
jpeg_icc_setup_read_profile (j_decompress_ptr cinfo)
|
||||
{
|
||||
/* Tell the library to keep any APP2 data it may find */
|
||||
jpeg_save_markers(cinfo, ICC_MARKER, 0xFFFF);
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* Handy subroutine to test whether a saved marker is an ICC profile marker.
|
||||
*/
|
||||
|
@ -28,23 +28,12 @@ void jpeg_icc_write_profile (j_compress_ptr cinfo,
|
||||
* Reading a JPEG file that may contain an ICC profile requires two steps:
|
||||
*
|
||||
* 1. After jpeg_create_decompress() but before jpeg_read_header(),
|
||||
* call jpeg_icc_setup_read_profile(). This routine tells the IJG
|
||||
* library to save in memory any APP2 markers it may find in the
|
||||
* file.
|
||||
* ask the IJG library to save in memory any APP2 markers it may find
|
||||
* in the file.
|
||||
*
|
||||
* 2. After jpeg_read_header(), call jpeg_icc_read_profile() to find
|
||||
* out whether there was a profile and obtain it if so.
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
* Prepare for reading an ICC profile
|
||||
*/
|
||||
|
||||
void jpeg_icc_setup_read_profile (j_decompress_ptr cinfo);
|
||||
|
||||
|
||||
/*
|
||||
*
|
||||
* See if there was an ICC profile in the JPEG file being read;
|
||||
* if so, reassemble and return the profile data.
|
||||
*
|
||||
|
@ -36,13 +36,16 @@
|
||||
|
||||
#include "libgimp/stdplugins-intl.h"
|
||||
|
||||
#include "gimpexif.h"
|
||||
|
||||
#include "jpeg.h"
|
||||
#include "jpeg-icc.h"
|
||||
#include "jpeg-load.h"
|
||||
|
||||
|
||||
static void jpeg_load_resolution (gint32 image_ID,
|
||||
struct jpeg_decompress_struct *cinfo);
|
||||
static void jpeg_load_resolution (gint32 image_ID,
|
||||
struct jpeg_decompress_struct *cinfo);
|
||||
static void jpeg_sanitize_comment (gchar *comment);
|
||||
|
||||
|
||||
gint32 volatile image_ID_global;
|
||||
@ -72,10 +75,8 @@ load_image (const gchar *filename,
|
||||
gint tile_height;
|
||||
gint scanlines;
|
||||
gint i, start, end;
|
||||
GString *local_image_comments = NULL;
|
||||
GimpParasite * volatile comment_parasite = NULL;
|
||||
jpeg_saved_marker_ptr marker;
|
||||
gboolean found_exif = FALSE;
|
||||
gint orientation = 0;
|
||||
|
||||
/* We set up the normal JPEG error routines. */
|
||||
cinfo.err = jpeg_std_error (&jerr.pub);
|
||||
@ -140,13 +141,11 @@ load_image (const gchar *filename,
|
||||
/* - step 2.1: tell the lib to save the comments */
|
||||
jpeg_save_markers (&cinfo, JPEG_COM, 0xffff);
|
||||
|
||||
/* - step 2.2: tell the lib to save APP1 markers
|
||||
* (may contain EXIF or XMP)
|
||||
*/
|
||||
/* - step 2.2: tell the lib to save APP1 data (EXIF or XMP) */
|
||||
jpeg_save_markers (&cinfo, JPEG_APP0 + 1, 0xffff);
|
||||
|
||||
/* - step 2.3: tell the lib to keep any APP2 data it may find */
|
||||
jpeg_icc_setup_read_profile (&cinfo);
|
||||
/* - step 2.3: tell the lib to save APP2 data (ICC profiles) */
|
||||
jpeg_save_markers (&cinfo, JPEG_APP0 + 2, 0xffff);
|
||||
}
|
||||
|
||||
/* Step 3: read file parameters with jpeg_read_header() */
|
||||
@ -261,98 +260,120 @@ load_image (const gchar *filename,
|
||||
gimp_pixel_rgn_init (&pixel_rgn, drawable, 0, 0,
|
||||
drawable->width, drawable->height, TRUE, FALSE);
|
||||
|
||||
/* Step 5.2: check for metadata (comments, markers containing EXIF or XMP) */
|
||||
for (marker = cinfo.marker_list; marker; marker = marker->next)
|
||||
if (! preview)
|
||||
{
|
||||
const gchar *data = (const gchar *) marker->data;
|
||||
gsize len = marker->data_length;
|
||||
GString *comment_buffer = NULL;
|
||||
#ifdef HAVE_EXIF
|
||||
ExifData *exif_data = NULL;
|
||||
#endif
|
||||
|
||||
if (marker->marker == JPEG_COM)
|
||||
/* Step 5.1: check for comments, or EXIF metadata in APP1 markers */
|
||||
for (marker = cinfo.marker_list; marker; marker = marker->next)
|
||||
{
|
||||
g_print ("jpeg-load: found image comment (%d bytes)\n",
|
||||
marker->data_length);
|
||||
const gchar *data = (const gchar *) marker->data;
|
||||
gsize len = marker->data_length;
|
||||
|
||||
if (!local_image_comments)
|
||||
if (marker->marker == JPEG_COM)
|
||||
{
|
||||
local_image_comments = g_string_new_len (data, len);
|
||||
g_print ("jpeg-load: found image comment (%d bytes)\n",
|
||||
marker->data_length);
|
||||
|
||||
if (! comment_buffer)
|
||||
{
|
||||
comment_buffer = g_string_new_len (data, len);
|
||||
}
|
||||
else
|
||||
{
|
||||
/* concatenate multiple comments, separate them with LF */
|
||||
g_string_append_c (comment_buffer, '\n');
|
||||
g_string_append_len (comment_buffer, data, len);
|
||||
}
|
||||
}
|
||||
else
|
||||
else if ((marker->marker == JPEG_APP0 + 1)
|
||||
&& (len > sizeof (JPEG_APP_HEADER_EXIF) + 8)
|
||||
&& ! strcmp (JPEG_APP_HEADER_EXIF, data))
|
||||
{
|
||||
g_string_append_c (local_image_comments, '\n');
|
||||
g_string_append_len (local_image_comments, data, len);
|
||||
g_print ("jpeg-load: found EXIF block (%d bytes)\n",
|
||||
(gint) (len - sizeof (JPEG_APP_HEADER_EXIF)));
|
||||
#ifdef HAVE_EXIF
|
||||
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
|
||||
}
|
||||
}
|
||||
else if ((marker->marker == JPEG_APP0 + 1)
|
||||
&& (len > 13)
|
||||
&& ! strcmp (JPEG_APP_HEADER_EXIF, data))
|
||||
/* if we found any comments, then make a parasite for them */
|
||||
if (comment_buffer && comment_buffer->len)
|
||||
{
|
||||
/* FIXME: handle EXIF here once we don't use libexif anymore */
|
||||
g_print ("jpeg-load: found EXIF block (%d bytes)\n",
|
||||
(gint) (len - sizeof (JPEG_APP_HEADER_EXIF)));
|
||||
/* FIXME: this flag is a workaround until we handle exif here */
|
||||
found_exif = TRUE;
|
||||
/* Note: maybe split the loop to ensure that the EXIF block is */
|
||||
/* always parsed before any XMP packet */
|
||||
GimpParasite *parasite;
|
||||
|
||||
jpeg_sanitize_comment (comment_buffer->str);
|
||||
parasite = gimp_parasite_new ("gimp-comment",
|
||||
GIMP_PARASITE_PERSISTENT,
|
||||
strlen (comment_buffer->str) + 1,
|
||||
comment_buffer->str);
|
||||
gimp_image_parasite_attach (image_ID, parasite);
|
||||
gimp_parasite_free (parasite);
|
||||
|
||||
g_string_free (comment_buffer, TRUE);
|
||||
}
|
||||
else if ((marker->marker == JPEG_APP0 + 1)
|
||||
&& (len > 37)
|
||||
&& ! strcmp (JPEG_APP_HEADER_XMP, data))
|
||||
/* if we found any EXIF block, then attach the metadata to the image */
|
||||
if (exif_data)
|
||||
{
|
||||
GimpParam *return_vals;
|
||||
gint nreturn_vals;
|
||||
gchar *xmp_packet;
|
||||
gimp_metadata_store_exif (image_ID, exif_data);
|
||||
orientation = jpeg_exif_get_orientation (exif_data);
|
||||
exif_data_unref (exif_data);
|
||||
exif_data = NULL;
|
||||
}
|
||||
|
||||
g_print ("jpeg-load: found XMP packet (%d bytes)\n",
|
||||
(gint) (len - sizeof (JPEG_APP_HEADER_XMP)));
|
||||
/* 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;
|
||||
|
||||
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)
|
||||
if ((marker->marker == JPEG_APP0 + 1)
|
||||
&& (len > sizeof (JPEG_APP_HEADER_XMP) + 20)
|
||||
&& ! strcmp (JPEG_APP_HEADER_XMP, data))
|
||||
{
|
||||
g_warning ("JPEG - unable to decode XMP metadata packet");
|
||||
GimpParam *return_vals;
|
||||
gint nreturn_vals;
|
||||
gchar *xmp_packet;
|
||||
|
||||
g_print ("jpeg-load: found XMP packet (%d bytes)\n",
|
||||
(gint) (len - sizeof (JPEG_APP_HEADER_XMP)));
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
gimp_destroy_params (return_vals, nreturn_vals);
|
||||
g_free (xmp_packet);
|
||||
}
|
||||
}
|
||||
|
||||
/* Step 5.3: check for an embedded ICC profile */
|
||||
if (!preview && jpeg_icc_read_profile (&cinfo, &profile, &profile_size))
|
||||
{
|
||||
GimpParasite *parasite = gimp_parasite_new ("icc-profile",
|
||||
0, profile_size, profile);
|
||||
gimp_image_parasite_attach (image_ID, parasite);
|
||||
gimp_parasite_free (parasite);
|
||||
|
||||
g_free (profile);
|
||||
}
|
||||
|
||||
if (!preview)
|
||||
{
|
||||
/* if we had any comments then make a parasite for them */
|
||||
if (local_image_comments && local_image_comments->len)
|
||||
/* Step 5.3: check for an embedded ICC profile in APP2 markers */
|
||||
if (jpeg_icc_read_profile (&cinfo, &profile, &profile_size))
|
||||
{
|
||||
gchar *comment = local_image_comments->str;
|
||||
GimpParasite *parasite = gimp_parasite_new ("icc-profile",
|
||||
0,
|
||||
profile_size, profile);
|
||||
gimp_image_parasite_attach (image_ID, parasite);
|
||||
gimp_parasite_free (parasite);
|
||||
|
||||
g_string_free (local_image_comments, FALSE);
|
||||
|
||||
local_image_comments = NULL;
|
||||
|
||||
if (g_utf8_validate (comment, -1, NULL))
|
||||
comment_parasite = gimp_parasite_new ("gimp-comment",
|
||||
GIMP_PARASITE_PERSISTENT,
|
||||
strlen (comment) + 1,
|
||||
comment);
|
||||
g_free (comment);
|
||||
g_free (profile);
|
||||
}
|
||||
|
||||
/* Do not attach the "jpeg-save-options" parasite to the image
|
||||
@ -470,26 +491,8 @@ load_image (const gchar *filename,
|
||||
gimp_drawable_detach (drawable);
|
||||
gimp_image_add_layer (image_ID, layer_ID, 0);
|
||||
|
||||
/* pw - Last of all, attach the parasites (couldn't do it earlier -
|
||||
there was no image. */
|
||||
|
||||
if (!preview)
|
||||
{
|
||||
if (comment_parasite)
|
||||
{
|
||||
gimp_image_parasite_attach (image_ID, comment_parasite);
|
||||
gimp_parasite_free (comment_parasite);
|
||||
|
||||
comment_parasite = NULL;
|
||||
}
|
||||
|
||||
#ifdef HAVE_EXIF
|
||||
|
||||
if (found_exif && ! GPOINTER_TO_INT (cinfo.client_data))
|
||||
jpeg_apply_exif_data_to_image (filename, image_ID);
|
||||
|
||||
#endif
|
||||
}
|
||||
if (orientation > 0)
|
||||
jpeg_exif_rotate (image_ID, orientation);
|
||||
|
||||
return image_ID;
|
||||
}
|
||||
@ -536,6 +539,31 @@ jpeg_load_resolution (gint32 image_ID,
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* 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
|
||||
* versions of GIMP. It is not possible to reliably detect the character
|
||||
* set used, but it is better to keep all characters in the ASCII range
|
||||
* and replace the non-ASCII characters instead of discarding the whole
|
||||
* comment. This is especially useful if the comment contains only a few
|
||||
* non-ASCII characters such as a copyright sign, a soft hyphen, etc.
|
||||
*/
|
||||
static void
|
||||
jpeg_sanitize_comment (gchar *comment)
|
||||
{
|
||||
if (! g_utf8_validate (comment, -1, NULL))
|
||||
{
|
||||
gchar *c;
|
||||
|
||||
for (c = comment; *c; c++)
|
||||
{
|
||||
if (*c > 126 || (*c < 32 && *c != '\t' && *c != '\n' && *c != '\r'))
|
||||
*c = '?';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#ifdef HAVE_EXIF
|
||||
|
||||
typedef struct
|
||||
|
@ -22,7 +22,7 @@
|
||||
#define PLUG_IN_BINARY "jpeg"
|
||||
|
||||
/* headers used in some APPn markers */
|
||||
#define JPEG_APP_HEADER_EXIF "Exif"
|
||||
#define JPEG_APP_HEADER_EXIF "Exif\0\0"
|
||||
#define JPEG_APP_HEADER_XMP "http://ns.adobe.com/xap/1.0/"
|
||||
|
||||
typedef struct my_error_mgr
|
||||
@ -71,11 +71,12 @@ gint32 load_thumbnail_image (const gchar *filename,
|
||||
ExifData * jpeg_exif_data_new_from_file (const gchar *filename,
|
||||
GError **error);
|
||||
|
||||
void jpeg_apply_exif_data_to_image (const gchar *filename,
|
||||
const gint32 image_ID);
|
||||
gint jpeg_exif_get_orientation (ExifData *exif_data);
|
||||
|
||||
void jpeg_setup_exif_for_save (ExifData *exif_data,
|
||||
const gint32 image_ID);
|
||||
|
||||
void jpeg_exif_rotate (gint32 image_ID,
|
||||
gint orientation);
|
||||
#endif /* HAVE_EXIF */
|
||||
|
||||
|
Reference in New Issue
Block a user