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