Ignore:
Timestamp:
2013-08-09T18:05:11+02:00 (13 years ago)
Author:
bastiK
Message:

applied #8895 - Upgrade metadata-extractor to v. 2.6.4 (patch by ebourg)

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/com/drew/metadata/exif/ExifReader.java

    r4231 r6127  
    11/*
    2  * EXIFExtractor.java
     2 * Copyright 2002-2012 Drew Noakes
    33 *
    4  * This class based upon code from Jhead, a C program for extracting and
    5  * manipulating the Exif data within files written by Matthias Wandel.
    6  *   http://www.sentex.net/~mwandel/jhead/
     4 *    Licensed under the Apache License, Version 2.0 (the "License");
     5 *    you may not use this file except in compliance with the License.
     6 *    You may obtain a copy of the License at
    77 *
    8  * Jhead is public domain software - that is, you can do whatever you want
    9  * with it, and include it software that is licensed under the GNU or the
    10  * BSD license, or whatever other licence you choose, including proprietary
    11  * closed source licenses.  Similarly, I release this Java version under the
    12  * same license, though I do ask that you leave this header in tact.
     8 *        http://www.apache.org/licenses/LICENSE-2.0
    139 *
    14  * If you make modifications to this code that you think would benefit the
    15  * wider community, please send me a copy and I'll post it on my site.  Unlike
    16  * Jhead, this code (as it stands) only supports reading of Exif data - no
    17  * manipulation, and no thumbnail stuff.
     10 *    Unless required by applicable law or agreed to in writing, software
     11 *    distributed under the License is distributed on an "AS IS" BASIS,
     12 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13 *    See the License for the specific language governing permissions and
     14 *    limitations under the License.
    1815 *
    19  * If you make use of this code, I'd appreciate hearing about it.
    20  *   drew.noakes@drewnoakes.com
    21  * Latest version of this software kept at
    22  *   http://drewnoakes.com/
     16 * More information about this project is available at:
    2317 *
    24  * Created on 28 April 2002, 23:54
    25  * Modified 04 Aug 2002
    26  * - Renamed constants to be inline with changes to ExifTagValues interface
    27  * - Substituted usage of JDK 1.4 features (java.nio package)
    28  * Modified 29 Oct 2002 (v1.2)
    29  * - Proper traversing of Exif file structure and complete refactor & tidy of
    30  *   the codebase (a few unnoticed bugs removed)
    31  * - Reads makernote data for 6 families of camera (5 makes)
    32  * - Tags now stored in directories... use the IFD_* constants to refer to the
    33  *   image file directory you require (Exif, Interop, GPS and Makernote*) --
    34  *   this avoids collisions where two tags share the same code
    35  * - Takes componentCount of unknown tags into account
    36  * - Now understands GPS tags (thanks to Colin Briton for his help with this)
    37  * - Some other bug fixes, pointed out by users around the world.  Thanks!
    38  * Modified 27 Nov 2002 (v2.0)
    39  * - Renamed to ExifReader
    40  * - Moved to new package com.drew.metadata.exif
    41  * Modified since, however changes have not been logged.  See release notes for
    42  * library-wide modifications.
     18 *    http://drewnoakes.com/code/exif/
     19 *    http://code.google.com/p/metadata-extractor/
    4320 */
    4421package com.drew.metadata.exif;
    4522
    46 import com.drew.imaging.jpeg.JpegProcessingException;
    47 import com.drew.imaging.jpeg.JpegSegmentData;
    48 import com.drew.imaging.jpeg.JpegSegmentReader;
     23import com.drew.lang.BufferBoundsException;
     24import com.drew.lang.BufferReader;
    4925import com.drew.lang.Rational;
     26import com.drew.lang.annotations.NotNull;
    5027import com.drew.metadata.Directory;
    5128import com.drew.metadata.Metadata;
    5229import com.drew.metadata.MetadataReader;
    5330
    54 import java.io.File;
    55 import java.io.InputStream;
    56 import java.util.HashMap;
     31import java.util.HashSet;
     32import java.util.Set;
    5733
    5834/**
    59  * Extracts Exif data from a JPEG header segment, providing information about the
    60  * camera/scanner/capture device (if available).  Information is encapsulated in
    61  * an <code>Metadata</code> object.
    62  * @author  Drew Noakes http://drewnoakes.com
     35 * Decodes Exif binary data, populating a {@link Metadata} object with tag values in {@link ExifSubIFDDirectory},
     36 * {@link ExifThumbnailDirectory}, {@link ExifInteropDirectory}, {@link GpsDirectory} and one of the many camera makernote directories.
     37 *
     38 * @author Drew Noakes http://drewnoakes.com
    6339 */
    6440public class ExifReader implements MetadataReader
    6541{
    66     /**
    67      * The JPEG segment as an array of bytes.
    68      */
    69     private final byte[] _data;
    70 
    71     /**
    72      * Represents the native byte ordering used in the JPEG segment.  If true,
    73      * then we're using Motorolla ordering (Big endian), else we're using Intel
    74      * ordering (Little endian).
    75      */
    76     private boolean _isMotorollaByteOrder;
    77 
    78     /**
    79      * Bean instance to store information about the image and camera/scanner/capture
    80      * device.
    81      */
    82     private Metadata _metadata;
    83 
    84     /**
    85      * The number of bytes used per format descriptor.
    86      */
    87     private static final int[] BYTES_PER_FORMAT = {0, 1, 1, 2, 4, 8, 1, 1, 2, 4, 8, 4, 8};
    88 
    89     /**
    90      * The number of formats known.
    91      */
     42    // TODO extract a reusable TiffReader from this class with hooks for special tag handling and subdir following
     43   
     44    /** The number of bytes used per format descriptor. */
     45    @NotNull
     46    private static final int[] BYTES_PER_FORMAT = { 0, 1, 1, 2, 4, 8, 1, 1, 2, 4, 8, 4, 8 };
     47
     48    /** The number of formats known. */
    9249    private static final int MAX_FORMAT_CODE = 12;
    9350
    9451    // Format types
    95     // Note: Cannot use the DataFormat enumeration in the case statement that uses these tags.
    96     //       Is there a better way?
     52    // TODO use an enum for these?
     53    /** An 8-bit unsigned integer. */
    9754    private static final int FMT_BYTE = 1;
     55    /** A fixed-length character string. */
    9856    private static final int FMT_STRING = 2;
     57    /** An unsigned 16-bit integer. */
    9958    private static final int FMT_USHORT = 3;
     59    /** An unsigned 32-bit integer. */
    10060    private static final int FMT_ULONG = 4;
    10161    private static final int FMT_URATIONAL = 5;
     62    /** An 8-bit signed integer. */
    10263    private static final int FMT_SBYTE = 6;
    10364    private static final int FMT_UNDEFINED = 7;
     65    /** A signed 16-bit integer. */
    10466    private static final int FMT_SSHORT = 8;
     67    /** A signed 32-bit integer. */
    10568    private static final int FMT_SLONG = 9;
    10669    private static final int FMT_SRATIONAL = 10;
     70    /** A 32-bit floating point number. */
    10771    private static final int FMT_SINGLE = 11;
     72    /** A 64-bit floating point number. */
    10873    private static final int FMT_DOUBLE = 12;
    10974
    110     public static final int TAG_EXIF_OFFSET = 0x8769;
     75    /** This tag is a pointer to the Exif SubIFD. */
     76    public static final int TAG_EXIF_SUB_IFD_OFFSET = 0x8769;
     77    /** This tag is a pointer to the Exif Interop IFD. */
    11178    public static final int TAG_INTEROP_OFFSET = 0xA005;
     79    /** This tag is a pointer to the Exif GPS IFD. */
    11280    public static final int TAG_GPS_INFO_OFFSET = 0x8825;
    113     public static final int TAG_MAKER_NOTE = 0x927C;
     81    /** This tag is a pointer to the Exif Makernote IFD. */
     82    public static final int TAG_MAKER_NOTE_OFFSET = 0x927C;
    11483
    11584    public static final int TIFF_HEADER_START_OFFSET = 6;
    116 
    117     /**
    118      * Creates an ExifReader for a JpegSegmentData object.
    119      * @param segmentData
    120      */
    121     public ExifReader(JpegSegmentData segmentData)
    122     {
    123         this(segmentData.getSegment(JpegSegmentReader.SEGMENT_APP1));
    124     }
    125 
    126     /**
    127      * Creates an ExifReader for a Jpeg file.
    128      * @param file
    129      * @throws JpegProcessingException
    130      */
    131     public ExifReader(File file) throws JpegProcessingException
    132     {
    133         this(new JpegSegmentReader(file).readSegment(JpegSegmentReader.SEGMENT_APP1));
    134     }
    135 
    136     /**
    137      * Creates an ExifReader for a Jpeg stream.
    138      * @param is JPEG stream. Stream will be closed.
    139      */
    140     public ExifReader(InputStream is) throws JpegProcessingException
    141     {
    142         this(new JpegSegmentReader(is).readSegment(JpegSegmentReader.SEGMENT_APP1));
    143     }
    144 
    145     /**
    146      * Creates an ExifReader for the given JPEG header segment.
    147      */
    148     public ExifReader(byte[] data)
    149     {
    150         _data = data;
    151     }
    152 
    153     /**
    154      * Performs the Exif data extraction, returning a new instance of <code>Metadata</code>.
    155      */
    156     public Metadata extract()
    157     {
    158         return extract(new Metadata());
    159     }
    16085
    16186    /**
    16287     * Performs the Exif data extraction, adding found values to the specified
    16388     * instance of <code>Metadata</code>.
     89     *
     90     * @param reader   The buffer reader from which Exif data should be read.
     91     * @param metadata The Metadata object into which extracted values should be merged.
    16492     */
    165     public Metadata extract(Metadata metadata)
    166     {
    167         _metadata = metadata;
    168         if (_data==null)
    169             return _metadata;
    170 
    171         // once we know there's some data, create the directory and start working on it
    172         ExifDirectory directory = (ExifDirectory)_metadata.getDirectory(ExifDirectory.class);
     93    public void extract(@NotNull final BufferReader reader, @NotNull Metadata metadata)
     94    {
     95        final ExifSubIFDDirectory directory = metadata.getOrCreateDirectory(ExifSubIFDDirectory.class);
    17396
    17497        // check for the header length
    175         if (_data.length<=14) {
     98        if (reader.getLength() <= 14) {
    17699            directory.addError("Exif data segment must contain at least 14 bytes");
    177             return _metadata;
     100            return;
    178101        }
    179102
    180103        // check for the header preamble
    181         if (!"Exif\0\0".equals(new String(_data, 0, 6))) {
    182             directory.addError("Exif data segment doesn't begin with 'Exif'");
    183             return _metadata;
    184         }
    185 
     104        try {
     105            if (!reader.getString(0, 6).equals("Exif\0\0")) {
     106                directory.addError("Exif data segment doesn't begin with 'Exif'");
     107                return;
     108            }
     109
     110            extractIFD(metadata, metadata.getOrCreateDirectory(ExifIFD0Directory.class), TIFF_HEADER_START_OFFSET, reader);
     111        } catch (BufferBoundsException e) {
     112            directory.addError("Exif data segment ended prematurely");
     113        }
     114    }
     115
     116    /**
     117     * Performs the Exif data extraction on a TIFF/RAW, adding found values to the specified
     118     * instance of <code>Metadata</code>.
     119     *
     120     * @param reader   The BufferReader from which TIFF data should be read.
     121     * @param metadata The Metadata object into which extracted values should be merged.
     122     */
     123    public void extractTiff(@NotNull BufferReader reader, @NotNull Metadata metadata)
     124    {
     125        final ExifIFD0Directory directory = metadata.getOrCreateDirectory(ExifIFD0Directory.class);
     126
     127        try {
     128            extractIFD(metadata, directory, 0, reader);
     129        } catch (BufferBoundsException e) {
     130            directory.addError("Exif data segment ended prematurely");
     131        }
     132    }
     133
     134    private void extractIFD(@NotNull Metadata metadata, @NotNull final ExifIFD0Directory directory, int tiffHeaderOffset, @NotNull BufferReader reader) throws BufferBoundsException
     135    {
    186136        // this should be either "MM" or "II"
    187         String byteOrderIdentifier = new String(_data, 6, 2);
    188         if (!setByteOrder(byteOrderIdentifier)) {
     137        String byteOrderIdentifier = reader.getString(tiffHeaderOffset, 2);
     138
     139        if ("MM".equals(byteOrderIdentifier)) {
     140            reader.setMotorolaByteOrder(true);
     141        } else if ("II".equals(byteOrderIdentifier)) {
     142            reader.setMotorolaByteOrder(false);
     143        } else {
    189144            directory.addError("Unclear distinction between Motorola/Intel byte ordering: " + byteOrderIdentifier);
    190             return _metadata;
     145            return;
    191146        }
    192147
    193148        // Check the next two values for correctness.
    194         if (get16Bits(8)!=0x2a) {
    195             directory.addError("Invalid Exif start - should have 0x2A at offset 8 in Exif header");
    196             return _metadata;
    197         }
    198 
    199         int firstDirectoryOffset = get32Bits(10) + TIFF_HEADER_START_OFFSET;
    200 
    201         // David Ekholm sent an digital camera image that has this problem
    202         if (firstDirectoryOffset>=_data.length - 1) {
     149        final int tiffMarker = reader.getUInt16(2 + tiffHeaderOffset);
     150
     151        final int standardTiffMarker = 0x002A;
     152        final int olympusRawTiffMarker = 0x4F52; // for ORF files
     153        final int panasonicRawTiffMarker = 0x0055; // for RW2 files
     154
     155        if (tiffMarker != standardTiffMarker && tiffMarker != olympusRawTiffMarker && tiffMarker != panasonicRawTiffMarker) {
     156            directory.addError("Unexpected TIFF marker after byte order identifier: 0x" + Integer.toHexString(tiffMarker));
     157            return;
     158        }
     159
     160        int firstDirectoryOffset = reader.getInt32(4 + tiffHeaderOffset) + tiffHeaderOffset;
     161
     162        // David Ekholm sent a digital camera image that has this problem
     163        if (firstDirectoryOffset >= reader.getLength() - 1) {
    203164            directory.addError("First exif directory offset is beyond end of Exif data segment");
    204165            // First directory normally starts 14 bytes in -- try it here and catch another error in the worst case
     
    206167        }
    207168
    208         HashMap processedDirectoryOffsets = new HashMap();
    209 
    210         // 0th IFD (we merge with Exif IFD)
    211         processDirectory(directory, processedDirectoryOffsets, firstDirectoryOffset, TIFF_HEADER_START_OFFSET);
     169        Set<Integer> processedDirectoryOffsets = new HashSet<Integer>();
     170
     171        processDirectory(directory, processedDirectoryOffsets, firstDirectoryOffset, tiffHeaderOffset, metadata, reader);
    212172
    213173        // after the extraction process, if we have the correct tags, we may be able to store thumbnail information
    214         storeThumbnailBytes(directory, TIFF_HEADER_START_OFFSET);
    215 
    216         return _metadata;
    217     }
    218 
    219     private void storeThumbnailBytes(ExifDirectory exifDirectory, int tiffHeaderOffset)
    220     {
    221         if (!exifDirectory.containsTag(ExifDirectory.TAG_COMPRESSION))
    222                 return;
    223 
    224         if (!exifDirectory.containsTag(ExifDirectory.TAG_THUMBNAIL_LENGTH) ||
    225             !exifDirectory.containsTag(ExifDirectory.TAG_THUMBNAIL_OFFSET))
    226             return;
    227 
    228         try {
    229             int offset = exifDirectory.getInt(ExifDirectory.TAG_THUMBNAIL_OFFSET);
    230             int length = exifDirectory.getInt(ExifDirectory.TAG_THUMBNAIL_LENGTH);
    231             byte[] result = new byte[length];
    232             for (int i = 0; i<result.length; i++) {
    233                 result[i] = _data[tiffHeaderOffset + offset + i];
    234             }
    235             exifDirectory.setByteArray(ExifDirectory.TAG_THUMBNAIL_DATA, result);
    236         } catch (Throwable e) {
    237             exifDirectory.addError("Unable to extract thumbnail: " + e.getMessage());
    238         }
    239     }
    240 
    241     private boolean setByteOrder(String byteOrderIdentifier)
    242     {
    243         if ("MM".equals(byteOrderIdentifier)) {
    244             _isMotorollaByteOrder = true;
    245         } else if ("II".equals(byteOrderIdentifier)) {
    246             _isMotorollaByteOrder = false;
    247         } else {
    248             return false;
    249         }
    250         return true;
     174        ExifThumbnailDirectory thumbnailDirectory = metadata.getDirectory(ExifThumbnailDirectory.class);
     175        if (thumbnailDirectory!=null && thumbnailDirectory.containsTag(ExifThumbnailDirectory.TAG_THUMBNAIL_COMPRESSION)) {
     176            Integer offset = thumbnailDirectory.getInteger(ExifThumbnailDirectory.TAG_THUMBNAIL_OFFSET);
     177            Integer length = thumbnailDirectory.getInteger(ExifThumbnailDirectory.TAG_THUMBNAIL_LENGTH);
     178            if (offset != null && length != null) {
     179                try {
     180                    byte[] thumbnailData = reader.getBytes(tiffHeaderOffset + offset, length);
     181                    thumbnailDirectory.setThumbnailData(thumbnailData);
     182                } catch (BufferBoundsException ex) {
     183                    directory.addError("Invalid thumbnail data specification: " + ex.getMessage());
     184                }
     185            }
     186        }
    251187    }
    252188
    253189    /**
    254190     * Process one of the nested Tiff IFD directories.
     191     * <p/>
     192     * Header
    255193     * 2 bytes: number of tags
    256      * for each tag
    257      *   2 bytes: tag type
    258      *   2 bytes: format code
    259      *   4 bytes: component count
     194     * <p/>
     195     * Then for each tag
     196     * 2 bytes: tag type
     197     * 2 bytes: format code
     198     * 4 bytes: component count
    260199     */
    261     private void processDirectory(Directory directory, HashMap processedDirectoryOffsets, int dirStartOffset, int tiffHeaderOffset)
     200    private void processDirectory(@NotNull Directory directory, @NotNull Set<Integer> processedDirectoryOffsets, int dirStartOffset, int tiffHeaderOffset, @NotNull final Metadata metadata, @NotNull final BufferReader reader) throws BufferBoundsException
    262201    {
    263202        // check for directories we've already visited to avoid stack overflows when recursive/cyclic directory structures exist
    264         if (processedDirectoryOffsets.containsKey(new Integer(dirStartOffset)))
     203        if (processedDirectoryOffsets.contains(Integer.valueOf(dirStartOffset)))
    265204            return;
    266205
    267206        // remember that we've visited this directory so that we don't visit it again later
    268         processedDirectoryOffsets.put(new Integer(dirStartOffset), "processed");
    269 
    270         if (dirStartOffset>=_data.length || dirStartOffset<0) {
    271             directory.addError("Ignored directory marked to start outside data segement");
    272             return;
    273         }
    274 
    275         if (!isDirectoryLengthValid(dirStartOffset, tiffHeaderOffset)) {
     207        processedDirectoryOffsets.add(dirStartOffset);
     208
     209        if (dirStartOffset >= reader.getLength() || dirStartOffset < 0) {
     210            directory.addError("Ignored directory marked to start outside data segment");
     211            return;
     212        }
     213
     214        // First two bytes in the IFD are the number of tags in this directory
     215        int dirTagCount = reader.getUInt16(dirStartOffset);
     216
     217        int dirLength = (2 + (12 * dirTagCount) + 4);
     218        if (dirLength + dirStartOffset > reader.getLength()) {
    276219            directory.addError("Illegally sized directory");
    277220            return;
    278221        }
    279222
    280         // First two bytes in the IFD are the number of tags in this directory
    281         int dirTagCount = get16Bits(dirStartOffset);
    282 
    283223        // Handle each tag in this directory
    284         for (int tagNumber = 0; tagNumber<dirTagCount; tagNumber++)
    285         {
     224        for (int tagNumber = 0; tagNumber < dirTagCount; tagNumber++) {
    286225            final int tagOffset = calculateTagOffset(dirStartOffset, tagNumber);
    287226
    288227            // 2 bytes for the tag type
    289             final int tagType = get16Bits(tagOffset);
     228            final int tagType = reader.getUInt16(tagOffset);
    290229
    291230            // 2 bytes for the format code
    292             final int formatCode = get16Bits(tagOffset + 2);
    293             if (formatCode<1 || formatCode>MAX_FORMAT_CODE) {
    294                 directory.addError("Invalid format code: " + formatCode);
    295                 continue;
     231            final int formatCode = reader.getUInt16(tagOffset + 2);
     232            if (formatCode < 1 || formatCode > MAX_FORMAT_CODE) {
     233                // This error suggests that we are processing at an incorrect index and will generate
     234                // rubbish until we go out of bounds (which may be a while).  Exit now.
     235                directory.addError("Invalid TIFF tag format code: " + formatCode);
     236                return;
    296237            }
    297238
    298239            // 4 bytes dictate the number of components in this tag's data
    299             final int componentCount = get32Bits(tagOffset + 4);
    300             if (componentCount<0) {
    301                 directory.addError("Negative component count in EXIF");
     240            final int componentCount = reader.getInt32(tagOffset + 4);
     241            if (componentCount < 0) {
     242                directory.addError("Negative TIFF tag component count");
    302243                continue;
    303244            }
    304245            // each component may have more than one byte... calculate the total number of bytes
    305246            final int byteCount = componentCount * BYTES_PER_FORMAT[formatCode];
    306             final int tagValueOffset = calculateTagValueOffset(byteCount, tagOffset, tiffHeaderOffset);
    307             if (tagValueOffset<0 || tagValueOffset > _data.length) {
    308                 directory.addError("Illegal pointer offset value in EXIF");
     247            final int tagValueOffset;
     248            if (byteCount > 4) {
     249                // If it's bigger than 4 bytes, the dir entry contains an offset.
     250                // dirEntryOffset must be passed, as some makernote implementations (e.g. FujiFilm) incorrectly use an
     251                // offset relative to the start of the makernote itself, not the TIFF segment.
     252                final int offsetVal = reader.getInt32(tagOffset + 8);
     253                if (offsetVal + byteCount > reader.getLength()) {
     254                    // Bogus pointer offset and / or byteCount value
     255                    directory.addError("Illegal TIFF tag pointer offset");
     256                    continue;
     257                }
     258                tagValueOffset = tiffHeaderOffset + offsetVal;
     259            } else {
     260                // 4 bytes or less and value is in the dir entry itself
     261                tagValueOffset = tagOffset + 8;
     262            }
     263
     264            if (tagValueOffset < 0 || tagValueOffset > reader.getLength()) {
     265                directory.addError("Illegal TIFF tag pointer offset");
    309266                continue;
    310267            }
     
    312269            // Check that this tag isn't going to allocate outside the bounds of the data array.
    313270            // This addresses an uncommon OutOfMemoryError.
    314             if (byteCount < 0 || tagValueOffset + byteCount > _data.length)
    315             {
     271            if (byteCount < 0 || tagValueOffset + byteCount > reader.getLength()) {
    316272                directory.addError("Illegal number of bytes: " + byteCount);
    317273                continue;
    318274            }
    319275
    320             // Calculate the value as an offset for cases where the tag represents directory
    321             final int subdirOffset = tiffHeaderOffset + get32Bits(tagValueOffset);
    322 
    323276            switch (tagType) {
    324                 case TAG_EXIF_OFFSET:
    325                     processDirectory(_metadata.getDirectory(ExifDirectory.class), processedDirectoryOffsets, subdirOffset, tiffHeaderOffset);
     277                case TAG_EXIF_SUB_IFD_OFFSET: {
     278                    final int subdirOffset = tiffHeaderOffset + reader.getInt32(tagValueOffset);
     279                    processDirectory(metadata.getOrCreateDirectory(ExifSubIFDDirectory.class), processedDirectoryOffsets, subdirOffset, tiffHeaderOffset, metadata, reader);
    326280                    continue;
    327                 case TAG_INTEROP_OFFSET:
    328                     processDirectory(_metadata.getDirectory(ExifInteropDirectory.class), processedDirectoryOffsets, subdirOffset, tiffHeaderOffset);
     281                }
     282                case TAG_INTEROP_OFFSET: {
     283                    final int subdirOffset = tiffHeaderOffset + reader.getInt32(tagValueOffset);
     284                    processDirectory(metadata.getOrCreateDirectory(ExifInteropDirectory.class), processedDirectoryOffsets, subdirOffset, tiffHeaderOffset, metadata, reader);
    329285                    continue;
    330                 case TAG_GPS_INFO_OFFSET:
    331                     processDirectory(_metadata.getDirectory(GpsDirectory.class), processedDirectoryOffsets, subdirOffset, tiffHeaderOffset);
     286                }
     287                case TAG_GPS_INFO_OFFSET: {
     288                    final int subdirOffset = tiffHeaderOffset + reader.getInt32(tagValueOffset);
     289                    processDirectory(metadata.getOrCreateDirectory(GpsDirectory.class), processedDirectoryOffsets, subdirOffset, tiffHeaderOffset, metadata, reader);
    332290                    continue;
    333                 case TAG_MAKER_NOTE:
    334                     processMakerNote(tagValueOffset, processedDirectoryOffsets, tiffHeaderOffset);
     291                }
     292                case TAG_MAKER_NOTE_OFFSET: {
     293                    processMakerNote(tagValueOffset, processedDirectoryOffsets, tiffHeaderOffset, metadata, reader);
    335294                    continue;
    336                 default:
    337                     processTag(directory, tagType, tagValueOffset, componentCount, formatCode);
     295                }
     296                default: {
     297                    processTag(directory, tagType, tagValueOffset, componentCount, formatCode, reader);
    338298                    break;
     299                }
    339300            }
    340301        }
     
    342303        // at the end of each IFD is an optional link to the next IFD
    343304        final int finalTagOffset = calculateTagOffset(dirStartOffset, dirTagCount);
    344         int nextDirectoryOffset = get32Bits(finalTagOffset);
    345         if (nextDirectoryOffset!=0) {
     305        int nextDirectoryOffset = reader.getInt32(finalTagOffset);
     306        if (nextDirectoryOffset != 0) {
    346307            nextDirectoryOffset += tiffHeaderOffset;
    347             if (nextDirectoryOffset>=_data.length) {
     308            if (nextDirectoryOffset >= reader.getLength()) {
    348309                // Last 4 bytes of IFD reference another IFD with an address that is out of bounds
    349310                // Note this could have been caused by jhead 1.3 cropping too much
     
    353314                return;
    354315            }
    355             // the next directory is of same type as this one
    356             processDirectory(directory, processedDirectoryOffsets, nextDirectoryOffset, tiffHeaderOffset);
    357         }
    358     }
    359 
    360     private void processMakerNote(int subdirOffset, HashMap processedDirectoryOffsets, int tiffHeaderOffset)
     316            // TODO in Exif, the only known 'follower' IFD is the thumbnail one, however this may not be the case
     317            final ExifThumbnailDirectory nextDirectory = metadata.getOrCreateDirectory(ExifThumbnailDirectory.class);
     318            processDirectory(nextDirectory, processedDirectoryOffsets, nextDirectoryOffset, tiffHeaderOffset, metadata, reader);
     319        }
     320    }
     321
     322    private void processMakerNote(int subdirOffset, @NotNull Set<Integer> processedDirectoryOffsets, int tiffHeaderOffset, @NotNull final Metadata metadata, @NotNull BufferReader reader) throws BufferBoundsException
    361323    {
    362324        // Determine the camera model and makernote format
    363         Directory exifDirectory = _metadata.getDirectory(ExifDirectory.class);
    364 
    365         if (exifDirectory==null)
    366             return;
    367 
    368         String cameraModel = exifDirectory.getString(ExifDirectory.TAG_MAKE);
    369         final String firstTwoChars = new String(_data, subdirOffset, 2);
    370         final String firstThreeChars = new String(_data, subdirOffset, 3);
    371         final String firstFourChars = new String(_data, subdirOffset, 4);
    372         final String firstFiveChars = new String(_data, subdirOffset, 5);
    373         final String firstSixChars = new String(_data, subdirOffset, 6);
    374         final String firstSevenChars = new String(_data, subdirOffset, 7);
    375         final String firstEightChars = new String(_data, subdirOffset, 8);
    376         if ("OLYMP".equals(firstFiveChars) || "EPSON".equals(firstFiveChars) || "AGFA".equals(firstFourChars))
    377         {
     325        Directory ifd0Directory = metadata.getDirectory(ExifIFD0Directory.class);
     326
     327        if (ifd0Directory==null)
     328            return;
     329
     330        String cameraModel = ifd0Directory.getString(ExifIFD0Directory.TAG_MAKE);
     331
     332        //final String firstTwoChars = reader.getString(subdirOffset, 2);
     333        final String firstThreeChars = reader.getString(subdirOffset, 3);
     334        final String firstFourChars = reader.getString(subdirOffset, 4);
     335        final String firstFiveChars = reader.getString(subdirOffset, 5);
     336        final String firstSixChars = reader.getString(subdirOffset, 6);
     337        final String firstSevenChars = reader.getString(subdirOffset, 7);
     338        final String firstEightChars = reader.getString(subdirOffset, 8);
     339        final String firstTwelveChars = reader.getString(subdirOffset, 12);
     340
     341        if ("OLYMP".equals(firstFiveChars) || "EPSON".equals(firstFiveChars) || "AGFA".equals(firstFourChars)) {
    378342            // Olympus Makernote
    379             // Epson and Agfa use Olypus maker note standard, see:
    380             //     http://www.ozhiker.com/electronics/pjmt/jpeg_info/
    381             processDirectory(_metadata.getDirectory(OlympusMakernoteDirectory.class), processedDirectoryOffsets, subdirOffset + 8, tiffHeaderOffset);
    382         }
    383         else if (cameraModel!=null && cameraModel.trim().toUpperCase().startsWith("NIKON"))
    384         {
    385             if ("Nikon".equals(firstFiveChars))
    386             {
     343            // Epson and Agfa use Olympus maker note standard: http://www.ozhiker.com/electronics/pjmt/jpeg_info/
     344            processDirectory(metadata.getOrCreateDirectory(OlympusMakernoteDirectory.class), processedDirectoryOffsets, subdirOffset + 8, tiffHeaderOffset, metadata, reader);
     345        } else if (cameraModel != null && cameraModel.trim().toUpperCase().startsWith("NIKON")) {
     346            if ("Nikon".equals(firstFiveChars)) {
    387347                /* There are two scenarios here:
    388348                 * Type 1:                  **
     
    393353                 * :0010: 00 08 00 1E 00 01 00 07-00 00 00 04 30 32 30 30 ............0200
    394354                 */
    395                 if (_data[subdirOffset+6]==1)
    396                     processDirectory(_metadata.getDirectory(NikonType1MakernoteDirectory.class), processedDirectoryOffsets, subdirOffset + 8, tiffHeaderOffset);
    397                 else if (_data[subdirOffset+6]==2)
    398                     processDirectory(_metadata.getDirectory(NikonType2MakernoteDirectory.class), processedDirectoryOffsets, subdirOffset + 18, subdirOffset + 10);
    399                 else
    400                     exifDirectory.addError("Unsupported makernote data ignored.");
    401             }
     355                switch (reader.getUInt8(subdirOffset + 6)) {
     356                    case 1:
     357                        processDirectory(metadata.getOrCreateDirectory(NikonType1MakernoteDirectory.class), processedDirectoryOffsets, subdirOffset + 8, tiffHeaderOffset, metadata, reader);
     358                        break;
     359                    case 2:
     360                        processDirectory(metadata.getOrCreateDirectory(NikonType2MakernoteDirectory.class), processedDirectoryOffsets, subdirOffset + 18, subdirOffset + 10, metadata, reader);
     361                        break;
     362                    default:
     363                        ifd0Directory.addError("Unsupported Nikon makernote data ignored.");
     364                        break;
     365                }
     366            } else {
     367                // The IFD begins with the first MakerNote byte (no ASCII name).  This occurs with CoolPix 775, E990 and D1 models.
     368                processDirectory(metadata.getOrCreateDirectory(NikonType2MakernoteDirectory.class), processedDirectoryOffsets, subdirOffset, tiffHeaderOffset, metadata, reader);
     369            }
     370        } else if ("SONY CAM".equals(firstEightChars) || "SONY DSC".equals(firstEightChars)) {
     371            processDirectory(metadata.getOrCreateDirectory(SonyType1MakernoteDirectory.class), processedDirectoryOffsets, subdirOffset + 12, tiffHeaderOffset, metadata, reader);
     372        } else if ("SIGMA\u0000\u0000\u0000".equals(firstEightChars) || "FOVEON\u0000\u0000".equals(firstEightChars)) {
     373            processDirectory(metadata.getOrCreateDirectory(SigmaMakernoteDirectory.class), processedDirectoryOffsets, subdirOffset + 10, tiffHeaderOffset, metadata, reader);
     374        } else if ("SEMC MS\u0000\u0000\u0000\u0000\u0000".equals(firstTwelveChars)) {
     375            // force MM for this directory
     376            boolean isMotorola = reader.isMotorolaByteOrder();
     377            reader.setMotorolaByteOrder(true);
     378            // skip 12 byte header + 2 for "MM" + 6
     379            processDirectory(metadata.getOrCreateDirectory(SonyType6MakernoteDirectory.class), processedDirectoryOffsets, subdirOffset + 20, tiffHeaderOffset, metadata, reader);
     380            reader.setMotorolaByteOrder(isMotorola);
     381        } else if ("KDK".equals(firstThreeChars)) {
     382            processDirectory(metadata.getOrCreateDirectory(KodakMakernoteDirectory.class), processedDirectoryOffsets, subdirOffset + 20, tiffHeaderOffset, metadata, reader);
     383        } else if ("Canon".equalsIgnoreCase(cameraModel)) {
     384            processDirectory(metadata.getOrCreateDirectory(CanonMakernoteDirectory.class), processedDirectoryOffsets, subdirOffset, tiffHeaderOffset, metadata, reader);
     385        } else if (cameraModel != null && cameraModel.toUpperCase().startsWith("CASIO")) {
     386            if ("QVC\u0000\u0000\u0000".equals(firstSixChars))
     387                processDirectory(metadata.getOrCreateDirectory(CasioType2MakernoteDirectory.class), processedDirectoryOffsets, subdirOffset + 6, tiffHeaderOffset, metadata, reader);
    402388            else
    403             {
    404                 // The IFD begins with the first MakerNote byte (no ASCII name).  This occurs with CoolPix 775, E990 and D1 models.
    405                 processDirectory(_metadata.getDirectory(NikonType2MakernoteDirectory.class), processedDirectoryOffsets, subdirOffset, tiffHeaderOffset);
    406             }
    407         }
    408         else if ("SONY CAM".equals(firstEightChars) || "SONY DSC".equals(firstEightChars))
    409         {
    410             processDirectory(_metadata.getDirectory(SonyMakernoteDirectory.class), processedDirectoryOffsets, subdirOffset + 12, tiffHeaderOffset);
    411         }
    412         else if ("KDK".equals(firstThreeChars))
    413         {
    414             processDirectory(_metadata.getDirectory(KodakMakernoteDirectory.class), processedDirectoryOffsets, subdirOffset + 20, tiffHeaderOffset);
    415         }
    416         else if ("Canon".equalsIgnoreCase(cameraModel))
    417         {
    418             processDirectory(_metadata.getDirectory(CanonMakernoteDirectory.class), processedDirectoryOffsets, subdirOffset, tiffHeaderOffset);
    419         }
    420         else if (cameraModel!=null && cameraModel.toUpperCase().startsWith("CASIO"))
    421         {
    422             if ("QVC\u0000\u0000\u0000".equals(firstSixChars))
    423                 processDirectory(_metadata.getDirectory(CasioType2MakernoteDirectory.class), processedDirectoryOffsets, subdirOffset + 6, tiffHeaderOffset);
    424             else
    425                 processDirectory(_metadata.getDirectory(CasioType1MakernoteDirectory.class), processedDirectoryOffsets, subdirOffset, tiffHeaderOffset);
    426         }
    427         else if ("FUJIFILM".equals(firstEightChars) || "Fujifilm".equalsIgnoreCase(cameraModel))
    428         {
    429             // TODO make this field a passed parameter, to avoid threading issues
    430             boolean byteOrderBefore = _isMotorollaByteOrder;
     389                processDirectory(metadata.getOrCreateDirectory(CasioType1MakernoteDirectory.class), processedDirectoryOffsets, subdirOffset, tiffHeaderOffset, metadata, reader);
     390        } else if ("FUJIFILM".equals(firstEightChars) || "Fujifilm".equalsIgnoreCase(cameraModel)) {
     391            boolean byteOrderBefore = reader.isMotorolaByteOrder();
    431392            // bug in fujifilm makernote ifd means we temporarily use Intel byte ordering
    432             _isMotorollaByteOrder = false;
     393            reader.setMotorolaByteOrder(false);
    433394            // the 4 bytes after "FUJIFILM" in the makernote point to the start of the makernote
    434395            // IFD, though the offset is relative to the start of the makernote, not the TIFF
    435396            // header (like everywhere else)
    436             int ifdStart = subdirOffset + get32Bits(subdirOffset + 8);
    437             processDirectory(_metadata.getDirectory(FujifilmMakernoteDirectory.class), processedDirectoryOffsets, ifdStart, tiffHeaderOffset);
    438             _isMotorollaByteOrder = byteOrderBefore;
    439         }
    440         else if (cameraModel!=null && cameraModel.toUpperCase().startsWith("MINOLTA"))
    441         {
     397            int ifdStart = subdirOffset + reader.getInt32(subdirOffset + 8);
     398            processDirectory(metadata.getOrCreateDirectory(FujifilmMakernoteDirectory.class), processedDirectoryOffsets, ifdStart, tiffHeaderOffset, metadata, reader);
     399            reader.setMotorolaByteOrder(byteOrderBefore);
     400        } else if (cameraModel != null && cameraModel.toUpperCase().startsWith("MINOLTA")) {
    442401            // Cases seen with the model starting with MINOLTA in capitals seem to have a valid Olympus makernote
    443402            // area that commences immediately.
    444             processDirectory(_metadata.getDirectory(OlympusMakernoteDirectory.class), processedDirectoryOffsets, subdirOffset, tiffHeaderOffset);
    445         }
    446         else if ("KC".equals(firstTwoChars) || "MINOL".equals(firstFiveChars) || "MLY".equals(firstThreeChars) || "+M+M+M+M".equals(firstEightChars))
    447         {
    448             // This Konica data is not understood.  Header identified in accordance with information at this site:
    449             // http://www.ozhiker.com/electronics/pjmt/jpeg_info/minolta_mn.html
    450             // TODO determine how to process the information described at the above website
    451             exifDirectory.addError("Unsupported Konica/Minolta data ignored.");
    452         }
    453         else if ("KYOCERA".equals(firstSevenChars))
    454         {
     403            processDirectory(metadata.getOrCreateDirectory(OlympusMakernoteDirectory.class), processedDirectoryOffsets, subdirOffset, tiffHeaderOffset, metadata, reader);
     404        } else if ("KYOCERA".equals(firstSevenChars)) {
    455405            // http://www.ozhiker.com/electronics/pjmt/jpeg_info/kyocera_mn.html
    456             processDirectory(_metadata.getDirectory(KyoceraMakernoteDirectory.class), processedDirectoryOffsets, subdirOffset + 22, tiffHeaderOffset);
    457         }
    458         else if ("Panasonic\u0000\u0000\u0000".equals(new String(_data, subdirOffset, 12)))
    459         {
     406            processDirectory(metadata.getOrCreateDirectory(KyoceraMakernoteDirectory.class), processedDirectoryOffsets, subdirOffset + 22, tiffHeaderOffset, metadata, reader);
     407        } else if ("Panasonic\u0000\u0000\u0000".equals(reader.getString(subdirOffset, 12))) {
    460408            // NON-Standard TIFF IFD Data using Panasonic Tags. There is no Next-IFD pointer after the IFD
    461409            // Offsets are relative to the start of the TIFF header at the beginning of the EXIF segment
    462410            // more information here: http://www.ozhiker.com/electronics/pjmt/jpeg_info/panasonic_mn.html
    463             processDirectory(_metadata.getDirectory(PanasonicMakernoteDirectory.class), processedDirectoryOffsets, subdirOffset + 12, tiffHeaderOffset);
    464         }
    465         else if ("AOC\u0000".equals(firstFourChars))
    466         {
     411            processDirectory(metadata.getOrCreateDirectory(PanasonicMakernoteDirectory.class), processedDirectoryOffsets, subdirOffset + 12, tiffHeaderOffset, metadata, reader);
     412        } else if ("AOC\u0000".equals(firstFourChars)) {
    467413            // NON-Standard TIFF IFD Data using Casio Type 2 Tags
    468414            // IFD has no Next-IFD pointer at end of IFD, and
     
    470416            // Observed for:
    471417            // - Pentax ist D
    472             processDirectory(_metadata.getDirectory(CasioType2MakernoteDirectory.class), processedDirectoryOffsets, subdirOffset + 6, subdirOffset);
    473         }
    474         else if (cameraModel!=null && (cameraModel.toUpperCase().startsWith("PENTAX") || cameraModel.toUpperCase().startsWith("ASAHI")))
    475         {
     418            processDirectory(metadata.getOrCreateDirectory(CasioType2MakernoteDirectory.class), processedDirectoryOffsets, subdirOffset + 6, subdirOffset, metadata, reader);
     419        } else if (cameraModel != null && (cameraModel.toUpperCase().startsWith("PENTAX") || cameraModel.toUpperCase().startsWith("ASAHI"))) {
    476420            // NON-Standard TIFF IFD Data using Pentax Tags
    477421            // IFD has no Next-IFD pointer at end of IFD, and
     
    480424            // - PENTAX Optio 330
    481425            // - PENTAX Optio 430
    482             processDirectory(_metadata.getDirectory(PentaxMakernoteDirectory.class), processedDirectoryOffsets, subdirOffset, subdirOffset);
    483         }
    484         else
    485         {
     426            processDirectory(metadata.getOrCreateDirectory(PentaxMakernoteDirectory.class), processedDirectoryOffsets, subdirOffset, subdirOffset, metadata, reader);
     427//        } else if ("KC".equals(firstTwoChars) || "MINOL".equals(firstFiveChars) || "MLY".equals(firstThreeChars) || "+M+M+M+M".equals(firstEightChars)) {
     428//            // This Konica data is not understood.  Header identified in accordance with information at this site:
     429//            // http://www.ozhiker.com/electronics/pjmt/jpeg_info/minolta_mn.html
     430//            // TODO add support for minolta/konica cameras
     431//            exifDirectory.addError("Unsupported Konica/Minolta data ignored.");
     432        } else {
    486433            // TODO how to store makernote data when it's not from a supported camera model?
    487434            // this is difficult as the starting offset is not known.  we could look for it...
    488             exifDirectory.addError("Unsupported makernote data ignored.");
    489         }
    490     }
    491 
    492     private boolean isDirectoryLengthValid(int dirStartOffset, int tiffHeaderOffset)
    493     {
    494         int dirTagCount = get16Bits(dirStartOffset);
    495         int dirLength = (2 + (12 * dirTagCount) + 4);
    496         if (dirLength + dirStartOffset + tiffHeaderOffset>=_data.length) {
    497             // Note: Files that had thumbnails trimmed with jhead 1.3 or earlier might trigger this
    498             return false;
    499         }
    500         return true;
    501     }
    502 
    503     private void processTag(Directory directory, int tagType, int tagValueOffset, int componentCount, int formatCode)
     435        }
     436    }
     437
     438    private void processTag(@NotNull Directory directory, int tagType, int tagValueOffset, int componentCount, int formatCode, @NotNull final BufferReader reader) throws BufferBoundsException
    504439    {
    505440        // Directory simply stores raw values
    506441        // The display side uses a Descriptor class per directory to turn the raw values into 'pretty' descriptions
    507         switch (formatCode)
    508         {
     442        switch (formatCode) {
    509443            case FMT_UNDEFINED:
    510444                // this includes exif user comments
    511                 final byte[] tagBytes = new byte[componentCount];
    512                 final int byteCount = componentCount * BYTES_PER_FORMAT[formatCode];
    513                 for (int i=0; i<byteCount; i++)
    514                     tagBytes[i] = _data[tagValueOffset + i];
    515                 directory.setByteArray(tagType, tagBytes);
     445                directory.setByteArray(tagType, reader.getBytes(tagValueOffset, componentCount));
    516446                break;
    517447            case FMT_STRING:
    518                 directory.setString(tagType, readString(tagValueOffset, componentCount));
     448                String string = reader.getNullTerminatedString(tagValueOffset, componentCount);
     449                directory.setString(tagType, string);
     450/*
     451                // special handling for certain known tags, proposed by Yuri Binev but left out for now,
     452                // as it gives the false impression that the image was captured in the same timezone
     453                // in which the string is parsed
     454                if (tagType==ExifSubIFDDirectory.TAG_DATETIME ||
     455                    tagType==ExifSubIFDDirectory.TAG_DATETIME_ORIGINAL ||
     456                    tagType==ExifSubIFDDirectory.TAG_DATETIME_DIGITIZED) {
     457                    String[] datePatterns = {
     458                        "yyyy:MM:dd HH:mm:ss",
     459                        "yyyy:MM:dd HH:mm",
     460                        "yyyy-MM-dd HH:mm:ss",
     461                        "yyyy-MM-dd HH:mm"};
     462                    for (String datePattern : datePatterns) {
     463                        try {
     464                            DateFormat parser = new SimpleDateFormat(datePattern);
     465                            Date date = parser.parse(string);
     466                            directory.setDate(tagType, date);
     467                            break;
     468                        } catch (ParseException ex) {
     469                            // simply try the next pattern
     470                        }
     471                    }
     472                }
     473*/
    519474                break;
    520475            case FMT_SRATIONAL:
     476                if (componentCount == 1) {
     477                    directory.setRational(tagType, new Rational(reader.getInt32(tagValueOffset), reader.getInt32(tagValueOffset + 4)));
     478                } else if (componentCount > 1) {
     479                    Rational[] rationals = new Rational[componentCount];
     480                    for (int i = 0; i < componentCount; i++)
     481                        rationals[i] = new Rational(reader.getInt32(tagValueOffset + (8 * i)), reader.getInt32(tagValueOffset + 4 + (8 * i)));
     482                    directory.setRationalArray(tagType, rationals);
     483                }
     484                break;
    521485            case FMT_URATIONAL:
    522                 if (componentCount==1) {
    523                     Rational rational = new Rational(get32Bits(tagValueOffset), get32Bits(tagValueOffset + 4));
    524                     directory.setRational(tagType, rational);
    525                 } else {
     486                if (componentCount == 1) {
     487                    directory.setRational(tagType, new Rational(reader.getUInt32(tagValueOffset), reader.getUInt32(tagValueOffset + 4)));
     488                } else if (componentCount > 1) {
    526489                    Rational[] rationals = new Rational[componentCount];
    527                     for (int i = 0; i<componentCount; i++)
    528                         rationals[i] = new Rational(get32Bits(tagValueOffset + (8 * i)), get32Bits(tagValueOffset + 4 + (8 * i)));
     490                    for (int i = 0; i < componentCount; i++)
     491                        rationals[i] = new Rational(reader.getUInt32(tagValueOffset + (8 * i)), reader.getUInt32(tagValueOffset + 4 + (8 * i)));
    529492                    directory.setRationalArray(tagType, rationals);
    530493                }
    531494                break;
     495            case FMT_SINGLE:
     496                if (componentCount == 1) {
     497                    directory.setFloat(tagType, reader.getFloat32(tagValueOffset));
     498                } else {
     499                    float[] floats = new float[componentCount];
     500                    for (int i = 0; i < componentCount; i++)
     501                        floats[i] = reader.getFloat32(tagValueOffset + (i * 4));
     502                    directory.setFloatArray(tagType, floats);
     503                }
     504                break;
     505            case FMT_DOUBLE:
     506                if (componentCount == 1) {
     507                    directory.setDouble(tagType, reader.getDouble64(tagValueOffset));
     508                } else {
     509                    double[] doubles = new double[componentCount];
     510                    for (int i = 0; i < componentCount; i++)
     511                        doubles[i] = reader.getDouble64(tagValueOffset + (i * 4));
     512                    directory.setDoubleArray(tagType, doubles);
     513                }
     514                break;
     515
     516            //
     517            // Note that all integral types are stored as int32 internally (the largest supported by TIFF)
     518            //
     519
    532520            case FMT_SBYTE:
     521                if (componentCount == 1) {
     522                    directory.setInt(tagType, reader.getInt8(tagValueOffset));
     523                } else {
     524                    int[] bytes = new int[componentCount];
     525                    for (int i = 0; i < componentCount; i++)
     526                        bytes[i] = reader.getInt8(tagValueOffset + i);
     527                    directory.setIntArray(tagType, bytes);
     528                }
     529                break;
    533530            case FMT_BYTE:
    534                 if (componentCount==1) {
    535                     // this may need to be a byte, but I think casting to int is fine
    536                     int b = _data[tagValueOffset];
    537                     directory.setInt(tagType, b);
     531                if (componentCount == 1) {
     532                    directory.setInt(tagType, reader.getUInt8(tagValueOffset));
    538533                } else {
    539534                    int[] bytes = new int[componentCount];
    540                     for (int i = 0; i<componentCount; i++)
    541                         bytes[i] = _data[tagValueOffset + i];
     535                    for (int i = 0; i < componentCount; i++)
     536                        bytes[i] = reader.getUInt8(tagValueOffset + i);
    542537                    directory.setIntArray(tagType, bytes);
    543538                }
    544539                break;
    545             case FMT_SINGLE:
    546             case FMT_DOUBLE:
    547                 if (componentCount==1) {
    548                     int i = _data[tagValueOffset];
     540            case FMT_USHORT:
     541                if (componentCount == 1) {
     542                    int i = reader.getUInt16(tagValueOffset);
    549543                    directory.setInt(tagType, i);
    550544                } else {
    551545                    int[] ints = new int[componentCount];
    552                     for (int i = 0; i<componentCount; i++)
    553                         ints[i] = _data[tagValueOffset + i];
     546                    for (int i = 0; i < componentCount; i++)
     547                        ints[i] = reader.getUInt16(tagValueOffset + (i * 2));
    554548                    directory.setIntArray(tagType, ints);
    555549                }
    556550                break;
    557             case FMT_USHORT:
    558551            case FMT_SSHORT:
    559                 if (componentCount==1) {
    560                     int i = get16Bits(tagValueOffset);
     552                if (componentCount == 1) {
     553                    int i = reader.getInt16(tagValueOffset);
    561554                    directory.setInt(tagType, i);
    562555                } else {
    563556                    int[] ints = new int[componentCount];
    564                     for (int i = 0; i<componentCount; i++)
    565                         ints[i] = get16Bits(tagValueOffset + (i * 2));
     557                    for (int i = 0; i < componentCount; i++)
     558                        ints[i] = reader.getInt16(tagValueOffset + (i * 2));
    566559                    directory.setIntArray(tagType, ints);
    567560                }
     
    569562            case FMT_SLONG:
    570563            case FMT_ULONG:
    571                 if (componentCount==1) {
    572                     int i = get32Bits(tagValueOffset);
     564                // NOTE 'long' in this case means 32 bit, not 64
     565                if (componentCount == 1) {
     566                    int i = reader.getInt32(tagValueOffset);
    573567                    directory.setInt(tagType, i);
    574568                } else {
    575569                    int[] ints = new int[componentCount];
    576                     for (int i = 0; i<componentCount; i++)
    577                         ints[i] = get32Bits(tagValueOffset + (i * 4));
     570                    for (int i = 0; i < componentCount; i++)
     571                        ints[i] = reader.getInt32(tagValueOffset + (i * 4));
    578572                    directory.setIntArray(tagType, ints);
    579573                }
     
    584578    }
    585579
    586     private int calculateTagValueOffset(int byteCount, int dirEntryOffset, int tiffHeaderOffset)
    587     {
    588         if (byteCount>4) {
    589             // If its bigger than 4 bytes, the dir entry contains an offset.
    590             // dirEntryOffset must be passed, as some makernote implementations (e.g. FujiFilm) incorrectly use an
    591             // offset relative to the start of the makernote itself, not the TIFF segment.
    592             final int offsetVal = get32Bits(dirEntryOffset + 8);
    593             if (offsetVal + byteCount>_data.length) {
    594                 // Bogus pointer offset and / or bytecount value
    595                 return -1; // signal error
    596             }
    597             return tiffHeaderOffset + offsetVal;
    598         } else {
    599             // 4 bytes or less and value is in the dir entry itself
    600             return dirEntryOffset + 8;
    601         }
    602     }
    603 
    604     /**
    605      * Creates a String from the _data buffer starting at the specified offset,
    606      * and ending where byte=='\0' or where length==maxLength.
    607      */
    608     private String readString(int offset, int maxLength)
    609     {
    610         int length = 0;
    611         while ((offset + length)<_data.length && _data[offset + length]!='\0' && length<maxLength)
    612             length++;
    613 
    614         return new String(_data, offset, length);
    615     }
    616 
    617580    /**
    618581     * Determine the offset at which a given InteropArray entry begins within the specified IFD.
     582     *
    619583     * @param dirStartOffset the offset at which the IFD starts
    620      * @param entryNumber the zero-based entry number
     584     * @param entryNumber    the zero-based entry number
    621585     */
    622586    private int calculateTagOffset(int dirStartOffset, int entryNumber)
     
    626590        return dirStartOffset + 2 + (12 * entryNumber);
    627591    }
    628 
    629     /**
    630      * Get a 16 bit value from file's native byte order.  Between 0x0000 and 0xFFFF.
    631      */
    632     private int get16Bits(int offset)
    633     {
    634         if (offset<0 || offset+2>_data.length)
    635             throw new ArrayIndexOutOfBoundsException("attempt to read data outside of exif segment (index " + offset + " where max index is " + (_data.length - 1) + ")");
    636 
    637         if (_isMotorollaByteOrder) {
    638             // Motorola - MSB first
    639             return (_data[offset] << 8 & 0xFF00) | (_data[offset + 1] & 0xFF);
    640         } else {
    641             // Intel ordering - LSB first
    642             return (_data[offset + 1] << 8 & 0xFF00) | (_data[offset] & 0xFF);
    643         }
    644     }
    645 
    646     /**
    647      * Get a 32 bit value from file's native byte order.
    648      */
    649     private int get32Bits(int offset)
    650     {
    651         if (offset<0 || offset+4>_data.length)
    652             throw new ArrayIndexOutOfBoundsException("attempt to read data outside of exif segment (index " + offset + " where max index is " + (_data.length - 1) + ")");
    653 
    654         if (_isMotorollaByteOrder) {
    655             // Motorola - MSB first
    656             return (_data[offset] << 24 & 0xFF000000) |
    657                     (_data[offset + 1] << 16 & 0xFF0000) |
    658                     (_data[offset + 2] << 8 & 0xFF00) |
    659                     (_data[offset + 3] & 0xFF);
    660         } else {
    661             // Intel ordering - LSB first
    662             return (_data[offset + 3] << 24 & 0xFF000000) |
    663                     (_data[offset + 2] << 16 & 0xFF0000) |
    664                     (_data[offset + 1] << 8 & 0xFF00) |
    665                     (_data[offset] & 0xFF);
    666         }
    667     }
    668592}
Note: See TracChangeset for help on using the changeset viewer.