xref: /haiku/src/add-ons/translators/jpeg/exif_parser.cpp (revision 893988af824e65e49e55f517b157db8386e8002b)
1 /*
2  * Copyright 2007-2008, Axel Dörfler, axeld@pinc-software.de. All rights reserved.
3  * Distributed under the terms of the MIT License.
4  */
5 
6 
7 #include "exif_parser.h"
8 
9 #include <ctype.h>
10 #include <set>
11 #include <stdio.h>
12 #include <stdlib.h>
13 
14 #include <Message.h>
15 
16 #include <ReadHelper.h>
17 
18 using std::set;
19 
20 enum {
21 	TAG_EXIF_OFFSET		= 0x8769,
22 	TAG_SUB_DIR_OFFSET	= 0xa005,
23 
24 	TAG_MAKER			= 0x10f,
25 	TAG_MODEL			= 0x110,
26 	TAG_ORIENTATION		= 0x112,
27 	TAG_EXPOSURE_TIME	= 0x829a,
28 	TAG_ISO				= 0x8827,
29 };
30 
31 static const convert_tag kDefaultTags[] = {
32 	{TAG_MAKER,			B_ANY_TYPE,		"Maker"},
33 	{TAG_MODEL,			B_ANY_TYPE,		"Model"},
34 	{TAG_ORIENTATION,	B_INT32_TYPE,	"Orientation"},
35 	{TAG_EXPOSURE_TIME,	B_DOUBLE_TYPE,	"ExposureTime"},
36 	{TAG_ISO,			B_INT32_TYPE,	"ISO"},
37 };
38 static const size_t kNumDefaultTags = sizeof(kDefaultTags)
39 	/ sizeof(kDefaultTags[0]);
40 
41 
42 static status_t parse_tiff_directory(TReadHelper& read, set<off_t>& visited,
43 	BMessage& target, const convert_tag* tags, size_t tagCount);
44 
45 
46 static status_t
47 add_to_message(TReadHelper& source, BMessage& target, tiff_tag& tag,
48 	const char* name, type_code type)
49 {
50 	type_code defaultType = B_INT32_TYPE;
51 	double doubleValue = 0.0;
52 	int32 intValue = 0;
53 
54 	switch (tag.type) {
55 		case TIFF_STRING_TYPE:
56 		{
57 			if (type != B_ANY_TYPE && type != B_STRING_TYPE)
58 				return B_BAD_VALUE;
59 
60 			char* buffer = (char*)malloc(tag.length);
61 			if (buffer == NULL)
62 				return B_NO_MEMORY;
63 
64 			source(buffer, tag.length);
65 
66 			// remove trailing spaces
67 			int32 i = tag.length;
68 			while (--i > 0 && isspace(buffer[i]) || !buffer[i]) {
69 				buffer[i] = '\0';
70 			}
71 
72 			status_t status = target.AddString(name, buffer);
73 			free(buffer);
74 
75 			return status;
76 		}
77 
78 		case TIFF_UNDEFINED_TYPE:
79 		{
80 			if (type != B_ANY_TYPE && type != B_STRING_TYPE && type != B_RAW_TYPE)
81 				return B_BAD_VALUE;
82 
83 			char* buffer = (char*)malloc(tag.length);
84 			if (buffer == NULL)
85 				return B_NO_MEMORY;
86 
87 			source(buffer, tag.length);
88 
89 			status_t status;
90 			if (type == B_STRING_TYPE)
91 				status = target.AddString(name, buffer);
92 			else
93 				status = target.AddData(name, B_RAW_TYPE, buffer, tag.length);
94 
95 			free(buffer);
96 
97 			return status;
98 		}
99 
100 		// unsigned
101 		case TIFF_UINT8_TYPE:
102 			intValue = source.Next<uint8>();
103 			break;
104 		case TIFF_UINT16_TYPE:
105 			defaultType = B_INT32_TYPE;
106 			intValue = source.Next<uint16>();
107 			break;
108 		case TIFF_UINT32_TYPE:
109 			defaultType = B_INT32_TYPE;
110 			intValue = source.Next<uint32>();
111 			break;
112 		case TIFF_UFRACTION_TYPE:
113 		{
114 			defaultType = B_DOUBLE_TYPE;
115 			double value = source.Next<uint32>();
116 			doubleValue = value / source.Next<uint32>();
117 			break;
118 		}
119 
120 		// signed
121 		case TIFF_INT8_TYPE:
122 			intValue = source.Next<int8>();
123 			break;
124 		case TIFF_INT16_TYPE:
125 			intValue = source.Next<int16>();
126 			break;
127 		case TIFF_INT32_TYPE:
128 			intValue = source.Next<int32>();
129 			break;
130 		case TIFF_FRACTION_TYPE:
131 		{
132 			defaultType = B_DOUBLE_TYPE;
133 			double value = source.Next<int32>();
134 			doubleValue = value / source.Next<int32>();
135 		}
136 
137 		// floating point
138 		case TIFF_FLOAT_TYPE:
139 			defaultType = B_FLOAT_TYPE;
140 			doubleValue = source.Next<float>();
141 			break;
142 		case TIFF_DOUBLE_TYPE:
143 			defaultType = B_DOUBLE_TYPE;
144 			doubleValue = source.Next<double>();
145 			break;
146 
147 		default:
148 			return B_BAD_VALUE;
149 	}
150 
151 	if (defaultType == B_INT32_TYPE)
152 		doubleValue = intValue;
153 	else
154 		intValue = int32(doubleValue + 0.5);
155 
156 	if (type == B_ANY_TYPE)
157 		type = defaultType;
158 
159 	switch (type) {
160 		case B_INT32_TYPE:
161 			return target.AddInt32(name, intValue);
162 		case B_FLOAT_TYPE:
163 			return target.AddFloat(name, doubleValue);
164 		case B_DOUBLE_TYPE:
165 			return target.AddDouble(name, doubleValue);
166 
167 		default:
168 			return B_BAD_VALUE;
169 	}
170 }
171 
172 
173 static const convert_tag*
174 find_convert_tag(uint16 id, const convert_tag* tags, size_t count)
175 {
176 	for (size_t i = 0; i < count; i++) {
177 		if (tags[i].tag == id)
178 			return &tags[i];
179 	}
180 
181 	return NULL;
182 }
183 
184 
185 /*!
186 	Reads a TIFF tag and positions the file stream to its data section
187 */
188 void
189 parse_tiff_tag(TReadHelper& read, tiff_tag& tag, off_t& offset)
190 {
191 	read(tag.tag);
192 	read(tag.type);
193 	read(tag.length);
194 
195 	offset = read.Position() + 4;
196 
197 	uint32 length = tag.length;
198 
199 	switch (tag.type) {
200 		case TIFF_UINT16_TYPE:
201 		case TIFF_INT16_TYPE:
202 			length *= 2;
203 			break;
204 
205 		case TIFF_UINT32_TYPE:
206 		case TIFF_INT32_TYPE:
207 		case TIFF_FLOAT_TYPE:
208 			length *= 4;
209 			break;
210 
211 		case TIFF_UFRACTION_TYPE:
212 		case TIFF_FRACTION_TYPE:
213 		case TIFF_DOUBLE_TYPE:
214 			length *= 8;
215 			break;
216 
217 		default:
218 			break;
219 	}
220 
221 	if (length > 4) {
222 		uint32 position;
223 		read(position);
224 
225 		read.Seek(position, SEEK_SET);
226 	}
227 }
228 
229 
230 static status_t
231 parse_tiff_directory(TReadHelper& read, set<off_t>& visited, off_t offset,
232 	BMessage& target, const convert_tag* convertTags, size_t convertTagCount)
233 {
234 	if (visited.find(offset) != visited.end()) {
235 		// The EXIF data is obviously corrupt
236 		return B_BAD_DATA;
237 	}
238 
239 	read.Seek(offset, SEEK_SET);
240 	visited.insert(offset);
241 
242 	uint16 tags;
243 	read(tags);
244 	if (tags > 512)
245 		return B_BAD_DATA;
246 
247 	while (tags--) {
248 		off_t nextOffset;
249 		tiff_tag tag;
250 		parse_tiff_tag(read, tag, nextOffset);
251 
252 		//printf("TAG %u\n", tag.tag);
253 
254 		switch (tag.tag) {
255 			case TAG_EXIF_OFFSET:
256 			case TAG_SUB_DIR_OFFSET:
257 			{
258 				status_t status = parse_tiff_directory(read, visited, target,
259 					convertTags, convertTagCount);
260 				if (status < B_OK)
261 					return status;
262 				break;
263 			}
264 
265 			default:
266 				const convert_tag* convertTag = find_convert_tag(tag.tag,
267 					convertTags, convertTagCount);
268 				if (convertTag != NULL) {
269 					add_to_message(read, target, tag, convertTag->name,
270 						convertTag->type);
271 				}
272 				break;
273 		}
274 
275 		if (visited.find(nextOffset) != visited.end())
276 			return B_BAD_DATA;
277 
278 		read.Seek(nextOffset, SEEK_SET);
279 		visited.insert(nextOffset);
280 	}
281 
282 	return B_OK;
283 }
284 
285 
286 static status_t
287 parse_tiff_directory(TReadHelper& read, set<off_t>& visited, BMessage& target,
288 	const convert_tag* tags, size_t tagCount)
289 {
290 	while (true) {
291 		int32 offset;
292 		read(offset);
293 		if (offset == 0)
294 			break;
295 
296 		status_t status = parse_tiff_directory(read, visited, offset, target,
297 			tags, tagCount);
298 		if (status < B_OK)
299 			return status;
300 	}
301 
302 	return B_OK;
303 }
304 
305 
306 //	#pragma mark -
307 
308 
309 /*!	Converts the EXIF data that starts in \a source to a BMessage in \a target.
310 	If the EXIF data is corrupt, this function will return an appropriate error
311 	code. Nevertheless, there might be some data ending up in \a target that
312 	was parsed until this point.
313 */
314 status_t
315 convert_exif_to_message(BPositionIO& source, BMessage& target,
316 	const convert_tag* tags, size_t tagCount)
317 {
318 	TReadHelper read(source);
319 
320 	uint16 endian;
321 	read(endian);
322 	if (endian != 'MM' && endian != 'II')
323 		return B_BAD_TYPE;
324 
325 #if B_HOST_IS_LENDIAN
326 	read.SetSwap(endian == 'MM');
327 #else
328 	read.SetSwap(endian == 'II');
329 #endif
330 
331 	int16 magic;
332 	read(magic);
333 	if (magic != 42)
334 		return B_BAD_TYPE;
335 
336 	set<off_t> visitedOffsets;
337 	return parse_tiff_directory(read, visitedOffsets, target, tags, tagCount);
338 }
339 
340 
341 status_t
342 convert_exif_to_message(BPositionIO& source, BMessage& target)
343 {
344 	return convert_exif_to_message(source, target, kDefaultTags,
345 		kNumDefaultTags);
346 }
347