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;
 }