diff options
author | Perberos <[email protected]> | 2011-11-06 19:30:49 -0300 |
---|---|---|
committer | Perberos <[email protected]> | 2011-11-06 19:30:49 -0300 |
commit | a8d28a6ce7e0c56dacba5d527d9134573a008902 (patch) | |
tree | 8852602004b5a13cc5d1ce3ecd7a314be81d1198 /src/eom-metadata-reader-jpg.c | |
download | eom-a8d28a6ce7e0c56dacba5d527d9134573a008902.tar.bz2 eom-a8d28a6ce7e0c56dacba5d527d9134573a008902.tar.xz |
inicial
Diffstat (limited to 'src/eom-metadata-reader-jpg.c')
-rw-r--r-- | src/eom-metadata-reader-jpg.c | 673 |
1 files changed, 673 insertions, 0 deletions
diff --git a/src/eom-metadata-reader-jpg.c b/src/eom-metadata-reader-jpg.c new file mode 100644 index 0000000..788d2d3 --- /dev/null +++ b/src/eom-metadata-reader-jpg.c @@ -0,0 +1,673 @@ +/* Eye Of MATE -- JPEG Metadata Reader + * + * Copyright (C) 2008 The Free Software Foundation + * + * Author: Felix Riemann <[email protected]> + * + * Based on the original EomMetadataReader code. + * + * 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 2 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, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#ifdef HAVE_CONFIG_H +#include <config.h> +#endif + +#include <string.h> + +#include "eom-metadata-reader.h" +#include "eom-metadata-reader-jpg.h" +#include "eom-debug.h" + +typedef enum { + EMR_READ = 0, + EMR_READ_SIZE_HIGH_BYTE, + EMR_READ_SIZE_LOW_BYTE, + EMR_READ_MARKER, + EMR_SKIP_BYTES, + EMR_READ_APP1, + EMR_READ_EXIF, + EMR_READ_XMP, + EMR_READ_ICC, + EMR_READ_IPTC, + EMR_FINISHED +} EomMetadataReaderState; + +typedef enum { + EJA_EXIF = 0, + EJA_XMP, + EJA_OTHER +} EomJpegApp1Type; + + +#define EOM_JPEG_MARKER_START 0xFF +#define EOM_JPEG_MARKER_SOI 0xD8 +#define EOM_JPEG_MARKER_APP1 0xE1 +#define EOM_JPEG_MARKER_APP2 0xE2 +#define EOM_JPEG_MARKER_APP14 0xED + +#define IS_FINISHED(priv) (priv->state == EMR_READ && \ + priv->exif_chunk != NULL && \ + priv->icc_chunk != NULL && \ + priv->iptc_chunk != NULL && \ + priv->xmp_chunk != NULL) + +struct _EomMetadataReaderJpgPrivate { + EomMetadataReaderState state; + + /* data fields */ + guint exif_len; + gpointer exif_chunk; + + gpointer iptc_chunk; + guint iptc_len; + + guint icc_len; + gpointer icc_chunk; + + gpointer xmp_chunk; + guint xmp_len; + + /* management fields */ + int size; + int last_marker; + int bytes_read; +}; + +#define EOM_METADATA_READER_JPG_GET_PRIVATE(object) \ + (G_TYPE_INSTANCE_GET_PRIVATE ((object), EOM_TYPE_METADATA_READER_JPG, EomMetadataReaderJpgPrivate)) + +static void +eom_metadata_reader_jpg_init_emr_iface (gpointer g_iface, gpointer iface_data); + + +G_DEFINE_TYPE_WITH_CODE (EomMetadataReaderJpg, eom_metadata_reader_jpg, + G_TYPE_OBJECT, + G_IMPLEMENT_INTERFACE (EOM_TYPE_METADATA_READER, + eom_metadata_reader_jpg_init_emr_iface)) + + +static void +eom_metadata_reader_jpg_dispose (GObject *object) +{ + EomMetadataReaderJpg *emr = EOM_METADATA_READER_JPG (object); + + if (emr->priv->exif_chunk != NULL) { + g_free (emr->priv->exif_chunk); + emr->priv->exif_chunk = NULL; + } + + if (emr->priv->iptc_chunk != NULL) { + g_free (emr->priv->iptc_chunk); + emr->priv->iptc_chunk = NULL; + } + + if (emr->priv->xmp_chunk != NULL) { + g_free (emr->priv->xmp_chunk); + emr->priv->xmp_chunk = NULL; + } + + if (emr->priv->icc_chunk != NULL) { + g_free (emr->priv->icc_chunk); + emr->priv->icc_chunk = NULL; + } + + G_OBJECT_CLASS (eom_metadata_reader_jpg_parent_class)->dispose (object); +} + +static void +eom_metadata_reader_jpg_init (EomMetadataReaderJpg *obj) +{ + EomMetadataReaderJpgPrivate *priv; + + priv = obj->priv = EOM_METADATA_READER_JPG_GET_PRIVATE (obj); + priv->exif_chunk = NULL; + priv->exif_len = 0; + priv->iptc_chunk = NULL; + priv->iptc_len = 0; + priv->icc_chunk = NULL; + priv->icc_len = 0; +} + +static void +eom_metadata_reader_jpg_class_init (EomMetadataReaderJpgClass *klass) +{ + GObjectClass *object_class = (GObjectClass*) klass; + + object_class->dispose = eom_metadata_reader_jpg_dispose; + + g_type_class_add_private (klass, sizeof (EomMetadataReaderJpgPrivate)); +} + +static gboolean +eom_metadata_reader_jpg_finished (EomMetadataReaderJpg *emr) +{ + g_return_val_if_fail (EOM_IS_METADATA_READER_JPG (emr), TRUE); + + return (emr->priv->state == EMR_FINISHED); +} + + +static EomJpegApp1Type +eom_metadata_identify_app1 (gchar *buf, guint len) +{ + if (len < 5) { + return EJA_OTHER; + } + + if (len < 29) { + return (strncmp ("Exif", buf, 5) == 0 ? EJA_EXIF : EJA_OTHER); + } + + if (strncmp ("Exif", buf, 5) == 0) { + return EJA_EXIF; + } else if (strncmp ("http://ns.adobe.com/xap/1.0/", buf, 29) == 0) { + return EJA_XMP; + } + + return EJA_OTHER; +} + +static void +eom_metadata_reader_get_next_block (EomMetadataReaderJpgPrivate* priv, + guchar *chunk, + int* i, + const guchar *buf, + int len, + EomMetadataReaderState state) +{ + if (*i + priv->size < len) { + /* read data in one block */ + memcpy ((guchar*) (chunk) + priv->bytes_read, &buf[*i], priv->size); + priv->state = EMR_READ; + *i = *i + priv->size - 1; /* the for-loop consumes the other byte */ + } else { + int chunk_len = len - *i; + memcpy ((guchar*) (chunk) + priv->bytes_read, &buf[*i], chunk_len); + priv->bytes_read += chunk_len; /* bytes already read */ + priv->size = (*i + priv->size) - len; /* remaining data to read */ + *i = len - 1; + priv->state = state; + } +} + +static void +eom_metadata_reader_jpg_consume (EomMetadataReaderJpg *emr, const guchar *buf, guint len) +{ + EomMetadataReaderJpgPrivate *priv; + EomJpegApp1Type app1_type; + int i; + EomMetadataReaderState next_state = EMR_READ; + guchar *chunk = NULL; + + g_return_if_fail (EOM_IS_METADATA_READER_JPG (emr)); + + priv = emr->priv; + + if (priv->state == EMR_FINISHED) return; + + for (i = 0; (i < len) && (priv->state != EMR_FINISHED); i++) { + + switch (priv->state) { + case EMR_READ: + if (buf[i] == EOM_JPEG_MARKER_START) { + priv->state = EMR_READ_MARKER; + } + else { + priv->state = EMR_FINISHED; + } + break; + + case EMR_READ_MARKER: + if ((buf [i] & 0xF0) == 0xE0 || buf[i] == 0xFE) { + /* we are reading some sort of APPxx or COM marker */ + /* these are always followed by 2 bytes of size information */ + priv->last_marker = buf [i]; + priv->size = 0; + priv->state = EMR_READ_SIZE_HIGH_BYTE; + + eom_debug_message (DEBUG_IMAGE_DATA, "APPx or COM Marker Found: %x", priv->last_marker); + } + else { + /* otherwise simply consume the byte */ + priv->state = EMR_READ; + } + break; + + case EMR_READ_SIZE_HIGH_BYTE: + priv->size = (buf [i] & 0xff) << 8; + priv->state = EMR_READ_SIZE_LOW_BYTE; + break; + + case EMR_READ_SIZE_LOW_BYTE: + priv->size |= (buf [i] & 0xff); + + if (priv->size > 2) /* ignore the two size-bytes */ + priv->size -= 2; + + if (priv->size == 0) { + priv->state = EMR_READ; + } else if (priv->last_marker == EOM_JPEG_MARKER_APP1 && + ((priv->exif_chunk == NULL) || (priv->xmp_chunk == NULL))) + { + priv->state = EMR_READ_APP1; + } else if (priv->last_marker == EOM_JPEG_MARKER_APP2 && + priv->icc_chunk == NULL && priv->size > 14) + { + /* Chunk has 14 bytes identification data */ + priv->state = EMR_READ_ICC; + } else if (priv->last_marker == EOM_JPEG_MARKER_APP14 && + priv->iptc_chunk == NULL) + { + priv->state = EMR_READ_IPTC; + } else { + priv->state = EMR_SKIP_BYTES; + } + + priv->last_marker = 0; + break; + + case EMR_SKIP_BYTES: + eom_debug_message (DEBUG_IMAGE_DATA, "Skip bytes: %i", priv->size); + + if (i + priv->size < len) { + i = i + priv->size - 1; /* the for-loop consumes the other byte */ + priv->size = 0; + } + else { + priv->size = (i + priv->size) - len; + i = len - 1; + } + if (priv->size == 0) { /* don't need to skip any more bytes */ + priv->state = EMR_READ; + } + break; + + case EMR_READ_APP1: + eom_debug_message (DEBUG_IMAGE_DATA, "Read APP1 data, Length: %i", priv->size); + + app1_type = eom_metadata_identify_app1 ((gchar*) &buf[i], priv->size); + + switch (app1_type) { + case EJA_EXIF: + if (priv->exif_chunk == NULL) { + priv->exif_chunk = g_new0 (guchar, priv->size); + priv->exif_len = priv->size; + priv->bytes_read = 0; + chunk = priv->exif_chunk; + next_state = EMR_READ_EXIF; + } else { + chunk = NULL; + priv->state = EMR_SKIP_BYTES; + } + break; + case EJA_XMP: + if (priv->xmp_chunk == NULL) { + priv->xmp_chunk = g_new0 (guchar, priv->size); + priv->xmp_len = priv->size; + priv->bytes_read = 0; + chunk = priv->xmp_chunk; + next_state = EMR_READ_XMP; + } else { + chunk = NULL; + priv->state = EMR_SKIP_BYTES; + } + break; + case EJA_OTHER: + default: + /* skip unknown data */ + chunk = NULL; + priv->state = EMR_SKIP_BYTES; + break; + } + + if (chunk) { + eom_metadata_reader_get_next_block (priv, chunk, + &i, buf, + len, + next_state); + } + + if (IS_FINISHED(priv)) + priv->state = EMR_FINISHED; + break; + + case EMR_READ_EXIF: + eom_debug_message (DEBUG_IMAGE_DATA, "Read continuation of EXIF data, length: %i", priv->size); + { + eom_metadata_reader_get_next_block (priv, priv->exif_chunk, + &i, buf, len, EMR_READ_EXIF); + } + if (IS_FINISHED(priv)) + priv->state = EMR_FINISHED; + break; + + case EMR_READ_XMP: + eom_debug_message (DEBUG_IMAGE_DATA, "Read continuation of XMP data, length: %i", priv->size); + { + eom_metadata_reader_get_next_block (priv, priv->xmp_chunk, + &i, buf, len, EMR_READ_XMP); + } + if (IS_FINISHED (priv)) + priv->state = EMR_FINISHED; + break; + + case EMR_READ_ICC: + eom_debug_message (DEBUG_IMAGE_DATA, + "Read continuation of ICC data, " + "length: %i", priv->size); + + if (priv->icc_chunk == NULL) { + priv->icc_chunk = g_new0 (guchar, priv->size); + priv->icc_len = priv->size; + priv->bytes_read = 0; + } + + eom_metadata_reader_get_next_block (priv, + priv->icc_chunk, + &i, buf, len, + EMR_READ_ICC); + + /* Test that the chunk actually contains ICC data. */ + if (priv->state == EMR_READ && priv->icc_chunk) { + const char* icc_chunk = priv->icc_chunk; + gboolean valid = TRUE; + + /* Chunk should begin with the + * ICC_PROFILE\0 identifier */ + valid &= strncmp (icc_chunk, + "ICC_PROFILE\0",12) == 0; + /* Make sure this is the first and only + * ICC chunk in the file as we don't + * support merging chunks yet. */ + valid &= *(guint16*)(icc_chunk+12) == 0x101; + + if (!valid) { + /* This no ICC data. Throw it away. */ + eom_debug_message (DEBUG_IMAGE_DATA, + "Supposed ICC chunk didn't validate. " + "Ignoring."); + g_free (priv->icc_chunk); + priv->icc_chunk = NULL; + priv->icc_len = 0; + } + } + + if (IS_FINISHED(priv)) + priv->state = EMR_FINISHED; + break; + + case EMR_READ_IPTC: + eom_debug_message (DEBUG_IMAGE_DATA, + "Read continuation of IPTC data, " + "length: %i", priv->size); + + if (priv->iptc_chunk == NULL) { + priv->iptc_chunk = g_new0 (guchar, priv->size); + priv->iptc_len = priv->size; + priv->bytes_read = 0; + } + + eom_metadata_reader_get_next_block (priv, + priv->iptc_chunk, + &i, buf, len, + EMR_READ_IPTC); + + if (IS_FINISHED(priv)) + priv->state = EMR_FINISHED; + break; + + default: + g_assert_not_reached (); + } + } +} + +/* Returns the raw exif data. NOTE: The caller of this function becomes + * the new owner of this piece of memory and is responsible for freeing it! + */ +static void +eom_metadata_reader_jpg_get_exif_chunk (EomMetadataReaderJpg *emr, guchar **data, guint *len) +{ + EomMetadataReaderJpgPrivate *priv; + + g_return_if_fail (EOM_IS_METADATA_READER (emr)); + priv = emr->priv; + + *data = (guchar*) priv->exif_chunk; + *len = priv->exif_len; + + priv->exif_chunk = NULL; + priv->exif_len = 0; +} + +#ifdef HAVE_EXIF +static gpointer +eom_metadata_reader_jpg_get_exif_data (EomMetadataReaderJpg *emr) +{ + EomMetadataReaderJpgPrivate *priv; + ExifData *data = NULL; + + g_return_val_if_fail (EOM_IS_METADATA_READER (emr), NULL); + priv = emr->priv; + + if (priv->exif_chunk != NULL) { + data = exif_data_new_from_data (priv->exif_chunk, priv->exif_len); + } + + return data; +} +#endif + + +#ifdef HAVE_EXEMPI + +/* skip the signature */ +#define EOM_XMP_OFFSET (29) + +static gpointer +eom_metadata_reader_jpg_get_xmp_data (EomMetadataReaderJpg *emr ) +{ + EomMetadataReaderJpgPrivate *priv; + XmpPtr xmp = NULL; + + g_return_val_if_fail (EOM_IS_METADATA_READER (emr), NULL); + + priv = emr->priv; + + if (priv->xmp_chunk != NULL) { + xmp = xmp_new (priv->xmp_chunk+EOM_XMP_OFFSET, + priv->xmp_len-EOM_XMP_OFFSET); + } + + return (gpointer)xmp; +} +#endif + +/* + * FIXME: very broken, assumes the profile fits in a single chunk. Change to + * parse the sections and construct a single memory chunk, or maybe even parse + * the profile. + */ +#ifdef HAVE_LCMS +static gpointer +eom_metadata_reader_jpg_get_icc_profile (EomMetadataReaderJpg *emr) +{ + EomMetadataReaderJpgPrivate *priv; + cmsHPROFILE profile = NULL; + + g_return_val_if_fail (EOM_IS_METADATA_READER (emr), NULL); + + priv = emr->priv; + + if (priv->icc_chunk) { + cmsErrorAction (LCMS_ERROR_SHOW); + + profile = cmsOpenProfileFromMem(priv->icc_chunk + 14, priv->icc_len - 14); + + if (profile) { + eom_debug_message (DEBUG_LCMS, "JPEG has ICC profile"); + } else { + eom_debug_message (DEBUG_LCMS, "JPEG has invalid ICC profile"); + } + } + +#ifdef HAVE_EXIF + if (!profile && priv->exif_chunk != NULL) { + ExifEntry *entry; + ExifByteOrder o; + gint color_space; + ExifData *exif = eom_metadata_reader_jpg_get_exif_data (emr); + + if (!exif) return NULL; + + o = exif_data_get_byte_order (exif); + + entry = exif_data_get_entry (exif, EXIF_TAG_COLOR_SPACE); + + if (entry == NULL) { + exif_data_unref (exif); + return NULL; + } + + color_space = exif_get_short (entry->data, o); + + switch (color_space) { + case 1: + eom_debug_message (DEBUG_LCMS, "JPEG is sRGB"); + + profile = cmsCreate_sRGBProfile (); + + break; + case 2: + eom_debug_message (DEBUG_LCMS, "JPEG is Adobe RGB (Disabled)"); + + /* TODO: create Adobe RGB profile */ + //profile = cmsCreate_Adobe1998Profile (); + + break; + case 0xFFFF: + { + cmsCIExyY whitepoint; + cmsCIExyYTRIPLE primaries; + LPGAMMATABLE gamma[3]; + double gammaValue; + ExifRational r; + + const int offset = exif_format_get_size (EXIF_FORMAT_RATIONAL); + + entry = exif_data_get_entry (exif, EXIF_TAG_WHITE_POINT); + + if (entry && entry->components == 2) { + r = exif_get_rational (entry->data, o); + whitepoint.x = (double) r.numerator / r.denominator; + + r = exif_get_rational (entry->data + offset, o); + whitepoint.y = (double) r.numerator / r.denominator; + whitepoint.Y = 1.0; + } else { + eom_debug_message (DEBUG_LCMS, "No whitepoint found"); + break; + } + + entry = exif_data_get_entry (exif, EXIF_TAG_PRIMARY_CHROMATICITIES); + + if (entry && entry->components == 6) { + r = exif_get_rational (entry->data + 0 * offset, o); + primaries.Red.x = (double) r.numerator / r.denominator; + + r = exif_get_rational (entry->data + 1 * offset, o); + primaries.Red.y = (double) r.numerator / r.denominator; + + r = exif_get_rational (entry->data + 2 * offset, o); + primaries.Green.x = (double) r.numerator / r.denominator; + + r = exif_get_rational (entry->data + 3 * offset, o); + primaries.Green.y = (double) r.numerator / r.denominator; + + r = exif_get_rational (entry->data + 4 * offset, o); + primaries.Blue.x = (double) r.numerator / r.denominator; + + r = exif_get_rational (entry->data + 5 * offset, o); + primaries.Blue.y = (double) r.numerator / r.denominator; + + primaries.Red.Y = primaries.Green.Y = primaries.Blue.Y = 1.0; + } else { + eom_debug_message (DEBUG_LCMS, "No primary chromaticities found"); + break; + } + + entry = exif_data_get_entry (exif, EXIF_TAG_GAMMA); + + if (entry) { + r = exif_get_rational (entry->data, o); + gammaValue = (double) r.numerator / r.denominator; + } else { + eom_debug_message (DEBUG_LCMS, "No gamma found"); + gammaValue = 2.2; + } + + gamma[0] = gamma[1] = gamma[2] = cmsBuildGamma (256, gammaValue); + + profile = cmsCreateRGBProfile (&whitepoint, &primaries, gamma); + + cmsFreeGamma(gamma[0]); + + eom_debug_message (DEBUG_LCMS, "JPEG is calibrated"); + + break; + } + } + + exif_data_unref (exif); + } +#endif + return profile; +} +#endif + +static void +eom_metadata_reader_jpg_init_emr_iface (gpointer g_iface, gpointer iface_data) +{ + EomMetadataReaderInterface *iface; + + iface = (EomMetadataReaderInterface*)g_iface; + + iface->consume = + (void (*) (EomMetadataReader *self, const guchar *buf, guint len)) + eom_metadata_reader_jpg_consume; + iface->finished = + (gboolean (*) (EomMetadataReader *self)) + eom_metadata_reader_jpg_finished; + iface->get_raw_exif = + (void (*) (EomMetadataReader *self, guchar **data, guint *len)) + eom_metadata_reader_jpg_get_exif_chunk; +#ifdef HAVE_EXIF + iface->get_exif_data = + (gpointer (*) (EomMetadataReader *self)) + eom_metadata_reader_jpg_get_exif_data; +#endif +#ifdef HAVE_LCMS + iface->get_icc_profile = + (gpointer (*) (EomMetadataReader *self)) + eom_metadata_reader_jpg_get_icc_profile; +#endif +#ifdef HAVE_EXEMPI + iface->get_xmp_ptr = + (gpointer (*) (EomMetadataReader *self)) + eom_metadata_reader_jpg_get_xmp_data; +#endif +} + |