]> gitweb.fperrin.net Git - GpsPrune.git/blob - tim/prune/jpeg/drew/ExifReader.java
Version 11, August 2010
[GpsPrune.git] / tim / prune / jpeg / drew / ExifReader.java
1 package tim.prune.jpeg.drew;\r
2 \r
3 import java.io.File;\r
4 import java.util.HashMap;\r
5 \r
6 import tim.prune.jpeg.JpegData;\r
7 \r
8 /**\r
9  * Extracts Exif data from a JPEG header segment\r
10  * Based on Drew Noakes' Metadata extractor at http://drewnoakes.com\r
11  * which in turn is based on code from Jhead http://www.sentex.net/~mwandel/jhead/\r
12  */\r
13 public class ExifReader\r
14 {\r
15         /** The JPEG segment as an array of bytes */\r
16         private final byte[] _data;\r
17 \r
18         /**\r
19          * Represents the native byte ordering used in the JPEG segment.  If true,\r
20          * then we're using Motorola ordering (Big endian), else we're using Intel\r
21          * ordering (Little endian).\r
22          */\r
23         private boolean _isMotorolaByteOrder;\r
24 \r
25         /** Thumbnail offset */\r
26         private int _thumbnailOffset = -1;\r
27         /** Thumbnail length */\r
28         private int _thumbnailLength = -1;\r
29 \r
30         /** The number of bytes used per format descriptor */\r
31         private static final int[] BYTES_PER_FORMAT = {0, 1, 1, 2, 4, 8, 1, 1, 2, 4, 8, 4, 8};\r
32 \r
33         /** The number of formats known */\r
34         private static final int MAX_FORMAT_CODE = 12;\r
35 \r
36         // Format types\r
37         // Note: Cannot use the DataFormat enumeration in the case statement that uses these tags.\r
38         //         Is there a better way?\r
39         //private static final int FMT_BYTE = 1;\r
40         private static final int FMT_STRING = 2;\r
41         //private static final int FMT_USHORT = 3;\r
42         //private static final int FMT_ULONG = 4;\r
43         private static final int FMT_URATIONAL = 5;\r
44         //private static final int FMT_SBYTE = 6;\r
45         //private static final int FMT_UNDEFINED = 7;\r
46         //private static final int FMT_SSHORT = 8;\r
47         //private static final int FMT_SLONG = 9;\r
48         private static final int FMT_SRATIONAL = 10;\r
49         //private static final int FMT_SINGLE = 11;\r
50         //private static final int FMT_DOUBLE = 12;\r
51 \r
52         public static final int TAG_EXIF_OFFSET = 0x8769;\r
53         public static final int TAG_INTEROP_OFFSET = 0xA005;\r
54         public static final int TAG_GPS_INFO_OFFSET = 0x8825;\r
55         public static final int TAG_MAKER_NOTE = 0x927C;\r
56 \r
57         public static final int TIFF_HEADER_START_OFFSET = 6;\r
58 \r
59         /** GPS tag version GPSVersionID 0 0 BYTE 4 */\r
60         public static final int TAG_GPS_VERSION_ID = 0x0000;\r
61         /** North or South Latitude GPSLatitudeRef 1 1 ASCII 2 */\r
62         public static final int TAG_GPS_LATITUDE_REF = 0x0001;\r
63         /** Latitude GPSLatitude 2 2 RATIONAL 3 */\r
64         public static final int TAG_GPS_LATITUDE = 0x0002;\r
65         /** East or West Longitude GPSLongitudeRef 3 3 ASCII 2 */\r
66         public static final int TAG_GPS_LONGITUDE_REF = 0x0003;\r
67         /** Longitude GPSLongitude 4 4 RATIONAL 3 */\r
68         public static final int TAG_GPS_LONGITUDE = 0x0004;\r
69         /** Altitude reference GPSAltitudeRef 5 5 BYTE 1 */\r
70         public static final int TAG_GPS_ALTITUDE_REF = 0x0005;\r
71         /** Altitude GPSAltitude 6 6 RATIONAL 1 */\r
72         public static final int TAG_GPS_ALTITUDE = 0x0006;\r
73         /** GPS time (atomic clock) GPSTimeStamp 7 7 RATIONAL 3 */\r
74         public static final int TAG_GPS_TIMESTAMP = 0x0007;\r
75         /** GPS date (atomic clock) GPSDateStamp 23 1d RATIONAL 3 */\r
76         public static final int TAG_GPS_DATESTAMP = 0x001d;\r
77         /** "Original" Exif timestamp */\r
78         public static final int TAG_DATETIME_ORIGINAL = 0x9003;\r
79         /** "Creation" or "Digitized" timestamp */\r
80     public static final int TAG_DATETIME_DIGITIZED = 0x9004;\r
81         /** Thumbnail offset */\r
82         private static final int TAG_THUMBNAIL_OFFSET = 0x0201;\r
83         /** Thumbnail length */\r
84         private static final int TAG_THUMBNAIL_LENGTH = 0x0202;\r
85         /** Orientation of image */\r
86         private static final int TAG_ORIENTATION = 0x0112;\r
87 \r
88 \r
89         /**\r
90          * Creates an ExifReader for a Jpeg file\r
91          * @param inFile File object to attempt to read from\r
92          * @throws JpegException on failure\r
93          */\r
94         public ExifReader(File inFile) throws JpegException\r
95         {\r
96                 JpegSegmentData segments = JpegSegmentReader.readSegments(inFile);\r
97                 _data = segments.getSegment(JpegSegmentReader.SEGMENT_APP1);\r
98         }\r
99 \r
100         /**\r
101          * Performs the Exif data extraction\r
102          * @return the GPS data found in the file\r
103          */\r
104         public JpegData extract()\r
105         {\r
106                 JpegData metadata = new JpegData();\r
107                 if (_data==null)\r
108                         return metadata;\r
109 \r
110                 // check for the header length\r
111                 if (_data.length<=14)\r
112                 {\r
113                         metadata.addError("Exif data segment must contain at least 14 bytes");\r
114                         return metadata;\r
115                 }\r
116 \r
117                 // check for the header preamble\r
118                 if (!"Exif\0\0".equals(new String(_data, 0, 6)))\r
119                 {\r
120                         metadata.addError("Exif data segment doesn't begin with 'Exif'");\r
121                         return metadata;\r
122                 }\r
123 \r
124                 // this should be either "MM" or "II"\r
125                 String byteOrderIdentifier = new String(_data, 6, 2);\r
126                 if (!setByteOrder(byteOrderIdentifier))\r
127                 {\r
128                         metadata.addError("Unclear distinction between Motorola/Intel byte ordering: " + byteOrderIdentifier);\r
129                         return metadata;\r
130                 }\r
131 \r
132                 // Check the next two values are 0x2A as expected\r
133                 if (get16Bits(8)!=0x2a)\r
134                 {\r
135                         metadata.addError("Invalid Exif start - should have 0x2A at offset 8 in Exif header");\r
136                         return metadata;\r
137                 }\r
138 \r
139                 int firstDirectoryOffset = get32Bits(10) + TIFF_HEADER_START_OFFSET;\r
140 \r
141                 // Check that offset is within range\r
142                 if (firstDirectoryOffset>=_data.length - 1)\r
143                 {\r
144                         metadata.addError("First exif directory offset is beyond end of Exif data segment");\r
145                         // First directory normally starts 14 bytes in -- try it here and catch another error in the worst case\r
146                         firstDirectoryOffset = 14;\r
147                 }\r
148 \r
149                 HashMap<Integer, String> processedDirectoryOffsets = new HashMap<Integer, String>();\r
150 \r
151                 // 0th IFD (we merge with Exif IFD)\r
152                 processDirectory(metadata, false, processedDirectoryOffsets, firstDirectoryOffset, TIFF_HEADER_START_OFFSET);\r
153 \r
154                 return metadata;\r
155         }\r
156 \r
157 \r
158         /**\r
159          * Set the byte order identifier\r
160          * @param byteOrderIdentifier String from exif\r
161          * @return true if recognised, false otherwise\r
162          */\r
163         private boolean setByteOrder(String byteOrderIdentifier)\r
164         {\r
165                 if ("MM".equals(byteOrderIdentifier)) {\r
166                         _isMotorolaByteOrder = true;\r
167                 } else if ("II".equals(byteOrderIdentifier)) {\r
168                         _isMotorolaByteOrder = false;\r
169                 } else {\r
170                         return false;\r
171                 }\r
172                 return true;\r
173         }\r
174 \r
175 \r
176         /**\r
177          * Recursive call to process one of the nested Tiff IFD directories.\r
178          * 2 bytes: number of tags\r
179          * for each tag\r
180          *   2 bytes: tag type\r
181          *   2 bytes: format code\r
182          *   4 bytes: component count\r
183          */\r
184         private void processDirectory(JpegData inMetadata, boolean inIsGPS, HashMap<Integer, String> inDirectoryOffsets,\r
185                 int inDirOffset, int inTiffHeaderOffset)\r
186         {\r
187                 // check for directories we've already visited to avoid stack overflows when recursive/cyclic directory structures exist\r
188                 if (inDirectoryOffsets.containsKey(Integer.valueOf(inDirOffset)))\r
189                         return;\r
190 \r
191                 // remember that we've visited this directory so that we don't visit it again later\r
192                 inDirectoryOffsets.put(Integer.valueOf(inDirOffset), "processed");\r
193 \r
194                 if (inDirOffset >= _data.length || inDirOffset < 0)\r
195                 {\r
196                         inMetadata.addError("Ignored directory marked to start outside data segment");\r
197                         return;\r
198                 }\r
199 \r
200                 // First two bytes in the IFD are the number of tags in this directory\r
201                 int dirTagCount = get16Bits(inDirOffset);\r
202                 // If no tags, exit without complaint\r
203                 if (dirTagCount == 0) return;\r
204 \r
205                 if (!isDirectoryLengthValid(inDirOffset, inTiffHeaderOffset))\r
206                 {\r
207                         inMetadata.addError("Directory length is not valid");\r
208                         return;\r
209                 }\r
210 \r
211                 inMetadata.setExifDataPresent();\r
212                 // Handle each tag in this directory\r
213                 for (int tagNumber = 0; tagNumber<dirTagCount; tagNumber++)\r
214                 {\r
215                         final int tagOffset = calculateTagOffset(inDirOffset, tagNumber);\r
216 \r
217                         // 2 bytes for the tag type\r
218                         final int tagType = get16Bits(tagOffset);\r
219 \r
220                         // 2 bytes for the format code\r
221                         final int formatCode = get16Bits(tagOffset + 2);\r
222                         if (formatCode < 1 || formatCode > MAX_FORMAT_CODE)\r
223                         {\r
224                                 inMetadata.addError("Invalid format code: " + formatCode);\r
225                                 continue;\r
226                         }\r
227 \r
228                         // 4 bytes dictate the number of components in this tag's data\r
229                         final int componentCount = get32Bits(tagOffset + 4);\r
230                         if (componentCount < 0)\r
231                         {\r
232                                 inMetadata.addError("Negative component count in EXIF");\r
233                                 continue;\r
234                         }\r
235                         // each component may have more than one byte... calculate the total number of bytes\r
236                         final int byteCount = componentCount * BYTES_PER_FORMAT[formatCode];\r
237                         final int tagValueOffset = calculateTagValueOffset(byteCount, tagOffset, inTiffHeaderOffset);\r
238                         if (tagValueOffset < 0 || tagValueOffset > _data.length)\r
239                         {\r
240                                 inMetadata.addError("Illegal pointer offset value in EXIF");\r
241                                 continue;\r
242                         }\r
243 \r
244                         // Check that this tag isn't going to allocate outside the bounds of the data array.\r
245                         // This addresses an uncommon OutOfMemoryError.\r
246                         if (byteCount < 0 || tagValueOffset + byteCount > _data.length)\r
247                         {\r
248                                 inMetadata.addError("Illegal number of bytes: " + byteCount);\r
249                                 continue;\r
250                         }\r
251 \r
252                         // Calculate the value as an offset for cases where the tag represents a directory\r
253                         final int subdirOffset = inTiffHeaderOffset + get32Bits(tagValueOffset);\r
254 \r
255                         // Look in both basic Exif tags (for timestamp, thumbnail) and Gps tags (for lat, long, altitude, timestamp)\r
256                         switch (tagType)\r
257                         {\r
258                                 case TAG_EXIF_OFFSET:\r
259                                         processDirectory(inMetadata, false, inDirectoryOffsets, subdirOffset, inTiffHeaderOffset);\r
260                                         continue;\r
261                                 case TAG_INTEROP_OFFSET:\r
262                                         // ignore\r
263                                         continue;\r
264                                 case TAG_GPS_INFO_OFFSET:\r
265                                         processDirectory(inMetadata, true, inDirectoryOffsets, subdirOffset, inTiffHeaderOffset);\r
266                                         continue;\r
267                                 case TAG_MAKER_NOTE:\r
268                                         // ignore\r
269                                         continue;\r
270                                 default:\r
271                                         // not a known directory, so must just be a normal tag\r
272                                         if (inIsGPS)\r
273                                         {\r
274                                                 processGpsTag(inMetadata, tagType, tagValueOffset, componentCount, formatCode);\r
275                                         }\r
276                                         else\r
277                                         {\r
278                                                 processExifTag(inMetadata, tagType, tagValueOffset, componentCount, formatCode);\r
279                                         }\r
280                                         break;\r
281                         }\r
282                 }\r
283 \r
284                 // at the end of each IFD is an optional link to the next IFD\r
285                 final int finalTagOffset = calculateTagOffset(inDirOffset, dirTagCount);\r
286                 int nextDirectoryOffset = get32Bits(finalTagOffset);\r
287                 if (nextDirectoryOffset != 0)\r
288                 {\r
289                         nextDirectoryOffset += inTiffHeaderOffset;\r
290                         if (nextDirectoryOffset>=_data.length)\r
291                         {\r
292                                 // Last 4 bytes of IFD reference another IFD with an address that is out of bounds\r
293                                 return;\r
294                         }\r
295                         else if (nextDirectoryOffset < inDirOffset)\r
296                         {\r
297                                 // Last 4 bytes of IFD reference another IFD with an address before the start of this directory\r
298                                 return;\r
299                         }\r
300                         // the next directory is of same type as this one\r
301                         processDirectory(inMetadata, false, inDirectoryOffsets, nextDirectoryOffset, inTiffHeaderOffset);\r
302                 }\r
303         }\r
304 \r
305 \r
306         /**\r
307          * Check if the directory length is valid\r
308          * @param dirStartOffset start offset for directory\r
309          * @param tiffHeaderOffset Tiff header offeset\r
310          * @return true if length is valid\r
311          */\r
312         private boolean isDirectoryLengthValid(int inDirStartOffset, int inTiffHeaderOffset)\r
313         {\r
314                 int dirTagCount = get16Bits(inDirStartOffset);\r
315                 int dirLength = (2 + (12 * dirTagCount) + 4);\r
316                 if (dirLength + inDirStartOffset + inTiffHeaderOffset >= _data.length)\r
317                 {\r
318                         // Note: Files that had thumbnails trimmed with jhead 1.3 or earlier might trigger this\r
319                         return false;\r
320                 }\r
321                 return true;\r
322         }\r
323 \r
324 \r
325         /**\r
326          * Process a GPS tag and put the contents in the given metadata\r
327          * @param inMetadata metadata holding extracted values\r
328          * @param inTagType tag type (eg latitude)\r
329          * @param inTagValueOffset start offset in data array\r
330          * @param inComponentCount component count for tag\r
331          * @param inFormatCode format code, eg byte\r
332          */\r
333         private void processGpsTag(JpegData inMetadata, int inTagType, int inTagValueOffset,\r
334                 int inComponentCount, int inFormatCode)\r
335         {\r
336                 try\r
337                 {\r
338                         // Only interested in tags latref, lat, longref, lon, altref, alt and gps timestamp\r
339                         switch (inTagType)\r
340                         {\r
341                                 case TAG_GPS_LATITUDE_REF:\r
342                                         inMetadata.setLatitudeRef(readString(inTagValueOffset, inFormatCode, inComponentCount));\r
343                                         break;\r
344                                 case TAG_GPS_LATITUDE:\r
345                                         Rational[] latitudes = readRationalArray(inTagValueOffset, inFormatCode, inComponentCount);\r
346                                         inMetadata.setLatitude(new double[] {latitudes[0].doubleValue(), latitudes[1].doubleValue(), latitudes[2].doubleValue()});\r
347                                         break;\r
348                                 case TAG_GPS_LONGITUDE_REF:\r
349                                         inMetadata.setLongitudeRef(readString(inTagValueOffset, inFormatCode, inComponentCount));\r
350                                         break;\r
351                                 case TAG_GPS_LONGITUDE:\r
352                                         Rational[] longitudes = readRationalArray(inTagValueOffset, inFormatCode, inComponentCount);\r
353                                         inMetadata.setLongitude(new double[] {longitudes[0].doubleValue(), longitudes[1].doubleValue(), longitudes[2].doubleValue()});\r
354                                         break;\r
355                                 case TAG_GPS_ALTITUDE_REF:\r
356                                         inMetadata.setAltitudeRef(_data[inTagValueOffset]);\r
357                                         break;\r
358                                 case TAG_GPS_ALTITUDE:\r
359                                         inMetadata.setAltitude(readRational(inTagValueOffset, inFormatCode, inComponentCount).intValue());\r
360                                         break;\r
361                                 case TAG_GPS_TIMESTAMP:\r
362                                         Rational[] times = readRationalArray(inTagValueOffset, inFormatCode, inComponentCount);\r
363                                         inMetadata.setGpsTimestamp(new int[] {times[0].intValue(), times[1].intValue(), times[2].intValue()});\r
364                                         break;\r
365                                 case TAG_GPS_DATESTAMP:\r
366                                         Rational[] dates = readRationalArray(inTagValueOffset, inFormatCode, inComponentCount);\r
367                                         if (dates != null) {\r
368                                                 inMetadata.setGpsDatestamp(new int[] {dates[0].intValue(), dates[1].intValue(), dates[2].intValue()});\r
369                                         }\r
370                                         else {\r
371                                                 // Not in rational array format, but maybe as String?\r
372                                                 String date = readString(inTagValueOffset, inFormatCode, inComponentCount);\r
373                                                 if (date != null && date.length() == 10) {\r
374                                                         inMetadata.setGpsDatestamp(new int[] {Integer.parseInt(date.substring(0, 4)),\r
375                                                                 Integer.parseInt(date.substring(5, 7)), Integer.parseInt(date.substring(8))});\r
376                                                 }\r
377                                         }\r
378                                         break;\r
379                                 default: // ignore all other tags\r
380                         }\r
381                 }\r
382                 catch (Exception e) {} // ignore and continue\r
383         }\r
384 \r
385 \r
386         /**\r
387          * Process a general Exif tag\r
388          * @param inMetadata metadata holding extracted values\r
389          * @param inTagType tag type (eg latitude)\r
390          * @param inTagValueOffset start offset in data array\r
391          * @param inComponentCount component count for tag\r
392          * @param inFormatCode format code, eg byte\r
393          */\r
394         private void processExifTag(JpegData inMetadata, int inTagType, int inTagValueOffset,\r
395                 int inComponentCount, int inFormatCode)\r
396         {\r
397                 // Only interested in original timestamp, thumbnail offset and thumbnail length\r
398                 if (inTagType == TAG_DATETIME_ORIGINAL) {\r
399                         inMetadata.setOriginalTimestamp(readString(inTagValueOffset, inFormatCode, inComponentCount));\r
400                 }\r
401                 else if (inTagType == TAG_DATETIME_DIGITIZED) {\r
402                         inMetadata.setDigitizedTimestamp(readString(inTagValueOffset, inFormatCode, inComponentCount));\r
403                 }\r
404                 else if (inTagType == TAG_THUMBNAIL_OFFSET) {\r
405                         _thumbnailOffset = TIFF_HEADER_START_OFFSET + get16Bits(inTagValueOffset);\r
406                         extractThumbnail(inMetadata);\r
407                 }\r
408                 else if (inTagType == TAG_THUMBNAIL_LENGTH) {\r
409                         _thumbnailLength = get16Bits(inTagValueOffset);\r
410                         extractThumbnail(inMetadata);\r
411                 }\r
412                 else if (inTagType == TAG_ORIENTATION) {\r
413                         if (inMetadata.getOrientationCode() < 1) {\r
414                                 inMetadata.setOrientationCode(get16Bits(inTagValueOffset));\r
415                         }\r
416                 }\r
417         }\r
418 \r
419         /**\r
420          * Attempt to extract the thumbnail image\r
421          */\r
422         private void extractThumbnail(JpegData inMetadata)\r
423         {\r
424                 if (_thumbnailOffset > 0 && _thumbnailLength > 0 && inMetadata.getThumbnailImage() == null)\r
425                 {\r
426                         byte[] thumbnailBytes = new byte[_thumbnailLength];\r
427                         System.arraycopy(_data, _thumbnailOffset, thumbnailBytes, 0, _thumbnailLength);\r
428                         inMetadata.setThumbnailImage(thumbnailBytes);\r
429                 }\r
430         }\r
431 \r
432 \r
433         /**\r
434          * Calculate the tag value offset\r
435          * @param inByteCount\r
436          * @param inDirEntryOffset\r
437          * @param inTiffHeaderOffset\r
438          * @return new offset\r
439          */\r
440         private int calculateTagValueOffset(int inByteCount, int inDirEntryOffset, int inTiffHeaderOffset)\r
441         {\r
442                 if (inByteCount > 4)\r
443                 {\r
444                         // If it's bigger than 4 bytes, the dir entry contains an offset.\r
445                         // dirEntryOffset must be passed, as some makers (e.g. FujiFilm) incorrectly use an\r
446                         // offset relative to the start of the makernote itself, not the TIFF segment.\r
447                         final int offsetVal = get32Bits(inDirEntryOffset + 8);\r
448                         if (offsetVal + inByteCount > _data.length)\r
449                         {\r
450                                 // Bogus pointer offset and / or bytecount value\r
451                                 return -1; // signal error\r
452                         }\r
453                         return inTiffHeaderOffset + offsetVal;\r
454                 }\r
455                 else\r
456                 {\r
457                         // 4 bytes or less and value is in the dir entry itself\r
458                         return inDirEntryOffset + 8;\r
459                 }\r
460         }\r
461 \r
462 \r
463         /**\r
464          * Creates a String from the _data buffer starting at the specified offset,\r
465          * and ending where byte=='\0' or where length==maxLength.\r
466          * @param inOffset start offset\r
467          * @param inFormatCode format code - should be string\r
468          * @param inMaxLength max length of string\r
469          * @return contents of tag, or null if format incorrect\r
470          */\r
471         private String readString(int inOffset, int inFormatCode, int inMaxLength)\r
472         {\r
473                 if (inFormatCode != FMT_STRING) return null;\r
474                 // Calculate length\r
475                 int length = 0;\r
476                 while ((inOffset + length)<_data.length\r
477                         && _data[inOffset + length]!='\0'\r
478                         && length < inMaxLength)\r
479                 {\r
480                         length++;\r
481                 }\r
482                 return new String(_data, inOffset, length);\r
483         }\r
484 \r
485         /**\r
486          * Creates a Rational from the _data buffer starting at the specified offset\r
487          * @param inOffset start offset\r
488          * @param inFormatCode format code - should be srational or urational\r
489          * @param inCount component count - should be 1\r
490          * @return contents of tag as a Rational object\r
491          */\r
492         private Rational readRational(int inOffset, int inFormatCode, int inCount)\r
493         {\r
494                 // Check the format is a single rational as expected\r
495                 if (inFormatCode != FMT_SRATIONAL && inFormatCode != FMT_URATIONAL\r
496                         || inCount != 1) return null;\r
497                 return new Rational(get32Bits(inOffset), get32Bits(inOffset + 4));\r
498         }\r
499 \r
500 \r
501         /**\r
502          * Creates a Rational array from the _data buffer starting at the specified offset\r
503          * @param inOffset start offset\r
504          * @param inFormatCode format code - should be srational or urational\r
505          * @param inCount component count - number of components\r
506          * @return contents of tag as an array of Rational objects\r
507          */\r
508         private Rational[] readRationalArray(int inOffset, int inFormatCode, int inCount)\r
509         {\r
510                 // Check the format is rational as expected\r
511                 if (inFormatCode != FMT_SRATIONAL && inFormatCode != FMT_URATIONAL)\r
512                         return null;\r
513                 // Build array of Rationals\r
514                 Rational[] answer = new Rational[inCount];\r
515                 for (int i=0; i<inCount; i++)\r
516                         answer[i] = new Rational(get32Bits(inOffset + (8 * i)), get32Bits(inOffset + 4 + (8 * i)));\r
517                 return answer;\r
518         }\r
519 \r
520 \r
521         /**\r
522          * Determine the offset at which a given InteropArray entry begins within the specified IFD.\r
523          * @param dirStartOffset the offset at which the IFD starts\r
524          * @param entryNumber the zero-based entry number\r
525          */\r
526         private int calculateTagOffset(int dirStartOffset, int entryNumber)\r
527         {\r
528                 // add 2 bytes for the tag count\r
529                 // each entry is 12 bytes, so we skip 12 * the number seen so far\r
530                 return dirStartOffset + 2 + (12 * entryNumber);\r
531         }\r
532 \r
533 \r
534         /**\r
535          * Get a 16 bit value from file's native byte order.  Between 0x0000 and 0xFFFF.\r
536          */\r
537         private int get16Bits(int offset)\r
538         {\r
539                 if (offset<0 || offset+2>_data.length)\r
540                         throw new ArrayIndexOutOfBoundsException("attempt to read data outside of exif segment (index "\r
541                                 + offset + " where max index is " + (_data.length - 1) + ")");\r
542 \r
543                 if (_isMotorolaByteOrder) {\r
544                         // Motorola - MSB first\r
545                         return (_data[offset] << 8 & 0xFF00) | (_data[offset + 1] & 0xFF);\r
546                 } else {\r
547                         // Intel ordering - LSB first\r
548                         return (_data[offset + 1] << 8 & 0xFF00) | (_data[offset] & 0xFF);\r
549                 }\r
550         }\r
551 \r
552 \r
553         /**\r
554          * Get a 32 bit value from file's native byte order.\r
555          */\r
556         private int get32Bits(int offset)\r
557         {\r
558                 if (offset < 0 || offset+4 > _data.length)\r
559                         throw new ArrayIndexOutOfBoundsException("attempt to read data outside of exif segment (index "\r
560                                 + offset + " where max index is " + (_data.length - 1) + ")");\r
561 \r
562                 if (_isMotorolaByteOrder)\r
563                 {\r
564                         // Motorola - MSB first\r
565                         return (_data[offset] << 24 & 0xFF000000) |\r
566                                         (_data[offset + 1] << 16 & 0xFF0000) |\r
567                                         (_data[offset + 2] << 8 & 0xFF00) |\r
568                                         (_data[offset + 3] & 0xFF);\r
569                 }\r
570                 else\r
571                 {\r
572                         // Intel ordering - LSB first\r
573                         return (_data[offset + 3] << 24 & 0xFF000000) |\r
574                                         (_data[offset + 2] << 16 & 0xFF0000) |\r
575                                         (_data[offset + 1] << 8 & 0xFF00) |\r
576                                         (_data[offset] & 0xFF);\r
577                 }\r
578         }\r
579 }\r