Changeset 8132 in josm for trunk/src/com/drew/metadata/exif


Ignore:
Timestamp:
2015-03-10T01:17:39+01:00 (7 years ago)
Author:
Don-vip
Message:

fix #11162 - update to metadata-extractor 2.7.2

Location:
trunk/src/com/drew/metadata/exif
Files:
38 added
29 deleted
11 edited

Legend:

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

    r6127 r8132  
    11/*
    2  * Copyright 2002-2012 Drew Noakes
     2 * Copyright 2002-2015 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    1616 * More information about this project is available at:
    1717 *
    18  *    http://drewnoakes.com/code/exif/
    19  *    http://code.google.com/p/metadata-extractor/
     18 *    https://drewnoakes.com/code/exif/
     19 *    https://github.com/drewnoakes/metadata-extractor
    2020 */
    2121
     
    2929import java.io.UnsupportedEncodingException;
    3030
     31import static com.drew.metadata.exif.ExifIFD0Directory.*;
     32
    3133/**
    32  * Provides human-readable string representations of tag values stored in a <code>ExifIFD0Directory</code>.
    33  *
    34  * @author Drew Noakes http://drewnoakes.com
     34 * Provides human-readable string representations of tag values stored in a {@link ExifIFD0Directory}.
     35 *
     36 * @author Drew Noakes https://drewnoakes.com
    3537 */
    3638public class ExifIFD0Descriptor extends TagDescriptor<ExifIFD0Directory>
     
    5456
    5557    /**
    56      * Returns a descriptive value of the the specified tag for this image.
     58     * Returns a descriptive value of the specified tag for this image.
    5759     * Where possible, known values will be substituted here in place of the raw
    5860     * tokens actually kept in the Exif segment.  If no substitution is
     
    6264     *         <code>null</code> if the tag hasn't been defined.
    6365     */
     66    @Override
    6467    @Nullable
    6568    public String getDescription(int tagType)
    6669    {
    6770        switch (tagType) {
    68             case ExifIFD0Directory.TAG_RESOLUTION_UNIT:
     71            case TAG_RESOLUTION_UNIT:
    6972                return getResolutionDescription();
    70             case ExifIFD0Directory.TAG_YCBCR_POSITIONING:
     73            case TAG_YCBCR_POSITIONING:
    7174                return getYCbCrPositioningDescription();
    72             case ExifIFD0Directory.TAG_X_RESOLUTION:
     75            case TAG_X_RESOLUTION:
    7376                return getXResolutionDescription();
    74             case ExifIFD0Directory.TAG_Y_RESOLUTION:
     77            case TAG_Y_RESOLUTION:
    7578                return getYResolutionDescription();
    76             case ExifIFD0Directory.TAG_REFERENCE_BLACK_WHITE:
     79            case TAG_REFERENCE_BLACK_WHITE:
    7780                return getReferenceBlackWhiteDescription();
    78             case ExifIFD0Directory.TAG_ORIENTATION:
     81            case TAG_ORIENTATION:
    7982                return getOrientationDescription();
    8083
    81             case ExifIFD0Directory.TAG_WIN_AUTHOR:
     84            case TAG_WIN_AUTHOR:
    8285               return getWindowsAuthorDescription();
    83             case ExifIFD0Directory.TAG_WIN_COMMENT:
     86            case TAG_WIN_COMMENT:
    8487               return getWindowsCommentDescription();
    85             case ExifIFD0Directory.TAG_WIN_KEYWORDS:
     88            case TAG_WIN_KEYWORDS:
    8689               return getWindowsKeywordsDescription();
    87             case ExifIFD0Directory.TAG_WIN_SUBJECT:
     90            case TAG_WIN_SUBJECT:
    8891               return getWindowsSubjectDescription();
    89             case ExifIFD0Directory.TAG_WIN_TITLE:
     92            case TAG_WIN_TITLE:
    9093               return getWindowsTitleDescription();
    9194
     
    98101    public String getReferenceBlackWhiteDescription()
    99102    {
    100         int[] ints = _directory.getIntArray(ExifIFD0Directory.TAG_REFERENCE_BLACK_WHITE);
    101         if (ints==null)
     103        int[] ints = _directory.getIntArray(TAG_REFERENCE_BLACK_WHITE);
     104        if (ints==null || ints.length < 6)
    102105            return null;
    103106        int blackR = ints[0];
     
    107110        int blackB = ints[4];
    108111        int whiteB = ints[5];
    109         return "[" + blackR + "," + blackG + "," + blackB + "] " +
    110                "[" + whiteR + "," + whiteG + "," + whiteB + "]";
     112        return String.format("[%d,%d,%d] [%d,%d,%d]", blackR, blackG, blackB, whiteR, whiteG, whiteB);
    111113    }
    112114
     
    114116    public String getYResolutionDescription()
    115117    {
    116         Rational value = _directory.getRational(ExifIFD0Directory.TAG_Y_RESOLUTION);
     118        Rational value = _directory.getRational(TAG_Y_RESOLUTION);
    117119        if (value==null)
    118120            return null;
    119121        final String unit = getResolutionDescription();
    120         return value.toSimpleString(_allowDecimalRepresentationOfRationals) +
    121                 " dots per " +
    122                 (unit==null ? "unit" : unit.toLowerCase());
     122        return String.format("%s dots per %s",
     123            value.toSimpleString(_allowDecimalRepresentationOfRationals),
     124            unit == null ? "unit" : unit.toLowerCase());
    123125    }
    124126
     
    126128    public String getXResolutionDescription()
    127129    {
    128         Rational value = _directory.getRational(ExifIFD0Directory.TAG_X_RESOLUTION);
     130        Rational value = _directory.getRational(TAG_X_RESOLUTION);
    129131        if (value==null)
    130132            return null;
    131133        final String unit = getResolutionDescription();
    132         return value.toSimpleString(_allowDecimalRepresentationOfRationals) +
    133                 " dots per " +
    134                 (unit==null ? "unit" : unit.toLowerCase());
     134        return String.format("%s dots per %s",
     135            value.toSimpleString(_allowDecimalRepresentationOfRationals),
     136            unit == null ? "unit" : unit.toLowerCase());
    135137    }
    136138
     
    138140    public String getYCbCrPositioningDescription()
    139141    {
    140         Integer value = _directory.getInteger(ExifIFD0Directory.TAG_YCBCR_POSITIONING);
    141         if (value==null)
    142             return null;
    143         switch (value) {
    144             case 1: return "Center of pixel array";
    145             case 2: return "Datum point";
    146             default:
    147                 return String.valueOf(value);
     142        return getIndexedDescription(TAG_YCBCR_POSITIONING, 1, "Center of pixel array", "Datum point");
     143    }
     144
     145    @Nullable
     146    public String getOrientationDescription()
     147    {
     148        return getIndexedDescription(TAG_ORIENTATION, 1,
     149            "Top, left side (Horizontal / normal)",
     150            "Top, right side (Mirror horizontal)",
     151            "Bottom, right side (Rotate 180)",
     152            "Bottom, left side (Mirror vertical)",
     153            "Left side, top (Mirror horizontal and rotate 270 CW)",
     154            "Right side, top (Rotate 90 CW)",
     155            "Right side, bottom (Mirror horizontal and rotate 90 CW)",
     156            "Left side, bottom (Rotate 270 CW)");
     157    }
     158
     159    @Nullable
     160    public String getResolutionDescription()
     161    {
     162        // '1' means no-unit, '2' means inch, '3' means centimeter. Default value is '2'(inch)
     163        return getIndexedDescription(TAG_RESOLUTION_UNIT, 1, "(No unit)", "Inch", "cm");
     164    }
     165
     166    /** The Windows specific tags uses plain Unicode. */
     167    @Nullable
     168    private String getUnicodeDescription(int tag)
     169    {
     170        byte[] bytes = _directory.getByteArray(tag);
     171        if (bytes == null)
     172            return null;
     173        try {
     174            // Decode the unicode string and trim the unicode zero "\0" from the end.
     175            return new String(bytes, "UTF-16LE").trim();
     176        } catch (UnsupportedEncodingException ex) {
     177            return null;
    148178        }
    149179    }
    150180
    151181    @Nullable
    152     public String getOrientationDescription()
    153     {
    154         Integer value = _directory.getInteger(ExifIFD0Directory.TAG_ORIENTATION);
    155         if (value==null)
    156             return null;
    157         switch (value) {
    158             case 1: return "Top, left side (Horizontal / normal)";
    159             case 2: return "Top, right side (Mirror horizontal)";
    160             case 3: return "Bottom, right side (Rotate 180)";
    161             case 4: return "Bottom, left side (Mirror vertical)";
    162             case 5: return "Left side, top (Mirror horizontal and rotate 270 CW)";
    163             case 6: return "Right side, top (Rotate 90 CW)";
    164             case 7: return "Right side, bottom (Mirror horizontal and rotate 90 CW)";
    165             case 8: return "Left side, bottom (Rotate 270 CW)";
    166             default:
    167                 return String.valueOf(value);
    168         }
    169     }
    170 
    171     @Nullable
    172     public String getResolutionDescription()
    173     {
    174         // '1' means no-unit, '2' means inch, '3' means centimeter. Default value is '2'(inch)
    175         Integer value = _directory.getInteger(ExifIFD0Directory.TAG_RESOLUTION_UNIT);
    176         if (value==null)
    177             return null;
    178         switch (value) {
    179             case 1: return "(No unit)";
    180             case 2: return "Inch";
    181             case 3: return "cm";
    182             default:
    183                 return "";
    184         }
    185     }
    186 
    187     /** The Windows specific tags uses plain Unicode. */
    188     @Nullable
    189     private String getUnicodeDescription(int tag)
    190     {
    191          byte[] commentBytes = _directory.getByteArray(tag);
    192         if (commentBytes==null)
    193             return null;
    194          try {
    195              // Decode the unicode string and trim the unicode zero "\0" from the end.
    196             return new String(commentBytes, "UTF-16LE").trim();
    197          }
    198          catch (UnsupportedEncodingException ex) {
    199             return null;
    200          }
    201     }
    202 
    203     @Nullable
    204182    public String getWindowsAuthorDescription()
    205183    {
    206        return getUnicodeDescription(ExifIFD0Directory.TAG_WIN_AUTHOR);
     184       return getUnicodeDescription(TAG_WIN_AUTHOR);
    207185    }
    208186
     
    210188    public String getWindowsCommentDescription()
    211189    {
    212        return getUnicodeDescription(ExifIFD0Directory.TAG_WIN_COMMENT);
     190       return getUnicodeDescription(TAG_WIN_COMMENT);
    213191    }
    214192
     
    216194    public String getWindowsKeywordsDescription()
    217195    {
    218        return getUnicodeDescription(ExifIFD0Directory.TAG_WIN_KEYWORDS);
     196       return getUnicodeDescription(TAG_WIN_KEYWORDS);
    219197    }
    220198
     
    222200    public String getWindowsTitleDescription()
    223201    {
    224        return getUnicodeDescription(ExifIFD0Directory.TAG_WIN_TITLE);
     202       return getUnicodeDescription(TAG_WIN_TITLE);
    225203    }
    226204
     
    228206    public String getWindowsSubjectDescription()
    229207    {
    230        return getUnicodeDescription(ExifIFD0Directory.TAG_WIN_SUBJECT);
     208       return getUnicodeDescription(TAG_WIN_SUBJECT);
    231209    }
    232210}
  • trunk/src/com/drew/metadata/exif/ExifIFD0Directory.java

    r6127 r8132  
    11/*
    2  * Copyright 2002-2012 Drew Noakes
     2 * Copyright 2002-2015 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    1616 * More information about this project is available at:
    1717 *
    18  *    http://drewnoakes.com/code/exif/
    19  *    http://code.google.com/p/metadata-extractor/
     18 *    https://drewnoakes.com/code/exif/
     19 *    https://github.com/drewnoakes/metadata-extractor
    2020 */
    2121
     
    3030 * Describes Exif tags from the IFD0 directory.
    3131 *
    32  * @author Drew Noakes http://drewnoakes.com
     32 * @author Drew Noakes https://drewnoakes.com
    3333 */
    3434public class ExifIFD0Directory extends Directory
     
    4646    public static final int TAG_WHITE_POINT = 0x013E;
    4747    public static final int TAG_PRIMARY_CHROMATICITIES = 0x013F;
     48
    4849    public static final int TAG_YCBCR_COEFFICIENTS = 0x0211;
    4950    public static final int TAG_YCBCR_POSITIONING = 0x0213;
    5051    public static final int TAG_REFERENCE_BLACK_WHITE = 0x0214;
     52
     53
     54    /** This tag is a pointer to the Exif SubIFD. */
     55    public static final int TAG_EXIF_SUB_IFD_OFFSET = 0x8769;
     56
     57    /** This tag is a pointer to the Exif GPS IFD. */
     58    public static final int TAG_GPS_INFO_OFFSET = 0x8825;
     59
    5160    public static final int TAG_COPYRIGHT = 0x8298;
     61
     62    /** Non-standard, but in use. */
     63    public static final int TAG_TIME_ZONE_OFFSET = 0x882a;
    5264
    5365    /** The image title, as used by Windows XP. */
     
    8294        _tagNameMap.put(TAG_YCBCR_POSITIONING, "YCbCr Positioning");
    8395        _tagNameMap.put(TAG_REFERENCE_BLACK_WHITE, "Reference Black/White");
     96
    8497        _tagNameMap.put(TAG_COPYRIGHT, "Copyright");
     98
     99        _tagNameMap.put(TAG_TIME_ZONE_OFFSET, "Time Zone Offset");
    85100
    86101        _tagNameMap.put(TAG_WIN_AUTHOR, "Windows XP Author");
     
    96111    }
    97112
     113    @Override
    98114    @NotNull
    99115    public String getName()
     
    102118    }
    103119
     120    @Override
    104121    @NotNull
    105122    protected HashMap<Integer, String> getTagNameMap()
  • trunk/src/com/drew/metadata/exif/ExifInteropDescriptor.java

    r6127 r8132  
    11/*
    2  * Copyright 2002-2012 Drew Noakes
     2 * Copyright 2002-2015 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    1616 * More information about this project is available at:
    1717 *
    18  *    http://drewnoakes.com/code/exif/
    19  *    http://code.google.com/p/metadata-extractor/
     18 *    https://drewnoakes.com/code/exif/
     19 *    https://github.com/drewnoakes/metadata-extractor
    2020 */
    2121package com.drew.metadata.exif;
     
    2525import com.drew.metadata.TagDescriptor;
    2626
     27import static com.drew.metadata.exif.ExifInteropDirectory.*;
     28
    2729/**
    28  * Provides human-readable string representations of tag values stored in a <code>ExifInteropDirectory</code>.
     30 * Provides human-readable string representations of tag values stored in a {@link ExifInteropDirectory}.
    2931 *
    30  * @author Drew Noakes http://drewnoakes.com
     32 * @author Drew Noakes https://drewnoakes.com
    3133 */
    3234public class ExifInteropDescriptor extends TagDescriptor<ExifInteropDirectory>
     
    3739    }
    3840
     41    @Override
    3942    @Nullable
    4043    public String getDescription(int tagType)
    4144    {
    4245        switch (tagType) {
    43             case ExifInteropDirectory.TAG_INTEROP_INDEX:
     46            case TAG_INTEROP_INDEX:
    4447                return getInteropIndexDescription();
    45             case ExifInteropDirectory.TAG_INTEROP_VERSION:
     48            case TAG_INTEROP_VERSION:
    4649                return getInteropVersionDescription();
    4750            default:
     
    5356    public String getInteropVersionDescription()
    5457    {
    55         int[] ints = _directory.getIntArray(ExifInteropDirectory.TAG_INTEROP_VERSION);
    56         return convertBytesToVersionString(ints, 2);
     58        return getVersionBytesDescription(TAG_INTEROP_VERSION, 2);
    5759    }
    5860
     
    6062    public String getInteropIndexDescription()
    6163    {
    62         String value = _directory.getString(ExifInteropDirectory.TAG_INTEROP_INDEX);
     64        String value = _directory.getString(TAG_INTEROP_INDEX);
    6365
    64         if (value==null)
     66        if (value == null)
    6567            return null;
    6668
  • trunk/src/com/drew/metadata/exif/ExifInteropDirectory.java

    r6127 r8132  
    11/*
    2  * Copyright 2002-2012 Drew Noakes
     2 * Copyright 2002-2015 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    1616 * More information about this project is available at:
    1717 *
    18  *    http://drewnoakes.com/code/exif/
    19  *    http://code.google.com/p/metadata-extractor/
     18 *    https://drewnoakes.com/code/exif/
     19 *    https://github.com/drewnoakes/metadata-extractor
    2020 */
    2121package com.drew.metadata.exif;
     
    2929 * Describes Exif interoperability tags.
    3030 *
    31  * @author Drew Noakes http://drewnoakes.com
     31 * @author Drew Noakes https://drewnoakes.com
    3232 */
    3333public class ExifInteropDirectory extends Directory
     
    5656    }
    5757
     58    @Override
    5859    @NotNull
    5960    public String getName()
     
    6263    }
    6364
     65    @Override
    6466    @NotNull
    6567    protected HashMap<Integer, String> getTagNameMap()
  • trunk/src/com/drew/metadata/exif/ExifReader.java

    r6209 r8132  
    11/*
    2  * Copyright 2002-2012 Drew Noakes
     2 * Copyright 2002-2015 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    1616 * More information about this project is available at:
    1717 *
    18  *    http://drewnoakes.com/code/exif/
    19  *    http://code.google.com/p/metadata-extractor/
     18 *    https://drewnoakes.com/code/exif/
     19 *    https://github.com/drewnoakes/metadata-extractor
    2020 */
    2121package com.drew.metadata.exif;
    2222
    23 import java.util.HashSet;
    24 import java.util.Set;
     23import com.drew.imaging.jpeg.JpegSegmentMetadataReader;
     24import com.drew.imaging.jpeg.JpegSegmentType;
     25import com.drew.imaging.tiff.TiffProcessingException;
     26import com.drew.imaging.tiff.TiffReader;
     27import com.drew.lang.ByteArrayReader;
     28import com.drew.lang.annotations.NotNull;
     29import com.drew.metadata.Metadata;
    2530
    26 import com.drew.lang.BufferBoundsException;
    27 import com.drew.lang.BufferReader;
    28 import com.drew.lang.Rational;
    29 import com.drew.lang.annotations.NotNull;
    30 import com.drew.metadata.Directory;
    31 import com.drew.metadata.Metadata;
    32 import com.drew.metadata.MetadataReader;
     31import java.io.IOException;
     32import java.util.Arrays;
    3333
    3434/**
    3535 * 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.
     36 * {@link ExifThumbnailDirectory}, {@link ExifInteropDirectory}, {@link GpsDirectory} and one of the many camera
     37 * makernote directories.
    3738 *
    38  * @author Drew Noakes http://drewnoakes.com
     39 * @author Drew Noakes https://drewnoakes.com
    3940 */
    40 public class ExifReader implements MetadataReader
     41public class ExifReader implements JpegSegmentMetadataReader
    4142{
    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. */
     43    /**
     44     * The offset at which the TIFF data actually starts. This may be necessary when, for example, processing
     45     * JPEG Exif data from APP0 which has a 6-byte preamble before starting the TIFF data.
     46     */
     47    private static final String JPEG_EXIF_SEGMENT_PREAMBLE = "Exif\0\0";
     48
     49    private boolean _storeThumbnailBytes = true;
     50
     51    public boolean isStoreThumbnailBytes()
     52    {
     53        return _storeThumbnailBytes;
     54    }
     55
     56    public void setStoreThumbnailBytes(boolean storeThumbnailBytes)
     57    {
     58        _storeThumbnailBytes = storeThumbnailBytes;
     59    }
     60
    4561    @NotNull
    46     private static final int[] BYTES_PER_FORMAT = { 0, 1, 1, 2, 4, 8, 1, 1, 2, 4, 8, 4, 8 };
     62    public Iterable<JpegSegmentType> getSegmentTypes()
     63    {
     64        return Arrays.asList(JpegSegmentType.APP1);
     65    }
    4766
    48     /** The number of formats known. */
    49     private static final int MAX_FORMAT_CODE = 12;
     67    public boolean canProcess(@NotNull final byte[] segmentBytes, @NotNull final JpegSegmentType segmentType)
     68    {
     69        return segmentBytes.length >= JPEG_EXIF_SEGMENT_PREAMBLE.length() && new String(segmentBytes, 0, JPEG_EXIF_SEGMENT_PREAMBLE.length()).equalsIgnoreCase(JPEG_EXIF_SEGMENT_PREAMBLE);
     70    }
    5071
    51     // Format types
    52     // TODO use an enum for these?
    53     /** An 8-bit unsigned integer. */
    54     private static final int FMT_BYTE = 1;
    55     /** A fixed-length character string. */
    56     private static final int FMT_STRING = 2;
    57     /** An unsigned 16-bit integer. */
    58     private static final int FMT_USHORT = 3;
    59     /** An unsigned 32-bit integer. */
    60     private static final int FMT_ULONG = 4;
    61     private static final int FMT_URATIONAL = 5;
    62     /** An 8-bit signed integer. */
    63     private static final int FMT_SBYTE = 6;
    64     private static final int FMT_UNDEFINED = 7;
    65     /** A signed 16-bit integer. */
    66     private static final int FMT_SSHORT = 8;
    67     /** A signed 32-bit integer. */
    68     private static final int FMT_SLONG = 9;
    69     private static final int FMT_SRATIONAL = 10;
    70     /** A 32-bit floating point number. */
    71     private static final int FMT_SINGLE = 11;
    72     /** A 64-bit floating point number. */
    73     private static final int FMT_DOUBLE = 12;
     72    public void extract(@NotNull final byte[] segmentBytes, @NotNull final Metadata metadata, @NotNull final JpegSegmentType segmentType)
     73    {
     74        if (segmentBytes == null)
     75            throw new NullPointerException("segmentBytes cannot be null");
     76        if (metadata == null)
     77            throw new NullPointerException("metadata cannot be null");
     78        if (segmentType == null)
     79            throw new NullPointerException("segmentType cannot be null");
    7480
    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. */
    78     public static final int TAG_INTEROP_OFFSET = 0xA005;
    79     /** This tag is a pointer to the Exif GPS IFD. */
    80     public static final int TAG_GPS_INFO_OFFSET = 0x8825;
    81     /** This tag is a pointer to the Exif Makernote IFD. */
    82     public static final int TAG_MAKER_NOTE_OFFSET = 0x927C;
     81        try {
     82            ByteArrayReader reader = new ByteArrayReader(segmentBytes);
    8383
    84     public static final int TIFF_HEADER_START_OFFSET = 6;
    85 
    86     /**
    87      * Performs the Exif data extraction, adding found values to the specified
    88      * 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.
    92      */
    93     public void extract(@NotNull final BufferReader reader, @NotNull Metadata metadata)
    94     {
    95         final ExifSubIFDDirectory directory = metadata.getOrCreateDirectory(ExifSubIFDDirectory.class);
    96 
    97         // check for the header length
    98         if (reader.getLength() <= 14) {
    99             directory.addError("Exif data segment must contain at least 14 bytes");
    100             return;
    101         }
    102 
    103         // check for the header preamble
    104         try {
    105             if (!reader.getString(0, 6).equals("Exif\0\0")) {
    106                 directory.addError("Exif data segment doesn't begin with 'Exif'");
     84            //
     85            // Check for the header preamble
     86            //
     87            try {
     88                if (!reader.getString(0, JPEG_EXIF_SEGMENT_PREAMBLE.length()).equals(JPEG_EXIF_SEGMENT_PREAMBLE)) {
     89                    // TODO what do to with this error state?
     90                    System.err.println("Invalid JPEG Exif segment preamble");
     91                    return;
     92                }
     93            } catch (IOException e) {
     94                // TODO what do to with this error state?
     95                e.printStackTrace(System.err);
    10796                return;
    10897            }
    10998
    110             extractIFD(metadata, metadata.getOrCreateDirectory(ExifIFD0Directory.class), TIFF_HEADER_START_OFFSET, reader);
    111         } catch (BufferBoundsException e) {
    112             directory.addError("Exif data segment ended prematurely");
     99            //
     100            // Read the TIFF-formatted Exif data
     101            //
     102            new TiffReader().processTiff(
     103                reader,
     104                new ExifTiffHandler(metadata, _storeThumbnailBytes),
     105                JPEG_EXIF_SEGMENT_PREAMBLE.length()
     106            );
     107
     108        } catch (TiffProcessingException e) {
     109            // TODO what do to with this error state?
     110            e.printStackTrace(System.err);
     111        } catch (IOException e) {
     112            // TODO what do to with this error state?
     113            e.printStackTrace(System.err);
    113114        }
    114115    }
    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     {
    136         // this should be either "MM" or "II"
    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 {
    144             directory.addError("Unclear distinction between Motorola/Intel byte ordering: " + byteOrderIdentifier);
    145             return;
    146         }
    147 
    148         // Check the next two values for correctness.
    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) {
    164             directory.addError("First exif directory offset is beyond end of Exif data segment");
    165             // First directory normally starts 14 bytes in -- try it here and catch another error in the worst case
    166             firstDirectoryOffset = 14;
    167         }
    168 
    169         Set<Integer> processedDirectoryOffsets = new HashSet<Integer>();
    170 
    171         processDirectory(directory, processedDirectoryOffsets, firstDirectoryOffset, tiffHeaderOffset, metadata, reader);
    172 
    173         // after the extraction process, if we have the correct tags, we may be able to store thumbnail information
    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         }
    187     }
    188 
    189     /**
    190      * Process one of the nested Tiff IFD directories.
    191      * <p/>
    192      * Header
    193      * 2 bytes: number of tags
    194      * <p/>
    195      * Then for each tag
    196      * 2 bytes: tag type
    197      * 2 bytes: format code
    198      * 4 bytes: component count
    199      */
    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
    201     {
    202         // check for directories we've already visited to avoid stack overflows when recursive/cyclic directory structures exist
    203         if (processedDirectoryOffsets.contains(Integer.valueOf(dirStartOffset)))
    204             return;
    205 
    206         // remember that we've visited this directory so that we don't visit it again later
    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()) {
    219             directory.addError("Illegally sized directory");
    220             return;
    221         }
    222 
    223         // Handle each tag in this directory
    224         for (int tagNumber = 0; tagNumber < dirTagCount; tagNumber++) {
    225             final int tagOffset = calculateTagOffset(dirStartOffset, tagNumber);
    226 
    227             // 2 bytes for the tag type
    228             final int tagType = reader.getUInt16(tagOffset);
    229 
    230             // 2 bytes for the format code
    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                 continue; // JOSM patch to fix #9030
    237             }
    238 
    239             // 4 bytes dictate the number of components in this tag's data
    240             final int componentCount = reader.getInt32(tagOffset + 4);
    241             if (componentCount < 0) {
    242                 directory.addError("Negative TIFF tag component count");
    243                 continue;
    244             }
    245             // each component may have more than one byte... calculate the total number of bytes
    246             final int byteCount = componentCount * BYTES_PER_FORMAT[formatCode];
    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");
    266                 continue;
    267             }
    268 
    269             // Check that this tag isn't going to allocate outside the bounds of the data array.
    270             // This addresses an uncommon OutOfMemoryError.
    271             if (byteCount < 0 || tagValueOffset + byteCount > reader.getLength()) {
    272                 directory.addError("Illegal number of bytes: " + byteCount);
    273                 continue;
    274             }
    275 
    276             switch (tagType) {
    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);
    280                     continue;
    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);
    285                     continue;
    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);
    290                     continue;
    291                 }
    292                 case TAG_MAKER_NOTE_OFFSET: {
    293                     processMakerNote(tagValueOffset, processedDirectoryOffsets, tiffHeaderOffset, metadata, reader);
    294                     continue;
    295                 }
    296                 default: {
    297                     processTag(directory, tagType, tagValueOffset, componentCount, formatCode, reader);
    298                     break;
    299                 }
    300             }
    301         }
    302 
    303         // at the end of each IFD is an optional link to the next IFD
    304         final int finalTagOffset = calculateTagOffset(dirStartOffset, dirTagCount);
    305         int nextDirectoryOffset = reader.getInt32(finalTagOffset);
    306         if (nextDirectoryOffset != 0) {
    307             nextDirectoryOffset += tiffHeaderOffset;
    308             if (nextDirectoryOffset >= reader.getLength()) {
    309                 // Last 4 bytes of IFD reference another IFD with an address that is out of bounds
    310                 // Note this could have been caused by jhead 1.3 cropping too much
    311                 return;
    312             } else if (nextDirectoryOffset < dirStartOffset) {
    313                 // Last 4 bytes of IFD reference another IFD with an address that is before the start of this directory
    314                 return;
    315             }
    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
    323     {
    324         // Determine the camera model and makernote format
    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)) {
    342             // Olympus Makernote
    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)) {
    347                 /* There are two scenarios here:
    348                  * Type 1:                  **
    349                  * :0000: 4E 69 6B 6F 6E 00 01 00-05 00 02 00 02 00 06 00 Nikon...........
    350                  * :0010: 00 00 EC 02 00 00 03 00-03 00 01 00 00 00 06 00 ................
    351                  * Type 3:                  **
    352                  * :0000: 4E 69 6B 6F 6E 00 02 00-00 00 4D 4D 00 2A 00 00 Nikon....MM.*...
    353                  * :0010: 00 08 00 1E 00 01 00 07-00 00 00 04 30 32 30 30 ............0200
    354                  */
    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);
    388             else
    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();
    392             // bug in fujifilm makernote ifd means we temporarily use Intel byte ordering
    393             reader.setMotorolaByteOrder(false);
    394             // the 4 bytes after "FUJIFILM" in the makernote point to the start of the makernote
    395             // IFD, though the offset is relative to the start of the makernote, not the TIFF
    396             // header (like everywhere else)
    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")) {
    401             // Cases seen with the model starting with MINOLTA in capitals seem to have a valid Olympus makernote
    402             // area that commences immediately.
    403             processDirectory(metadata.getOrCreateDirectory(OlympusMakernoteDirectory.class), processedDirectoryOffsets, subdirOffset, tiffHeaderOffset, metadata, reader);
    404         } else if ("KYOCERA".equals(firstSevenChars)) {
    405             // http://www.ozhiker.com/electronics/pjmt/jpeg_info/kyocera_mn.html
    406             processDirectory(metadata.getOrCreateDirectory(KyoceraMakernoteDirectory.class), processedDirectoryOffsets, subdirOffset + 22, tiffHeaderOffset, metadata, reader);
    407         } else if ("Panasonic\u0000\u0000\u0000".equals(reader.getString(subdirOffset, 12))) {
    408             // NON-Standard TIFF IFD Data using Panasonic Tags. There is no Next-IFD pointer after the IFD
    409             // Offsets are relative to the start of the TIFF header at the beginning of the EXIF segment
    410             // more information here: http://www.ozhiker.com/electronics/pjmt/jpeg_info/panasonic_mn.html
    411             processDirectory(metadata.getOrCreateDirectory(PanasonicMakernoteDirectory.class), processedDirectoryOffsets, subdirOffset + 12, tiffHeaderOffset, metadata, reader);
    412         } else if ("AOC\u0000".equals(firstFourChars)) {
    413             // NON-Standard TIFF IFD Data using Casio Type 2 Tags
    414             // IFD has no Next-IFD pointer at end of IFD, and
    415             // Offsets are relative to the start of the current IFD tag, not the TIFF header
    416             // Observed for:
    417             // - Pentax ist D
    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"))) {
    420             // NON-Standard TIFF IFD Data using Pentax Tags
    421             // IFD has no Next-IFD pointer at end of IFD, and
    422             // Offsets are relative to the start of the current IFD tag, not the TIFF header
    423             // Observed for:
    424             // - PENTAX Optio 330
    425             // - PENTAX Optio 430
    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 {
    433             // TODO how to store makernote data when it's not from a supported camera model?
    434             // this is difficult as the starting offset is not known.  we could look for it...
    435         }
    436     }
    437 
    438     private void processTag(@NotNull Directory directory, int tagType, int tagValueOffset, int componentCount, int formatCode, @NotNull final BufferReader reader) throws BufferBoundsException
    439     {
    440         // Directory simply stores raw values
    441         // The display side uses a Descriptor class per directory to turn the raw values into 'pretty' descriptions
    442         switch (formatCode) {
    443             case FMT_UNDEFINED:
    444                 // this includes exif user comments
    445                 directory.setByteArray(tagType, reader.getBytes(tagValueOffset, componentCount));
    446                 break;
    447             case FMT_STRING:
    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 */
    474                 break;
    475             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;
    485             case FMT_URATIONAL:
    486                 if (componentCount == 1) {
    487                     directory.setRational(tagType, new Rational(reader.getUInt32(tagValueOffset), reader.getUInt32(tagValueOffset + 4)));
    488                 } else if (componentCount > 1) {
    489                     Rational[] rationals = new Rational[componentCount];
    490                     for (int i = 0; i < componentCount; i++)
    491                         rationals[i] = new Rational(reader.getUInt32(tagValueOffset + (8 * i)), reader.getUInt32(tagValueOffset + 4 + (8 * i)));
    492                     directory.setRationalArray(tagType, rationals);
    493                 }
    494                 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 
    520             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;
    530             case FMT_BYTE:
    531                 if (componentCount == 1) {
    532                     directory.setInt(tagType, reader.getUInt8(tagValueOffset));
    533                 } else {
    534                     int[] bytes = new int[componentCount];
    535                     for (int i = 0; i < componentCount; i++)
    536                         bytes[i] = reader.getUInt8(tagValueOffset + i);
    537                     directory.setIntArray(tagType, bytes);
    538                 }
    539                 break;
    540             case FMT_USHORT:
    541                 if (componentCount == 1) {
    542                     int i = reader.getUInt16(tagValueOffset);
    543                     directory.setInt(tagType, i);
    544                 } else {
    545                     int[] ints = new int[componentCount];
    546                     for (int i = 0; i < componentCount; i++)
    547                         ints[i] = reader.getUInt16(tagValueOffset + (i * 2));
    548                     directory.setIntArray(tagType, ints);
    549                 }
    550                 break;
    551             case FMT_SSHORT:
    552                 if (componentCount == 1) {
    553                     int i = reader.getInt16(tagValueOffset);
    554                     directory.setInt(tagType, i);
    555                 } else {
    556                     int[] ints = new int[componentCount];
    557                     for (int i = 0; i < componentCount; i++)
    558                         ints[i] = reader.getInt16(tagValueOffset + (i * 2));
    559                     directory.setIntArray(tagType, ints);
    560                 }
    561                 break;
    562             case FMT_SLONG:
    563             case FMT_ULONG:
    564                 // NOTE 'long' in this case means 32 bit, not 64
    565                 if (componentCount == 1) {
    566                     int i = reader.getInt32(tagValueOffset);
    567                     directory.setInt(tagType, i);
    568                 } else {
    569                     int[] ints = new int[componentCount];
    570                     for (int i = 0; i < componentCount; i++)
    571                         ints[i] = reader.getInt32(tagValueOffset + (i * 4));
    572                     directory.setIntArray(tagType, ints);
    573                 }
    574                 break;
    575             default:
    576                 directory.addError("Unknown format code " + formatCode + " for tag " + tagType);
    577         }
    578     }
    579 
    580     /**
    581      * Determine the offset at which a given InteropArray entry begins within the specified IFD.
    582      *
    583      * @param dirStartOffset the offset at which the IFD starts
    584      * @param entryNumber    the zero-based entry number
    585      */
    586     private int calculateTagOffset(int dirStartOffset, int entryNumber)
    587     {
    588         // add 2 bytes for the tag count
    589         // each entry is 12 bytes, so we skip 12 * the number seen so far
    590         return dirStartOffset + 2 + (12 * entryNumber);
    591     }
    592116}
  • trunk/src/com/drew/metadata/exif/ExifSubIFDDescriptor.java

    r6127 r8132  
    11/*
    2  * Copyright 2002-2012 Drew Noakes
     2 * Copyright 2002-2015 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    1616 * More information about this project is available at:
    1717 *
    18  *    http://drewnoakes.com/code/exif/
    19  *    http://code.google.com/p/metadata-extractor/
     18 *    https://drewnoakes.com/code/exif/
     19 *    https://github.com/drewnoakes/metadata-extractor
    2020 */
    2121package com.drew.metadata.exif;
     
    3232import java.util.Map;
    3333
     34import static com.drew.metadata.exif.ExifSubIFDDirectory.*;
     35
    3436/**
    35  * Provides human-readable string representations of tag values stored in a <code>ExifSubIFDDirectory</code>.
     37 * Provides human-readable string representations of tag values stored in a {@link ExifSubIFDDirectory}.
    3638 *
    37  * @author Drew Noakes http://drewnoakes.com
     39 * @author Drew Noakes https://drewnoakes.com
    3840 */
    3941public class ExifSubIFDDescriptor extends TagDescriptor<ExifSubIFDDirectory>
     
    6062
    6163    /**
    62      * Returns a descriptive value of the the specified tag for this image.
     64     * Returns a descriptive value of the specified tag for this image.
    6365     * Where possible, known values will be substituted here in place of the raw
    6466     * tokens actually kept in the Exif segment.  If no substitution is
    6567     * available, the value provided by getString(int) will be returned.
     68     *
    6669     * @param tagType the tag to find a description for
    6770     * @return a description of the image's value for the specified tag, or
    6871     *         <code>null</code> if the tag hasn't been defined.
    6972     */
     73    @Override
    7074    @Nullable
    7175    public String getDescription(int tagType)
    7276    {
    7377        switch (tagType) {
    74             case ExifSubIFDDirectory.TAG_NEW_SUBFILE_TYPE:
     78            case TAG_NEW_SUBFILE_TYPE:
    7579                return getNewSubfileTypeDescription();
    76             case ExifSubIFDDirectory.TAG_SUBFILE_TYPE:
     80            case TAG_SUBFILE_TYPE:
    7781                return getSubfileTypeDescription();
    78             case ExifSubIFDDirectory.TAG_THRESHOLDING:
     82            case TAG_THRESHOLDING:
    7983                return getThresholdingDescription();
    80             case ExifSubIFDDirectory.TAG_FILL_ORDER:
     84            case TAG_FILL_ORDER:
    8185                return getFillOrderDescription();
    82             case ExifSubIFDDirectory.TAG_EXPOSURE_TIME:
     86            case TAG_EXPOSURE_TIME:
    8387                return getExposureTimeDescription();
    84             case ExifSubIFDDirectory.TAG_SHUTTER_SPEED:
     88            case TAG_SHUTTER_SPEED:
    8589                return getShutterSpeedDescription();
    86             case ExifSubIFDDirectory.TAG_FNUMBER:
     90            case TAG_FNUMBER:
    8791                return getFNumberDescription();
    88             case ExifSubIFDDirectory.TAG_COMPRESSED_AVERAGE_BITS_PER_PIXEL:
     92            case TAG_COMPRESSED_AVERAGE_BITS_PER_PIXEL:
    8993                return getCompressedAverageBitsPerPixelDescription();
    90             case ExifSubIFDDirectory.TAG_SUBJECT_DISTANCE:
     94            case TAG_SUBJECT_DISTANCE:
    9195                return getSubjectDistanceDescription();
    92             case ExifSubIFDDirectory.TAG_METERING_MODE:
     96            case TAG_METERING_MODE:
    9397                return getMeteringModeDescription();
    94             case ExifSubIFDDirectory.TAG_WHITE_BALANCE:
     98            case TAG_WHITE_BALANCE:
    9599                return getWhiteBalanceDescription();
    96             case ExifSubIFDDirectory.TAG_FLASH:
     100            case TAG_FLASH:
    97101                return getFlashDescription();
    98             case ExifSubIFDDirectory.TAG_FOCAL_LENGTH:
     102            case TAG_FOCAL_LENGTH:
    99103                return getFocalLengthDescription();
    100             case ExifSubIFDDirectory.TAG_COLOR_SPACE:
     104            case TAG_COLOR_SPACE:
    101105                return getColorSpaceDescription();
    102             case ExifSubIFDDirectory.TAG_EXIF_IMAGE_WIDTH:
     106            case TAG_EXIF_IMAGE_WIDTH:
    103107                return getExifImageWidthDescription();
    104             case ExifSubIFDDirectory.TAG_EXIF_IMAGE_HEIGHT:
     108            case TAG_EXIF_IMAGE_HEIGHT:
    105109                return getExifImageHeightDescription();
    106             case ExifSubIFDDirectory.TAG_FOCAL_PLANE_UNIT:
     110            case TAG_FOCAL_PLANE_RESOLUTION_UNIT:
    107111                return getFocalPlaneResolutionUnitDescription();
    108             case ExifSubIFDDirectory.TAG_FOCAL_PLANE_X_RES:
     112            case TAG_FOCAL_PLANE_X_RESOLUTION:
    109113                return getFocalPlaneXResolutionDescription();
    110             case ExifSubIFDDirectory.TAG_FOCAL_PLANE_Y_RES:
     114            case TAG_FOCAL_PLANE_Y_RESOLUTION:
    111115                return getFocalPlaneYResolutionDescription();
    112             case ExifSubIFDDirectory.TAG_BITS_PER_SAMPLE:
     116            case TAG_BITS_PER_SAMPLE:
    113117                return getBitsPerSampleDescription();
    114             case ExifSubIFDDirectory.TAG_PHOTOMETRIC_INTERPRETATION:
     118            case TAG_PHOTOMETRIC_INTERPRETATION:
    115119                return getPhotometricInterpretationDescription();
    116             case ExifSubIFDDirectory.TAG_ROWS_PER_STRIP:
     120            case TAG_ROWS_PER_STRIP:
    117121                return getRowsPerStripDescription();
    118             case ExifSubIFDDirectory.TAG_STRIP_BYTE_COUNTS:
     122            case TAG_STRIP_BYTE_COUNTS:
    119123                return getStripByteCountsDescription();
    120             case ExifSubIFDDirectory.TAG_SAMPLES_PER_PIXEL:
     124            case TAG_SAMPLES_PER_PIXEL:
    121125                return getSamplesPerPixelDescription();
    122             case ExifSubIFDDirectory.TAG_PLANAR_CONFIGURATION:
     126            case TAG_PLANAR_CONFIGURATION:
    123127                return getPlanarConfigurationDescription();
    124             case ExifSubIFDDirectory.TAG_YCBCR_SUBSAMPLING:
     128            case TAG_YCBCR_SUBSAMPLING:
    125129                return getYCbCrSubsamplingDescription();
    126             case ExifSubIFDDirectory.TAG_EXPOSURE_PROGRAM:
     130            case TAG_EXPOSURE_PROGRAM:
    127131                return getExposureProgramDescription();
    128             case ExifSubIFDDirectory.TAG_APERTURE:
     132            case TAG_APERTURE:
    129133                return getApertureValueDescription();
    130             case ExifSubIFDDirectory.TAG_MAX_APERTURE:
     134            case TAG_MAX_APERTURE:
    131135                return getMaxApertureValueDescription();
    132             case ExifSubIFDDirectory.TAG_SENSING_METHOD:
     136            case TAG_SENSING_METHOD:
    133137                return getSensingMethodDescription();
    134             case ExifSubIFDDirectory.TAG_EXPOSURE_BIAS:
     138            case TAG_EXPOSURE_BIAS:
    135139                return getExposureBiasDescription();
    136             case ExifSubIFDDirectory.TAG_FILE_SOURCE:
     140            case TAG_FILE_SOURCE:
    137141                return getFileSourceDescription();
    138             case ExifSubIFDDirectory.TAG_SCENE_TYPE:
     142            case TAG_SCENE_TYPE:
    139143                return getSceneTypeDescription();
    140             case ExifSubIFDDirectory.TAG_COMPONENTS_CONFIGURATION:
     144            case TAG_COMPONENTS_CONFIGURATION:
    141145                return getComponentConfigurationDescription();
    142             case ExifSubIFDDirectory.TAG_EXIF_VERSION:
     146            case TAG_EXIF_VERSION:
    143147                return getExifVersionDescription();
    144             case ExifSubIFDDirectory.TAG_FLASHPIX_VERSION:
     148            case TAG_FLASHPIX_VERSION:
    145149                return getFlashPixVersionDescription();
    146             case ExifSubIFDDirectory.TAG_ISO_EQUIVALENT:
     150            case TAG_ISO_EQUIVALENT:
    147151                return getIsoEquivalentDescription();
    148             case ExifSubIFDDirectory.TAG_USER_COMMENT:
     152            case TAG_USER_COMMENT:
    149153                return getUserCommentDescription();
    150             case ExifSubIFDDirectory.TAG_CUSTOM_RENDERED:
     154            case TAG_CUSTOM_RENDERED:
    151155                return getCustomRenderedDescription();
    152             case ExifSubIFDDirectory.TAG_EXPOSURE_MODE:
     156            case TAG_EXPOSURE_MODE:
    153157                return getExposureModeDescription();
    154             case ExifSubIFDDirectory.TAG_WHITE_BALANCE_MODE:
     158            case TAG_WHITE_BALANCE_MODE:
    155159                return getWhiteBalanceModeDescription();
    156             case ExifSubIFDDirectory.TAG_DIGITAL_ZOOM_RATIO:
     160            case TAG_DIGITAL_ZOOM_RATIO:
    157161                return getDigitalZoomRatioDescription();
    158             case ExifSubIFDDirectory.TAG_35MM_FILM_EQUIV_FOCAL_LENGTH:
     162            case TAG_35MM_FILM_EQUIV_FOCAL_LENGTH:
    159163                return get35mmFilmEquivFocalLengthDescription();
    160             case ExifSubIFDDirectory.TAG_SCENE_CAPTURE_TYPE:
     164            case TAG_SCENE_CAPTURE_TYPE:
    161165                return getSceneCaptureTypeDescription();
    162             case ExifSubIFDDirectory.TAG_GAIN_CONTROL:
     166            case TAG_GAIN_CONTROL:
    163167                return getGainControlDescription();
    164             case ExifSubIFDDirectory.TAG_CONTRAST:
     168            case TAG_CONTRAST:
    165169                return getContrastDescription();
    166             case ExifSubIFDDirectory.TAG_SATURATION:
     170            case TAG_SATURATION:
    167171                return getSaturationDescription();
    168             case ExifSubIFDDirectory.TAG_SHARPNESS:
     172            case TAG_SHARPNESS:
    169173                return getSharpnessDescription();
    170             case ExifSubIFDDirectory.TAG_SUBJECT_DISTANCE_RANGE:
     174            case TAG_SUBJECT_DISTANCE_RANGE:
    171175                return getSubjectDistanceRangeDescription();
    172176            default:
     
    178182    public String getNewSubfileTypeDescription()
    179183    {
    180         Integer value = _directory.getInteger(ExifSubIFDDirectory.TAG_NEW_SUBFILE_TYPE);
    181         if (value==null)
    182             return null;
    183         switch (value) {
    184             case 1: return "Full-resolution image";
    185             case 2: return "Reduced-resolution image";
    186             case 3: return "Single page of multi-page reduced-resolution image";
    187             case 4: return "Transparency mask";
    188             case 5: return "Transparency mask of reduced-resolution image";
    189             case 6: return "Transparency mask of multi-page image";
    190             case 7: return "Transparency mask of reduced-resolution multi-page image";
    191             default:
    192                 return "Unknown (" + value + ")";
    193         }
     184        return getIndexedDescription(TAG_NEW_SUBFILE_TYPE, 1,
     185            "Full-resolution image",
     186            "Reduced-resolution image",
     187            "Single page of multi-page reduced-resolution image",
     188            "Transparency mask",
     189            "Transparency mask of reduced-resolution image",
     190            "Transparency mask of multi-page image",
     191            "Transparency mask of reduced-resolution multi-page image"
     192        );
    194193    }
    195194
     
    197196    public String getSubfileTypeDescription()
    198197    {
    199         Integer value = _directory.getInteger(ExifSubIFDDirectory.TAG_SUBFILE_TYPE);
    200         if (value==null)
    201             return null;
    202         switch (value) {
    203             case 1: return "Full-resolution image";
    204             case 2: return "Reduced-resolution image";
    205             case 3: return "Single page of multi-page image";
    206             default:
    207                 return "Unknown (" + value + ")";
    208         }
     198        return getIndexedDescription(TAG_SUBFILE_TYPE, 1,
     199            "Full-resolution image",
     200            "Reduced-resolution image",
     201            "Single page of multi-page image"
     202        );
    209203    }
    210204
     
    212206    public String getThresholdingDescription()
    213207    {
    214         Integer value = _directory.getInteger(ExifSubIFDDirectory.TAG_THRESHOLDING);
    215         if (value==null)
    216             return null;
    217         switch (value) {
    218             case 1: return "No dithering or halftoning";
    219             case 2: return "Ordered dither or halftone";
    220             case 3: return "Randomized dither";
    221             default:
    222                 return "Unknown (" + value + ")";
    223         }
     208        return getIndexedDescription(TAG_THRESHOLDING, 1,
     209            "No dithering or halftoning",
     210            "Ordered dither or halftone",
     211            "Randomized dither"
     212        );
    224213    }
    225214
     
    227216    public String getFillOrderDescription()
    228217    {
    229         Integer value = _directory.getInteger(ExifSubIFDDirectory.TAG_FILL_ORDER);
    230         if (value==null)
    231             return null;
    232         switch (value) {
    233             case 1: return "Normal";
    234             case 2: return "Reversed";
    235             default:
    236                 return "Unknown (" + value + ")";
    237         }
     218        return getIndexedDescription(TAG_FILL_ORDER, 1,
     219            "Normal",
     220            "Reversed"
     221        );
    238222    }
    239223
     
    241225    public String getSubjectDistanceRangeDescription()
    242226    {
    243         Integer value = _directory.getInteger(ExifSubIFDDirectory.TAG_SUBJECT_DISTANCE_RANGE);
    244         if (value==null)
    245             return null;
    246         switch (value) {
    247             case 0: return "Unknown";
    248             case 1: return "Macro";
    249             case 2: return "Close view";
    250             case 3: return "Distant view";
    251             default:
    252                 return "Unknown (" + value + ")";
    253         }
     227        return getIndexedDescription(TAG_SUBJECT_DISTANCE_RANGE,
     228            "Unknown",
     229            "Macro",
     230            "Close view",
     231            "Distant view"
     232        );
    254233    }
    255234
     
    257236    public String getSharpnessDescription()
    258237    {
    259         Integer value = _directory.getInteger(ExifSubIFDDirectory.TAG_SHARPNESS);
    260         if (value==null)
    261             return null;
    262         switch (value) {
    263             case 0: return "None";
    264             case 1: return "Low";
    265             case 2: return "Hard";
    266             default:
    267                 return "Unknown (" + value + ")";
    268         }
     238        return getIndexedDescription(TAG_SHARPNESS,
     239            "None",
     240            "Low",
     241            "Hard"
     242        );
    269243    }
    270244
     
    272246    public String getSaturationDescription()
    273247    {
    274         Integer value = _directory.getInteger(ExifSubIFDDirectory.TAG_SATURATION);
    275         if (value==null)
    276             return null;
    277         switch (value) {
    278             case 0: return "None";
    279             case 1: return "Low saturation";
    280             case 2: return "High saturation";
    281             default:
    282                 return "Unknown (" + value + ")";
    283         }
     248        return getIndexedDescription(TAG_SATURATION,
     249            "None",
     250            "Low saturation",
     251            "High saturation"
     252        );
    284253    }
    285254
     
    287256    public String getContrastDescription()
    288257    {
    289         Integer value = _directory.getInteger(ExifSubIFDDirectory.TAG_CONTRAST);
    290         if (value==null)
    291             return null;
    292         switch (value) {
    293             case 0: return "None";
    294             case 1: return "Soft";
    295             case 2: return "Hard";
    296             default:
    297                 return "Unknown (" + value + ")";
    298         }
     258        return getIndexedDescription(TAG_CONTRAST,
     259            "None",
     260            "Soft",
     261            "Hard"
     262        );
    299263    }
    300264
     
    302266    public String getGainControlDescription()
    303267    {
    304         Integer value = _directory.getInteger(ExifSubIFDDirectory.TAG_GAIN_CONTROL);
    305         if (value==null)
    306             return null;
    307         switch (value) {
    308             case 0: return "None";
    309             case 1: return "Low gain up";
    310             case 2: return "Low gain down";
    311             case 3: return "High gain up";
    312             case 4: return "High gain down";
    313             default:
    314                 return "Unknown (" + value + ")";
    315         }
     268        return getIndexedDescription(TAG_GAIN_CONTROL,
     269            "None",
     270            "Low gain up",
     271            "Low gain down",
     272            "High gain up",
     273            "High gain down"
     274        );
    316275    }
    317276
     
    319278    public String getSceneCaptureTypeDescription()
    320279    {
    321         Integer value = _directory.getInteger(ExifSubIFDDirectory.TAG_SCENE_CAPTURE_TYPE);
    322         if (value==null)
    323             return null;
    324         switch (value) {
    325             case 0: return "Standard";
    326             case 1: return "Landscape";
    327             case 2: return "Portrait";
    328             case 3: return "Night scene";
    329             default:
    330                 return "Unknown (" + value + ")";
    331         }
     280        return getIndexedDescription(TAG_SCENE_CAPTURE_TYPE,
     281            "Standard",
     282            "Landscape",
     283            "Portrait",
     284            "Night scene"
     285        );
    332286    }
    333287
     
    335289    public String get35mmFilmEquivFocalLengthDescription()
    336290    {
    337         Integer value = _directory.getInteger(ExifSubIFDDirectory.TAG_35MM_FILM_EQUIV_FOCAL_LENGTH);
    338         if (value==null)
    339             return null;
    340         if (value==0)
    341             return "Unknown";
    342         else
    343             return SimpleDecimalFormatter.format(value) + "mm";
     291        Integer value = _directory.getInteger(TAG_35MM_FILM_EQUIV_FOCAL_LENGTH);
     292        return value == null
     293            ? null
     294            : value == 0
     295            ? "Unknown"
     296            : SimpleDecimalFormatter.format(value) + "mm";
    344297    }
    345298
     
    347300    public String getDigitalZoomRatioDescription()
    348301    {
    349         Rational value = _directory.getRational(ExifSubIFDDirectory.TAG_DIGITAL_ZOOM_RATIO);
    350         if (value==null)
    351             return null;
    352         if (value.getNumerator()==0)
    353             return "Digital zoom not used.";
    354         return SimpleDecimalFormatter.format(value.doubleValue());
     302        Rational value = _directory.getRational(TAG_DIGITAL_ZOOM_RATIO);
     303        return value == null
     304            ? null
     305            : value.getNumerator() == 0
     306            ? "Digital zoom not used."
     307            : SimpleDecimalFormatter.format(value.doubleValue());
    355308    }
    356309
     
    358311    public String getWhiteBalanceModeDescription()
    359312    {
    360         Integer value = _directory.getInteger(ExifSubIFDDirectory.TAG_WHITE_BALANCE_MODE);
    361         if (value==null)
    362             return null;
    363         switch (value) {
    364             case 0: return "Auto white balance";
    365             case 1: return "Manual white balance";
    366             default:
    367                 return "Unknown (" + value + ")";
    368         }
     313        return getIndexedDescription(TAG_WHITE_BALANCE_MODE,
     314            "Auto white balance",
     315            "Manual white balance"
     316        );
    369317    }
    370318
     
    372320    public String getExposureModeDescription()
    373321    {
    374         Integer value = _directory.getInteger(ExifSubIFDDirectory.TAG_EXPOSURE_MODE);
    375         if (value==null)
    376             return null;
    377         switch (value) {
    378             case 0: return "Auto exposure";
    379             case 1: return "Manual exposure";
    380             case 2: return "Auto bracket";
    381             default:
    382                 return "Unknown (" + value + ")";
    383         }
     322        return getIndexedDescription(TAG_EXPOSURE_MODE,
     323            "Auto exposure",
     324            "Manual exposure",
     325            "Auto bracket"
     326        );
    384327    }
    385328
     
    387330    public String getCustomRenderedDescription()
    388331    {
    389         Integer value = _directory.getInteger(ExifSubIFDDirectory.TAG_CUSTOM_RENDERED);
    390         if (value==null)
    391             return null;
    392         switch (value) {
    393             case 0: return "Normal process";
    394             case 1: return "Custom process";
    395             default:
    396                 return "Unknown (" + value + ")";
    397         }
     332        return getIndexedDescription(TAG_CUSTOM_RENDERED,
     333            "Normal process",
     334            "Custom process"
     335        );
    398336    }
    399337
     
    401339    public String getUserCommentDescription()
    402340    {
    403         byte[] commentBytes = _directory.getByteArray(ExifSubIFDDirectory.TAG_USER_COMMENT);
    404         if (commentBytes==null)
     341        byte[] commentBytes = _directory.getByteArray(TAG_USER_COMMENT);
     342        if (commentBytes == null)
    405343            return null;
    406344        if (commentBytes.length == 0)
     
    408346
    409347        final Map<String, String> encodingMap = new HashMap<String, String>();
    410         encodingMap.put("ASCII",    System.getProperty("file.encoding")); // Someone suggested "ISO-8859-1".
    411         encodingMap.put("UNICODE",  "UTF-16LE");
    412         encodingMap.put("JIS",      "Shift-JIS"); // We assume this charset for now.  Another suggestion is "JIS".
     348        encodingMap.put("ASCII", System.getProperty("file.encoding")); // Someone suggested "ISO-8859-1".
     349        encodingMap.put("UNICODE", "UTF-16LE");
     350        encodingMap.put("JIS", "Shift-JIS"); // We assume this charset for now.  Another suggestion is "JIS".
    413351
    414352        try {
     
    442380    {
    443381        // Have seen an exception here from files produced by ACDSEE that stored an int[] here with two values
    444         Integer isoEquiv = _directory.getInteger(ExifSubIFDDirectory.TAG_ISO_EQUIVALENT);
    445         if (isoEquiv==null)
    446             return null;
     382        Integer isoEquiv = _directory.getInteger(TAG_ISO_EQUIVALENT);
    447383        // There used to be a check here that multiplied ISO values < 50 by 200.
    448384        // Issue 36 shows a smart-phone image from a Samsung Galaxy S2 with ISO-40.
    449         return Integer.toString(isoEquiv);
     385        return isoEquiv != null
     386            ? Integer.toString(isoEquiv)
     387            : null;
    450388    }
    451389
     
    453391    public String getExifVersionDescription()
    454392    {
    455         int[] ints = _directory.getIntArray(ExifSubIFDDirectory.TAG_EXIF_VERSION);
    456         if (ints==null)
    457             return null;
    458         return ExifSubIFDDescriptor.convertBytesToVersionString(ints, 2);
     393        return getVersionBytesDescription(TAG_EXIF_VERSION, 2);
    459394    }
    460395
     
    462397    public String getFlashPixVersionDescription()
    463398    {
    464         int[] ints = _directory.getIntArray(ExifSubIFDDirectory.TAG_FLASHPIX_VERSION);
    465         if (ints==null)
    466             return null;
    467         return ExifSubIFDDescriptor.convertBytesToVersionString(ints, 2);
     399        return getVersionBytesDescription(TAG_FLASHPIX_VERSION, 2);
    468400    }
    469401
     
    471403    public String getSceneTypeDescription()
    472404    {
    473         Integer sceneType = _directory.getInteger(ExifSubIFDDirectory.TAG_SCENE_TYPE);
    474         if (sceneType==null)
    475             return null;
    476         return sceneType == 1
    477                 ? "Directly photographed image"
    478                 : "Unknown (" + sceneType + ")";
     405        return getIndexedDescription(TAG_SCENE_TYPE,
     406            1,
     407            "Directly photographed image"
     408        );
    479409    }
    480410
     
    482412    public String getFileSourceDescription()
    483413    {
    484         Integer fileSource = _directory.getInteger(ExifSubIFDDirectory.TAG_FILE_SOURCE);
    485         if (fileSource==null)
    486             return null;
    487         return fileSource == 3
    488                 ? "Digital Still Camera (DSC)"
    489                 : "Unknown (" + fileSource + ")";
     414        return getIndexedDescription(TAG_FILE_SOURCE,
     415            1,
     416            "Film Scanner",
     417            "Reflection Print Scanner",
     418            "Digital Still Camera (DSC)"
     419        );
    490420    }
    491421
     
    493423    public String getExposureBiasDescription()
    494424    {
    495         Rational value = _directory.getRational(ExifSubIFDDirectory.TAG_EXPOSURE_BIAS);
    496         if (value==null)
     425        Rational value = _directory.getRational(TAG_EXPOSURE_BIAS);
     426        if (value == null)
    497427            return null;
    498428        return value.toSimpleString(true) + " EV";
     
    502432    public String getMaxApertureValueDescription()
    503433    {
    504         Double aperture = _directory.getDoubleObject(ExifSubIFDDirectory.TAG_MAX_APERTURE);
    505         if (aperture==null)
     434        Double aperture = _directory.getDoubleObject(TAG_MAX_APERTURE);
     435        if (aperture == null)
    506436            return null;
    507437        double fStop = PhotographicConversions.apertureToFStop(aperture);
     
    512442    public String getApertureValueDescription()
    513443    {
    514         Double aperture = _directory.getDoubleObject(ExifSubIFDDirectory.TAG_APERTURE);
    515         if (aperture==null)
     444        Double aperture = _directory.getDoubleObject(TAG_APERTURE);
     445        if (aperture == null)
    516446            return null;
    517447        double fStop = PhotographicConversions.apertureToFStop(aperture);
     
    522452    public String getExposureProgramDescription()
    523453    {
    524         // '1' means manual control, '2' program normal, '3' aperture priority,
    525         // '4' shutter priority, '5' program creative (slow program),
    526         // '6' program action(high-speed program), '7' portrait mode, '8' landscape mode.
    527         Integer value = _directory.getInteger(ExifSubIFDDirectory.TAG_EXPOSURE_PROGRAM);
    528         if (value==null)
    529             return null;
    530         switch (value) {
    531             case 1: return "Manual control";
    532             case 2: return "Program normal";
    533             case 3: return "Aperture priority";
    534             case 4: return "Shutter priority";
    535             case 5: return "Program creative (slow program)";
    536             case 6: return "Program action (high-speed program)";
    537             case 7: return "Portrait mode";
    538             case 8: return "Landscape mode";
    539             default:
    540                 return "Unknown program (" + value + ")";
    541         }
     454        return getIndexedDescription(TAG_EXPOSURE_PROGRAM,
     455            1,
     456            "Manual control",
     457            "Program normal",
     458            "Aperture priority",
     459            "Shutter priority",
     460            "Program creative (slow program)",
     461            "Program action (high-speed program)",
     462            "Portrait mode",
     463            "Landscape mode"
     464        );
    542465    }
    543466
     
    545468    public String getYCbCrSubsamplingDescription()
    546469    {
    547         int[] positions = _directory.getIntArray(ExifSubIFDDirectory.TAG_YCBCR_SUBSAMPLING);
    548         if (positions==null)
     470        int[] positions = _directory.getIntArray(TAG_YCBCR_SUBSAMPLING);
     471        if (positions == null)
    549472            return null;
    550473        if (positions[0] == 2 && positions[1] == 1) {
     
    564487        // pixel. If value is '2', Y/Cb/Cr value is separated and stored to Y plane/Cb plane/Cr
    565488        // plane format.
    566         Integer value = _directory.getInteger(ExifSubIFDDirectory.TAG_PLANAR_CONFIGURATION);
    567         if (value==null)
    568             return null;
    569         switch (value) {
    570             case 1: return "Chunky (contiguous for each subsampling pixel)";
    571             case 2: return "Separate (Y-plane/Cb-plane/Cr-plane format)";
    572             default:
    573                 return "Unknown configuration";
    574         }
     489        return getIndexedDescription(TAG_PLANAR_CONFIGURATION,
     490            1,
     491            "Chunky (contiguous for each subsampling pixel)",
     492            "Separate (Y-plane/Cb-plane/Cr-plane format)"
     493        );
    575494    }
    576495
     
    578497    public String getSamplesPerPixelDescription()
    579498    {
    580         String value = _directory.getString(ExifSubIFDDirectory.TAG_SAMPLES_PER_PIXEL);
    581         return value==null ? null : value + " samples/pixel";
     499        String value = _directory.getString(TAG_SAMPLES_PER_PIXEL);
     500        return value == null ? null : value + " samples/pixel";
    582501    }
    583502
     
    585504    public String getRowsPerStripDescription()
    586505    {
    587         final String value = _directory.getString(ExifSubIFDDirectory.TAG_ROWS_PER_STRIP);
    588         return value==null ? null : value + " rows/strip";
     506        final String value = _directory.getString(TAG_ROWS_PER_STRIP);
     507        return value == null ? null : value + " rows/strip";
    589508    }
    590509
     
    592511    public String getStripByteCountsDescription()
    593512    {
    594         final String value = _directory.getString(ExifSubIFDDirectory.TAG_STRIP_BYTE_COUNTS);
    595         return value==null ? null : value + " bytes";
     513        final String value = _directory.getString(TAG_STRIP_BYTE_COUNTS);
     514        return value == null ? null : value + " bytes";
    596515    }
    597516
     
    600519    {
    601520        // Shows the color space of the image data components
    602         Integer value = _directory.getInteger(ExifSubIFDDirectory.TAG_PHOTOMETRIC_INTERPRETATION);
    603         if (value==null)
     521        Integer value = _directory.getInteger(TAG_PHOTOMETRIC_INTERPRETATION);
     522        if (value == null)
    604523            return null;
    605524        switch (value) {
     
    626545    public String getBitsPerSampleDescription()
    627546    {
    628         String value = _directory.getString(ExifSubIFDDirectory.TAG_BITS_PER_SAMPLE);
    629         return value==null ? null : value + " bits/component/pixel";
     547        String value = _directory.getString(TAG_BITS_PER_SAMPLE);
     548        return value == null ? null : value + " bits/component/pixel";
    630549    }
    631550
     
    633552    public String getFocalPlaneXResolutionDescription()
    634553    {
    635         Rational rational = _directory.getRational(ExifSubIFDDirectory.TAG_FOCAL_PLANE_X_RES);
    636         if (rational==null)
     554        Rational rational = _directory.getRational(TAG_FOCAL_PLANE_X_RESOLUTION);
     555        if (rational == null)
    637556            return null;
    638557        final String unit = getFocalPlaneResolutionUnitDescription();
    639558        return rational.getReciprocal().toSimpleString(_allowDecimalRepresentationOfRationals)
    640             + (unit==null ? "" : " " + unit.toLowerCase());
     559            + (unit == null ? "" : " " + unit.toLowerCase());
    641560    }
    642561
     
    644563    public String getFocalPlaneYResolutionDescription()
    645564    {
    646         Rational rational = _directory.getRational(ExifSubIFDDirectory.TAG_FOCAL_PLANE_Y_RES);
    647         if (rational==null)
     565        Rational rational = _directory.getRational(TAG_FOCAL_PLANE_Y_RESOLUTION);
     566        if (rational == null)
    648567            return null;
    649568        final String unit = getFocalPlaneResolutionUnitDescription();
    650569        return rational.getReciprocal().toSimpleString(_allowDecimalRepresentationOfRationals)
    651             + (unit==null ? "" : " " + unit.toLowerCase());
     570            + (unit == null ? "" : " " + unit.toLowerCase());
    652571    }
    653572
     
    655574    public String getFocalPlaneResolutionUnitDescription()
    656575    {
    657         // Unit of FocalPlaneXResolution/FocalPlaneYResolution. '1' means no-unit,
    658         // '2' inch, '3' centimeter.
    659         Integer value = _directory.getInteger(ExifSubIFDDirectory.TAG_FOCAL_PLANE_UNIT);
    660         if (value==null)
    661             return null;
    662         switch (value) {
    663             case 1: return "(No unit)";
    664             case 2: return "Inches";
    665             case 3: return "cm";
    666             default:
    667                 return "";
    668         }
     576        // Unit of FocalPlaneXResolution/FocalPlaneYResolution.
     577        // '1' means no-unit, '2' inch, '3' centimeter.
     578        return getIndexedDescription(TAG_FOCAL_PLANE_RESOLUTION_UNIT,
     579            1,
     580            "(No unit)",
     581            "Inches",
     582            "cm"
     583        );
    669584    }
    670585
     
    672587    public String getExifImageWidthDescription()
    673588    {
    674         final Integer value = _directory.getInteger(ExifSubIFDDirectory.TAG_EXIF_IMAGE_WIDTH);
    675         if (value==null)
    676             return null;
    677         return value + " pixels";
     589        final Integer value = _directory.getInteger(TAG_EXIF_IMAGE_WIDTH);
     590        return value == null ? null : value + " pixels";
    678591    }
    679592
     
    681594    public String getExifImageHeightDescription()
    682595    {
    683         final Integer value = _directory.getInteger(ExifSubIFDDirectory.TAG_EXIF_IMAGE_HEIGHT);
    684         if (value==null)
    685             return null;
    686         return value + " pixels";
     596        final Integer value = _directory.getInteger(TAG_EXIF_IMAGE_HEIGHT);
     597        return value == null ? null : value + " pixels";
    687598    }
    688599
     
    690601    public String getColorSpaceDescription()
    691602    {
    692         final Integer value = _directory.getInteger(ExifSubIFDDirectory.TAG_COLOR_SPACE);
    693         if (value==null)
     603        final Integer value = _directory.getInteger(TAG_COLOR_SPACE);
     604        if (value == null)
    694605            return null;
    695606        if (value == 1)
     
    697608        if (value == 65535)
    698609            return "Undefined";
    699         return "Unknown";
     610        return "Unknown (" + value + ")";
    700611    }
    701612
     
    703614    public String getFocalLengthDescription()
    704615    {
    705         Rational value = _directory.getRational(ExifSubIFDDirectory.TAG_FOCAL_LENGTH);
    706         if (value==null)
     616        Rational value = _directory.getRational(TAG_FOCAL_LENGTH);
     617        if (value == null)
    707618            return null;
    708619        java.text.DecimalFormat formatter = new DecimalFormat("0.0##");
     
    714625    {
    715626        /*
    716          * This is a bitmask.
     627         * This is a bit mask.
    717628         * 0 = flash fired
    718629         * 1 = return detected
     
    724635         */
    725636
    726         final Integer value = _directory.getInteger(ExifSubIFDDirectory.TAG_FLASH);
    727 
    728         if (value==null)
     637        final Integer value = _directory.getInteger(TAG_FLASH);
     638
     639        if (value == null)
    729640            return null;
    730641
    731642        StringBuilder sb = new StringBuilder();
    732643
    733         if ((value & 0x1)!=0)
     644        if ((value & 0x1) != 0)
    734645            sb.append("Flash fired");
    735646        else
     
    737648
    738649        // check if we're able to detect a return, before we mention it
    739         if ((value & 0x4)!=0)
    740         {
    741             if ((value & 0x2)!=0)
     650        if ((value & 0x4) != 0) {
     651            if ((value & 0x2) != 0)
    742652                sb.append(", return detected");
    743653            else
     
    745655        }
    746656
    747         if ((value & 0x10)!=0)
     657        if ((value & 0x10) != 0)
    748658            sb.append(", auto");
    749659
    750         if ((value & 0x40)!=0)
     660        if ((value & 0x40) != 0)
    751661            sb.append(", red-eye reduction");
    752662
     
    760670        // '17' standard light A, '18' standard light B, '19' standard light C, '20' D55,
    761671        // '21' D65, '22' D75, '255' other.
    762         final Integer value = _directory.getInteger(ExifSubIFDDirectory.TAG_WHITE_BALANCE);
    763         if (value==null)
     672        final Integer value = _directory.getInteger(TAG_WHITE_BALANCE);
     673        if (value == null)
    764674            return null;
    765675        switch (value) {
     
    786696        // '0' means unknown, '1' average, '2' center weighted average, '3' spot
    787697        // '4' multi-spot, '5' multi-segment, '6' partial, '255' other
    788         Integer value = _directory.getInteger(ExifSubIFDDirectory.TAG_METERING_MODE);
    789         if (value==null)
     698        Integer value = _directory.getInteger(TAG_METERING_MODE);
     699        if (value == null)
    790700            return null;
    791701        switch (value) {
     
    806716    public String getSubjectDistanceDescription()
    807717    {
    808         Rational value = _directory.getRational(ExifSubIFDDirectory.TAG_SUBJECT_DISTANCE);
    809         if (value==null)
     718        Rational value = _directory.getRational(TAG_SUBJECT_DISTANCE);
     719        if (value == null)
    810720            return null;
    811721        java.text.DecimalFormat formatter = new DecimalFormat("0.0##");
     
    816726    public String getCompressedAverageBitsPerPixelDescription()
    817727    {
    818         Rational value = _directory.getRational(ExifSubIFDDirectory.TAG_COMPRESSED_AVERAGE_BITS_PER_PIXEL);
    819         if (value==null)
     728        Rational value = _directory.getRational(TAG_COMPRESSED_AVERAGE_BITS_PER_PIXEL);
     729        if (value == null)
    820730            return null;
    821731        String ratio = value.toSimpleString(_allowDecimalRepresentationOfRationals);
    822         if (value.isInteger() && value.intValue() == 1) {
    823             return ratio + " bit/pixel";
    824         } else {
    825             return ratio + " bits/pixel";
    826         }
     732        return value.isInteger() && value.intValue() == 1
     733            ? ratio + " bit/pixel"
     734            : ratio + " bits/pixel";
    827735    }
    828736
     
    830738    public String getExposureTimeDescription()
    831739    {
    832         String value = _directory.getString(ExifSubIFDDirectory.TAG_EXPOSURE_TIME);
    833         return value==null ? null : value + " sec";
     740        String value = _directory.getString(TAG_EXPOSURE_TIME);
     741        return value == null ? null : value + " sec";
    834742    }
    835743
     
    847755        // description (spotted bug using a Canon EOS 300D)
    848756        // thanks also to Gli Blr for spotting this bug
    849         Float apexValue = _directory.getFloatObject(ExifSubIFDDirectory.TAG_SHUTTER_SPEED);
    850         if (apexValue==null)
    851             return null;
    852         if (apexValue<=1) {
    853             float apexPower = (float)(1/(Math.exp(apexValue*Math.log(2))));
     757        Float apexValue = _directory.getFloatObject(TAG_SHUTTER_SPEED);
     758        if (apexValue == null)
     759            return null;
     760        if (apexValue <= 1) {
     761            float apexPower = (float)(1 / (Math.exp(apexValue * Math.log(2))));
    854762            long apexPower10 = Math.round((double)apexPower * 10.0);
    855             float fApexPower = (float) apexPower10 / 10.0f;
     763            float fApexPower = (float)apexPower10 / 10.0f;
    856764            return fApexPower + " sec";
    857765        } else {
    858             int apexPower = (int)((Math.exp(apexValue*Math.log(2))));
     766            int apexPower = (int)((Math.exp(apexValue * Math.log(2))));
    859767            return "1/" + apexPower + " sec";
    860768        }
     
    879787        return sb.toString();
    880788*/
    881 
    882789    }
    883790
     
    885792    public String getFNumberDescription()
    886793    {
    887         Rational value = _directory.getRational(ExifSubIFDDirectory.TAG_FNUMBER);
    888         if (value==null)
     794        Rational value = _directory.getRational(TAG_FNUMBER);
     795        if (value == null)
    889796            return null;
    890797        return "F" + SimpleDecimalFormatter.format(value.doubleValue());
     
    897804        // '4' Three-chip color area sensor, '5' Color sequential area sensor
    898805        // '7' Trilinear sensor '8' Color sequential linear sensor,  'Other' reserved
    899         Integer value = _directory.getInteger(ExifSubIFDDirectory.TAG_SENSING_METHOD);
    900         if (value==null)
    901             return null;
    902         switch (value) {
    903             case 1: return "(Not defined)";
    904             case 2: return "One-chip color area sensor";
    905             case 3: return "Two-chip color area sensor";
    906             case 4: return "Three-chip color area sensor";
    907             case 5: return "Color sequential area sensor";
    908             case 7: return "Trilinear sensor";
    909             case 8: return "Color sequential linear sensor";
    910             default:
    911                 return "";
    912         }
     806        return getIndexedDescription(TAG_SENSING_METHOD,
     807            1,
     808            "(Not defined)",
     809            "One-chip color area sensor",
     810            "Two-chip color area sensor",
     811            "Three-chip color area sensor",
     812            "Color sequential area sensor",
     813            null,
     814            "Trilinear sensor",
     815            "Color sequential linear sensor"
     816        );
    913817    }
    914818
     
    916820    public String getComponentConfigurationDescription()
    917821    {
    918         int[] components = _directory.getIntArray(ExifSubIFDDirectory.TAG_COMPONENTS_CONFIGURATION);
    919         if (components==null)
     822        int[] components = _directory.getIntArray(TAG_COMPONENTS_CONFIGURATION);
     823        if (components == null)
    920824            return null;
    921825        String[] componentStrings = {"", "Y", "Cb", "Cr", "R", "G", "B"};
  • trunk/src/com/drew/metadata/exif/ExifSubIFDDirectory.java

    r6127 r8132  
    11/*
    2  * Copyright 2002-2012 Drew Noakes
     2 * Copyright 2002-2015 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    1616 * More information about this project is available at:
    1717 *
    18  *    http://drewnoakes.com/code/exif/
    19  *    http://code.google.com/p/metadata-extractor/
     18 *    https://drewnoakes.com/code/exif/
     19 *    https://github.com/drewnoakes/metadata-extractor
    2020 */
    2121package com.drew.metadata.exif;
     
    2929 * Describes Exif tags from the SubIFD directory.
    3030 *
    31  * @author Drew Noakes http://drewnoakes.com
     31 * @author Drew Noakes https://drewnoakes.com
    3232 */
    3333public class ExifSubIFDDirectory extends Directory
     
    133133    /**
    134134     * Indicates the Opto-Electric Conversion Function (OECF) specified in ISO 14524.
    135      * <p/>
     135     * <p>
    136136     * OECF is the relationship between the camera optical input and the image values.
    137      * <p/>
     137     * <p>
    138138     * The values are:
    139139     * <ul>
     
    260260     */
    261261    public static final int TAG_FOCAL_LENGTH = 0x920A;
     262
     263    /**
     264     * This tag holds the Exif Makernote. Makernotes are free to be in any format, though they are often IFDs.
     265     * To determine the format, we consider the starting bytes of the makernote itself and sometimes the
     266     * camera model and make.
     267     * <p>
     268     * The component count for this tag includes all of the bytes needed for the makernote.
     269     */
     270    public static final int TAG_MAKERNOTE = 0x927C;
     271
    262272    public static final int TAG_USER_COMMENT = 0x9286;
     273
    263274    public static final int TAG_SUBSECOND_TIME = 0x9290;
    264275    public static final int TAG_SUBSECOND_TIME_ORIGINAL = 0x9291;
    265276    public static final int TAG_SUBSECOND_TIME_DIGITIZED = 0x9292;
     277
    266278    public static final int TAG_FLASHPIX_VERSION = 0xA000;
    267279    /**
     
    274286    public static final int TAG_EXIF_IMAGE_HEIGHT = 0xA003;
    275287    public static final int TAG_RELATED_SOUND_FILE = 0xA004;
    276     public static final int TAG_FOCAL_PLANE_X_RES = 0xA20E;
    277     public static final int TAG_FOCAL_PLANE_Y_RES = 0xA20F;
     288
     289    /** This tag is a pointer to the Exif Interop IFD. */
     290    public static final int TAG_INTEROP_OFFSET = 0xA005;
     291
     292    public static final int TAG_FOCAL_PLANE_X_RESOLUTION = 0xA20E;
     293    public static final int TAG_FOCAL_PLANE_Y_RESOLUTION = 0xA20F;
    278294    /**
    279295     * Unit of FocalPlaneXResolution/FocalPlaneYResolution. '1' means no-unit,
     
    285301     * been changed to use value '2' but it doesn't match to actual value also.
    286302     */
    287     public static final int TAG_FOCAL_PLANE_UNIT = 0xA210;
     303    public static final int TAG_FOCAL_PLANE_RESOLUTION_UNIT = 0xA210;
    288304    public static final int TAG_EXPOSURE_INDEX = 0xA215;
    289305    public static final int TAG_SENSING_METHOD = 0xA217;
     
    512528        _tagNameMap.put(0x0200, "JPEG Proc");
    513529        _tagNameMap.put(TAG_COMPRESSED_AVERAGE_BITS_PER_PIXEL, "Compressed Bits Per Pixel");
    514         _tagNameMap.put(0x927C, "Maker Note");
    515         _tagNameMap.put(0xA005, "Interoperability Offset");
     530        _tagNameMap.put(TAG_MAKERNOTE, "Makernote");
     531        _tagNameMap.put(TAG_INTEROP_OFFSET, "Interoperability Offset");
    516532
    517533        _tagNameMap.put(TAG_NEW_SUBFILE_TYPE, "New Subfile Type");
     
    586602        _tagNameMap.put(TAG_SPATIAL_FREQ_RESPONSE_2, "Spatial Frequency Response");
    587603        // 0x920E in TIFF/EP
    588         _tagNameMap.put(TAG_FOCAL_PLANE_X_RES, "Focal Plane X Resolution");
     604        _tagNameMap.put(TAG_FOCAL_PLANE_X_RESOLUTION, "Focal Plane X Resolution");
    589605        // 0x920F in TIFF/EP
    590         _tagNameMap.put(TAG_FOCAL_PLANE_Y_RES, "Focal Plane Y Resolution");
     606        _tagNameMap.put(TAG_FOCAL_PLANE_Y_RESOLUTION, "Focal Plane Y Resolution");
    591607        // 0x9210 in TIFF/EP
    592         _tagNameMap.put(TAG_FOCAL_PLANE_UNIT, "Focal Plane Resolution Unit");
     608        _tagNameMap.put(TAG_FOCAL_PLANE_RESOLUTION_UNIT, "Focal Plane Resolution Unit");
    593609        // 0x9214 in TIFF/EP
    594610        _tagNameMap.put(TAG_SUBJECT_LOCATION_2, "Subject Location");
     
    614630        _tagNameMap.put(TAG_SUBJECT_DISTANCE_RANGE, "Subject Distance Range");
    615631        _tagNameMap.put(TAG_IMAGE_UNIQUE_ID, "Unique Image ID");
    616        
     632
    617633        _tagNameMap.put(TAG_CAMERA_OWNER_NAME, "Camera Owner Name");
    618634        _tagNameMap.put(TAG_BODY_SERIAL_NUMBER, "Body Serial Number");
     
    634650    }
    635651
     652    @Override
    636653    @NotNull
    637654    public String getName()
     
    640657    }
    641658
     659    @Override
    642660    @NotNull
    643661    protected HashMap<Integer, String> getTagNameMap()
  • trunk/src/com/drew/metadata/exif/ExifThumbnailDescriptor.java

    r6127 r8132  
    11/*
    2  * Copyright 2002-2012 Drew Noakes
     2 * Copyright 2002-2015 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    1616 * More information about this project is available at:
    1717 *
    18  *    http://drewnoakes.com/code/exif/
    19  *    http://code.google.com/p/metadata-extractor/
     18 *    https://drewnoakes.com/code/exif/
     19 *    https://github.com/drewnoakes/metadata-extractor
    2020 */
    2121
     
    2727import com.drew.metadata.TagDescriptor;
    2828
     29import static com.drew.metadata.exif.ExifThumbnailDirectory.*;
     30
    2931/**
    30  * Provides human-readable string representations of tag values stored in a <code>ExifThumbnailDirectory</code>.
    31  *
    32  * @author Drew Noakes http://drewnoakes.com
     32 * Provides human-readable string representations of tag values stored in a {@link ExifThumbnailDirectory}.
     33 *
     34 * @author Drew Noakes https://drewnoakes.com
    3335 */
    3436public class ExifThumbnailDescriptor extends TagDescriptor<ExifThumbnailDirectory>
     
    5254
    5355    /**
    54      * Returns a descriptive value of the the specified tag for this image.
     56     * Returns a descriptive value of the specified tag for this image.
    5557     * Where possible, known values will be substituted here in place of the raw
    5658     * tokens actually kept in the Exif segment.  If no substitution is
    5759     * available, the value provided by getString(int) will be returned.
     60     *
    5861     * @param tagType the tag to find a description for
    5962     * @return a description of the image's value for the specified tag, or
    6063     *         <code>null</code> if the tag hasn't been defined.
    6164     */
     65    @Override
    6266    @Nullable
    6367    public String getDescription(int tagType)
    6468    {
    6569        switch (tagType) {
    66             case ExifThumbnailDirectory.TAG_ORIENTATION:
     70            case TAG_ORIENTATION:
    6771                return getOrientationDescription();
    68             case ExifThumbnailDirectory.TAG_RESOLUTION_UNIT:
     72            case TAG_RESOLUTION_UNIT:
    6973                return getResolutionDescription();
    70             case ExifThumbnailDirectory.TAG_YCBCR_POSITIONING:
     74            case TAG_YCBCR_POSITIONING:
    7175                return getYCbCrPositioningDescription();
    72             case ExifThumbnailDirectory.TAG_X_RESOLUTION:
     76            case TAG_X_RESOLUTION:
    7377                return getXResolutionDescription();
    74             case ExifThumbnailDirectory.TAG_Y_RESOLUTION:
     78            case TAG_Y_RESOLUTION:
    7579                return getYResolutionDescription();
    76             case ExifThumbnailDirectory.TAG_THUMBNAIL_OFFSET:
     80            case TAG_THUMBNAIL_OFFSET:
    7781                return getThumbnailOffsetDescription();
    78             case ExifThumbnailDirectory.TAG_THUMBNAIL_LENGTH:
     82            case TAG_THUMBNAIL_LENGTH:
    7983                return getThumbnailLengthDescription();
    80             case ExifThumbnailDirectory.TAG_THUMBNAIL_IMAGE_WIDTH:
     84            case TAG_THUMBNAIL_IMAGE_WIDTH:
    8185                return getThumbnailImageWidthDescription();
    82             case ExifThumbnailDirectory.TAG_THUMBNAIL_IMAGE_HEIGHT:
     86            case TAG_THUMBNAIL_IMAGE_HEIGHT:
    8387                return getThumbnailImageHeightDescription();
    84             case ExifThumbnailDirectory.TAG_BITS_PER_SAMPLE:
     88            case TAG_BITS_PER_SAMPLE:
    8589                return getBitsPerSampleDescription();
    86             case ExifThumbnailDirectory.TAG_THUMBNAIL_COMPRESSION:
     90            case TAG_THUMBNAIL_COMPRESSION:
    8791                return getCompressionDescription();
    88             case ExifThumbnailDirectory.TAG_PHOTOMETRIC_INTERPRETATION:
     92            case TAG_PHOTOMETRIC_INTERPRETATION:
    8993                return getPhotometricInterpretationDescription();
    90             case ExifThumbnailDirectory.TAG_ROWS_PER_STRIP:
     94            case TAG_ROWS_PER_STRIP:
    9195                return getRowsPerStripDescription();
    92             case ExifThumbnailDirectory.TAG_STRIP_BYTE_COUNTS:
     96            case TAG_STRIP_BYTE_COUNTS:
    9397                return getStripByteCountsDescription();
    94             case ExifThumbnailDirectory.TAG_SAMPLES_PER_PIXEL:
     98            case TAG_SAMPLES_PER_PIXEL:
    9599                return getSamplesPerPixelDescription();
    96             case ExifThumbnailDirectory.TAG_PLANAR_CONFIGURATION:
     100            case TAG_PLANAR_CONFIGURATION:
    97101                return getPlanarConfigurationDescription();
    98             case ExifThumbnailDirectory.TAG_YCBCR_SUBSAMPLING:
     102            case TAG_YCBCR_SUBSAMPLING:
    99103                return getYCbCrSubsamplingDescription();
    100             case ExifThumbnailDirectory.TAG_REFERENCE_BLACK_WHITE:
     104            case TAG_REFERENCE_BLACK_WHITE:
    101105                return getReferenceBlackWhiteDescription();
    102106            default:
     
    108112    public String getReferenceBlackWhiteDescription()
    109113    {
    110         int[] ints = _directory.getIntArray(ExifThumbnailDirectory.TAG_REFERENCE_BLACK_WHITE);
    111         if (ints==null)
     114        int[] ints = _directory.getIntArray(TAG_REFERENCE_BLACK_WHITE);
     115        if (ints == null || ints.length < 6)
    112116            return null;
    113117        int blackR = ints[0];
     
    117121        int blackB = ints[4];
    118122        int whiteB = ints[5];
    119         return "[" + blackR + "," + blackG + "," + blackB + "] " +
    120                "[" + whiteR + "," + whiteG + "," + whiteB + "]";
     123        return String.format("[%d,%d,%d] [%d,%d,%d]", blackR, blackG, blackB, whiteR, whiteG, whiteB);
    121124    }
    122125
     
    124127    public String getYCbCrSubsamplingDescription()
    125128    {
    126         int[] positions = _directory.getIntArray(ExifThumbnailDirectory.TAG_YCBCR_SUBSAMPLING);
    127         if (positions==null || positions.length < 2)
     129        int[] positions = _directory.getIntArray(TAG_YCBCR_SUBSAMPLING);
     130        if (positions == null || positions.length < 2)
    128131            return null;
    129132        if (positions[0] == 2 && positions[1] == 1) {
     
    143146        // pixel. If value is '2', Y/Cb/Cr value is separated and stored to Y plane/Cb plane/Cr
    144147        // plane format.
    145         Integer value = _directory.getInteger(ExifThumbnailDirectory.TAG_PLANAR_CONFIGURATION);
    146         if (value==null)
    147             return null;
    148         switch (value) {
    149             case 1: return "Chunky (contiguous for each subsampling pixel)";
    150             case 2: return "Separate (Y-plane/Cb-plane/Cr-plane format)";
    151             default:
    152                 return "Unknown configuration";
    153         }
     148        return getIndexedDescription(TAG_PLANAR_CONFIGURATION,
     149            1,
     150            "Chunky (contiguous for each subsampling pixel)",
     151            "Separate (Y-plane/Cb-plane/Cr-plane format)"
     152        );
    154153    }
    155154
     
    157156    public String getSamplesPerPixelDescription()
    158157    {
    159         String value = _directory.getString(ExifThumbnailDirectory.TAG_SAMPLES_PER_PIXEL);
    160         return value==null ? null : value + " samples/pixel";
     158        String value = _directory.getString(TAG_SAMPLES_PER_PIXEL);
     159        return value == null ? null : value + " samples/pixel";
    161160    }
    162161
     
    164163    public String getRowsPerStripDescription()
    165164    {
    166         final String value = _directory.getString(ExifThumbnailDirectory.TAG_ROWS_PER_STRIP);
    167         return value==null ? null : value + " rows/strip";
     165        final String value = _directory.getString(TAG_ROWS_PER_STRIP);
     166        return value == null ? null : value + " rows/strip";
    168167    }
    169168
     
    171170    public String getStripByteCountsDescription()
    172171    {
    173         final String value = _directory.getString(ExifThumbnailDirectory.TAG_STRIP_BYTE_COUNTS);
    174         return value==null ? null : value + " bytes";
     172        final String value = _directory.getString(TAG_STRIP_BYTE_COUNTS);
     173        return value == null ? null : value + " bytes";
    175174    }
    176175
     
    179178    {
    180179        // Shows the color space of the image data components
    181         Integer value = _directory.getInteger(ExifThumbnailDirectory.TAG_PHOTOMETRIC_INTERPRETATION);
    182         if (value==null)
     180        Integer value = _directory.getInteger(TAG_PHOTOMETRIC_INTERPRETATION);
     181        if (value == null)
    183182            return null;
    184183        switch (value) {
     
    205204    public String getCompressionDescription()
    206205    {
    207         Integer value = _directory.getInteger(ExifThumbnailDirectory.TAG_THUMBNAIL_COMPRESSION);
    208         if (value==null)
     206        Integer value = _directory.getInteger(TAG_THUMBNAIL_COMPRESSION);
     207        if (value == null)
    209208            return null;
    210209        switch (value) {
     
    244243    public String getBitsPerSampleDescription()
    245244    {
    246         String value = _directory.getString(ExifThumbnailDirectory.TAG_BITS_PER_SAMPLE);
    247         return value==null ? null : value + " bits/component/pixel";
     245        String value = _directory.getString(TAG_BITS_PER_SAMPLE);
     246        return value == null ? null : value + " bits/component/pixel";
    248247    }
    249248
     
    251250    public String getThumbnailImageWidthDescription()
    252251    {
    253         String value = _directory.getString(ExifThumbnailDirectory.TAG_THUMBNAIL_IMAGE_WIDTH);
    254         return value==null ? null : value + " pixels";
     252        String value = _directory.getString(TAG_THUMBNAIL_IMAGE_WIDTH);
     253        return value == null ? null : value + " pixels";
    255254    }
    256255
     
    258257    public String getThumbnailImageHeightDescription()
    259258    {
    260         String value = _directory.getString(ExifThumbnailDirectory.TAG_THUMBNAIL_IMAGE_HEIGHT);
    261         return value==null ? null : value + " pixels";
     259        String value = _directory.getString(TAG_THUMBNAIL_IMAGE_HEIGHT);
     260        return value == null ? null : value + " pixels";
    262261    }
    263262
     
    265264    public String getThumbnailLengthDescription()
    266265    {
    267         String value = _directory.getString(ExifThumbnailDirectory.TAG_THUMBNAIL_LENGTH);
    268         return value==null ? null : value + " bytes";
     266        String value = _directory.getString(TAG_THUMBNAIL_LENGTH);
     267        return value == null ? null : value + " bytes";
    269268    }
    270269
     
    272271    public String getThumbnailOffsetDescription()
    273272    {
    274         String value = _directory.getString(ExifThumbnailDirectory.TAG_THUMBNAIL_OFFSET);
    275         return value==null ? null : value + " bytes";
     273        String value = _directory.getString(TAG_THUMBNAIL_OFFSET);
     274        return value == null ? null : value + " bytes";
    276275    }
    277276
     
    279278    public String getYResolutionDescription()
    280279    {
    281         Rational value = _directory.getRational(ExifThumbnailDirectory.TAG_Y_RESOLUTION);
    282         if (value==null)
     280        Rational value = _directory.getRational(TAG_Y_RESOLUTION);
     281        if (value == null)
    283282            return null;
    284283        final String unit = getResolutionDescription();
    285284        return value.toSimpleString(_allowDecimalRepresentationOfRationals) +
    286                 " dots per " +
    287                 (unit==null ? "unit" : unit.toLowerCase());
     285            " dots per " +
     286            (unit == null ? "unit" : unit.toLowerCase());
    288287    }
    289288
     
    291290    public String getXResolutionDescription()
    292291    {
    293         Rational value = _directory.getRational(ExifThumbnailDirectory.TAG_X_RESOLUTION);
    294         if (value==null)
     292        Rational value = _directory.getRational(TAG_X_RESOLUTION);
     293        if (value == null)
    295294            return null;
    296295        final String unit = getResolutionDescription();
    297296        return value.toSimpleString(_allowDecimalRepresentationOfRationals) +
    298                 " dots per " +
    299                 (unit==null ? "unit" : unit.toLowerCase());
     297            " dots per " +
     298            (unit == null ? "unit" : unit.toLowerCase());
    300299    }
    301300
     
    303302    public String getYCbCrPositioningDescription()
    304303    {
    305         Integer value = _directory.getInteger(ExifThumbnailDirectory.TAG_YCBCR_POSITIONING);
    306         if (value==null)
    307             return null;
    308         switch (value) {
    309             case 1: return "Center of pixel array";
    310             case 2: return "Datum point";
    311             default:
    312                 return String.valueOf(value);
    313         }
     304        return getIndexedDescription(TAG_YCBCR_POSITIONING, 1, "Center of pixel array", "Datum point");
    314305    }
    315306
     
    317308    public String getOrientationDescription()
    318309    {
    319         Integer value = _directory.getInteger(ExifThumbnailDirectory.TAG_ORIENTATION);
    320         if (value==null)
    321             return null;
    322         switch (value) {
    323             case 1: return "Top, left side (Horizontal / normal)";
    324             case 2: return "Top, right side (Mirror horizontal)";
    325             case 3: return "Bottom, right side (Rotate 180)";
    326             case 4: return "Bottom, left side (Mirror vertical)";
    327             case 5: return "Left side, top (Mirror horizontal and rotate 270 CW)";
    328             case 6: return "Right side, top (Rotate 90 CW)";
    329             case 7: return "Right side, bottom (Mirror horizontal and rotate 90 CW)";
    330             case 8: return "Left side, bottom (Rotate 270 CW)";
    331             default:
    332                 return String.valueOf(value);
    333         }
     310        return getIndexedDescription(TAG_ORIENTATION, 1,
     311            "Top, left side (Horizontal / normal)",
     312            "Top, right side (Mirror horizontal)",
     313            "Bottom, right side (Rotate 180)",
     314            "Bottom, left side (Mirror vertical)",
     315            "Left side, top (Mirror horizontal and rotate 270 CW)",
     316            "Right side, top (Rotate 90 CW)",
     317            "Right side, bottom (Mirror horizontal and rotate 90 CW)",
     318            "Left side, bottom (Rotate 270 CW)");
    334319    }
    335320
     
    338323    {
    339324        // '1' means no-unit, '2' means inch, '3' means centimeter. Default value is '2'(inch)
    340         Integer value = _directory.getInteger(ExifThumbnailDirectory.TAG_RESOLUTION_UNIT);
    341         if (value==null)
    342             return null;
    343         switch (value) {
    344             case 1: return "(No unit)";
    345             case 2: return "Inch";
    346             case 3: return "cm";
    347             default:
    348                 return "";
    349         }
     325        return getIndexedDescription(TAG_RESOLUTION_UNIT, 1, "(No unit)", "Inch", "cm");
    350326    }
    351327}
  • trunk/src/com/drew/metadata/exif/ExifThumbnailDirectory.java

    r6127 r8132  
    11/*
    2  * Copyright 2002-2012 Drew Noakes
     2 * Copyright 2002-2015 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    1616 * More information about this project is available at:
    1717 *
    18  *    http://drewnoakes.com/code/exif/
    19  *    http://code.google.com/p/metadata-extractor/
     18 *    https://drewnoakes.com/code/exif/
     19 *    https://github.com/drewnoakes/metadata-extractor
    2020 */
    2121
     
    3434 * One of several Exif directories.  Otherwise known as IFD1, this directory holds information about an embedded thumbnail image.
    3535 *
    36  * @author Drew Noakes http://drewnoakes.com
     36 * @author Drew Noakes https://drewnoakes.com
    3737 */
    3838public class ExifThumbnailDirectory extends Directory
     
    5757     * 7 = JPEG
    5858     * 8 = Adobe Deflate
    59      * 9 = JBIG B&W
     59     * 9 = JBIG B&amp;W
    6060     * 10 = JBIG Color
    6161     * 32766 = Next
     
    9898    public static final int TAG_PHOTOMETRIC_INTERPRETATION = 0x0106;
    9999
    100     /** The position in the file of raster data. */
     100    /**
     101     * The position in the file of raster data.
     102     */
    101103    public static final int TAG_STRIP_OFFSETS = 0x0111;
    102104    public static final int TAG_ORIENTATION = 0x0112;
    103     /** Each pixel is composed of this many samples. */
     105    /**
     106     * Each pixel is composed of this many samples.
     107     */
    104108    public static final int TAG_SAMPLES_PER_PIXEL = 0x0115;
    105     /** The raster is codified by a single block of data holding this many rows. */
     109    /**
     110     * The raster is codified by a single block of data holding this many rows.
     111     */
    106112    public static final int TAG_ROWS_PER_STRIP = 0x116;
    107     /** The size of the raster data in bytes. */
     113    /**
     114     * The size of the raster data in bytes.
     115     */
    108116    public static final int TAG_STRIP_BYTE_COUNTS = 0x0117;
    109117    /**
     
    117125    public static final int TAG_PLANAR_CONFIGURATION = 0x011C;
    118126    public static final int TAG_RESOLUTION_UNIT = 0x0128;
    119     /** The offset to thumbnail image bytes. */
     127    /**
     128     * The offset to thumbnail image bytes.
     129     */
    120130    public static final int TAG_THUMBNAIL_OFFSET = 0x0201;
    121     /** The size of the thumbnail image data in bytes. */
     131    /**
     132     * The size of the thumbnail image data in bytes.
     133     */
    122134    public static final int TAG_THUMBNAIL_LENGTH = 0x0202;
    123135    public static final int TAG_YCBCR_COEFFICIENTS = 0x0211;
     
    129141    protected static final HashMap<Integer, String> _tagNameMap = new HashMap<Integer, String>();
    130142
    131     static
    132     {
     143    static {
    133144        _tagNameMap.put(TAG_THUMBNAIL_IMAGE_WIDTH, "Thumbnail Image Width");
    134145        _tagNameMap.put(TAG_THUMBNAIL_IMAGE_HEIGHT, "Thumbnail Image Height");
     
    161172    }
    162173
     174    @Override
    163175    @NotNull
    164176    public String getName()
     
    167179    }
    168180
     181    @Override
    169182    @NotNull
    170183    protected HashMap<Integer, String> getTagNameMap()
     
    193206        byte[] data = _thumbnailData;
    194207
    195         if (data==null)
     208        if (data == null)
    196209            throw new MetadataException("No thumbnail data exists.");
    197210
     
    201214            stream.write(data);
    202215        } finally {
    203             if (stream!=null)
     216            if (stream != null)
    204217                stream.close();
    205218        }
  • trunk/src/com/drew/metadata/exif/GpsDescriptor.java

    r6127 r8132  
    11/*
    2  * Copyright 2002-2012 Drew Noakes
     2 * Copyright 2002-2015 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    1616 * More information about this project is available at:
    1717 *
    18  *    http://drewnoakes.com/code/exif/
    19  *    http://code.google.com/p/metadata-extractor/
     18 *    https://drewnoakes.com/code/exif/
     19 *    https://github.com/drewnoakes/metadata-extractor
    2020 */
    2121package com.drew.metadata.exif;
     
    2929import java.text.DecimalFormat;
    3030
     31import static com.drew.metadata.exif.GpsDirectory.*;
     32
    3133/**
    32  * Provides human-readable string representations of tag values stored in a <code>GpsDirectory</code>.
    33  *
    34  * @author Drew Noakes http://drewnoakes.com
     34 * Provides human-readable string representations of tag values stored in a {@link GpsDirectory}.
     35 *
     36 * @author Drew Noakes https://drewnoakes.com
    3537 */
    3638public class GpsDescriptor extends TagDescriptor<GpsDirectory>
     
    4143    }
    4244
     45    @Override
    4346    @Nullable
    4447    public String getDescription(int tagType)
    4548    {
    4649        switch (tagType) {
    47             case GpsDirectory.TAG_GPS_VERSION_ID:
     50            case TAG_VERSION_ID:
    4851                return getGpsVersionIdDescription();
    49             case GpsDirectory.TAG_GPS_ALTITUDE:
     52            case TAG_ALTITUDE:
    5053                return getGpsAltitudeDescription();
    51             case GpsDirectory.TAG_GPS_ALTITUDE_REF:
     54            case TAG_ALTITUDE_REF:
    5255                return getGpsAltitudeRefDescription();
    53             case GpsDirectory.TAG_GPS_STATUS:
     56            case TAG_STATUS:
    5457                return getGpsStatusDescription();
    55             case GpsDirectory.TAG_GPS_MEASURE_MODE:
     58            case TAG_MEASURE_MODE:
    5659                return getGpsMeasureModeDescription();
    57             case GpsDirectory.TAG_GPS_SPEED_REF:
     60            case TAG_SPEED_REF:
    5861                return getGpsSpeedRefDescription();
    59             case GpsDirectory.TAG_GPS_TRACK_REF:
    60             case GpsDirectory.TAG_GPS_IMG_DIRECTION_REF:
    61             case GpsDirectory.TAG_GPS_DEST_BEARING_REF:
     62            case TAG_TRACK_REF:
     63            case TAG_IMG_DIRECTION_REF:
     64            case TAG_DEST_BEARING_REF:
    6265                return getGpsDirectionReferenceDescription(tagType);
    63             case GpsDirectory.TAG_GPS_TRACK:
    64             case GpsDirectory.TAG_GPS_IMG_DIRECTION:
    65             case GpsDirectory.TAG_GPS_DEST_BEARING:
     66            case TAG_TRACK:
     67            case TAG_IMG_DIRECTION:
     68            case TAG_DEST_BEARING:
    6669                return getGpsDirectionDescription(tagType);
    67             case GpsDirectory.TAG_GPS_DEST_DISTANCE_REF:
     70            case TAG_DEST_DISTANCE_REF:
    6871                return getGpsDestinationReferenceDescription();
    69             case GpsDirectory.TAG_GPS_TIME_STAMP:
     72            case TAG_TIME_STAMP:
    7073                return getGpsTimeStampDescription();
    71             case GpsDirectory.TAG_GPS_LONGITUDE:
     74            case TAG_LONGITUDE:
    7275                // three rational numbers -- displayed in HH"MM"SS.ss
    7376                return getGpsLongitudeDescription();
    74             case GpsDirectory.TAG_GPS_LATITUDE:
     77            case TAG_LATITUDE:
    7578                // three rational numbers -- displayed in HH"MM"SS.ss
    7679                return getGpsLatitudeDescription();
    77             case GpsDirectory.TAG_GPS_DIFFERENTIAL:
     80            case TAG_DIFFERENTIAL:
    7881                return getGpsDifferentialDescription();
    7982            default:
     
    8588    private String getGpsVersionIdDescription()
    8689    {
    87         return convertBytesToVersionString(_directory.getIntArray(GpsDirectory.TAG_GPS_VERSION_ID), 1);
     90        return getVersionBytesDescription(TAG_VERSION_ID, 1);
    8891    }
    8992
     
    9295    {
    9396        GeoLocation location = _directory.getGeoLocation();
    94 
    95         if (location == null)
    96             return null;
    97 
    98         return GeoLocation.decimalToDegreesMinutesSecondsString(location.getLatitude());
     97        return location == null ? null : GeoLocation.decimalToDegreesMinutesSecondsString(location.getLatitude());
    9998    }
    10099
     
    103102    {
    104103        GeoLocation location = _directory.getGeoLocation();
    105 
    106         if (location == null)
    107             return null;
    108 
    109         return GeoLocation.decimalToDegreesMinutesSecondsString(location.getLongitude());
     104        return location == null ? null : GeoLocation.decimalToDegreesMinutesSecondsString(location.getLongitude());
    110105    }
    111106
     
    114109    {
    115110        // time in hour, min, sec
    116         int[] timeComponents = _directory.getIntArray(GpsDirectory.TAG_GPS_TIME_STAMP);
    117         if (timeComponents==null)
    118             return null;
    119         StringBuilder description = new StringBuilder();
    120         description.append(timeComponents[0]);
    121         description.append(":");
    122         description.append(timeComponents[1]);
    123         description.append(":");
    124         description.append(timeComponents[2]);
    125         description.append(" UTC");
    126         return description.toString();
     111        int[] timeComponents = _directory.getIntArray(TAG_TIME_STAMP);
     112        return timeComponents == null ? null : String.format("%d:%d:%d UTC", timeComponents[0], timeComponents[1], timeComponents[2]);
    127113    }
    128114
     
    130116    public String getGpsDestinationReferenceDescription()
    131117    {
    132         final String value = _directory.getString(GpsDirectory.TAG_GPS_DEST_DISTANCE_REF);
    133         if (value==null)
     118        final String value = _directory.getString(TAG_DEST_DISTANCE_REF);
     119        if (value == null)
    134120            return null;
    135121        String distanceRef = value.trim();
     
    151137        // provide a decimal version of rational numbers in the description, to avoid strings like "35334/199 degrees"
    152138        String value = angle != null
    153                 ? new DecimalFormat("0.##").format(angle.doubleValue())
    154                 : _directory.getString(tagType);
    155         if (value==null || value.trim().length()==0)
    156             return null;
    157         return value.trim() + " degrees";
     139            ? new DecimalFormat("0.##").format(angle.doubleValue())
     140            : _directory.getString(tagType);
     141        return value == null || value.trim().length() == 0 ? null : value.trim() + " degrees";
    158142    }
    159143
     
    162146    {
    163147        final String value = _directory.getString(tagType);
    164         if (value==null)
     148        if (value == null)
    165149            return null;
    166150        String gpsDistRef = value.trim();
     
    177161    public String getGpsSpeedRefDescription()
    178162    {
    179         final String value = _directory.getString(GpsDirectory.TAG_GPS_SPEED_REF);
    180         if (value==null)
     163        final String value = _directory.getString(TAG_SPEED_REF);
     164        if (value == null)
    181165            return null;
    182166        String gpsSpeedRef = value.trim();
     
    195179    public String getGpsMeasureModeDescription()
    196180    {
    197         final String value = _directory.getString(GpsDirectory.TAG_GPS_MEASURE_MODE);
    198         if (value==null)
     181        final String value = _directory.getString(TAG_MEASURE_MODE);
     182        if (value == null)
    199183            return null;
    200184        String gpsSpeedMeasureMode = value.trim();
     
    211195    public String getGpsStatusDescription()
    212196    {
    213         final String value = _directory.getString(GpsDirectory.TAG_GPS_STATUS);
    214         if (value==null)
     197        final String value = _directory.getString(TAG_STATUS);
     198        if (value == null)
    215199            return null;
    216200        String gpsStatus = value.trim();
     
    227211    public String getGpsAltitudeRefDescription()
    228212    {
    229         Integer value = _directory.getInteger(GpsDirectory.TAG_GPS_ALTITUDE_REF);
    230         if (value==null)
    231             return null;
    232         if (value == 0)
    233             return "Sea level";
    234         if (value == 1)
    235             return "Below sea level";
    236         return "Unknown (" + value + ")";
     213        return getIndexedDescription(TAG_ALTITUDE_REF, "Sea level", "Below sea level");
    237214    }
    238215
     
    240217    public String getGpsAltitudeDescription()
    241218    {
    242         final Rational value = _directory.getRational(GpsDirectory.TAG_GPS_ALTITUDE);
    243         if (value==null)
    244             return null;
    245         return value.intValue() + " metres";
     219        final Rational value = _directory.getRational(TAG_ALTITUDE);
     220        return value == null ? null : value.intValue() + " metres";
    246221    }
    247222
     
    249224    public String getGpsDifferentialDescription()
    250225    {
    251         final Integer value = _directory.getInteger(GpsDirectory.TAG_GPS_DIFFERENTIAL);
    252         if (value==null)
    253             return null;
    254         if (value == 0)
    255             return "No Correction";
    256         if (value == 1)
    257             return "Differential Corrected";
    258         return "Unknown (" + value + ")";
     226        return getIndexedDescription(TAG_DIFFERENTIAL, "No Correction", "Differential Corrected");
    259227    }
    260228
     
    263231    {
    264232        GeoLocation location = _directory.getGeoLocation();
    265 
    266         if (location == null)
    267             return null;
    268 
    269         return location.toDMSString();
     233        return location == null ? null : location.toDMSString();
    270234    }
    271235}
  • trunk/src/com/drew/metadata/exif/GpsDirectory.java

    r6127 r8132  
    11/*
    2  * Copyright 2002-2012 Drew Noakes
     2 * Copyright 2002-2015 Drew Noakes
    33 *
    44 *    Licensed under the Apache License, Version 2.0 (the "License");
     
    1616 * More information about this project is available at:
    1717 *
    18  *    http://drewnoakes.com/code/exif/
    19  *    http://code.google.com/p/metadata-extractor/
     18 *    https://drewnoakes.com/code/exif/
     19 *    https://github.com/drewnoakes/metadata-extractor
    2020 */
    2121package com.drew.metadata.exif;
     
    3232 * Describes Exif tags that contain Global Positioning System (GPS) data.
    3333 *
    34  * @author Drew Noakes http://drewnoakes.com
     34 * @author Drew Noakes https://drewnoakes.com
    3535 */
    3636public class GpsDirectory extends Directory
    3737{
    3838    /** GPS tag version GPSVersionID 0 0 BYTE 4 */
    39     public static final int TAG_GPS_VERSION_ID = 0x0000;
     39    public static final int TAG_VERSION_ID = 0x0000;
    4040    /** North or South Latitude GPSLatitudeRef 1 1 ASCII 2 */
    41     public static final int TAG_GPS_LATITUDE_REF = 0x0001;
     41    public static final int TAG_LATITUDE_REF = 0x0001;
    4242    /** Latitude GPSLatitude 2 2 RATIONAL 3 */
    43     public static final int TAG_GPS_LATITUDE = 0x0002;
     43    public static final int TAG_LATITUDE = 0x0002;
    4444    /** East or West Longitude GPSLongitudeRef 3 3 ASCII 2 */
    45     public static final int TAG_GPS_LONGITUDE_REF = 0x0003;
     45    public static final int TAG_LONGITUDE_REF = 0x0003;
    4646    /** Longitude GPSLongitude 4 4 RATIONAL 3 */
    47     public static final int TAG_GPS_LONGITUDE = 0x0004;
     47    public static final int TAG_LONGITUDE = 0x0004;
    4848    /** Altitude reference GPSAltitudeRef 5 5 BYTE 1 */
    49     public static final int TAG_GPS_ALTITUDE_REF = 0x0005;
     49    public static final int TAG_ALTITUDE_REF = 0x0005;
    5050    /** Altitude GPSAltitude 6 6 RATIONAL 1 */
    51     public static final int TAG_GPS_ALTITUDE = 0x0006;
     51    public static final int TAG_ALTITUDE = 0x0006;
    5252    /** GPS time (atomic clock) GPSTimeStamp 7 7 RATIONAL 3 */
    53     public static final int TAG_GPS_TIME_STAMP = 0x0007;
     53    public static final int TAG_TIME_STAMP = 0x0007;
    5454    /** GPS satellites used for measurement GPSSatellites 8 8 ASCII Any */
    55     public static final int TAG_GPS_SATELLITES = 0x0008;
     55    public static final int TAG_SATELLITES = 0x0008;
    5656    /** GPS receiver status GPSStatus 9 9 ASCII 2 */
    57     public static final int TAG_GPS_STATUS = 0x0009;
     57    public static final int TAG_STATUS = 0x0009;
    5858    /** GPS measurement mode GPSMeasureMode 10 A ASCII 2 */
    59     public static final int TAG_GPS_MEASURE_MODE = 0x000A;
     59    public static final int TAG_MEASURE_MODE = 0x000A;
    6060    /** Measurement precision GPSDOP 11 B RATIONAL 1 */
    61     public static final int TAG_GPS_DOP = 0x000B;
     61    public static final int TAG_DOP = 0x000B;
    6262    /** Speed unit GPSSpeedRef 12 C ASCII 2 */
    63     public static final int TAG_GPS_SPEED_REF = 0x000C;
     63    public static final int TAG_SPEED_REF = 0x000C;
    6464    /** Speed of GPS receiver GPSSpeed 13 D RATIONAL 1 */
    65     public static final int TAG_GPS_SPEED = 0x000D;
     65    public static final int TAG_SPEED = 0x000D;
    6666    /** Reference for direction of movement GPSTrackRef 14 E ASCII 2 */
    67     public static final int TAG_GPS_TRACK_REF = 0x000E;
     67    public static final int TAG_TRACK_REF = 0x000E;
    6868    /** Direction of movement GPSTrack 15 F RATIONAL 1 */
    69     public static final int TAG_GPS_TRACK = 0x000F;
     69    public static final int TAG_TRACK = 0x000F;
    7070    /** Reference for direction of image GPSImgDirectionRef 16 10 ASCII 2 */
    71     public static final int TAG_GPS_IMG_DIRECTION_REF = 0x0010;
     71    public static final int TAG_IMG_DIRECTION_REF = 0x0010;
    7272    /** Direction of image GPSImgDirection 17 11 RATIONAL 1 */
    73     public static final int TAG_GPS_IMG_DIRECTION = 0x0011;
     73    public static final int TAG_IMG_DIRECTION = 0x0011;
    7474    /** Geodetic survey data used GPSMapDatum 18 12 ASCII Any */
    75     public static final int TAG_GPS_MAP_DATUM = 0x0012;
     75    public static final int TAG_MAP_DATUM = 0x0012;
    7676    /** Reference for latitude of destination GPSDestLatitudeRef 19 13 ASCII 2 */
    77     public static final int TAG_GPS_DEST_LATITUDE_REF = 0x0013;
     77    public static final int TAG_DEST_LATITUDE_REF = 0x0013;
    7878    /** Latitude of destination GPSDestLatitude 20 14 RATIONAL 3 */
    79     public static final int TAG_GPS_DEST_LATITUDE = 0x0014;
     79    public static final int TAG_DEST_LATITUDE = 0x0014;
    8080    /** Reference for longitude of destination GPSDestLongitudeRef 21 15 ASCII 2 */
    81     public static final int TAG_GPS_DEST_LONGITUDE_REF = 0x0015;
     81    public static final int TAG_DEST_LONGITUDE_REF = 0x0015;
    8282    /** Longitude of destination GPSDestLongitude 22 16 RATIONAL 3 */
    83     public static final int TAG_GPS_DEST_LONGITUDE = 0x0016;
     83    public static final int TAG_DEST_LONGITUDE = 0x0016;
    8484    /** Reference for bearing of destination GPSDestBearingRef 23 17 ASCII 2 */
    85     public static final int TAG_GPS_DEST_BEARING_REF = 0x0017;
     85    public static final int TAG_DEST_BEARING_REF = 0x0017;
    8686    /** Bearing of destination GPSDestBearing 24 18 RATIONAL 1 */
    87     public static final int TAG_GPS_DEST_BEARING = 0x0018;
     87    public static final int TAG_DEST_BEARING = 0x0018;
    8888    /** Reference for distance to destination GPSDestDistanceRef 25 19 ASCII 2 */
    89     public static final int TAG_GPS_DEST_DISTANCE_REF = 0x0019;
     89    public static final int TAG_DEST_DISTANCE_REF = 0x0019;
    9090    /** Distance to destination GPSDestDistance 26 1A RATIONAL 1 */
    91     public static final int TAG_GPS_DEST_DISTANCE = 0x001A;
     91    public static final int TAG_DEST_DISTANCE = 0x001A;
    9292
    9393    /** Values of "GPS", "CELLID", "WLAN" or "MANUAL" by the EXIF spec. */
    94     public static final int TAG_GPS_PROCESSING_METHOD = 0x001B;
    95     public static final int TAG_GPS_AREA_INFORMATION = 0x001C;
    96     public static final int TAG_GPS_DATE_STAMP = 0x001D;
    97     public static final int TAG_GPS_DIFFERENTIAL = 0x001E;
     94    public static final int TAG_PROCESSING_METHOD = 0x001B;
     95    public static final int TAG_AREA_INFORMATION = 0x001C;
     96    public static final int TAG_DATE_STAMP = 0x001D;
     97    public static final int TAG_DIFFERENTIAL = 0x001E;
    9898
    9999    @NotNull
     
    102102    static
    103103    {
    104         _tagNameMap.put(TAG_GPS_VERSION_ID, "GPS Version ID");
    105         _tagNameMap.put(TAG_GPS_LATITUDE_REF, "GPS Latitude Ref");
    106         _tagNameMap.put(TAG_GPS_LATITUDE, "GPS Latitude");
    107         _tagNameMap.put(TAG_GPS_LONGITUDE_REF, "GPS Longitude Ref");
    108         _tagNameMap.put(TAG_GPS_LONGITUDE, "GPS Longitude");
    109         _tagNameMap.put(TAG_GPS_ALTITUDE_REF, "GPS Altitude Ref");
    110         _tagNameMap.put(TAG_GPS_ALTITUDE, "GPS Altitude");
    111         _tagNameMap.put(TAG_GPS_TIME_STAMP, "GPS Time-Stamp");
    112         _tagNameMap.put(TAG_GPS_SATELLITES, "GPS Satellites");
    113         _tagNameMap.put(TAG_GPS_STATUS, "GPS Status");
    114         _tagNameMap.put(TAG_GPS_MEASURE_MODE, "GPS Measure Mode");
    115         _tagNameMap.put(TAG_GPS_DOP, "GPS DOP");
    116         _tagNameMap.put(TAG_GPS_SPEED_REF, "GPS Speed Ref");
    117         _tagNameMap.put(TAG_GPS_SPEED, "GPS Speed");
    118         _tagNameMap.put(TAG_GPS_TRACK_REF, "GPS Track Ref");
    119         _tagNameMap.put(TAG_GPS_TRACK, "GPS Track");
    120         _tagNameMap.put(TAG_GPS_IMG_DIRECTION_REF, "GPS Img Direction Ref");
    121         _tagNameMap.put(TAG_GPS_IMG_DIRECTION, "GPS Img Direction");
    122         _tagNameMap.put(TAG_GPS_MAP_DATUM, "GPS Map Datum");
    123         _tagNameMap.put(TAG_GPS_DEST_LATITUDE_REF, "GPS Dest Latitude Ref");
    124         _tagNameMap.put(TAG_GPS_DEST_LATITUDE, "GPS Dest Latitude");
    125         _tagNameMap.put(TAG_GPS_DEST_LONGITUDE_REF, "GPS Dest Longitude Ref");
    126         _tagNameMap.put(TAG_GPS_DEST_LONGITUDE, "GPS Dest Longitude");
    127         _tagNameMap.put(TAG_GPS_DEST_BEARING_REF, "GPS Dest Bearing Ref");
    128         _tagNameMap.put(TAG_GPS_DEST_BEARING, "GPS Dest Bearing");
    129         _tagNameMap.put(TAG_GPS_DEST_DISTANCE_REF, "GPS Dest Distance Ref");
    130         _tagNameMap.put(TAG_GPS_DEST_DISTANCE, "GPS Dest Distance");
    131         _tagNameMap.put(TAG_GPS_PROCESSING_METHOD, "GPS Processing Method");
    132         _tagNameMap.put(TAG_GPS_AREA_INFORMATION, "GPS Area Information");
    133         _tagNameMap.put(TAG_GPS_DATE_STAMP, "GPS Date Stamp");
    134         _tagNameMap.put(TAG_GPS_DIFFERENTIAL, "GPS Differential");
     104        _tagNameMap.put(TAG_VERSION_ID, "GPS Version ID");
     105        _tagNameMap.put(TAG_LATITUDE_REF, "GPS Latitude Ref");
     106        _tagNameMap.put(TAG_LATITUDE, "GPS Latitude");
     107        _tagNameMap.put(TAG_LONGITUDE_REF, "GPS Longitude Ref");
     108        _tagNameMap.put(TAG_LONGITUDE, "GPS Longitude");
     109        _tagNameMap.put(TAG_ALTITUDE_REF, "GPS Altitude Ref");
     110        _tagNameMap.put(TAG_ALTITUDE, "GPS Altitude");
     111        _tagNameMap.put(TAG_TIME_STAMP, "GPS Time-Stamp");
     112        _tagNameMap.put(TAG_SATELLITES, "GPS Satellites");
     113        _tagNameMap.put(TAG_STATUS, "GPS Status");
     114        _tagNameMap.put(TAG_MEASURE_MODE, "GPS Measure Mode");
     115        _tagNameMap.put(TAG_DOP, "GPS DOP");
     116        _tagNameMap.put(TAG_SPEED_REF, "GPS Speed Ref");
     117        _tagNameMap.put(TAG_SPEED, "GPS Speed");
     118        _tagNameMap.put(TAG_TRACK_REF, "GPS Track Ref");
     119        _tagNameMap.put(TAG_TRACK, "GPS Track");
     120        _tagNameMap.put(TAG_IMG_DIRECTION_REF, "GPS Img Direction Ref");
     121        _tagNameMap.put(TAG_IMG_DIRECTION, "GPS Img Direction");
     122        _tagNameMap.put(TAG_MAP_DATUM, "GPS Map Datum");
     123        _tagNameMap.put(TAG_DEST_LATITUDE_REF, "GPS Dest Latitude Ref");
     124        _tagNameMap.put(TAG_DEST_LATITUDE, "GPS Dest Latitude");
     125        _tagNameMap.put(TAG_DEST_LONGITUDE_REF, "GPS Dest Longitude Ref");
     126        _tagNameMap.put(TAG_DEST_LONGITUDE, "GPS Dest Longitude");
     127        _tagNameMap.put(TAG_DEST_BEARING_REF, "GPS Dest Bearing Ref");
     128        _tagNameMap.put(TAG_DEST_BEARING, "GPS Dest Bearing");
     129        _tagNameMap.put(TAG_DEST_DISTANCE_REF, "GPS Dest Distance Ref");
     130        _tagNameMap.put(TAG_DEST_DISTANCE, "GPS Dest Distance");
     131        _tagNameMap.put(TAG_PROCESSING_METHOD, "GPS Processing Method");
     132        _tagNameMap.put(TAG_AREA_INFORMATION, "GPS Area Information");
     133        _tagNameMap.put(TAG_DATE_STAMP, "GPS Date Stamp");
     134        _tagNameMap.put(TAG_DIFFERENTIAL, "GPS Differential");
    135135    }
    136136
     
    140140    }
    141141
     142    @Override
    142143    @NotNull
    143144    public String getName()
     
    146147    }
    147148
     149    @Override
    148150    @NotNull
    149151    protected HashMap<Integer, String> getTagNameMap()
     
    161163    public GeoLocation getGeoLocation()
    162164    {
    163         Rational[] latitudes = getRationalArray(GpsDirectory.TAG_GPS_LATITUDE);
    164         Rational[] longitudes = getRationalArray(GpsDirectory.TAG_GPS_LONGITUDE);
    165         String latitudeRef = getString(GpsDirectory.TAG_GPS_LATITUDE_REF);
    166         String longitudeRef = getString(GpsDirectory.TAG_GPS_LONGITUDE_REF);
     165        Rational[] latitudes = getRationalArray(GpsDirectory.TAG_LATITUDE);
     166        Rational[] longitudes = getRationalArray(GpsDirectory.TAG_LONGITUDE);
     167        String latitudeRef = getString(GpsDirectory.TAG_LATITUDE_REF);
     168        String longitudeRef = getString(GpsDirectory.TAG_LONGITUDE_REF);
    167169
    168170        // Make sure we have the required values
Note: See TracChangeset for help on using the changeset viewer.