Mercurial > geeqie
diff src/exif.c @ 114:50fc73e08550
Mon Nov 27 01:23:23 2006 John Ellis <johne@verizon.net>
* bar_exif.c, cache-loader.c, pan-view.c: Pass new arg for exif_read().
* color-man.[ch]: Add color_man_new_embedded for using in-memory color
profiles.
* exif.[ch]: Add support for extracting color profiles embedded in jpeg
and tiff images. This resulted in a rewrite of the jpeg parser; both
to allow searching for any marker type, and to make the code readable.
* format_raw.c: Add color profile tag to the debug code.
* image.c, layout.c: Use embedded color profiles when found and
enabled, also add toggle for the option in color profile menu.
author | gqview |
---|---|
date | Mon, 27 Nov 2006 06:37:48 +0000 |
parents | 9fbf210edc6f |
children | 71e1ebee420e |
line wrap: on
line diff
--- a/src/exif.c Sat Nov 25 03:00:33 2006 +0000 +++ b/src/exif.c Mon Nov 27 06:37:48 2006 +0000 @@ -377,7 +377,7 @@ { 0x828e, EXIF_FORMAT_BYTE_UNSIGNED, -1, "CFAPattern", NULL, NULL }, { 0x828f, EXIF_FORMAT_RATIONAL_UNSIGNED, 1, "BatteryLevel", NULL, NULL }, { 0x83bb, EXIF_FORMAT_LONG_UNSIGNED, -1, "IPTC/NAA", NULL, NULL }, -{ 0x8773, EXIF_FORMAT_UNDEFINED, -1, "InterColorProfile", NULL, NULL }, +{ 0x8773, EXIF_FORMAT_UNDEFINED, -1, "ColorProfile", NULL, NULL }, { 0x8825, EXIF_FORMAT_LONG_UNSIGNED, 1, "GPSInfo", "SubIFD GPS offset", NULL }, { 0x8829, EXIF_FORMAT_SHORT_UNSIGNED, 1, "Interlace", NULL, NULL }, { 0x882a, EXIF_FORMAT_SHORT, 1, "TimeZoneOffset", NULL, NULL }, @@ -523,21 +523,18 @@ return (brief) ? ExifFormatList[item->format].short_name : ExifFormatList[item->format].description; } - -#define UNDEFINED_TEXT_BYTE_COUNT 16 - static GString *string_append_raw_bytes(GString *string, gpointer data, gint ne) { gint i; - for (i = 0 ; i < ne && i < UNDEFINED_TEXT_BYTE_COUNT; i++) + for (i = 0 ; i < ne; i++) { unsigned char c = ((char *)data)[i]; if (c < 32 || c > 127) c = '.'; g_string_append_printf(string, "%c", c); } string = g_string_append(string, " : "); - for (i = 0 ; i < ne && i < UNDEFINED_TEXT_BYTE_COUNT; i++) + for (i = 0 ; i < ne; i++) { const gchar *spacer; if (i > 0) @@ -557,7 +554,6 @@ } g_string_append_printf(string, "%s%02x", spacer, ((char *)data)[i]); } - if (i >= UNDEFINED_TEXT_BYTE_COUNT) g_string_append_printf(string, " (%d bytes)", ne); return string; } @@ -947,83 +943,178 @@ *------------------------------------------------------------------- */ -#define MARKER_UNKNOWN 0x00 -#define MARKER_SOI 0xD8 -#define MARKER_APP1 0xE1 +#define JPEG_MARKER 0xFF +#define JPEG_MARKER_SOI 0xD8 +#define JPEG_MARKER_EOI 0xD9 +#define JPEG_MARKER_APP1 0xE1 +#define JPEG_MARKER_APP2 0xE2 -static gint jpeg_get_marker_size(unsigned char *data) -{ - /* Size is always in Motorola byte order */ - return exif_byte_get_int16(data + 2, EXIF_BYTE_ORDER_MOTOROLA); -} +/* jpeg container format: + all data markers start with 0XFF + 2 byte long file start and end markers: 0xFFD8(SOI) and 0XFFD9(EOI) + 4 byte long data segment markers in format: 0xFFTTSSSSNNN... + FF: 1 byte standard marker identifier + TT: 1 byte data type + SSSS: 2 bytes in Motorola byte alignment for length of the data. + This value includes these 2 bytes in the count, making actual + length of NN... == SSSS - 2. + NNN.: the data in this segment + */ -static gint jpeg_goto_next_marker(unsigned char **data, gint *size, gint *marker) +static gint exif_jpeg_segment_find(unsigned char *data, guint size, + guchar app_marker, const gchar *magic, guint magic_len, + guint *seg_offset, guint *seg_length) { - gint marker_size = 2; + guchar marker = 0; + guint offset = 0; + guint length = 0; - *marker = MARKER_UNKNOWN; + while (marker != app_marker && + marker != JPEG_MARKER_EOI) + { + offset += length; + length = 2; + + if (offset + 2 >= size || + data[offset] != JPEG_MARKER) return FALSE; - /* It is safe to access the marker and its size since we have checked - * the SOI and this function guaranties the whole next marker is - * available - */ - if (*(*data + 1) != MARKER_SOI) + marker = data[offset + 1]; + if (marker != JPEG_MARKER_SOI && + marker != JPEG_MARKER_EOI) + { + if (offset + 4 >= size) return FALSE; + length += exif_byte_get_int16(data + offset + 2, EXIF_BYTE_ORDER_MOTOROLA); + } + } + + if (marker == app_marker && + offset + length < size && + length >= 4 + magic_len && + memcmp(data + offset + 4, magic, magic_len) == 0) { - marker_size += jpeg_get_marker_size(*data); + *seg_offset = offset + 4; + *seg_length = length - 4; + return TRUE; } - *size -= marker_size; - - /* size should be at least 4, so we can read the marker and its size - * and check data are actually available - */ - if (*size < 4) return -1; - - /* Jump to the next marker and be sure it begins with 0xFF - */ - *data += marker_size; - if (**data != 0xFF) return -1; - - if (jpeg_get_marker_size(*data) + 2 > *size) return -1; - - *marker = *(*data + 1); - - return 0; + return FALSE; } +static ExifMarker jpeg_color_marker = { 0x8773, EXIF_FORMAT_UNDEFINED, -1, "ColorProfile", NULL, NULL }; -static gint exif_parse_JPEG(ExifData *exif, unsigned char *data, gint size, ExifMarker *list) +static gint exif_jpeg_parse_color(ExifData *exif, unsigned char *data, guint size) { - gint marker; - gint marker_size; + guint seg_offset = 0; + guint seg_length = 0; + guint chunk_offset[255]; + guint chunk_length[255]; + guint chunk_count = 0; + + /* For jpeg/jfif, ICC color profile data can be in more than one segment. + the data is in APP2 data segments that start with "ICC_PROFILE\x00\xNN\xTT" + NN = segment number for data + TT = total number of ICC segments (TT in each ICC segment should match) + */ + + while (exif_jpeg_segment_find(data + seg_offset + seg_length, + size - seg_offset - seg_length, + JPEG_MARKER_APP2, + "ICC_PROFILE\x00", 12, + &seg_offset, &seg_length)) + { + guchar chunk_num; + guchar chunk_tot; + + if (seg_length < 14) return FALSE; + + chunk_num = data[seg_offset + 12]; + chunk_tot = data[seg_offset + 13]; + + if (chunk_num == 0 || chunk_tot == 0) return FALSE; + + if (chunk_count == 0) + { + guint i; + + chunk_count = (guint)chunk_tot; + for (i = 0; i < chunk_count; i++) chunk_offset[i] = 0; + for (i = 0; i < chunk_count; i++) chunk_length[i] = 0; + } + + if (chunk_tot != chunk_count || + chunk_num > chunk_count) return FALSE; + + chunk_num--; + chunk_offset[chunk_num] = seg_offset + 14; + chunk_length[chunk_num] = seg_length - 14; + } - if (size < 4 || *data != 0xFF || *(data + 1) != MARKER_SOI) + if (chunk_count > 0) + { + ExifItem *item; + unsigned char *cp_data; + guint cp_length = 0; + guint i; + + for (i = 0; i < chunk_count; i++) cp_length += chunk_length[i]; + cp_data = g_malloc(cp_length); + + for (i = 0; i < chunk_count; i++) + { + if (chunk_offset[i] == 0) + { + /* error, we never saw this chunk */ + g_free(cp_data); + return FALSE; + } + memcpy(cp_data, data + chunk_offset[i], chunk_length[i]); + } + + item = exif_item_new(jpeg_color_marker.format, jpeg_color_marker.tag, 1, + &jpeg_color_marker); + g_free(item->data); + item->data = cp_data; + item->elements = cp_length; + item->data_len = cp_length; + exif->items = g_list_prepend(exif->items, item); + + return TRUE; + } + + return FALSE; +} + +static gint exif_jpeg_parse(ExifData *exif, + unsigned char *data, guint size, + ExifMarker *list, gint parse_color) +{ + guint seg_offset = 0; + guint seg_length = 0; + gint res = -1; + + if (size < 4 || + memcmp(data, "\xFF\xD8", 2) != 0) { return -2; } - do { - if (jpeg_goto_next_marker(&data, &size, &marker) == -1) - { - break; - } - } while (marker != MARKER_APP1); - - if (marker != MARKER_APP1) + if (exif_jpeg_segment_find(data, size, JPEG_MARKER_APP1, + "Exif\x00\x00", 6, + &seg_offset, &seg_length)) { - return -2; + res = exif_tiff_parse(exif, data + seg_offset + 6, seg_length - 6, list); } - marker_size = jpeg_get_marker_size(data) - 2; - - if (marker_size < 6 || memcmp(data + 4, "Exif\x00\x00", 6) != 0) + if (parse_color && + exif_jpeg_parse_color(exif, data, size)) { - return -2; + res = 0; } - return exif_tiff_parse(exif, data + 10, marker_size - 6, list); + return res; } + /* *------------------------------------------------------------------- * misc @@ -1090,7 +1181,7 @@ g_free(exif); } -ExifData *exif_read(const gchar *path) +ExifData *exif_read(const gchar *path, gint parse_color_profile) { ExifData *exif; void *f; @@ -1110,7 +1201,9 @@ exif = g_new0(ExifData, 1); exif->items = NULL; - if ((res = exif_parse_JPEG(exif, (unsigned char *)f, size, ExifKnownMarkersList)) == -2) + if ((res = exif_jpeg_parse(exif, (unsigned char *)f, size, + ExifKnownMarkersList, + parse_color_profile)) == -2) { res = exif_tiff_parse(exif, (unsigned char *)f, size, ExifKnownMarkersList); } @@ -1132,8 +1225,8 @@ ExifKnownMarkersList); break; case FORMAT_RAW_EXIF_JPEG: - res = exif_parse_JPEG(exif, (unsigned char*)f + offset, size - offset, - ExifKnownMarkersList); + res = exif_jpeg_parse(exif, (unsigned char*)f + offset, size - offset, + ExifKnownMarkersList, FALSE); break; case FORMAT_RAW_EXIF_IFD_II: case FORMAT_RAW_EXIF_IFD_MM: @@ -1187,6 +1280,8 @@ return NULL; } +#define EXIF_DATA_AS_TEXT_MAX_COUNT 16 + gchar *exif_item_get_data_as_text(ExifItem *item) { const ExifMarker *marker; @@ -1203,6 +1298,7 @@ data = item->data; ne = item->elements; + if (ne > EXIF_DATA_AS_TEXT_MAX_COUNT) ne = EXIF_DATA_AS_TEXT_MAX_COUNT; string = g_string_new(""); switch (item->format) { @@ -1310,8 +1406,14 @@ break; } - text = g_strdup(string->str); - g_string_free(string, TRUE); + if (item->elements > EXIF_DATA_AS_TEXT_MAX_COUNT && + item->format != EXIF_FORMAT_STRING) + { + g_string_append(string, " ..."); + } + + text = string->str; + g_string_free(string, FALSE); return text; }