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:
Raphael Quinet
2007-07-17 20:06:09 +00:00
committed by Raphaël Quinet
parent b47a3e52e5
commit 1065f0eb85
6 changed files with 164 additions and 160 deletions

View File

@ -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

View File

@ -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)
{

View File

@ -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.
*/

View File

@ -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.
*

View File

@ -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

View File

@ -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 */